From b1b328bd001b7de2943250e3f3bb53be71d2313c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 24 Apr 2023 23:31:20 +0900
Subject: [PATCH 0001/1027] arm64: dts: apple: t8112: Remove always-on from the
 PMP node

This should now work properly with power domain dependencies.

With "apple,always-on" removed from ps_pmp add it as dependency for the
dcp* power-domains. Fixes dcp crashes on power state changes.
TODO: investigate if it is enough to power ps_pmp on during
SetPowerState calls.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 7c050c6f2707a1..118694dd9b5f06 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -672,7 +672,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "disp0_fe";
-		power-domains = <&ps_disp0_sys>;
+		power-domains = <&ps_disp0_sys>, <&ps_pmp>;
 		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
@@ -691,7 +691,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "dispext_fe";
-		power-domains = <&ps_dispext_sys>;
+		power-domains = <&ps_dispext_sys>, <&ps_pmp>;
 	};
 
 	ps_dispext_cpu0: power-controller@3c8 {
@@ -773,7 +773,6 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "pmp";
-		apple,always-on;
 	};
 
 	ps_pms_sram: power-controller@418 {

From 5f3222175cea2e82c1006d5f098595c1e0142be1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 14 Feb 2023 10:07:49 +0100
Subject: [PATCH 0002/1027] arm64: dts: apple: t8112: Add wlan/bt PCIe device
 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 06fe257f08be49..4362d9081bd7b9 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -17,7 +17,9 @@
 	model = "Apple Mac mini (M2, 2023)";
 
 	aliases {
+		bluetooth0 = &bluetooth0;
 		ethernet0 = &ethernet0;
+		wifi0 = &wifi0;
 	};
 };
 
@@ -28,6 +30,22 @@
  */
 &port00 {
 	bus-range = <1 1>;
+	wifi0: wifi@0,0 {
+		compatible = "pci14e4,4434";
+		reg = <0x10000 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-mac-address = [00 10 18 00 00 10];
+		apple,antenna-sku = "XX";
+		brcm,board-type = "apple,miyake";
+	};
+
+	bluetooth0: bluetooth@0,1 {
+		compatible = "pci14e4,5f72";
+		reg = <0x10100 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-bd-address = [00 00 00 00 00 00];
+		brcm,board-type = "apple,miyake";
+	};
 };
 
 &port01 {

From 6b782249aed9cf1f4d1a13e0a79ff9b14021400e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:54:35 +0900
Subject: [PATCH 0003/1027] arm64: dts: apple: t8112: Add PMU NVMEM and SMC
 RTC/reboot nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 78 ++++++++++++++++++++++++++++
 1 file changed, 78 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 1666e6ab250bc0..e68f5042e1b339 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -641,6 +641,84 @@
 			};
 		};
 
+		nub_spmi: spmi@23d0d9300 {
+			compatible = "apple,t8112-spmi", "apple,spmi";
+			reg = <0x2 0x3d714000 0x0 0x100>;
+			#address-cells = <2>;
+			#size-cells = <0>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 256 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 384 IRQ_TYPE_LEVEL_HIGH>;
+
+			pmu1: pmu@e {
+				compatible = "apple,stowe-pmu", "apple,spmi-pmu";
+				reg = <0xe SPMI_USID>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				rtc_nvmem@f800 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0xf800 0x300>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					pm_setting: pm-setting@1 {
+						reg = <0x1 0x1>;
+					};
+
+					rtc_offset: rtc-offset@100 {
+						reg = <0x100 0x6>;
+					};
+				};
+
+				legacy_nvmem@f700 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0xf700 0x20>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					boot_stage: boot-stage@1 {
+						reg = <0x1 0x1>;
+					};
+
+					boot_error_count: boot-error-count@2 {
+						reg = <0x2 0x1>;
+						bits = <0 4>;
+					};
+
+					panic_count: panic-count@2 {
+						reg = <0x2 0x1>;
+						bits = <4 4>;
+					};
+
+					boot_error_stage: boot-error-stage@3 {
+						reg = <0x3 0x1>;
+					};
+
+					shutdown_flag: shutdown-flag@f {
+						reg = <0xf 0x1>;
+						bits = <3 1>;
+					};
+				};
+
+				scrpad_nvmem@8000 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0x8000 0x2800>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					fault_shadow: fault-shadow@67b {
+						reg = <0x67b 0x10>;
+					};
+
+					socd: socd@b00 {
+						reg = <0xb00 0x400>;
+					};
+				};
+
+			};
+		};
+
 		pinctrl_nub: pinctrl@23d1f0000 {
 			compatible = "apple,t8112-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3d1f0000 0x0 0x4000>;

From a1320c4324bc872b206333e5f3c6601957cddeeb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 2 Feb 2023 14:33:26 +0100
Subject: [PATCH 0004/1027] arm64: dts: apple: t8112-j493: Add spi3 node

Used for the touchbar, clock frequency is probably wrong.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 22 ++++++++++++++++++----
 1 file changed, 18 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index e68f5042e1b339..1b5031a3c932fd 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -467,6 +467,20 @@
 			status = "disabled";
 		};
 
+		spi3: spi@23510c000 {
+			compatible = "apple,t8112-spi", "apple,spi";
+			reg = <0x2 0x3510c000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 751 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clkref>;
+			pinctrl-0 = <&spi3_pins>;
+			pinctrl-names = "default";
+			power-domains = <&ps_spi3>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		serial0: serial@235200000 {
 			compatible = "apple,s5l-uart";
 			reg = <0x2 0x35200000 0x0 0x1000>;
@@ -627,10 +641,10 @@
 			};
 
 			spi3_pins: spi3-pins {
-				pinmux = <APPLE_PINMUX(46, 1)>,
-					<APPLE_PINMUX(47, 1)>,
-					<APPLE_PINMUX(48, 1)>,
-					<APPLE_PINMUX(49, 1)>;
+				pinmux = <APPLE_PINMUX(93, 1)>,
+					<APPLE_PINMUX(94, 1)>,
+					<APPLE_PINMUX(95, 1)>,
+					<APPLE_PINMUX(96, 1)>;
 			};
 
 			pcie_pins: pcie-pins {

From cebd871f2e2ed03f2c219634aaaa0ebe6292334f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Feb 2022 12:59:39 +0900
Subject: [PATCH 0005/1027] arm64: dts: apple: t8112: Add SMC node to
 devicetree

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts |  1 +
 arch/arm64/boot/dts/apple/t8112-j473.dts |  2 ++
 arch/arm64/boot/dts/apple/t8112-j493.dts |  1 +
 arch/arm64/boot/dts/apple/t8112.dtsi     | 38 ++++++++++++++++++++++++
 4 files changed, 42 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 6f69658623bf89..66049c75b8d0e1 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -42,6 +42,7 @@
  */
 &port00 {
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: wifi@0,0 {
 		compatible = "pci14e4,4433";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 4362d9081bd7b9..33904f3c9ff8f4 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -30,6 +30,7 @@
  */
 &port00 {
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: wifi@0,0 {
 		compatible = "pci14e4,4434";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
@@ -50,6 +51,7 @@
 
 &port01 {
 	bus-range = <2 2>;
+	pwren-gpios = <&smc_gpio 24 GPIO_ACTIVE_HIGH>;
 	status = "okay";
 };
 
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 0ad908349f5540..1ca48f9d71c75f 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -42,6 +42,7 @@
  */
 &port00 {
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: wifi@0,0 {
 		compatible = "pci14e4,4425";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 1b5031a3c932fd..c35d2a99715a1a 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -771,6 +771,44 @@
 			interrupts = <AIC_IRQ 379 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		smc_mbox: mbox@23e408000 {
+			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x3e408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 499 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 500 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 501 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 502 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+		};
+
+		smc: smc@23e400000 {
+			compatible = "apple,t8112-smc", "apple,smc";
+			reg = <0x2 0x3e400000 0x0 0x4000>,
+				<0x2 0x3fe00000 0x0 0x100000>;
+			reg-names = "smc", "sram";
+			mboxes = <&smc_mbox>;
+
+			smc_gpio: gpio {
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+
+			smc_rtc: rtc {
+				nvmem-cells = <&rtc_offset>;
+				nvmem-cell-names = "rtc_offset";
+			};
+
+			smc_reboot: reboot {
+				nvmem-cells = <&shutdown_flag>, <&boot_stage>,
+					<&boot_error_count>, <&panic_count>, <&pm_setting>;
+				nvmem-cell-names = "shutdown_flag", "boot_stage",
+					"boot_error_count", "panic_count", "pm_setting";
+			};
+		};
+
 		pinctrl_smc: pinctrl@23e820000 {
 			compatible = "apple,t8112-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3e820000 0x0 0x4000>;

From 95ae01eb93958c24677e2b4a543f728fdeceed81 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sat, 19 Feb 2022 09:49:59 +0100
Subject: [PATCH 0006/1027] arm64: dts: apple: t8112*: Put in audio nodes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 84 +++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8112-j473.dts | 52 +++++++++++++++
 arch/arm64/boot/dts/apple/t8112-j493.dts | 85 ++++++++++++++++++++++++
 3 files changed, 221 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 66049c75b8d0e1..be0a141cf99427 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -72,6 +72,51 @@
 	};
 };
 
+&i2c1 {
+	speaker_left_woof: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Woofer";
+	};
+
+	speaker_left_tweet: codec@39 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x39>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Tweeter";
+	};
+};
+
+&i2c3 {
+	speaker_right_woof: codec@3b {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3b>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Woofer";
+	};
+
+	speaker_right_tweet: codec@3c {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3c>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Tweeter";
+	};
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
 &i2c4 {
 	status = "okay";
 };
@@ -79,3 +124,42 @@
 &fpwm1 {
 	status = "okay";
 };
+
+/ {
+	sound {
+		compatible = "apple,j413-macaudio", "apple,macaudio";
+		model = "MacBook Air J413 integrated audio";
+
+		dai-link@0 {
+			link-name = "Speakers";
+
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left_woof>, <&speaker_left_tweet>,
+					    <&speaker_right_woof>, <&speaker_right_tweet>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 33904f3c9ff8f4..83b254b0a6e4dd 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -72,3 +72,55 @@
 &pcie2_dart {
 	status = "okay";
 };
+
+&i2c1 {
+	speaker_amp: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <149 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+/ {
+	sound {
+		compatible = "apple,j473-macaudio", "apple,macaudio";
+		model = "Mac mini J473";
+
+		dai-link@0 {
+			link-name = "Speaker";
+
+			cpu {
+				sound-dai = <&mca 0>;
+			};
+			codec {
+				sound-dai = <&speaker_amp>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 1ca48f9d71c75f..fc0b274991316b 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -61,6 +61,51 @@
 	};
 };
 
+&i2c1 {
+	speaker_left_rear: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Rear";
+	};
+
+	speaker_left_front: codec@39 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x39>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Front";
+	};
+};
+
+&i2c3 {
+	speaker_right_rear: codec@3b {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3b>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Rear";
+	};
+
+	speaker_right_front: codec@3c {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3c>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Front";
+	};
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
 &i2c4 {
 	status = "okay";
 };
@@ -68,3 +113,43 @@
 &fpwm1 {
 	status = "okay";
 };
+
+/ {
+	sound {
+		compatible = "apple,j493-macaudio", "apple,macaudio";
+		model = "MacBook Pro J493 integrated audio";
+
+		dai-link@0 {
+			link-name = "Speakers";
+
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left_front>, <&speaker_left_rear>,
+					    <&speaker_right_front>, <&speaker_right_rear>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
+

From b36036e95a9f6325cead834e382edfaf5dde83e1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 2 Feb 2023 12:50:56 +0100
Subject: [PATCH 0007/1027] arm64: dts: apple: t8112: Add dwc3 nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8112-j473.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8112-j493.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 51 +++++++++++++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi      | 60 +++++++++++++++++++++++
 5 files changed, 147 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index be0a141cf99427..0d070a89a8408e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -61,6 +61,18 @@
 	};
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Left-back";
+};
+
+&typec1 {
+	label = "USB-C Left-front";
+};
+
 &i2c0 {
 	/* MagSafe port */
 	hpm5: usb-pd@3a {
diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 83b254b0a6e4dd..4705ec980211c4 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -23,6 +23,18 @@
 	};
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Back-left";
+};
+
+&typec1 {
+	label = "USB-C Back-right";
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index fc0b274991316b..f158a004a28ca3 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -61,6 +61,18 @@
 	};
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Left-back";
+};
+
+&typec1 {
+	label = "USB-C Left-front";
+};
+
 &i2c1 {
 	speaker_left_rear: codec@38 {
 		compatible = "ti,sn012776", "ti,tas2764";
diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index f5edf61113e7aa..2c14f4479c401f 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -53,6 +53,23 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <8 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec0: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec0_con_hs: endpoint {
+						remote-endpoint = <&typec0_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm1: usb-pd@3f {
@@ -61,6 +78,40 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <8 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec1: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec1_con_hs: endpoint {
+						remote-endpoint = <&typec1_usb_hs>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers */
+&dwc3_0 {
+	port {
+		typec0_usb_hs: endpoint {
+			remote-endpoint = <&typec0_con_hs>;
+		};
+	};
+};
+
+&dwc3_1 {
+	port {
+		typec1_usb_hs: endpoint {
+			remote-endpoint = <&typec1_con_hs>;
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index c35d2a99715a1a..8ab5d6cd11d568 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -885,6 +885,66 @@
 			resets = <&ps_ans>;
 		};
 
+		dwc3_0: usb@382280000 {
+			compatible = "apple,t8112-dwc3", "apple,dwc3", "snps,dwc3";
+			reg = <0x3 0x82280000 0x0 0x100000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1031 IRQ_TYPE_LEVEL_HIGH>;
+			dr_mode = "otg";
+			usb-role-switch;
+			role-switch-default-mode = "host";
+			iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_0_dart_0: iommu@382f00000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x3 0x82f00000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1035 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_0_dart_1: iommu@382f80000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x3 0x82f80000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1035 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_1: usb@502280000 {
+			compatible = "apple,t8112-dwc3", "apple,dwc3", "snps,dwc3";
+			reg = <0x5 0x02280000 0x0 0x100000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1112 IRQ_TYPE_LEVEL_HIGH>;
+			dr_mode = "otg";
+			usb-role-switch;
+			role-switch-default-mode = "host";
+			iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
+		dwc3_1_dart_0: iommu@502f00000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x5 0x02f00000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1116 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
+		dwc3_1_dart_1: iommu@502f80000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x5 0x02f80000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 1116 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
 		pcie0_dart: iommu@681008000 {
 			compatible = "apple,t8110-dart";
 			reg = <0x6 0x81008000 0x0 0x4000>;

From bf8883cdc3d55623e2afe3ce4fc9758561454cbc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 2 Feb 2023 11:15:35 +0100
Subject: [PATCH 0008/1027] arm64: dts: apple: t8112: Add mtp device nodes for
 j413/j493

Those provide trackpad and keyboard for j413/j493.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 33 +++++++++++
 arch/arm64/boot/dts/apple/t8112-j493.dts | 32 +++++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi     | 73 ++++++++++++++++++++++++
 3 files changed, 138 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 0d070a89a8408e..bce2774ff0a406 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -175,3 +175,36 @@
 		};
 	};
 };
+
+&mtp {
+	status = "okay";
+};
+&mtp_mbox {
+	status = "okay";
+};
+&mtp_dart {
+	status = "okay";
+};
+&mtp_dockchannel {
+	status = "okay";
+};
+&mtp_hid {
+	apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>;
+	apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>;
+
+	multi-touch {
+		firmware-name = "apple/tpmtfw-j413.bin";
+	};
+
+	keyboard {
+	};
+
+	stm {
+	};
+
+	actuator {
+	};
+
+	tp_accel {
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index f158a004a28ca3..919b0762b723b5 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -165,3 +165,35 @@
 	};
 };
 
+&mtp {
+	status = "okay";
+};
+&mtp_mbox {
+	status = "okay";
+};
+&mtp_dart {
+	status = "okay";
+};
+&mtp_dockchannel {
+	status = "okay";
+};
+&mtp_hid {
+	apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>;
+	apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>;
+
+	multi-touch {
+		firmware-name = "apple/tpmtfw-j493.bin";
+	};
+
+	keyboard {
+	};
+
+	stm {
+	};
+
+	actuator {
+	};
+
+	tp_accel {
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 8ab5d6cd11d568..4904574aa2f931 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -851,6 +851,79 @@
 				     <AIC_IRQ 307 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		mtp: mtp@24e400000 {
+			compatible = "apple,t8112-mtp", "apple,t8112-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4";
+			reg = <0x2 0x4e400000 0x0 0x4000>,
+				<0x2 0x4ec00000 0x0 0x100000>;
+			reg-names = "asc", "sram";
+			mboxes = <&mtp_mbox>;
+			iommus = <&mtp_dart 1>;
+			#helper-cells = <0>;
+
+			status = "disabled";
+		};
+
+		mtp_mbox: mbox@24e408000 {
+			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x4e408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 864 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 865 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 866 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 867 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+
+			status = "disabled";
+		};
+
+		mtp_dart: iommu@24e808000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x2 0x4e808000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 848 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+
+			status = "disabled";
+		};
+
+		mtp_dockchannel: fifo@24eb14000 {
+			compatible = "apple,t8112-dockchannel", "apple,dockchannel";
+			reg = <0x2 0x4eb14000 0x0 0x4000>;
+			reg-names = "irq";
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 850 IRQ_TYPE_LEVEL_HIGH>;
+
+			ranges = <0 0x2 0x4eb28000 0x20000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			interrupt-controller;
+			#interrupt-cells = <2>;
+
+			status = "disabled";
+
+			mtp_hid: input@8000 {
+				compatible = "apple,dockchannel-hid";
+				reg = <0x8000 0x4000>,
+					<0xc000 0x4000>,
+					<0x0000 0x4000>,
+					<0x4000 0x4000>;
+				reg-names = "config", "data",
+					"rmt-config", "rmt-data";
+				iommus = <&mtp_dart 1>;
+				interrupt-parent = <&mtp_dockchannel>;
+				interrupts = <2 IRQ_TYPE_LEVEL_HIGH>,
+					<3 IRQ_TYPE_LEVEL_HIGH>;
+				interrupt-names = "tx", "rx";
+
+				apple,fifo-size = <0x800>;
+				apple,helper-cpu = <&mtp>;
+			};
+
+		};
+
 		ans_mbox: mbox@277408000 {
 			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
 			reg = <0x2 0x77408000 0x0 0x4000>;

From c940244de934886283dfae3e814b58535964e0ac Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 26 Nov 2021 15:37:23 +0900
Subject: [PATCH 0009/1027] arm64: dts: apple: t8103: Add dwc3 nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8103-j293.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8103-j313.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8103-j456.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8103-j457.dts  | 12 +++++
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 51 +++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi      | 60 +++++++++++++++++++++++
 7 files changed, 171 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 1c3e37f86d46d7..968fe22163d443 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -29,6 +29,18 @@
 	brcm,board-type = "apple,atlantisb";
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Back-left";
+};
+
+&typec1 {
+	label = "USB-C Back-right";
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 56b0c67bfcda32..58ab9b4f765ef7 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -38,6 +38,18 @@
 	brcm,board-type = "apple,honshu";
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Left-back";
+};
+
+&typec1 {
+	label = "USB-C Left-front";
+};
+
 &i2c2 {
 	status = "okay";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 97a4344d8dca68..bce9b911009e2b 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -41,3 +41,15 @@
 &fpwm1 {
 	status = "okay";
 };
+
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Left-back";
+};
+
+&typec1 {
+	label = "USB-C Left-front";
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index 58c8e43789b486..9983e11cacdf19 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -47,6 +47,18 @@
 	};
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Back-right";
+};
+
+&typec1 {
+	label = "USB-C Back-right-middle";
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 152f95fd49a211..a622ff607d4075 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -29,6 +29,18 @@
 	brcm,board-type = "apple,santorini";
 };
 
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Back-right";
+};
+
+&typec1 {
+	label = "USB-C Back-left";
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 5988a4eb6efaa0..36dba76c06fd47 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -53,6 +53,23 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <106 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec0: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec0_con_hs: endpoint {
+						remote-endpoint = <&typec0_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm1: usb-pd@3f {
@@ -61,6 +78,40 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <106 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec1: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec1_con_hs: endpoint {
+						remote-endpoint = <&typec1_usb_hs>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers */
+&dwc3_0 {
+	port {
+		typec0_usb_hs: endpoint {
+			remote-endpoint = <&typec0_con_hs>;
+		};
+	};
+};
+
+&dwc3_1 {
+	port {
+		typec1_usb_hs: endpoint {
+			remote-endpoint = <&typec1_con_hs>;
+		};
 	};
 };
 
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 9b0dad6b618444..4580f207fa1f07 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -717,6 +717,66 @@
 			resets = <&ps_ans2>;
 		};
 
+		dwc3_0: usb@382280000 {
+			compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
+			reg = <0x3 0x82280000 0x0 0x100000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>;
+			dr_mode = "otg";
+			usb-role-switch;
+			role-switch-default-mode = "host";
+			iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_0_dart_0: iommu@382f00000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x3 0x82f00000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 781 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_0_dart_1: iommu@382f80000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x3 0x82f80000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 781 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
+		dwc3_1: usb@502280000 {
+			compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
+			reg = <0x5 0x02280000 0x0 0x100000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 857 IRQ_TYPE_LEVEL_HIGH>;
+			dr_mode = "otg";
+			usb-role-switch;
+			role-switch-default-mode = "host";
+			iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
+		dwc3_1_dart_0: iommu@502f00000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x5 0x02f00000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 861 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
+		dwc3_1_dart_1: iommu@502f80000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x5 0x02f80000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 861 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
 		pcie0_dart_0: iommu@681008000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x6 0x81008000 0x0 0x4000>;

From 2ec0cb42c8bfb8d0c98591dcb6bb48a653516c08 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 26 Nov 2021 00:24:15 +0100
Subject: [PATCH 0010/1027] arm64: dts: apple: t8103: Add spi3/keyboard nodes

Enables keyboard and touchpad input on MacBook Air (M1, 2020) and
MacBook Pro (13-inch, M1, 2020).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 21 ++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103-j313.dts | 21 ++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi     | 28 ++++++++++++++++++++++++
 3 files changed, 70 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 58ab9b4f765ef7..0be0437c3ac230 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -50,6 +50,27 @@
 	label = "USB-C Left-front";
 };
 
+&spi3 {
+	status = "okay";
+
+	hid-transport@0 {
+		compatible = "apple,spi-hid-transport";
+		reg = <0>;
+		spi-max-frequency = <8000000>;
+		/*
+		 * Apple's ADT specifies 20us CS change delays, and the
+		 * SPI HID interface metadata specifies 45us. Using either
+		 * seems not to be reliable, but adding both works, so
+		 * best guess is they are cumulative.
+		 */
+		spi-cs-setup-delay-ns = <65000>;
+		spi-cs-hold-delay-ns = <65000>;
+		spi-cs-inactive-delay-ns = <250000>;
+		spien-gpios = <&pinctrl_ap 195 0>;
+		interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
 &i2c2 {
 	status = "okay";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index bce9b911009e2b..7b13e16957ead7 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -53,3 +53,24 @@
 &typec1 {
 	label = "USB-C Left-front";
 };
+
+&spi3 {
+	status = "okay";
+
+	hid-transport@0 {
+		compatible = "apple,spi-hid-transport";
+		reg = <0>;
+		spi-max-frequency = <8000000>;
+		/*
+		 * Apple's ADT specifies 20us CS change delays, and the
+		 * SPI HID interface metadata specifies 45us. Using either
+		 * seems not to be reliable, but adding both works, so
+		 * best guess is they are cumulative.
+		 */
+		spi-cs-setup-delay-ns = <65000>;
+		spi-cs-hold-delay-ns = <65000>;
+		spi-cs-inactive-delay-ns = <250000>;
+		spien-gpios = <&pinctrl_ap 195 0>;
+		interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 4580f207fa1f07..4318c89fc6cbe4 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -326,6 +326,13 @@
 		clock-output-names = "clkref";
 	};
 
+	clk_120m: clock-120m {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <120000000>;
+		clock-output-names = "clk_120m";
+	};
+
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
@@ -441,6 +448,20 @@
 			status = "disabled";
 		};
 
+		spi3: spi@23510c000 {
+			compatible = "apple,t8103-spi", "apple,spi";
+			reg = <0x2 0x3510c000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 617 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_120m>;
+			pinctrl-0 = <&spi3_pins>;
+			pinctrl-names = "default";
+			power-domains = <&ps_spi3>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled"; /* only used in J293/J313 */
+		};
+
 		serial0: serial@235200000 {
 			compatible = "apple,s5l-uart";
 			reg = <0x2 0x35200000 0x0 0x1000>;
@@ -597,6 +618,13 @@
 					 <APPLE_PINMUX(134, 1)>;
 			};
 
+			spi3_pins: spi3-pins {
+				pinmux = <APPLE_PINMUX(46, 1)>,
+					<APPLE_PINMUX(47, 1)>,
+					<APPLE_PINMUX(48, 1)>,
+					<APPLE_PINMUX(49, 1)>;
+			};
+
 			pcie_pins: pcie-pins {
 				pinmux = <APPLE_PINMUX(150, 1)>,
 					 <APPLE_PINMUX(151, 1)>,

From 052c4e84247785e3a8ad9360b9401640430cce9f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 6 Feb 2022 21:22:29 +0900
Subject: [PATCH 0011/1027] arm64: dts: apple: Add PCI power enable GPIOs

t8103:
- WLAN (SMC PMU GPIO #13)
t600x:
- WLAN (SMC PMU GPIO #13)
- SD (SMC PMU GPIO #26)

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 36dba76c06fd47..b70cd5f64b4cc6 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -122,6 +122,7 @@
  */
 &port00 {
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: network@0,0 {
 		compatible = "pci14e4,4425";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;

From e64bfdc722c3a1db9d50b7f35abee7652b98884c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Feb 2022 12:59:39 +0900
Subject: [PATCH 0012/1027] arm64: dts: apple: Add SMC node to t8103/t6001
 devicetrees

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 4318c89fc6cbe4..e28d07e6954d19 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -669,6 +669,32 @@
 			interrupts = <AIC_IRQ 338 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		smc_mbox: mbox@23e408000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x3e408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 400 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 401 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 402 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 403 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+		};
+
+		smc: smc@23e400000 {
+			compatible = "apple,t8103-smc", "apple,smc";
+			reg = <0x2 0x3e400000 0x0 0x4000>,
+				<0x2 0x3fe00000 0x0 0x100000>;
+			reg-names = "smc", "sram";
+			mboxes = <&smc_mbox>;
+
+			smc_gpio: gpio {
+				gpio-controller;
+				#gpio-cells = <2>;
+			};
+		};
+
 		pinctrl_smc: pinctrl@23e820000 {
 			compatible = "apple,t8103-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3e820000 0x0 0x4000>;

From d7ec904dbc428ea8795da845ff5dac001657c6d4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:54:35 +0900
Subject: [PATCH 0013/1027] arm64: dts: apple: Add PMU NVMEM and SMC RTC/reboot
 nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 88 ++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index e28d07e6954d19..db00ef3de56385 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/spmi/spmi.h>
 
 / {
 	compatible = "apple,t8103", "apple,arm-platform";
@@ -632,6 +633,81 @@
 			};
 		};
 
+		nub_spmi: spmi@23d0d9300 {
+			compatible = "apple,t8103-spmi", "apple,spmi";
+			reg = <0x2 0x3d0d9300 0x0 0x100>;
+			#address-cells = <2>;
+			#size-cells = <0>;
+
+			pmu1: pmu@f {
+				compatible = "apple,sera-pmu", "apple,spmi-pmu";
+				reg = <0xf SPMI_USID>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				rtc_nvmem@d000 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0xd000 0x300>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					pm_setting: pm-setting@1 {
+						reg = <0x1 0x1>;
+					};
+
+					rtc_offset: rtc-offset@100 {
+						reg = <0x100 0x6>;
+					};
+				};
+
+				legacy_nvmem@9f00 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0x9f00 0x20>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					boot_stage: boot-stage@1 {
+						reg = <0x1 0x1>;
+					};
+
+					boot_error_count: boot-error-count@2 {
+						reg = <0x2 0x1>;
+						bits = <0 4>;
+					};
+
+					panic_count: panic-count@2 {
+						reg = <0x2 0x1>;
+						bits = <4 4>;
+					};
+
+					boot_error_stage: boot-error-stage@3 {
+						reg = <0x3 0x1>;
+					};
+
+					shutdown_flag: shutdown-flag@f {
+						reg = <0xf 0x1>;
+						bits = <3 1>;
+					};
+				};
+
+				scrpad_nvmem@a000 {
+					compatible = "apple,spmi-pmu-nvmem";
+					reg = <0xa000 0x1000>;
+					#address-cells = <1>;
+					#size-cells = <1>;
+
+					fault_shadow: fault-shadow@67b {
+						reg = <0x67b 0x10>;
+					};
+
+					socd: socd@b00 {
+						reg = <0xb00 0x400>;
+					};
+				};
+
+			};
+		};
+
 		pinctrl_nub: pinctrl@23d1f0000 {
 			compatible = "apple,t8103-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3d1f0000 0x0 0x4000>;
@@ -693,6 +769,18 @@
 				gpio-controller;
 				#gpio-cells = <2>;
 			};
+
+			smc_rtc: rtc {
+				nvmem-cells = <&rtc_offset>;
+				nvmem-cell-names = "rtc_offset";
+			};
+
+			smc_reboot: reboot {
+				nvmem-cells = <&shutdown_flag>, <&boot_stage>,
+					<&boot_error_count>, <&panic_count>, <&pm_setting>;
+				nvmem-cell-names = "shutdown_flag", "boot_stage",
+					"boot_error_count", "panic_count", "pm_setting";
+			};
 		};
 
 		pinctrl_smc: pinctrl@23e820000 {

From 18801ec811eb34d9e60aafada4e6dfe61b82befe Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 3 Mar 2022 02:20:39 +0900
Subject: [PATCH 0014/1027] arm64: dts: apple: Mark ATC USB AON domains as
 always-on

Shutting these down breaks dwc3 init done by the firmware. We probably
never want to do this anyway. It might be possible remove this once
a PHY driver is in place to do the init properly, but it may not be
worth it.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 9645861a858c1a..1646e82bdc3692 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -1103,6 +1103,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "atc0_usb_aon";
+		apple,always-on; /* Needs to stay on for dwc3 to work */
 	};
 
 	ps_atc1_usb_aon: power-controller@90 {
@@ -1111,6 +1112,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "atc1_usb_aon";
+		apple,always-on; /* Needs to stay on for dwc3 to work */
 	};
 
 	ps_atc0_usb: power-controller@98 {

From 2212c9ab68dbf650e34e9f021abdd937d8cff406 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 17 Mar 2022 23:49:07 +0900
Subject: [PATCH 0015/1027] arm64: dts: apple: Keep PCIe power domain on

This causes flakiness if shut down; don't do it until we find out
what's going on.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 1646e82bdc3692..18c1814585eb24 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -717,6 +717,7 @@
 		#reset-cells = <0>;
 		label = "apcie_gp";
 		power-domains = <&ps_apcie>;
+		apple,always-on; /* Breaks things if shut down */
 	};
 
 	ps_ans2: power-controller@3f0 {

From 162e7e5a4323ed48a8ae245010d8355b7928d763 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Feb 2022 12:59:39 +0900
Subject: [PATCH 0016/1027] arm64: dts: apple: Add SMC node to t600x
 devicetrees

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 26 +++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index b1c875e692c8fb..f787d9267c2fc8 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -24,6 +24,32 @@
 		power-domains = <&ps_aic>;
 	};
 
+	smc_mbox: mbox@290408000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0x90408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 754 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 755 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 756 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 757 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+	};
+
+	smc: smc@290400000 {
+		compatible = "apple,t6000-smc", "apple,smc";
+		reg = <0x2 0x90400000 0x0 0x4000>,
+			<0x2 0x91e00000 0x0 0x100000>;
+		reg-names = "smc", "sram";
+		mboxes = <&smc_mbox>;
+
+		smc_gpio: gpio {
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+	};
+
 	pinctrl_smc: pinctrl@290820000 {
 		compatible = "apple,t6000-pinctrl", "apple,pinctrl";
 		reg = <0x2 0x90820000 0x0 0x4000>;

From b1e0b793bdf97564a9e9293391cef4c7b7e8ec82 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:54:35 +0900
Subject: [PATCH 0017/1027] arm64: dts: apple: Add PMU NVMEM and SMC RTC/reboot
 nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6001.dtsi      |  1 +
 arch/arm64/boot/dts/apple/t6002.dtsi      |  1 +
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 87 +++++++++++++++++++++++
 3 files changed, 89 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi
index 620b17e4031f06..d2cf81926f284c 100644
--- a/arch/arm64/boot/dts/apple/t6001.dtsi
+++ b/arch/arm64/boot/dts/apple/t6001.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/spmi/spmi.h>
 
 #include "multi-die-cpp.h"
 
diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi
index a963a5011799a0..e36f422d257d8f 100644
--- a/arch/arm64/boot/dts/apple/t6002.dtsi
+++ b/arch/arm64/boot/dts/apple/t6002.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/spmi/spmi.h>
 
 #include "multi-die-cpp.h"
 
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index f787d9267c2fc8..6fa873ffcbb5c0 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -48,6 +48,18 @@
 			gpio-controller;
 			#gpio-cells = <2>;
 		};
+
+		smc_rtc: rtc {
+			nvmem-cells = <&rtc_offset>;
+			nvmem-cell-names = "rtc_offset";
+		};
+
+		smc_reboot: reboot {
+			nvmem-cells = <&shutdown_flag>, <&boot_stage>,
+				<&boot_error_count>, <&panic_count>, <&pm_setting>;
+			nvmem-cell-names = "shutdown_flag", "boot_stage",
+				"boot_error_count", "panic_count", "pm_setting";
+		};
 	};
 
 	pinctrl_smc: pinctrl@290820000 {
@@ -79,6 +91,81 @@
 		interrupts = <AIC_IRQ 0 631 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
+	nub_spmi0: spmi@2920a1300 {
+		compatible = "apple,t6000-spmi", "apple,spmi";
+		reg = <0x2 0x920a1300 0x0 0x100>;
+		#address-cells = <2>;
+		#size-cells = <0>;
+
+		pmu1: pmu@f {
+			compatible = "apple,maverick-pmu", "apple,spmi-pmu";
+			reg = <0xf SPMI_USID>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			rtc_nvmem@1400 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x1400 0x20>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				pm_setting: pm-setting@5 {
+					reg = <0x5 0x1>;
+				};
+
+				rtc_offset: rtc-offset@11 {
+					reg = <0x11 0x6>;
+				};
+			};
+
+			legacy_nvmem@6000 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x6000 0x20>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				boot_stage: boot-stage@1 {
+					reg = <0x1 0x1>;
+				};
+
+				boot_error_count: boot-error-count@2 {
+					reg = <0x2 0x1>;
+					bits = <0 4>;
+				};
+
+				panic_count: panic-count@2 {
+					reg = <0x2 0x1>;
+					bits = <4 4>;
+				};
+
+				boot_error_stage: boot-error-stage@3 {
+					reg = <0x3 0x1>;
+				};
+
+				shutdown_flag: shutdown-flag@f {
+					reg = <0xf 0x1>;
+					bits = <3 1>;
+				};
+			};
+
+			scrpad_nvmem@8000 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x8000 0x1000>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				fault_shadow: fault-shadow@67b {
+					reg = <0x67b 0x10>;
+				};
+
+				socd: socd@b00 {
+					reg = <0xb00 0x400>;
+				};
+			};
+
+		};
+	};
+
 	sio_dart_0: iommu@39b004000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x3 0x9b004000 0x0 0x4000>;

From 9324b874bdd65756a53595669b8f8117593a94e3 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 9 Dec 2021 21:58:10 +0900
Subject: [PATCH 0018/1027] arm64: dts: apple: t6000: Add spi1 node

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-common.dtsi    |  7 +++++++
 arch/arm64/boot/dts/apple/t600x-die0.dtsi      | 14 ++++++++++++++
 arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi |  7 +++++++
 3 files changed, 28 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index fa8ead69936366..87dfc13d74171f 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -362,6 +362,13 @@
 		clock-output-names = "clkref";
 	};
 
+	clk_200m: clock-200m {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <200000000>;
+		clock-output-names = "clk_200m";
+	};
+
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 6fa873ffcbb5c0..ac30b5933e8a0e 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -276,6 +276,20 @@
 		status = "disabled";
 	};
 
+	spi1: spi@39b104000 {
+		compatible = "apple,t6000-spi", "apple,spi";
+		reg = <0x3 0x9b104000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1107 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clk_200m>;
+		pinctrl-0 = <&spi1_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_spi1>;
+		status = "disabled";
+	};
+
 	serial0: serial@39b200000 {
 		compatible = "apple,s5l-uart";
 		reg = <0x3 0x9b200000 0x0 0x1000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
index b31f1a7a2b3fc3..855dcf30a50292 100644
--- a/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
@@ -36,6 +36,13 @@
 			<APPLE_PINMUX(101, 1)>;
 	};
 
+	spi1_pins: spi1-pins {
+		pinmux = <APPLE_PINMUX(10, 1)>,
+			<APPLE_PINMUX(11, 1)>,
+			<APPLE_PINMUX(32, 1)>,
+			<APPLE_PINMUX(33, 1)>;
+	};
+
 	pcie_pins: pcie-pins {
 		pinmux = <APPLE_PINMUX(0, 1)>,
 				<APPLE_PINMUX(1, 1)>,

From 1f7b8b70b391e10574d37abd12559dd4b6228eac Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 9 Dec 2021 21:58:29 +0900
Subject: [PATCH 0019/1027] arm64: dts: apple: t600x-j314-j316: Add NOR flash
 node

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 2e471dfe43cf88..c1f1a4b385fca7 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -94,6 +94,18 @@
 	clock-frequency = <1068000000>;
 };
 
+&spi1 {
+	status = "disabled";
+
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		reg = <0x0>;
+		spi-max-frequency = <25000000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+	};
+};
+
 /* PCIe devices */
 &port00 {
 	/* WLAN */

From de4fdcecb779dabdb44cec1eed09457abd3014e9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Nov 2021 21:31:21 +0100
Subject: [PATCH 0020/1027] arm64: dts: apple: t600x: Add spi3 node

Used for keyboard and touchpad input on MacBook Pro (14/16-inch,
M1 Pro/Max, 2021).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi      | 14 ++++++++++++++
 arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi |  7 +++++++
 2 files changed, 21 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index ac30b5933e8a0e..152c878ab3e005 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -290,6 +290,20 @@
 		status = "disabled";
 	};
 
+	spi3: spi@39b10c000 {
+		compatible = "apple,t6000-spi", "apple,spi";
+		reg = <0x3 0x9b10c000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1109 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clkref>;
+		pinctrl-0 = <&spi3_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_spi3>;
+		status = "disabled";
+	};
+
 	serial0: serial@39b200000 {
 		compatible = "apple,s5l-uart";
 		reg = <0x3 0x9b200000 0x0 0x1000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
index 855dcf30a50292..1a994c3c1b79f0 100644
--- a/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-gpio-pins.dtsi
@@ -43,6 +43,13 @@
 			<APPLE_PINMUX(33, 1)>;
 	};
 
+	spi3_pins: spi3-pins {
+		pinmux = <APPLE_PINMUX(52, 1)>,
+			<APPLE_PINMUX(53, 1)>,
+			<APPLE_PINMUX(54, 1)>,
+			<APPLE_PINMUX(55, 1)>;
+	};
+
 	pcie_pins: pcie-pins {
 		pinmux = <APPLE_PINMUX(0, 1)>,
 				<APPLE_PINMUX(1, 1)>,

From 927467bc7cdc404586a9b601ffd4deb9d8ac9389 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Nov 2021 21:31:21 +0100
Subject: [PATCH 0021/1027] arm64: dts: apple: j31[46]: Add keyboard nodes

Enables keyboard and touchpad input on MacBook Pro (14/16-inch,
M1 Pro/Max, 2021).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 21 +++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index c1f1a4b385fca7..02571363c7ae71 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -106,6 +106,27 @@
 	};
 };
 
+&spi3 {
+	status = "okay";
+
+	hid-transport@0 {
+		compatible = "apple,spi-hid-transport";
+		reg = <0>;
+		spi-max-frequency = <8000000>;
+		/*
+		 * Apple's ADT specifies 20us CS change delays, and the
+		 * SPI HID interface metadata specifies 45us. Using either
+		 * seems not to be reliable, but adding both works, so
+		 * best guess is they are cumulative.
+		*/
+		spi-cs-setup-delay-ns = <65000>;
+		spi-cs-hold-delay-ns = <65000>;
+		spi-cs-inactive-delay-ns = <250000>;
+		spien-gpios = <&pinctrl_ap 194 0>;
+		interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
 /* PCIe devices */
 &port00 {
 	/* WLAN */

From 44469f91ffd61ba1e05b56957f9e3e79ac69d6b5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 17 May 2022 23:54:26 +0200
Subject: [PATCH 0022/1027] arm64: dts: apple: t600x: Add dwc3 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6002-j375d.dts     |  64 +++++++++
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi     | 124 ++++++++++++++++++
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi |  92 +++++++++++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     | 105 +++++++++++++++
 4 files changed, 385 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index 3365429bdc8be9..d3d4f4461deecb 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -26,6 +26,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec4: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Front Right";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec4_con_hs: endpoint {
+						remote-endpoint = <&typec4_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	/* front-left */
@@ -35,9 +53,55 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec5: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Front Left";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec5_con_hs: endpoint {
+						remote-endpoint = <&typec5_usb_hs>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers on die 1 */
+&dwc3_0_die1 {
+	port {
+		typec4_usb_hs: endpoint {
+			remote-endpoint = <&typec4_con_hs>;
+		};
 	};
 };
 
+&dwc3_1_die1 {
+	port {
+		typec5_usb_hs: endpoint {
+			remote-endpoint = <&typec5_con_hs>;
+		};
+	};
+};
+
+/* delete unused USB nodes on die 1 */
+
+/delete-node/ &dwc3_2_dart_0_die1;
+/delete-node/ &dwc3_2_dart_1_die1;
+/delete-node/ &dwc3_2_die1;
+
+/delete-node/ &dwc3_3_dart_0_die1;
+/delete-node/ &dwc3_3_dart_1_die1;
+/delete-node/ &dwc3_3_die1;
+
+
 /* delete unused always-on power-domains on die 1 */
 
 /delete-node/ &ps_atc2_usb_aon_die1;
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index a32ff0c9d7b0c2..a5f2ef5aab7929 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -119,3 +119,127 @@
 		interrupt-controller;
 		#interrupt-cells = <2>;
 	};
+
+	DIE_NODE(dwc3_0_dart_0): iommu@702f00000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x7 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1194 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_0_dart_1): iommu@702f80000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x7 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1194 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_0): usb@702280000 {
+		compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0x7 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1190 IRQ_TYPE_LEVEL_HIGH>;
+		/* dr_mode = "otg"; */
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_0_dart_0) 0>,
+			<&DIE_NODE(dwc3_0_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+	};
+
+	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
+		compatible = "apple,t6000-dart";
+		reg = <0xb 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1211 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_1_dart_1): iommu@b02f80000 {
+		compatible = "apple,t6000-dart";
+		reg = <0xb 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1211 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_1): usb@b02280000 {
+		compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0xb 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1207 IRQ_TYPE_LEVEL_HIGH>;
+		/* dr_mode = "otg"; */
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_1_dart_0) 0>,
+			<&DIE_NODE(dwc3_1_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+	};
+
+	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
+		compatible = "apple,t6000-dart";
+		reg = <0xf 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1228 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_2_dart_1): iommu@f02f80000 {
+		compatible = "apple,t6000-dart";
+		reg = <0xf 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1228 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_2): usb@f02280000 {
+		compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0xf 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1224 IRQ_TYPE_LEVEL_HIGH>;
+		/* dr_mode = "otg"; */
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_2_dart_0) 0>,
+			<&DIE_NODE(dwc3_2_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+	};
+
+	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x13 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1245 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_3_dart_1): iommu@1302f80000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x13 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1245 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_3): usb@1302280000 {
+		compatible = "apple,t6000-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0x13 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1241 IRQ_TYPE_LEVEL_HIGH>;
+		/* dr_mode = "otg"; */
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_3_dart_0) 0>,
+			<&DIE_NODE(dwc3_3_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+	};
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 02571363c7ae71..ebd7db2e08efa4 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -62,6 +62,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec0: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Left Rear";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec0_con_hs: endpoint {
+						remote-endpoint = <&typec0_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm1: usb-pd@3f {
@@ -70,6 +88,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec1: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Left Front";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec1_con_hs: endpoint {
+						remote-endpoint = <&typec1_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm2: usb-pd@3b {
@@ -78,6 +114,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec2: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Right";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec2_con_hs: endpoint {
+						remote-endpoint = <&typec2_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	/* MagSafe port */
@@ -152,3 +206,41 @@
 &fpwm0 {
 	status = "okay";
 };
+
+/* USB controllers */
+&dwc3_0 {
+	port {
+		typec0_usb_hs: endpoint {
+			remote-endpoint = <&typec0_con_hs>;
+		};
+	};
+};
+
+&dwc3_1 {
+	port {
+		typec1_usb_hs: endpoint {
+			remote-endpoint = <&typec1_con_hs>;
+		};
+	};
+};
+
+&dwc3_2 {
+	port {
+		typec2_usb_hs: endpoint {
+			remote-endpoint = <&typec2_con_hs>;
+		};
+	};
+};
+
+/* ATC3 is used for DisplayPort -> HDMI only */
+&dwc3_3_dart_0 {
+	status = "disabled";
+};
+
+&dwc3_3_dart_1 {
+	status = "disabled";
+};
+
+&dwc3_3 {
+	status = "disabled";
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 1e5a19e49b089d..68cbe5bd801d25 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -48,6 +48,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec0: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back Left";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec0_con_hs: endpoint {
+						remote-endpoint = <&typec0_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm1: usb-pd@3f {
@@ -56,6 +74,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec1: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back Left Middle";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec1_con_hs: endpoint {
+						remote-endpoint = <&typec1_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm2: usb-pd@3b {
@@ -64,6 +100,24 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec2: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back Right Middle";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec2_con_hs: endpoint {
+						remote-endpoint = <&typec2_usb_hs>;
+					};
+				};
+			};
+		};
 	};
 
 	hpm3: usb-pd@3c {
@@ -72,6 +126,57 @@
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <174 IRQ_TYPE_LEVEL_LOW>;
 		interrupt-names = "irq";
+
+		typec3: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back Right";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec3_con_hs: endpoint {
+						remote-endpoint = <&typec3_usb_hs>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers */
+&dwc3_0 {
+	port {
+		typec0_usb_hs: endpoint {
+			remote-endpoint = <&typec0_con_hs>;
+		};
+	};
+};
+
+&dwc3_1 {
+	port {
+		typec1_usb_hs: endpoint {
+			remote-endpoint = <&typec1_con_hs>;
+		};
+	};
+};
+
+&dwc3_2 {
+	port {
+		typec2_usb_hs: endpoint {
+			remote-endpoint = <&typec2_con_hs>;
+		};
+	};
+};
+
+&dwc3_3 {
+	port {
+		typec3_usb_hs: endpoint {
+			remote-endpoint = <&typec3_con_hs>;
+		};
 	};
 };
 

From bc2fac7786e0188ead5930cac3c3376e3068d383 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 21 Dec 2021 17:07:17 +0900
Subject: [PATCH 0023/1027] arm64: dts: apple: Add WiFi module and antenna
 properties

Add the new module-instance/antenna-sku properties required to select
WiFi firmwares properly to all board device trees.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6000-j316s.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6001-j314c.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6001-j316c.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 3 +++
 6 files changed, 21 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index c9e192848fe3f9..ac35870ca129ce 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -16,3 +16,7 @@
 	compatible = "apple,j314s", "apple,t6000", "apple,arm-platform";
 	model = "Apple MacBook Pro (14-inch, M1 Pro, 2021)";
 };
+
+&wifi0 {
+	brcm,board-type = "apple,maldives";
+};
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index ff1803ce23001c..77d6d8c14d741e 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -16,3 +16,7 @@
 	compatible = "apple,j316s", "apple,t6000", "apple,arm-platform";
 	model = "Apple MacBook Pro (16-inch, M1 Pro, 2021)";
 };
+
+&wifi0 {
+	brcm,board-type = "apple,madagascar";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index 1761d15b98c12f..0a5655792a8f1c 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -16,3 +16,7 @@
 	compatible = "apple,j314c", "apple,t6001", "apple,arm-platform";
 	model = "Apple MacBook Pro (14-inch, M1 Max, 2021)";
 };
+
+&wifi0 {
+	brcm,board-type = "apple,maldives";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index 750e9beeffc0aa..9c215531ea543e 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -16,3 +16,7 @@
 	compatible = "apple,j316c", "apple,t6001", "apple,arm-platform";
 	model = "Apple MacBook Pro (16-inch, M1 Max, 2021)";
 };
+
+&wifi0 {
+	brcm,board-type = "apple,madagascar";
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index ebd7db2e08efa4..e3a7ed2706d7c6 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -186,9 +186,11 @@
 	/* WLAN */
 	bus-range = <1 1>;
 	wifi0: wifi@0,0 {
+		compatible = "pci14e4,4433";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
 		/* To be filled by the loader */
 		local-mac-address = [00 10 18 00 00 10];
+		apple,antenna-sku = "XX";
 	};
 };
 
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 68cbe5bd801d25..bb2f1efce4a8a4 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -190,6 +190,9 @@
 	bus-range = <1 1>;
 	wifi0: wifi@0,0 {
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
+		compatible = "pci14e4,4433";
+		brcm,board-type = "apple,okinawa";
+		apple,antenna-sku = "XX";
 		/* To be filled by the loader */
 		local-mac-address = [00 10 18 00 00 10];
 	};

From a6bbbf9ceec422221eff338b36629c91ca7faa82 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 6 Feb 2022 21:22:29 +0900
Subject: [PATCH 0024/1027] arm64: dts: apple: Add PCI power enable GPIOs

t8103:
- WLAN (SMC PMU GPIO #13)
t600x:
- WLAN (SMC PMU GPIO #13)
- SD (SMC PMU GPIO #26)

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 3 +++
 2 files changed, 5 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index e3a7ed2706d7c6..2343ea2d057e42 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -185,6 +185,7 @@
 &port00 {
 	/* WLAN */
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: wifi@0,0 {
 		compatible = "pci14e4,4433";
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
@@ -197,6 +198,7 @@
 &port01 {
 	/* SD card reader */
 	bus-range = <2 2>;
+	pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>;
 	sdhci0: mmc@0,0 {
 		compatible = "pci17a0,9755";
 		reg = <0x20000 0x0 0x0 0x0 0x0>;
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index bb2f1efce4a8a4..dea1d0be740468 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -188,6 +188,7 @@
 &port00 {
 	/* WLAN */
 	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 	wifi0: wifi@0,0 {
 		reg = <0x10000 0x0 0x0 0x0 0x0>;
 		compatible = "pci14e4,4433";
@@ -201,6 +202,7 @@
 &port01 {
 	/* SD card reader */
 	bus-range = <2 2>;
+	pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>;
 	sdhci0: mmc@0,0 {
 		compatible = "pci17a0,9755";
 		reg = <0x20000 0x0 0x0 0x0 0x0>;
@@ -223,6 +225,7 @@
 &port03 {
 	/* USB xHCI */
 	bus-range = <4 4>;
+	pwren-gpios = <&smc_gpio 20 GPIO_ACTIVE_HIGH>;
 	status = "okay";
 };
 

From 6cc9a93419aa75ddd84c2683258fb6aabd3dfe3a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 27 Jun 2022 22:21:34 +0900
Subject: [PATCH 0025/1027] arm64: dts: apple: t8103: Fix spi4 power domain
 sort order

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 18c1814585eb24..299b1f51b54179 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -387,6 +387,15 @@
 		power-domains = <&ps_sio>, <&ps_spi_p>;
 	};
 
+	ps_spi4: power-controller@260 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x260 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "spi4";
+		power-domains = <&ps_sio>, <&ps_spi_p>;
+	};
+
 	ps_uart_n: power-controller@268 {
 		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x268 4>;
@@ -558,15 +567,6 @@
 		apple,always-on; /* Memory controller */
 	};
 
-	ps_spi4: power-controller@260 {
-		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
-		reg = <0x260 4>;
-		#power-domain-cells = <0>;
-		#reset-cells = <0>;
-		label = "spi4";
-		power-domains = <&ps_sio>, <&ps_spi_p>;
-	};
-
 	ps_dcs0: power-controller@300 {
 		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x300 4>;

From 4da792a89614eaf811dc39ccdf78e05f166c7316 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Jul 2022 20:05:02 +0900
Subject: [PATCH 0026/1027] arm64: dts: apple: t600x: Add bluetooth device
 trees

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6000-j316s.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6001-j314c.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6001-j316c.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 8 ++++++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 9 +++++++++
 6 files changed, 33 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index ac35870ca129ce..1430b91ff1b152 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -20,3 +20,7 @@
 &wifi0 {
 	brcm,board-type = "apple,maldives";
 };
+
+&bluetooth0 {
+	brcm,board-type = "apple,maldives";
+};
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index 77d6d8c14d741e..da0cbe7d96736b 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -20,3 +20,7 @@
 &wifi0 {
 	brcm,board-type = "apple,madagascar";
 };
+
+&bluetooth0 {
+	brcm,board-type = "apple,madagascar";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index 0a5655792a8f1c..c37097dcfdb304 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -20,3 +20,7 @@
 &wifi0 {
 	brcm,board-type = "apple,maldives";
 };
+
+&bluetooth0 {
+	brcm,board-type = "apple,maldives";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index 9c215531ea543e..3bc6e0c3294cf9 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -20,3 +20,7 @@
 &wifi0 {
 	brcm,board-type = "apple,madagascar";
 };
+
+&bluetooth0 {
+	brcm,board-type = "apple,madagascar";
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 2343ea2d057e42..4fd90bf33e1f9e 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -13,6 +13,7 @@
 
 / {
 	aliases {
+		bluetooth0 = &bluetooth0;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -193,6 +194,13 @@
 		local-mac-address = [00 10 18 00 00 10];
 		apple,antenna-sku = "XX";
 	};
+
+	bluetooth0: network@0,1 {
+		compatible = "pci14e4,5f71";
+		reg = <0x10100 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-bd-address = [00 00 00 00 00 00];
+	};
 };
 
 &port01 {
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index dea1d0be740468..e9260a84695abe 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -11,6 +11,7 @@
 
 / {
 	aliases {
+		bluetooth0 = &bluetooth0;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -197,6 +198,14 @@
 		/* To be filled by the loader */
 		local-mac-address = [00 10 18 00 00 10];
 	};
+
+	bluetooth0: network@0,1 {
+		compatible = "pci14e4,5f71";
+		brcm,board-type = "apple,okinawa";
+		reg = <0x10100 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-bd-address = [00 00 00 00 00 00];
+	};
 };
 
 &port01 {

From ee5a331b920cde717fd58968aac2a36a8101a622 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sat, 19 Feb 2022 09:49:59 +0100
Subject: [PATCH 0027/1027] arm64: dts: apple: t8103*: Put in audio nodes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts | 50 ++++++++++++++
 arch/arm64/boot/dts/apple/t8103-j293.dts | 85 ++++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103-j313.dts | 68 +++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103-j456.dts | 31 +++++++++
 arch/arm64/boot/dts/apple/t8103-j457.dts | 31 +++++++++
 5 files changed, 265 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 968fe22163d443..6782d4bed42995 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -70,6 +70,56 @@
 	status = "okay";
 };
 
+&i2c1 {
+	speaker_amp: codec@31 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x31>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+	};
+};
+
 &i2c2 {
 	status = "okay";
+
+	jack_codec: codec@48 {
+		compatible = "cirrus,cs42l83";
+		reg = <0x48>;
+		reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <183 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+/ {
+	sound {
+		compatible = "apple,j274-macaudio", "apple,macaudio";
+		model = "Mac mini J274 integrated audio";
+
+		dai-link@0 {
+			link-name = "Speaker";
+
+			cpu {
+				sound-dai = <&mca 0>;
+			};
+			codec {
+				sound-dai = <&speaker_amp>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+
+	};
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 0be0437c3ac230..2ab533ae753917 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -71,8 +71,55 @@
 	};
 };
 
+&i2c1 {
+	speaker_left_rear: codec@31 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x31>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Rear";
+	};
+
+	speaker_left_front: codec@32 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x32>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Front";
+	};
+};
+
 &i2c2 {
 	status = "okay";
+
+	jack_codec: codec@48 {
+		compatible = "cirrus,cs42l83";
+		reg = <0x48>;
+		reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <183 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+&i2c3 {
+	speaker_right_rear: codec@34 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x34>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Rear";
+	};
+
+	speaker_right_front: codec@35 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x35>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Front";
+	};
 };
 
 &i2c4 {
@@ -82,3 +129,41 @@
 &fpwm1 {
 	status = "okay";
 };
+/ {
+	sound {
+		compatible = "apple,j293-macaudio", "apple,macaudio";
+		model = "MacBook Pro J293 integrated audio";
+
+		dai-link@0 {
+			link-name = "Speakers";
+
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left_front>, <&speaker_left_rear>,
+					    <&speaker_right_front>, <&speaker_right_rear>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 7b13e16957ead7..c20d7d109fa32d 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -74,3 +74,71 @@
 		interrupts-extended = <&pinctrl_nub 13 IRQ_TYPE_LEVEL_LOW>;
 	};
 };
+
+&i2c1 {
+	speaker_left: codec@31 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x31>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left";
+	};
+};
+
+&i2c3 {
+	speaker_right: codec@34 {
+		compatible = "ti,tas5770l", "ti,tas2770";
+		reg = <0x34>;
+		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right";
+	};
+
+	jack_codec: codec@48 {
+		compatible = "cirrus,cs42l83";
+		reg = <0x48>;
+		reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <183 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+/ {
+	sound {
+		compatible = "apple,j313-macaudio", "apple,macaudio";
+		model = "MacBook Air J313 integrated audio";
+
+		dai-link@0 {
+			link-name = "Speakers";
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left>, <&speaker_right>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index 9983e11cacdf19..fbecc8bcfad3d7 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -87,3 +87,34 @@
 &pcie0_dart_2 {
 	status = "okay";
 };
+
+&i2c1 {
+	jack_codec: codec@48 {
+		compatible = "cirrus,cs42l83";
+		reg = <0x48>;
+		reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <183 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+/ {
+	sound {
+		compatible = "apple,j456-macaudio", "apple,macaudio";
+		model = "iMac J456 integrated audio";
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index a622ff607d4075..bba32fffd378a6 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -60,3 +60,34 @@
 &pcie0_dart_2 {
 	status = "okay";
 };
+
+&i2c1 {
+	jack_codec: codec@48 {
+		compatible = "cirrus,cs42l83";
+		reg = <0x48>;
+		reset-gpios = <&pinctrl_nub 11 GPIO_ACTIVE_HIGH>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <183 IRQ_TYPE_LEVEL_LOW>;
+		#sound-dai-cells = <0>;
+		cirrus,ts-inv = <1>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+/ {
+	sound {
+		compatible = "apple,j457-macaudio", "apple,macaudio";
+		model = "iMac J457 integrated audio";
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};

From 3200a56fc1d5fba32cdd6db6443a59ccab166906 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 11 Mar 2022 22:16:25 +0100
Subject: [PATCH 0028/1027] arm64: dts: apple: t600x-jxxx: Put in audio nodes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts     |   4 +
 arch/arm64/boot/dts/apple/t6000-j316s.dts     |   4 +
 arch/arm64/boot/dts/apple/t6001-j314c.dts     |   4 +
 arch/arm64/boot/dts/apple/t6001-j316c.dts     |   4 +
 arch/arm64/boot/dts/apple/t6001-j375c.dts     |   5 +
 arch/arm64/boot/dts/apple/t6002-j375d.dts     |   5 +
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 117 ++++++++++++++++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     |  60 +++++++++
 8 files changed, 203 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index 1430b91ff1b152..6c016b3b0ff11f 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -24,3 +24,7 @@
 &bluetooth0 {
 	brcm,board-type = "apple,maldives";
 };
+
+&sound {
+	model = "MacBook Pro J314 integrated audio";
+};
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index da0cbe7d96736b..d20630560e71c2 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -24,3 +24,7 @@
 &bluetooth0 {
 	brcm,board-type = "apple,madagascar";
 };
+
+&sound {
+	model = "MacBook Pro J316 integrated audio";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index c37097dcfdb304..ef5846baf7fdd6 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -24,3 +24,7 @@
 &bluetooth0 {
 	brcm,board-type = "apple,maldives";
 };
+
+&sound {
+	model = "MacBook Pro J314 integrated audio";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index 3bc6e0c3294cf9..df0cee05a708a4 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -24,3 +24,7 @@
 &bluetooth0 {
 	brcm,board-type = "apple,madagascar";
 };
+
+&sound {
+	model = "MacBook Pro J316 integrated audio";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts
index 62ea437b58b25c..6e08f48490b380 100644
--- a/arch/arm64/boot/dts/apple/t6001-j375c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts
@@ -16,3 +16,8 @@
 	compatible = "apple,j375c", "apple,t6001", "apple,arm-platform";
 	model = "Apple Mac Studio (M1 Max, 2022)";
 };
+
+&sound {
+	compatible = "apple,j375-macaudio", "apple,macaudio";
+	model = "Mac Studio J375";
+};
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index d3d4f4461deecb..75eb1b2ec934cd 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -17,6 +17,11 @@
 	model = "Apple Mac Studio (M1 Ultra, 2022)";
 };
 
+&sound {
+	compatible = "apple,j375-macaudio", "apple,macaudio";
+	model = "Mac Studio J375";
+};
+
 /* USB Type C */
 &i2c0 {
 	/* front-right */
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 4fd90bf33e1f9e..06f009dfa373b9 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -145,6 +145,81 @@
 	};
 };
 
+&i2c1 {
+	status = "okay";
+
+	speaker_left_tweet: codec@3a {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3a>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Tweeter";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	speaker_left_woof1: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Woofer 1";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	speaker_left_woof2: codec@39 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x39>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Woofer 2";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
+&i2c2 {
+	status = "okay";
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+&i2c3 {
+	status = "okay";
+
+	speaker_right_tweet: codec@3d {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3d>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Tweeter";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	speaker_right_woof1: codec@3b {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3b>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Woofer 1";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	speaker_right_woof2: codec@3c {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3c>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Woofer 2";
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
 &nco_clkref {
 	clock-frequency = <1068000000>;
 };
@@ -256,3 +331,45 @@
 &dwc3_3 {
 	status = "disabled";
 };
+
+/ {
+	sound: sound {
+		compatible = "apple,j314-macaudio", "apple,macaudio";
+
+		dai-link@0 {
+			link-name = "Speakers";
+
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left_woof1>,
+					    <&speaker_left_tweet>,
+					    <&speaker_left_woof2>,
+					    <&speaker_right_woof1>,
+					    <&speaker_right_tweet>,
+					    <&speaker_right_woof2>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index e9260a84695abe..6023105f2f5868 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -181,10 +181,70 @@
 	};
 };
 
+/* Audio */
+&i2c1 {
+	status = "okay";
+
+	speaker: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+	};
+};
+
+&i2c2 {
+	status = "okay";
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
 &nco_clkref {
 	clock-frequency = <1068000000>;
 };
 
+/ {
+	sound: sound {
+		/* compatible is set per machine */
+
+		dai-link@0 {
+			link-name = "Speaker";
+			/*
+			* DANGER ZONE: You can blow your speakers!
+			*
+			* The drivers are not ready, and unless you are careful
+			* to attenuate the audio stream, you run the risk of
+			* blowing your speakers.
+			*/
+			status = "disabled";
+			cpu {
+				sound-dai = <&mca 0>;
+			};
+			codec {
+				sound-dai = <&speaker>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
+
 /* PCIe devices */
 &port00 {
 	/* WLAN */

From ccca99a2aff57fadf4a724c94f7453c34b8a5c83 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 30 Aug 2022 09:59:59 +0200
Subject: [PATCH 0029/1027] arm64: dts: apple: Drop 'integrated audio' from
 sound models
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Even though my preference would be to keep it in, the long name crops in
a bunch of places and the verbiage at the end needs to go.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts | 2 +-
 arch/arm64/boot/dts/apple/t6000-j316s.dts | 2 +-
 arch/arm64/boot/dts/apple/t6001-j314c.dts | 2 +-
 arch/arm64/boot/dts/apple/t6001-j316c.dts | 2 +-
 arch/arm64/boot/dts/apple/t8103-j274.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8103-j293.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8103-j313.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8103-j456.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8103-j457.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8112-j413.dts  | 2 +-
 arch/arm64/boot/dts/apple/t8112-j493.dts  | 2 +-
 11 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index 6c016b3b0ff11f..b514b1114c4d24 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -26,5 +26,5 @@
 };
 
 &sound {
-	model = "MacBook Pro J314 integrated audio";
+	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index d20630560e71c2..7f5c91bb5f3210 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -26,5 +26,5 @@
 };
 
 &sound {
-	model = "MacBook Pro J316 integrated audio";
+	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index ef5846baf7fdd6..35ff978b998702 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -26,5 +26,5 @@
 };
 
 &sound {
-	model = "MacBook Pro J314 integrated audio";
+	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index df0cee05a708a4..6a35c87aa7ab02 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -26,5 +26,5 @@
 };
 
 &sound {
-	model = "MacBook Pro J316 integrated audio";
+	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 6782d4bed42995..b214075000fb8a 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -97,7 +97,7 @@
 / {
 	sound {
 		compatible = "apple,j274-macaudio", "apple,macaudio";
-		model = "Mac mini J274 integrated audio";
+		model = "Mac mini J274";
 
 		dai-link@0 {
 			link-name = "Speaker";
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 2ab533ae753917..3c6ee9132fe61e 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -132,7 +132,7 @@
 / {
 	sound {
 		compatible = "apple,j293-macaudio", "apple,macaudio";
-		model = "MacBook Pro J293 integrated audio";
+		model = "MacBook Pro J293";
 
 		dai-link@0 {
 			link-name = "Speakers";
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index c20d7d109fa32d..6e96f7a4e77ae1 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -109,7 +109,7 @@
 / {
 	sound {
 		compatible = "apple,j313-macaudio", "apple,macaudio";
-		model = "MacBook Air J313 integrated audio";
+		model = "MacBook Air J313";
 
 		dai-link@0 {
 			link-name = "Speakers";
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index fbecc8bcfad3d7..7cf103418455a8 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -104,7 +104,7 @@
 / {
 	sound {
 		compatible = "apple,j456-macaudio", "apple,macaudio";
-		model = "iMac J456 integrated audio";
+		model = "iMac J456";
 
 		dai-link@1 {
 			link-name = "Headphone Jack";
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index bba32fffd378a6..3969d7449f5c13 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -77,7 +77,7 @@
 / {
 	sound {
 		compatible = "apple,j457-macaudio", "apple,macaudio";
-		model = "iMac J457 integrated audio";
+		model = "iMac J457";
 
 		dai-link@1 {
 			link-name = "Headphone Jack";
diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index bce2774ff0a406..c4ae6b5a6c7519 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -140,7 +140,7 @@
 / {
 	sound {
 		compatible = "apple,j413-macaudio", "apple,macaudio";
-		model = "MacBook Air J413 integrated audio";
+		model = "MacBook Air J413";
 
 		dai-link@0 {
 			link-name = "Speakers";
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 919b0762b723b5..45302227cf4be7 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -129,7 +129,7 @@
 / {
 	sound {
 		compatible = "apple,j493-macaudio", "apple,macaudio";
-		model = "MacBook Pro J493 integrated audio";
+		model = "MacBook Pro J493";
 
 		dai-link@0 {
 			link-name = "Speakers";

From c8e54b3d74dcb1a17cd748330d10e8e155654ba4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 17 Oct 2022 18:29:28 +0900
Subject: [PATCH 0030/1027] arm64: dts: apple: t6001-j375c: Add USB3 hub GPIO
 initialization

The Mac Studio M1 Max (t6001) model has a built-in USB3 hub. This hub
has a firmware flash which is also connected to an AP SPI controller.
The hub starts out in reset and the host is expected to bring it out of
reset, potentially after upgrading/validating the firmware.

We won't be doing anything with the firmware, so just use gpio-hog to
flip the two GPIOs needed to bring up the hub chip.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6001-j375c.dts | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts
index 6e08f48490b380..a71b1ebb29d956 100644
--- a/arch/arm64/boot/dts/apple/t6001-j375c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts
@@ -21,3 +21,19 @@
 	compatible = "apple,j375-macaudio", "apple,macaudio";
 	model = "Mac Studio J375";
 };
+
+&pinctrl_ap {
+	usb_hub_oe-hog {
+		gpio-hog;
+		gpios = <230 0>;
+		input;
+		line-name = "usb-hub-oe";
+	};
+
+	usb_hub_rst-hog {
+		gpio-hog;
+		gpios = <231 GPIO_ACTIVE_LOW>;
+		output-low;
+		line-name = "usb-hub-rst";
+	};
+};

From 3cdb8a014a16635e129d01eec84ee01aaf8c7134 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 25 Jan 2022 21:50:59 +0100
Subject: [PATCH 0031/1027] arch: arm64: apple: Add missing power state deps
 for display

The dcp co-processor crashes on HDMI unplug while it apparently tries
to notify pmp. Handle "notify_pmp" as a parent dependency for
"ps_disp0_fe" and "ps_dispext_fe".

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 299b1f51b54179..0966322c5c8e3f 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -645,7 +645,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "disp0_fe";
-		power-domains = <&ps_rmx>;
+		power-domains = <&ps_rmx>, <&ps_pmp>;
 		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
@@ -655,7 +655,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "dispext_fe";
-		power-domains = <&ps_rmx>;
+		power-domains = <&ps_rmx>, <&ps_pmp>;
 	};
 
 	ps_dispext_cpu0: power-controller@378 {

From e6bd75c4db804e4bf5474458dd48c41fe3ca8182 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 24 Apr 2022 11:20:31 +0200
Subject: [PATCH 0032/1027] arch: arm64: apple: t600x: Mark USB and PCIe as
 "dma-coherent"

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 4 ++++
 2 files changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 152c878ab3e005..40931fe6c93651 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -435,6 +435,8 @@
 		pinctrl-0 = <&pcie_pins>;
 		pinctrl-names = "default";
 
+		dma-coherent;
+
 		port00: pci@0,0 {
 			device_type = "pci";
 			reg = <0x0 0x0 0x0 0x0 0x0>;
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index a5f2ef5aab7929..5c25d843284ca0 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -149,6 +149,7 @@
 		iommus = <&DIE_NODE(dwc3_0_dart_0) 0>,
 			<&DIE_NODE(dwc3_0_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		dma-coherent;
 	};
 
 	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
@@ -180,6 +181,7 @@
 		iommus = <&DIE_NODE(dwc3_1_dart_0) 0>,
 			<&DIE_NODE(dwc3_1_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		dma-coherent;
 	};
 
 	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
@@ -211,6 +213,7 @@
 		iommus = <&DIE_NODE(dwc3_2_dart_0) 0>,
 			<&DIE_NODE(dwc3_2_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		dma-coherent;
 	};
 
 	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
@@ -242,4 +245,5 @@
 		iommus = <&DIE_NODE(dwc3_3_dart_0) 0>,
 			<&DIE_NODE(dwc3_3_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		dma-coherent;
 	};

From dfd6790099317f27396a15401cb5cee9118c3082 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 20 Sep 2021 02:27:09 +0900
Subject: [PATCH 0033/1027] arch: arm64: apple: Add display controller related
 device tree nodes

The display system is initialized by the bootloader to provide a simple
framebuffer at startup. Memory for the framebuffer and heap for the
display co-processor are alreay mapped through the IOMMU. IOMMU
intialization must preserve this mappings to avoid crashing the display
co-processor. The exisitng mappings are caried in the devicetree. They
are applied during device attach to ensure the IOMMU framework is aware
of these mapping.

Mappings are filled by m1n1 during boot.

Based on https://lore.kernel.org/asahi/20220923123557.866972-1-thierry.reding@gmail.com

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 10 +++
 arch/arm64/boot/dts/apple/t8103.dtsi      | 82 +++++++++++++++++++++++
 2 files changed, 92 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index b70cd5f64b4cc6..727cbd8f206d12 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -12,6 +12,9 @@
 / {
 	aliases {
 		bluetooth0 = &bluetooth0;
+		dcp = &dcp;
+		disp0 = &display;
+		disp0_piodma = &disp0_piodma;
 		serial0 = &serial0;
 		serial2 = &serial2;
 		wifi0 = &wifi0;
@@ -32,6 +35,13 @@
 		};
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		/* To be filled by loader */
+	};
+
 	memory@800000000 {
 		device_type = "memory";
 		reg = <0x8 0 0x2 0>; /* To be filled by loader */
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index db00ef3de56385..c40a07feaf7b55 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -334,6 +334,14 @@
 		clock-output-names = "clk_120m";
 	};
 
+	/* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
+	clk_disp0: clock-disp0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <533333328>;
+		clock-output-names = "clk_disp0";
+	};
+
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
@@ -364,6 +372,72 @@
 			#performance-domain-cells = <0>;
 		};
 
+		disp0_dart: iommu@231304000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x31304000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
+			status = "disabled";
+		};
+
+		dcp_dart: iommu@23130c000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x3130c000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
+		dcp_mbox: mbox@231c08000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x31c08000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 427 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 428 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 429 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 430 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_disp0_cpu0>;
+			resets = <&ps_disp0_cpu0>;
+		};
+
+		dcp: dcp@231c00000 {
+			compatible = "apple,t8103-dcp", "apple,dcp";
+			mboxes = <&dcp_mbox>;
+			mbox-names = "mbox";
+			iommus = <&dcp_dart 0>;
+
+			reg-names = "coproc", "disp-0", "disp-1", "disp-2",
+				"disp-3", "disp-4";
+			reg = <0x2 0x31c00000 0x0 0x4000>,
+				<0x2 0x30000000 0x0 0x3e8000>,
+				<0x2 0x31320000 0x0 0x4000>,
+				<0x2 0x31344000 0x0 0x4000>,
+				<0x2 0x31800000 0x0 0x800000>,
+				<0x2 0x3b3d0000 0x0 0x4000>;
+			apple,bw-scratch = <&pmgr_dcp 0 5 0x14>;
+			apple,bw-doorbell = <&pmgr_dcp 1 6>;
+			power-domains = <&ps_disp0_cpu0>;
+			clocks = <&clk_disp0>;
+			apple,asc-dram-mask = <0xf 0x00000000>;
+			phandle = <&dcp>;
+
+			disp0_piodma: piodma {
+				iommus = <&disp0_dart 4>;
+				phandle = <&disp0_piodma>;
+			};
+		};
+
+		display: display-subsystem {
+			compatible = "apple,display-subsystem";
+			iommus = <&disp0_dart 0>;
+			/* generate phandle explicitly for use in loader */
+			phandle = <&display>;
+		};
+
 		sio_dart: iommu@235004000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x2 0x35004000 0x0 0x4000>;
@@ -573,6 +647,14 @@
 			reg = <0x2 0x3b700000 0 0x14000>;
 		};
 
+		pmgr_dcp: power-management@23b738000 {
+			reg = <0x2 0x3b738000 0x0 0x1000>,
+				<0x2 0x3bc3c000 0x0 0x1000>;
+			reg-names = "dcp-bw-scratch", "dcp-bw-doorbell";
+			#apple,bw-scratch-cells = <3>;
+			#apple,bw-doorbell-cells = <2>;
+		};
+
 		pinctrl_ap: pinctrl@23c100000 {
 			compatible = "apple,t8103-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3c100000 0x0 0x100000>;

From 97615144dc9cf76cda0a049562222c187f73e68f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 11 Mar 2022 22:14:52 +0100
Subject: [PATCH 0034/1027] arch: arm64: apple: t600x: Add display controller
 related device tree nodes

The display system is initialized by the bootloader to provide a simple
framebuffer at startup. Memory for the framebuffer and heap for the
display co-processor are alreay mapped through the IOMMU. IOMMU
intialization must preserve this mappings to avoid crashing the display
co-processor. The exisitng mappings are caried in the devicetree. They
are applied during device attach to ensure the IOMMU framework is aware
of these mapping.

Mappings are filled by m1n1 during boot.

Based on https://lore.kernel.org/asahi/20220923123557.866972-1-thierry.reding@gmail.com

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-common.dtsi   |  6 ++
 arch/arm64/boot/dts/apple/t600x-die0.dtsi     | 68 +++++++++++++++++++
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 10 +++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     | 10 +++
 4 files changed, 94 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index 87dfc13d74171f..01385ef831ca91 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -369,6 +369,12 @@
 		clock-output-names = "clk_200m";
 	};
 
+	clk_disp0: clock-disp0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <237333328>;
+		clock-output-names = "clk_disp0";
+	};
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 40931fe6c93651..1b795ddbe552d5 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -24,6 +24,12 @@
 		power-domains = <&ps_aic>;
 	};
 
+	pmgr_dcp: power-management@28e3d0000 {
+		reg = <0x2 0x8e3d0000 0x0 0x4000>;
+		reg-names = "dcp-fw-pmgr";
+		#apple,bw-scratch-cells = <3>;
+	};
+
 	smc_mbox: mbox@290408000 {
 		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
 		reg = <0x2 0x90408000 0x0 0x4000>;
@@ -166,6 +172,68 @@
 		};
 	};
 
+	disp0_dart: iommu@38b304000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x8b304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
+		status = "disabled";
+	};
+
+	dcp_dart: iommu@38b30c000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x8b30c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	dcp_mbox: mbox@38bc08000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x8bc08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 842 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 843 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 844 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 845 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&ps_disp0_cpu0>;
+	};
+
+	dcp: dcp@38bc00000 {
+		compatible = "apple,t6000-dcp", "apple,dcp";
+		mboxes = <&dcp_mbox>;
+		mbox-names = "mbox";
+		iommus = <&dcp_dart 0>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x3 0x8bc00000 0x0 0x4000>,
+			<0x3 0x8a000000 0x0 0x3000000>,
+			<0x3 0x8b320000 0x0 0x4000>,
+			<0x3 0x8b344000 0x0 0x4000>,
+			<0x3 0x8b800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x988>;
+		power-domains = <&ps_disp0_cpu0>;
+		clocks = <&clk_disp0>;
+		apple,asc-dram-mask = <0x1f0 0x00000000>;
+		phandle = <&dcp>;
+
+		disp0_piodma: piodma {
+			iommus = <&disp0_dart 4>;
+			phandle = <&disp0_piodma>;
+		};
+	};
+
+	display: display-subsystem {
+		compatible = "apple,display-subsystem";
+		iommus = <&disp0_dart 0>;
+		/* generate phandle explicitly for use in loader */
+		phandle = <&display>;
+	};
+
 	sio_dart_0: iommu@39b004000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x3 0x9b004000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 06f009dfa373b9..bf6e6fab79e702 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -14,6 +14,9 @@
 / {
 	aliases {
 		bluetooth0 = &bluetooth0;
+		dcp = &dcp;
+		disp0 = &display;
+		disp0_piodma = &disp0_piodma;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -33,6 +36,13 @@
 		};
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		/* To be filled by loader */
+	};
+
 	memory@10000000000 {
 		device_type = "memory";
 		reg = <0x100 0 0x2 0>; /* To be filled by loader */
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 6023105f2f5868..1e6ac6e2bbfcc9 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -12,6 +12,9 @@
 / {
 	aliases {
 		bluetooth0 = &bluetooth0;
+		dcp = &dcp;
+		disp0 = &display;
+		disp0_piodma = &disp0_piodma;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -31,6 +34,13 @@
 		};
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		/* To be filled by loader */
+	};
+
 	memory@10000000000 {
 		device_type = "memory";
 		reg = <0x100 0 0x2 0>; /* To be filled by loader */

From f4fe44de2f662af2ada5d7829650c5feb2d3b6be Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 3 Oct 2022 17:44:37 +0200
Subject: [PATCH 0035/1027] arch: arm64: apple: t8103: Add connector type
 property for DCP*

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j293.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j313.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j456.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j457.dts | 4 ++++
 5 files changed, 20 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index b214075000fb8a..7169563c1b351a 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -21,6 +21,10 @@
 	};
 };
 
+&dcp {
+	apple,connector-type = "HDMI-A";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,atlantisb";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 3c6ee9132fe61e..1b87dc8fafea34 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -30,6 +30,10 @@
 	};
 };
 
+&dcp {
+	apple,connector-type = "eDP";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,honshu";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 6e96f7a4e77ae1..5061cc5d09e0da 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -30,6 +30,10 @@
 	};
 };
 
+&dcp {
+	apple,connector-type = "eDP";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,shikoku";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index 7cf103418455a8..ca74b7f87c0c3b 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -21,6 +21,10 @@
 	};
 };
 
+&dcp {
+	apple,connector-type = "eDP";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,capri";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 3969d7449f5c13..2f248178ce5885 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -21,6 +21,10 @@
 	};
 };
 
+&dcp {
+	apple,connector-type = "eDP";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,santorini";
 };

From 5f5dae232e106ffffbe223865f350213f0dfef4d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 22 Oct 2022 09:33:04 +0200
Subject: [PATCH 0036/1027] arch: arm64: apple: t600x: Add connector type
 property for DCP*

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 ++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index bf6e6fab79e702..74f452f3ecd367 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -65,6 +65,10 @@
 	status = "okay";
 };
 
+&dcp {
+	apple,connector-type = "eDP";
+};
+
 /* USB Type C */
 &i2c0 {
 	hpm0: usb-pd@38 {
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 1e6ac6e2bbfcc9..b86abaa21b546a 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -51,6 +51,10 @@
 	status = "okay";
 };
 
+&dcp {
+	apple,connector-type = "HDMI-A";
+};
+
 /* USB Type C */
 &i2c0 {
 	hpm0: usb-pd@38 {

From 1f18334cfd0235bad1db07ce919622f9bd1174e6 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Tue, 15 Nov 2022 12:09:48 +0100
Subject: [PATCH 0037/1027] arm64: dts: apple: t8103: Add eFuses node

---
 arch/arm64/boot/dts/apple/t8103.dtsi | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index c40a07feaf7b55..daade68a322a0d 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -941,6 +941,13 @@
 			resets = <&ps_ans2>;
 		};
 
+		efuse@23d2bc000 {
+			compatible = "apple,t8103-efuses", "apple,efuses";
+			reg = <0x2 0x3d2bc000 0x0 0x1000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+		};
+
 		dwc3_0: usb@382280000 {
 			compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
 			reg = <0x3 0x82280000 0x0 0x100000>;

From c9c792e84acc0ea932312c992843b35b59a6266b Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:11:09 +0100
Subject: [PATCH 0038/1027] arm64: dts: apple: t8103: Add ATCPHY node

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi |  31 ++++
 arch/arm64/boot/dts/apple/t8103.dtsi      | 179 ++++++++++++++++++++++
 2 files changed, 210 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 727cbd8f206d12..1ad2a57698afb9 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -18,6 +18,8 @@
 		serial0 = &serial0;
 		serial2 = &serial2;
 		wifi0 = &wifi0;
+		atcphy0 = &atcphy0;
+		atcphy1 = &atcphy1;
 	};
 
 	chosen {
@@ -78,6 +80,12 @@
 						remote-endpoint = <&typec0_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec0_con_ss: endpoint {
+						remote-endpoint = <&typec0_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -103,6 +111,12 @@
 						remote-endpoint = <&typec1_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec1_con_ss: endpoint {
+						remote-endpoint = <&typec1_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -125,6 +139,23 @@
 	};
 };
 
+/* Type-C PHYs */
+&atcphy0 {
+	port {
+		typec0_usb_ss: endpoint {
+			remote-endpoint = <&typec0_con_ss>;
+		};
+	};
+};
+
+&atcphy1 {
+	port {
+		typec1_usb_ss: endpoint {
+			remote-endpoint = <&typec1_con_ss>;
+		};
+	};
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index daade68a322a0d..4c64d4a3cd9263 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -12,6 +12,7 @@
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
 #include <dt-bindings/spmi/spmi.h>
+#include <dt-bindings/phy/phy.h>
 
 / {
 	compatible = "apple,t8103", "apple,arm-platform";
@@ -946,6 +947,100 @@
 			reg = <0x2 0x3d2bc000 0x0 0x1000>;
 			#address-cells = <1>;
 			#size-cells = <1>;
+			atcphy0_auspll_rodco_bias_adjust: efuse@430,26 {
+				reg = <0x430 4>;
+				bits = <26 3>;
+			};
+
+			atcphy0_auspll_rodco_encap: efuse@430,29 {
+				reg = <0x430 4>;
+				bits = <29 2>;
+			};
+
+			atcphy0_auspll_dtc_vreg_adjust: efuse@430,31 {
+				reg = <0x430 8>;
+				bits = <31 3>;
+			};
+
+			atcphy0_auspll_fracn_dll_start_capcode: efuse@434,2 {
+				reg = <0x434 4>;
+				bits = <2 2>;
+			};
+
+			atcphy0_aus_cmn_shm_vreg_trim: efuse@434,4 {
+				reg = <0x434 4>;
+				bits = <4 5>;
+			};
+
+			atcphy0_cio3pll_dco_coarsebin0: efuse@434,9 {
+				reg = <0x434 4>;
+				bits = <9 6>;
+			};
+
+			atcphy0_cio3pll_dco_coarsebin1: efuse@434,15 {
+				reg = <0x434 4>;
+				bits = <15 6>;
+			};
+
+			atcphy0_cio3pll_dll_start_capcode: efuse@434,21 {
+				reg = <0x434 4>;
+				bits = <21 2>;
+			};
+
+			atcphy0_cio3pll_dtc_vreg_adjust: efuse@434,23 {
+				reg = <0x434 0x4>;
+				bits = <23 3>;
+			};
+
+			atcphy1_auspll_rodco_bias_adjust: efuse@438,4 {
+				reg = <0x438 4>;
+				bits = <4 3>;
+			};
+
+			atcphy1_auspll_rodco_encap: efuse@438,7 {
+				reg = <0x438 4>;
+				bits = <7 2>;
+			};
+
+			atcphy1_auspll_dtc_vreg_adjust: efuse@438,9 {
+				reg = <0x438 4>;
+				bits = <9 3>;
+			};
+
+			atcphy1_auspll_fracn_dll_start_capcode: efuse@438,12 {
+				reg = <0x438 4>;
+				bits = <12 2>;
+			};
+
+			atcphy1_aus_cmn_shm_vreg_trim: efuse@438,14 {
+				reg = <0x438 4>;
+				bits = <14 5>;
+			};
+
+			atcphy1_cio3pll_dco_coarsebin0: efuse@438,19 {
+				reg = <0x438 4>;
+				bits = <19 6>;
+			};
+
+			atcphy1_cio3pll_dco_coarsebin1: efuse@438,25 {
+				reg = <0x438 4>;
+				bits = <25 6>;
+			};
+
+			atcphy1_cio3pll_dll_start_capcode: efuse@438,31 {
+				reg = <0x438 4>;
+				bits = <31 1>;
+			};
+
+			atcphy1_cio3pll_dll_start_capcode_workaround: efuse@43c,0 {
+				reg = <0x43c 0x4>;
+				bits = <0 1>;
+			};
+
+			atcphy1_cio3pll_dtc_vreg_adjust: efuse@43c,1 {
+				reg = <0x43c 0x4>;
+				bits = <1 3>;
+			};
 		};
 
 		dwc3_0: usb@382280000 {
@@ -958,6 +1053,9 @@
 			role-switch-default-mode = "host";
 			iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>;
 			power-domains = <&ps_atc0_usb>;
+			resets = <&atcphy0>;
+			phys = <&atcphy0 PHY_TYPE_USB2>, <&atcphy0 PHY_TYPE_USB3>;
+			phy-names = "usb2-phy", "usb3-phy";
 		};
 
 		dwc3_0_dart_0: iommu@382f00000 {
@@ -978,6 +1076,44 @@
 			power-domains = <&ps_atc0_usb>;
 		};
 
+		atcphy0: phy@383000000 {
+			compatible = "apple,t8103-atcphy";
+			reg = <0x3 0x83000000 0x0 0x4c000>,
+				<0x3 0x83050000 0x0 0x8000>,
+				<0x3 0x80000000 0x0 0x4000>,
+				<0x3 0x82a90000 0x0 0x4000>,
+				<0x3 0x82a84000 0x0 0x4000>;
+			reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+				"pipehandler";
+
+			#phy-cells = <1>;
+			#reset-cells = <0>;
+
+			nvmem-cells = <&atcphy0_aus_cmn_shm_vreg_trim>,
+				<&atcphy0_auspll_rodco_encap>,
+				<&atcphy0_auspll_rodco_bias_adjust>,
+				<&atcphy0_auspll_fracn_dll_start_capcode>,
+				<&atcphy0_auspll_dtc_vreg_adjust>,
+				<&atcphy0_cio3pll_dco_coarsebin0>,
+				<&atcphy0_cio3pll_dco_coarsebin1>,
+				<&atcphy0_cio3pll_dll_start_capcode>,
+				<&atcphy0_cio3pll_dtc_vreg_adjust>;
+			nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+				"auspll_rodco_encap",
+				"auspll_rodco_bias_adjust",
+				"auspll_fracn_dll_start_capcode",
+				"auspll_dtc_vreg_adjust",
+				"cio3pll_dco_coarsebin0",
+				"cio3pll_dco_coarsebin1",
+				"cio3pll_dll_start_capcode",
+				"cio3pll_dtc_vreg_adjust";
+
+			orientation-switch;
+			mode-switch;
+			svid = <0xff01>, <0x8087>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
 		dwc3_1: usb@502280000 {
 			compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
 			reg = <0x5 0x02280000 0x0 0x100000>;
@@ -988,6 +1124,9 @@
 			role-switch-default-mode = "host";
 			iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>;
 			power-domains = <&ps_atc1_usb>;
+			resets = <&atcphy1>;
+			phys = <&atcphy1 PHY_TYPE_USB2>, <&atcphy1 PHY_TYPE_USB3>;
+			phy-names = "usb2-phy", "usb3-phy";
 		};
 
 		dwc3_1_dart_0: iommu@502f00000 {
@@ -1008,6 +1147,46 @@
 			power-domains = <&ps_atc1_usb>;
 		};
 
+		atcphy1: phy@503000000 {
+			compatible = "apple,t8103-atcphy";
+			reg = <0x5 0x03000000 0x0 0x4c000>,
+				<0x5 0x03050000 0x0 0x8000>,
+				<0x5 0x0 0x0 0x4000>,
+				<0x5 0x02a90000 0x0 0x4000>,
+				<0x5 0x02a84000 0x0 0x4000>;
+			reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+				"pipehandler";
+
+			nvmem-cells = <&atcphy1_aus_cmn_shm_vreg_trim>,
+				<&atcphy1_auspll_rodco_encap>,
+				<&atcphy1_auspll_rodco_bias_adjust>,
+				<&atcphy1_auspll_fracn_dll_start_capcode>,
+				<&atcphy1_auspll_dtc_vreg_adjust>,
+				<&atcphy1_cio3pll_dco_coarsebin0>,
+				<&atcphy1_cio3pll_dco_coarsebin1>,
+				<&atcphy1_cio3pll_dll_start_capcode>,
+				<&atcphy1_cio3pll_dtc_vreg_adjust>,
+				<&atcphy1_cio3pll_dll_start_capcode_workaround>;
+			nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+				"auspll_rodco_encap",
+				"auspll_rodco_bias_adjust",
+				"auspll_fracn_dll_start_capcode",
+				"auspll_dtc_vreg_adjust",
+				"cio3pll_dco_coarsebin0",
+				"cio3pll_dco_coarsebin1",
+				"cio3pll_dll_start_capcode",
+				"cio3pll_dtc_vreg_adjust",
+				"cio3pll_dll_start_capcode_workaround";
+
+			#phy-cells = <1>;
+			#reset-cells = <0>;
+
+			orientation-switch;
+			mode-switch;
+			svid = <0xff01>, <0x8087>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
 		pcie0_dart_0: iommu@681008000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x6 0x81008000 0x0 0x4000>;

From 4402ced82dd7576a1d4c663c785dd330cd13db9e Mon Sep 17 00:00:00 2001
From: R <rqou@berkeley.edu>
Date: Tue, 15 Nov 2022 12:09:52 +0100
Subject: [PATCH 0039/1027] arch: arm64: dts: apple: t6000: Add eFuses node

Signed-off-by: R <rqou@berkeley.edu>
---
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index 5c25d843284ca0..d135a437d513ce 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -74,6 +74,13 @@
 		reg = <0x2 0x92280000 0 0x4000>;
 	};
 
+	DIE_NODE(efuse): efuse@2922bc000 {
+		compatible = "apple,t6000-efuses", "apple,efuses";
+		reg = <0x2 0x922bc000 0x0 0x2000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+	};
+
 	DIE_NODE(pinctrl_aop): pinctrl@293820000 {
 		compatible = "apple,t6000-pinctrl", "apple,pinctrl";
 		reg = <0x2 0x93820000 0x0 0x4000>;

From 868cc94f9f1b326fa41b621d7584a66531b28e3a Mon Sep 17 00:00:00 2001
From: R <rqou@berkeley.edu>
Date: Tue, 15 Nov 2022 12:09:54 +0100
Subject: [PATCH 0040/1027] arch: arm64: dts: apple: t600x: Add ATCPHY nodes

Signed-off-by: R <rqou@berkeley.edu>
---
 arch/arm64/boot/dts/apple/t6001.dtsi          |   1 +
 arch/arm64/boot/dts/apple/t6002-j375d.dts     |  35 ++
 arch/arm64/boot/dts/apple/t6002.dtsi          |   1 +
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi     | 352 +++++++++++++++++-
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi |  47 +++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     |  61 +++
 6 files changed, 493 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi
index d2cf81926f284c..0bdd1966f5302e 100644
--- a/arch/arm64/boot/dts/apple/t6001.dtsi
+++ b/arch/arm64/boot/dts/apple/t6001.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/phy/phy.h>
 #include <dt-bindings/spmi/spmi.h>
 
 #include "multi-die-cpp.h"
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index 75eb1b2ec934cd..95a783b9fb144a 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -15,6 +15,10 @@
 / {
 	compatible = "apple,j375d", "apple,t6002", "apple,arm-platform";
 	model = "Apple Mac Studio (M1 Ultra, 2022)";
+	aliases {
+		atcphy4 = &atcphy0_die1;
+		atcphy5 = &atcphy1_die1;
+	};
 };
 
 &sound {
@@ -47,6 +51,12 @@
 						remote-endpoint = <&typec4_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec4_con_ss: endpoint {
+						remote-endpoint = <&typec4_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -74,6 +84,12 @@
 						remote-endpoint = <&typec5_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec5_con_ss: endpoint {
+						remote-endpoint = <&typec5_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -96,15 +112,34 @@
 	};
 };
 
+/* Type-C PHYs */
+&atcphy0_die1 {
+	port {
+		typec4_usb_ss: endpoint {
+			remote-endpoint = <&typec4_con_ss>;
+		};
+	};
+};
+
+&atcphy1_die1 {
+	port {
+		typec5_usb_ss: endpoint {
+			remote-endpoint = <&typec5_con_ss>;
+		};
+	};
+};
+
 /* delete unused USB nodes on die 1 */
 
 /delete-node/ &dwc3_2_dart_0_die1;
 /delete-node/ &dwc3_2_dart_1_die1;
 /delete-node/ &dwc3_2_die1;
+/delete-node/ &atcphy2_die1;
 
 /delete-node/ &dwc3_3_dart_0_die1;
 /delete-node/ &dwc3_3_dart_1_die1;
 /delete-node/ &dwc3_3_die1;
+/delete-node/ &atcphy3_die1;
 
 
 /* delete unused always-on power-domains on die 1 */
diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi
index e36f422d257d8f..8fa2d8dd72ff7f 100644
--- a/arch/arm64/boot/dts/apple/t6002.dtsi
+++ b/arch/arm64/boot/dts/apple/t6002.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/phy/phy.h>
 #include <dt-bindings/spmi/spmi.h>
 
 #include "multi-die-cpp.h"
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index d135a437d513ce..3fca8efb2dcf17 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -79,6 +79,186 @@
 		reg = <0x2 0x922bc000 0x0 0x2000>;
 		#address-cells = <1>;
 		#size-cells = <1>;
+
+		DIE_NODE(atcphy0_auspll_rodco_bias_adjust): efuse@a10,22 {
+			reg = <0xa10 4>;
+			bits = <22 3>;
+		};
+
+		DIE_NODE(atcphy0_auspll_rodco_encap): efuse@a10,25 {
+			reg = <0xa10 4>;
+			bits = <25 2>;
+		};
+
+		DIE_NODE(atcphy0_auspll_dtc_vreg_adjust): efuse@a10,27 {
+			reg = <0xa10 4>;
+			bits = <27 3>;
+		};
+
+		DIE_NODE(atcphy0_auspll_fracn_dll_start_capcode): efuse@a10,30 {
+			reg = <0xa10 4>;
+			bits = <30 2>;
+		};
+
+		DIE_NODE(atcphy0_aus_cmn_shm_vreg_trim): efuse@a14,0 {
+			reg = <0xa14 4>;
+			bits = <0 5>;
+		};
+
+		DIE_NODE(atcphy0_cio3pll_dco_coarsebin0): efuse@a14,5 {
+			reg = <0xa14 4>;
+			bits = <5 6>;
+		};
+
+		DIE_NODE(atcphy0_cio3pll_dco_coarsebin1): efuse@a14,11 {
+			reg = <0xa14 4>;
+			bits = <11 6>;
+		};
+
+		DIE_NODE(atcphy0_cio3pll_dll_start_capcode): efuse@a14,17 {
+			reg = <0xa14 4>;
+			bits = <17 2>;
+		};
+
+		DIE_NODE(atcphy0_cio3pll_dtc_vreg_adjust): efuse@a14,19 {
+			reg = <0xa14 4>;
+			bits = <19 3>;
+		};
+
+		DIE_NODE(atcphy1_auspll_rodco_bias_adjust): efuse@a18,0 {
+			reg = <0xa18 4>;
+			bits = <0 3>;
+		};
+
+		DIE_NODE(atcphy1_auspll_rodco_encap): efuse@a18,3 {
+			reg = <0xa18 4>;
+			bits = <3 2>;
+		};
+
+		DIE_NODE(atcphy1_auspll_dtc_vreg_adjust): efuse@a18,5 {
+			reg = <0xa18 4>;
+			bits = <5 3>;
+		};
+
+		DIE_NODE(atcphy1_auspll_fracn_dll_start_capcode): efuse@a18,8 {
+			reg = <0xa18 4>;
+			bits = <8 2>;
+		};
+
+		DIE_NODE(atcphy1_aus_cmn_shm_vreg_trim): efuse@a18,10 {
+			reg = <0xa18 4>;
+			bits = <10 5>;
+		};
+
+		DIE_NODE(atcphy1_cio3pll_dco_coarsebin0): efuse@a18,15 {
+			reg = <0xa18 4>;
+			bits = <15 6>;
+		};
+
+		DIE_NODE(atcphy1_cio3pll_dco_coarsebin1): efuse@a18,21 {
+			reg = <0xa18 4>;
+			bits = <21 6>;
+		};
+
+		DIE_NODE(atcphy1_cio3pll_dll_start_capcode): efuse@a18,27 {
+			reg = <0xa18 4>;
+			bits = <27 2>;
+		};
+
+		DIE_NODE(atcphy1_cio3pll_dtc_vreg_adjust): efuse@a18,29 {
+			reg = <0xa18 4>;
+			bits = <29 3>;
+		};
+
+		DIE_NODE(atcphy2_auspll_rodco_bias_adjust): efuse@a1c,10 {
+			reg = <0xa1c 4>;
+			bits = <10 3>;
+		};
+
+		DIE_NODE(atcphy2_auspll_rodco_encap): efuse@a1c,13 {
+			reg = <0xa1c 4>;
+			bits = <13 2>;
+		};
+
+		DIE_NODE(atcphy2_auspll_dtc_vreg_adjust): efuse@a1c,15 {
+			reg = <0xa1c 4>;
+			bits = <15 3>;
+		};
+
+		DIE_NODE(atcphy2_auspll_fracn_dll_start_capcode): efuse@a1c,18 {
+			reg = <0xa1c 4>;
+			bits = <18 2>;
+		};
+
+		DIE_NODE(atcphy2_aus_cmn_shm_vreg_trim): efuse@a1c,20 {
+			reg = <0xa1c 4>;
+			bits = <20 5>;
+		};
+
+		DIE_NODE(atcphy2_cio3pll_dco_coarsebin0): efuse@a1c,25 {
+			reg = <0xa1c 4>;
+			bits = <25 6>;
+		};
+
+		DIE_NODE(atcphy2_cio3pll_dco_coarsebin1): efuse@a1c,31 {
+			reg = <0xa1c 8>;
+			bits = <31 6>;
+		};
+
+		DIE_NODE(atcphy2_cio3pll_dll_start_capcode): efuse@a20,5 {
+			reg = <0xa20 4>;
+			bits = <5 2>;
+		};
+
+		DIE_NODE(atcphy2_cio3pll_dtc_vreg_adjust): efuse@a20,7 {
+			reg = <0xa20 4>;
+			bits = <7 3>;
+		};
+
+		DIE_NODE(atcphy3_auspll_rodco_bias_adjust): efuse@a20,20 {
+			reg = <0xa20 4>;
+			bits = <20 3>;
+		};
+
+		DIE_NODE(atcphy3_auspll_rodco_encap): efuse@a20,23 {
+			reg = <0xa20 4>;
+			bits = <23 2>;
+		};
+
+		DIE_NODE(atcphy3_auspll_dtc_vreg_adjust): efuse@a20,25 {
+			reg = <0xa20 4>;
+			bits = <25 3>;
+		};
+
+		DIE_NODE(atcphy3_auspll_fracn_dll_start_capcode): efuse@a20,28 {
+			reg = <0xa20 4>;
+			bits = <28 2>;
+		};
+
+		DIE_NODE(atcphy3_aus_cmn_shm_vreg_trim): efuse@a20,30 {
+			reg = <0xa20 8>;
+			bits = <30 5>;
+		};
+
+		DIE_NODE(atcphy3_cio3pll_dco_coarsebin0): efuse@a24,3 {
+			reg = <0xa24 4>;
+			bits = <3 6>;
+		};
+
+		DIE_NODE(atcphy3_cio3pll_dco_coarsebin1): efuse@a24,9 {
+			reg = <0xa24 4>;
+			bits = <9 6>;
+		};
+
+		DIE_NODE(atcphy3_cio3pll_dll_start_capcode): efuse@a24,15 {
+			reg = <0xa24 4>;
+			bits = <15 2>;
+		};
+
+		DIE_NODE(atcphy3_cio3pll_dtc_vreg_adjust): efuse@a24,17 {
+			reg = <0xa24 4>;
+			bits = <17 3>;
+		};
 	};
 
 	DIE_NODE(pinctrl_aop): pinctrl@293820000 {
@@ -150,13 +330,54 @@
 		reg = <0x7 0x02280000 0x0 0x100000>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ DIE_NO 1190 IRQ_TYPE_LEVEL_HIGH>;
-		/* dr_mode = "otg"; */
+		dr_mode = "otg";
 		usb-role-switch;
 		role-switch-default-mode = "host";
 		iommus = <&DIE_NODE(dwc3_0_dart_0) 0>,
 			<&DIE_NODE(dwc3_0_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc0_usb)>;
 		dma-coherent;
+		resets = <&DIE_NODE(atcphy0)>;
+		phys = <&DIE_NODE(atcphy0) PHY_TYPE_USB2>, <&DIE_NODE(atcphy0) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy0): phy@703000000 {
+		compatible = "apple,t6000-atcphy", "apple,t8103-atcphy";
+		reg = <0x7 0x03000000 0x0 0x4c000>,
+			<0x7 0x03050000 0x0 0x8000>,
+			<0x7 0x00000000 0x0 0x4000>,
+			<0x7 0x02a90000 0x0 0x4000>,
+			<0x7 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		nvmem-cells = <&DIE_NODE(atcphy0_aus_cmn_shm_vreg_trim)>,
+			<&DIE_NODE(atcphy0_auspll_rodco_encap)>,
+			<&DIE_NODE(atcphy0_auspll_rodco_bias_adjust)>,
+			<&DIE_NODE(atcphy0_auspll_fracn_dll_start_capcode)>,
+			<&DIE_NODE(atcphy0_auspll_dtc_vreg_adjust)>,
+			<&DIE_NODE(atcphy0_cio3pll_dco_coarsebin0)>,
+			<&DIE_NODE(atcphy0_cio3pll_dco_coarsebin1)>,
+			<&DIE_NODE(atcphy0_cio3pll_dll_start_capcode)>,
+			<&DIE_NODE(atcphy0_cio3pll_dtc_vreg_adjust)>;
+		nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+			"auspll_rodco_encap",
+			"auspll_rodco_bias_adjust",
+			"auspll_fracn_dll_start_capcode",
+			"auspll_dtc_vreg_adjust",
+			"cio3pll_dco_coarsebin0",
+			"cio3pll_dco_coarsebin1",
+			"cio3pll_dll_start_capcode",
+			"cio3pll_dtc_vreg_adjust";
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
 	};
 
 	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
@@ -182,13 +403,54 @@
 		reg = <0xb 0x02280000 0x0 0x100000>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ DIE_NO 1207 IRQ_TYPE_LEVEL_HIGH>;
-		/* dr_mode = "otg"; */
+		dr_mode = "otg";
 		usb-role-switch;
 		role-switch-default-mode = "host";
 		iommus = <&DIE_NODE(dwc3_1_dart_0) 0>,
 			<&DIE_NODE(dwc3_1_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc1_usb)>;
 		dma-coherent;
+		resets = <&DIE_NODE(atcphy1)>;
+		phys = <&DIE_NODE(atcphy1) PHY_TYPE_USB2>, <&DIE_NODE(atcphy1) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy1): phy@b03000000 {
+		compatible = "apple,t6000-atcphy", "apple,t8103-atcphy";
+		reg = <0xb 0x03000000 0x0 0x4c000>,
+			<0xb 0x03050000 0x0 0x8000>,
+			<0xb 0x00000000 0x0 0x4000>,
+			<0xb 0x02a90000 0x0 0x4000>,
+			<0xb 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		nvmem-cells = <&DIE_NODE(atcphy1_aus_cmn_shm_vreg_trim)>,
+			<&DIE_NODE(atcphy1_auspll_rodco_encap)>,
+			<&DIE_NODE(atcphy1_auspll_rodco_bias_adjust)>,
+			<&DIE_NODE(atcphy1_auspll_fracn_dll_start_capcode)>,
+			<&DIE_NODE(atcphy1_auspll_dtc_vreg_adjust)>,
+			<&DIE_NODE(atcphy1_cio3pll_dco_coarsebin0)>,
+			<&DIE_NODE(atcphy1_cio3pll_dco_coarsebin1)>,
+			<&DIE_NODE(atcphy1_cio3pll_dll_start_capcode)>,
+			<&DIE_NODE(atcphy1_cio3pll_dtc_vreg_adjust)>;
+		nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+			"auspll_rodco_encap",
+			"auspll_rodco_bias_adjust",
+			"auspll_fracn_dll_start_capcode",
+			"auspll_dtc_vreg_adjust",
+			"cio3pll_dco_coarsebin0",
+			"cio3pll_dco_coarsebin1",
+			"cio3pll_dll_start_capcode",
+			"cio3pll_dtc_vreg_adjust";
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
 	};
 
 	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
@@ -214,13 +476,54 @@
 		reg = <0xf 0x02280000 0x0 0x100000>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ DIE_NO 1224 IRQ_TYPE_LEVEL_HIGH>;
-		/* dr_mode = "otg"; */
+		dr_mode = "otg";
 		usb-role-switch;
 		role-switch-default-mode = "host";
 		iommus = <&DIE_NODE(dwc3_2_dart_0) 0>,
 			<&DIE_NODE(dwc3_2_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc2_usb)>;
 		dma-coherent;
+		resets = <&DIE_NODE(atcphy2)>;
+		phys = <&DIE_NODE(atcphy2) PHY_TYPE_USB2>, <&DIE_NODE(atcphy2) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy2): phy@f03000000 {
+		compatible = "apple,t6000-atcphy", "apple,t8103-atcphy";
+		reg = <0xf 0x03000000 0x0 0x4c000>,
+			<0xf 0x03050000 0x0 0x8000>,
+			<0xf 0x00000000 0x0 0x4000>,
+			<0xf 0x02a90000 0x0 0x4000>,
+			<0xf 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		nvmem-cells = <&DIE_NODE(atcphy2_aus_cmn_shm_vreg_trim)>,
+			<&DIE_NODE(atcphy2_auspll_rodco_encap)>,
+			<&DIE_NODE(atcphy2_auspll_rodco_bias_adjust)>,
+			<&DIE_NODE(atcphy2_auspll_fracn_dll_start_capcode)>,
+			<&DIE_NODE(atcphy2_auspll_dtc_vreg_adjust)>,
+			<&DIE_NODE(atcphy2_cio3pll_dco_coarsebin0)>,
+			<&DIE_NODE(atcphy2_cio3pll_dco_coarsebin1)>,
+			<&DIE_NODE(atcphy2_cio3pll_dll_start_capcode)>,
+			<&DIE_NODE(atcphy2_cio3pll_dtc_vreg_adjust)>;
+		nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+			"auspll_rodco_encap",
+			"auspll_rodco_bias_adjust",
+			"auspll_fracn_dll_start_capcode",
+			"auspll_dtc_vreg_adjust",
+			"cio3pll_dco_coarsebin0",
+			"cio3pll_dco_coarsebin1",
+			"cio3pll_dll_start_capcode",
+			"cio3pll_dtc_vreg_adjust";
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
 	};
 
 	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
@@ -246,11 +549,52 @@
 		reg = <0x13 0x02280000 0x0 0x100000>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ DIE_NO 1241 IRQ_TYPE_LEVEL_HIGH>;
-		/* dr_mode = "otg"; */
+		dr_mode = "otg";
 		usb-role-switch;
 		role-switch-default-mode = "host";
 		iommus = <&DIE_NODE(dwc3_3_dart_0) 0>,
 			<&DIE_NODE(dwc3_3_dart_1) 1>;
 		power-domains = <&DIE_NODE(ps_atc3_usb)>;
 		dma-coherent;
+		resets = <&DIE_NODE(atcphy3)>;
+		phys = <&DIE_NODE(atcphy3) PHY_TYPE_USB2>, <&DIE_NODE(atcphy3) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy3): phy@1303000000 {
+		compatible = "apple,t6000-atcphy", "apple,t8103-atcphy";
+		reg = <0x13 0x03000000 0x0 0x4c000>,
+			<0x13 0x03050000 0x0 0x8000>,
+			<0x13 0x00000000 0x0 0x4000>,
+			<0x13 0x02a90000 0x0 0x4000>,
+			<0x13 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		nvmem-cells = <&DIE_NODE(atcphy3_aus_cmn_shm_vreg_trim)>,
+			<&DIE_NODE(atcphy3_auspll_rodco_encap)>,
+			<&DIE_NODE(atcphy3_auspll_rodco_bias_adjust)>,
+			<&DIE_NODE(atcphy3_auspll_fracn_dll_start_capcode)>,
+			<&DIE_NODE(atcphy3_auspll_dtc_vreg_adjust)>,
+			<&DIE_NODE(atcphy3_cio3pll_dco_coarsebin0)>,
+			<&DIE_NODE(atcphy3_cio3pll_dco_coarsebin1)>,
+			<&DIE_NODE(atcphy3_cio3pll_dll_start_capcode)>,
+			<&DIE_NODE(atcphy3_cio3pll_dtc_vreg_adjust)>;
+		nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+			"auspll_rodco_encap",
+			"auspll_rodco_bias_adjust",
+			"auspll_fracn_dll_start_capcode",
+			"auspll_dtc_vreg_adjust",
+			"cio3pll_dco_coarsebin0",
+			"cio3pll_dco_coarsebin1",
+			"cio3pll_dll_start_capcode",
+			"cio3pll_dtc_vreg_adjust";
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
 	};
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 74f452f3ecd367..b4f6496f2b1ace 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -13,6 +13,10 @@
 
 / {
 	aliases {
+		atcphy0 = &atcphy0;
+		atcphy1 = &atcphy1;
+		atcphy2 = &atcphy2;
+		atcphy3 = &atcphy3;
 		bluetooth0 = &bluetooth0;
 		dcp = &dcp;
 		disp0 = &display;
@@ -93,6 +97,12 @@
 						remote-endpoint = <&typec0_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec0_con_ss: endpoint {
+						remote-endpoint = <&typec0_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -119,6 +129,12 @@
 						remote-endpoint = <&typec1_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec1_con_ss: endpoint {
+						remote-endpoint = <&typec1_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -145,6 +161,12 @@
 						remote-endpoint = <&typec2_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec2_con_ss: endpoint {
+						remote-endpoint = <&typec2_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -333,6 +355,31 @@
 	};
 };
 
+/* Type-C PHYs */
+&atcphy0 {
+	port {
+		typec0_usb_ss: endpoint {
+			remote-endpoint = <&typec0_con_ss>;
+		};
+	};
+};
+
+&atcphy1 {
+	port {
+		typec1_usb_ss: endpoint {
+			remote-endpoint = <&typec1_con_ss>;
+		};
+	};
+};
+
+&atcphy2 {
+	port {
+		typec2_usb_ss: endpoint {
+			remote-endpoint = <&typec2_con_ss>;
+		};
+	};
+};
+
 /* ATC3 is used for DisplayPort -> HDMI only */
 &dwc3_3_dart_0 {
 	status = "disabled";
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index b86abaa21b546a..c5400f298a123e 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -11,6 +11,10 @@
 
 / {
 	aliases {
+		atcphy0 = &atcphy0;
+		atcphy1 = &atcphy1;
+		atcphy2 = &atcphy2;
+		atcphy3 = &atcphy3;
 		bluetooth0 = &bluetooth0;
 		dcp = &dcp;
 		disp0 = &display;
@@ -79,6 +83,12 @@
 						remote-endpoint = <&typec0_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec0_con_ss: endpoint {
+						remote-endpoint = <&typec0_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -105,6 +115,12 @@
 						remote-endpoint = <&typec1_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec1_con_ss: endpoint {
+						remote-endpoint = <&typec1_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -131,6 +147,12 @@
 						remote-endpoint = <&typec2_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec2_con_ss: endpoint {
+						remote-endpoint = <&typec2_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -157,6 +179,12 @@
 						remote-endpoint = <&typec3_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec3_con_ss: endpoint {
+						remote-endpoint = <&typec3_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -195,6 +223,39 @@
 	};
 };
 
+/* Type-C PHYs */
+&atcphy0 {
+	port {
+		typec0_usb_ss: endpoint {
+			remote-endpoint = <&typec0_con_ss>;
+		};
+	};
+};
+
+&atcphy1 {
+	port {
+		typec1_usb_ss: endpoint {
+			remote-endpoint = <&typec1_con_ss>;
+		};
+	};
+};
+
+&atcphy2 {
+	port {
+		typec2_usb_ss: endpoint {
+			remote-endpoint = <&typec2_con_ss>;
+		};
+	};
+};
+
+&atcphy3 {
+	port {
+		typec3_usb_ss: endpoint {
+			remote-endpoint = <&typec3_con_ss>;
+		};
+	};
+};
+
 /* Audio */
 &i2c1 {
 	status = "okay";

From bf1d61ae8a1f97c37341a17aa128c01fc1c6b65a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 31 Oct 2022 01:19:36 +0100
Subject: [PATCH 0041/1027] arch: arm64: apple: Add dcp panel node for t8103
 based laptops and imacs

The panel node will contain among other properties backlight control
related properties from the "backlight" node in the ADT.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 7 ++++++-
 arch/arm64/boot/dts/apple/t8103-j313.dts | 7 ++++++-
 arch/arm64/boot/dts/apple/t8103-j456.dts | 7 ++++++-
 arch/arm64/boot/dts/apple/t8103-j457.dts | 7 ++++++-
 4 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 1b87dc8fafea34..a845d92ee10c25 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -31,7 +31,12 @@
 };
 
 &dcp {
-	apple,connector-type = "eDP";
+	panel: panel {
+		compatible = "apple,panel-j293", "apple,panel";
+		width-mm = <286>;
+		height-mm = <179>;
+		apple,max-brightness = <525>;
+	};
 };
 
 &bluetooth0 {
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 5061cc5d09e0da..cc05341b24def4 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -31,7 +31,12 @@
 };
 
 &dcp {
-	apple,connector-type = "eDP";
+	panel: panel {
+		compatible = "apple,panel-j313", "apple,panel";
+		width-mm = <286>;
+		height-mm = <179>;
+		apple,max-brightness = <420>;
+	};
 };
 
 &bluetooth0 {
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index ca74b7f87c0c3b..19f14208a80e16 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -22,7 +22,12 @@
 };
 
 &dcp {
-	apple,connector-type = "eDP";
+	panel: panel {
+		compatible = "apple,panel-j456", "apple,panel";
+		width-mm = <522>;
+		height-mm = <294>;
+		apple,max-brightness = <525>;
+	};
 };
 
 &bluetooth0 {
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 2f248178ce5885..72a0a1b9e2db25 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -22,7 +22,12 @@
 };
 
 &dcp {
-	apple,connector-type = "eDP";
+	panel: panel {
+		compatible = "apple,panel-j457", "apple,panel";
+		width-mm = <522>;
+		height-mm = <294>;
+		apple,max-brightness = <525>;
+	};
 };
 
 &bluetooth0 {

From ea788b6e91b9a3ae5fd270ffec37713efea8f7cb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 31 Oct 2022 01:21:34 +0100
Subject: [PATCH 0042/1027] arch: arm64: apple: Add dcp panel node for t600x
 based laptops

The panel node will contain among other properties backlight control
related properties from the "backlight" node in the ADT.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts      | 7 +++++++
 arch/arm64/boot/dts/apple/t6000-j316s.dts      | 7 +++++++
 arch/arm64/boot/dts/apple/t6001-j314c.dts      | 7 +++++++
 arch/arm64/boot/dts/apple/t6001-j316c.dts      | 7 +++++++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 +++-
 5 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index b514b1114c4d24..a2ccc654216164 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -25,6 +25,13 @@
 	brcm,board-type = "apple,maldives";
 };
 
+&panel {
+	compatible = "apple,panel-j314", "apple,panel-mini-led", "apple,panel";
+	width-mm = <302>;
+	height-mm = <196>;
+	adj-height-mm = <189>;
+};
+
 &sound {
 	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index 7f5c91bb5f3210..99a76392b3b791 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -25,6 +25,13 @@
 	brcm,board-type = "apple,madagascar";
 };
 
+&panel {
+	compatible = "apple,panel-j316", "apple,panel-mini-led", "apple,panel";
+	width-mm = <346>;
+	height-mm = <223>;
+	adj-height-mm = <216>;
+};
+
 &sound {
 	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index 35ff978b998702..82d851d4cd3857 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -25,6 +25,13 @@
 	brcm,board-type = "apple,maldives";
 };
 
+&panel {
+	compatible = "apple,panel-j314", "apple,panel-mini-led", "apple,panel";
+	width-mm = <302>;
+	height-mm = <196>;
+	adj-height-mm = <189>;
+};
+
 &sound {
 	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index 6a35c87aa7ab02..a6987c8324dbd7 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -25,6 +25,13 @@
 	brcm,board-type = "apple,madagascar";
 };
 
+&panel {
+	compatible = "apple,panel-j316", "apple,panel-mini-led", "apple,panel";
+	width-mm = <346>;
+	height-mm = <223>;
+	adj-height-mm = <216>;
+};
+
 &sound {
 	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index b4f6496f2b1ace..39fdc7d3360f42 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -70,7 +70,9 @@
 };
 
 &dcp {
-	apple,connector-type = "eDP";
+	panel: panel {
+		apple,max-brightness = <500>;
+	};
 };
 
 /* USB Type C */

From da70a6a47711c6efe64d55b9d78f52ed8c212916 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Nov 2022 20:12:33 +0100
Subject: [PATCH 0043/1027] arm64: dts: apple: t8112: Add eFuses node

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 97 ++++++++++++++++++++++++++++
 1 file changed, 97 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 4904574aa2f931..e33c464f0ab8e6 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -771,6 +771,103 @@
 			interrupts = <AIC_IRQ 379 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		efuse@23d2c8000 {
+			compatible = "apple,t8112-efuses", "apple,efuses";
+			reg = <0x2 0x3d2c8000 0x0 0x1000>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			atcphy0_auspll_rodco_bias_adjust: efuse@480,20 {
+				reg = <0x480 4>;
+				bits = <20 3>;
+			};
+
+			atcphy0_auspll_rodco_encap: efuse@480,23 {
+				reg = <0x480 4>;
+				bits = <23 2>;
+			};
+
+			atcphy0_auspll_dtc_vreg_adjust: efuse@480,25 {
+				reg = <0x480 4>;
+				bits = <25 3>;
+			};
+
+			atcphy0_auspll_fracn_dll_start_capcode: efuse@480,28 {
+				reg = <0x480 4>;
+				bits = <28 2>;
+			};
+
+			atcphy0_aus_cmn_shm_vreg_trim: efuse@480,30 {
+				reg = <0x480 8>;
+				bits = <30 5>;
+			};
+
+			atcphy0_cio3pll_dco_coarsebin0: efuse@484,3 {
+				reg = <0x484 4>;
+				bits = <3 6>;
+			};
+
+			atcphy0_cio3pll_dco_coarsebin1: efuse@484,9 {
+				reg = <0x484 4>;
+				bits = <9 6>;
+			};
+
+			atcphy0_cio3pll_dll_start_capcode: efuse@484,15 {
+				reg = <0x484 4>;
+				bits = <15 2>;
+			};
+
+			atcphy0_cio3pll_dtc_vreg_adjust: efuse@484,17 {
+				reg = <0x484 0x4>;
+				bits = <17 3>;
+			};
+
+			atcphy1_auspll_rodco_bias_adjust: efuse@484,30 {
+				reg = <0x484 8>;
+				bits = <30 3>;
+			};
+
+			atcphy1_auspll_rodco_encap: efuse@488,1 {
+				reg = <0x488 8>;
+				bits = <1 2>;
+			};
+
+			atcphy1_auspll_dtc_vreg_adjust: efuse@488,3 {
+				reg = <0x488 4>;
+				bits = <3 3>;
+			};
+
+			atcphy1_auspll_fracn_dll_start_capcode: efuse@488,6 {
+				reg = <0x488 4>;
+				bits = <6 2>;
+			};
+
+			atcphy1_aus_cmn_shm_vreg_trim: efuse@488,8 {
+				reg = <0x488 4>;
+				bits = <8 5>;
+			};
+
+			atcphy1_cio3pll_dco_coarsebin0: efuse@488,13 {
+				reg = <0x488 4>;
+				bits = <13 6>;
+			};
+
+			atcphy1_cio3pll_dco_coarsebin1: efuse@488,19 {
+				reg = <0x488 4>;
+				bits = <19 6>;
+			};
+
+			atcphy1_cio3pll_dll_start_capcode: efuse@488,25 {
+				reg = <0x488 4>;
+				bits = <25 2>;
+			};
+
+			atcphy1_cio3pll_dtc_vreg_adjust: efuse@488,27 {
+				reg = <0x488 0x4>;
+				bits = <27 3>;
+			};
+		};
+
 		smc_mbox: mbox@23e408000 {
 			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
 			reg = <0x2 0x3e408000 0x0 0x4000>;

From af61cbbf61a7f8350393d07223383e82df5a2446 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Nov 2022 20:14:27 +0100
Subject: [PATCH 0044/1027] arm64: dts: apple: t8112: Add ATCPHY nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 31 +++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi      | 83 +++++++++++++++++++++++
 2 files changed, 114 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 2c14f4479c401f..1d0d1ab4a97cc9 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -11,6 +11,8 @@
 
 / {
 	aliases {
+		atcphy0 = &atcphy0;
+		atcphy1 = &atcphy1;
 		serial0 = &serial0;
 		serial2 = &serial2;
 	};
@@ -68,6 +70,12 @@
 						remote-endpoint = <&typec0_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec0_con_ss: endpoint {
+						remote-endpoint = <&typec0_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -93,6 +101,12 @@
 						remote-endpoint = <&typec1_usb_hs>;
 					};
 				};
+				port@1 {
+					reg = <1>;
+					typec1_con_ss: endpoint {
+						remote-endpoint = <&typec1_usb_ss>;
+					};
+				};
 			};
 		};
 	};
@@ -115,6 +129,23 @@
 	};
 };
 
+/* Type-C PHYs */
+&atcphy0 {
+	port {
+		typec0_usb_ss: endpoint {
+			remote-endpoint = <&typec0_con_ss>;
+		};
+	};
+};
+
+&atcphy1 {
+	port {
+		typec1_usb_ss: endpoint {
+			remote-endpoint = <&typec1_con_ss>;
+		};
+	};
+};
+
 &i2c1 {
 	status = "okay";
 };
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index e33c464f0ab8e6..d5edb738cd3d61 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -11,6 +11,7 @@
 #include <dt-bindings/interrupt-controller/apple-aic.h>
 #include <dt-bindings/interrupt-controller/irq.h>
 #include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/phy/phy.h>
 #include <dt-bindings/spmi/spmi.h>
 
 / {
@@ -1065,6 +1066,9 @@
 			role-switch-default-mode = "host";
 			iommus = <&dwc3_0_dart_0 0>, <&dwc3_0_dart_1 1>;
 			power-domains = <&ps_atc0_usb>;
+			resets = <&atcphy0>;
+			phys = <&atcphy0 PHY_TYPE_USB2>, <&atcphy0 PHY_TYPE_USB3>;
+			phy-names = "usb2-phy", "usb3-phy";
 		};
 
 		dwc3_0_dart_0: iommu@382f00000 {
@@ -1085,6 +1089,44 @@
 			power-domains = <&ps_atc0_usb>;
 		};
 
+		atcphy0: phy@383000000 {
+			compatible = "apple,t8112-atcphy", "apple,t8103-atcphy";
+			reg = <0x3 0x83000000 0x0 0x4c000>,
+				<0x3 0x83050000 0x0 0x8000>,
+				<0x3 0x80000000 0x0 0x4000>,
+				<0x3 0x82a90000 0x0 0x4000>,
+				<0x3 0x82a84000 0x0 0x4000>;
+			reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+				"pipehandler";
+
+			#phy-cells = <1>;
+			#reset-cells = <0>;
+
+			nvmem-cells = <&atcphy0_aus_cmn_shm_vreg_trim>,
+				<&atcphy0_auspll_rodco_encap>,
+				<&atcphy0_auspll_rodco_bias_adjust>,
+				<&atcphy0_auspll_fracn_dll_start_capcode>,
+				<&atcphy0_auspll_dtc_vreg_adjust>,
+				<&atcphy0_cio3pll_dco_coarsebin0>,
+				<&atcphy0_cio3pll_dco_coarsebin1>,
+				<&atcphy0_cio3pll_dll_start_capcode>,
+				<&atcphy0_cio3pll_dtc_vreg_adjust>;
+			nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+				"auspll_rodco_encap",
+				"auspll_rodco_bias_adjust",
+				"auspll_fracn_dll_start_capcode",
+				"auspll_dtc_vreg_adjust",
+				"cio3pll_dco_coarsebin0",
+				"cio3pll_dco_coarsebin1",
+				"cio3pll_dll_start_capcode",
+				"cio3pll_dtc_vreg_adjust";
+
+			orientation-switch;
+			mode-switch;
+			svid = <0xff01>, <0x8087>;
+			power-domains = <&ps_atc0_usb>;
+		};
+
 		dwc3_1: usb@502280000 {
 			compatible = "apple,t8112-dwc3", "apple,dwc3", "snps,dwc3";
 			reg = <0x5 0x02280000 0x0 0x100000>;
@@ -1095,6 +1137,9 @@
 			role-switch-default-mode = "host";
 			iommus = <&dwc3_1_dart_0 0>, <&dwc3_1_dart_1 1>;
 			power-domains = <&ps_atc1_usb>;
+			resets = <&atcphy1>;
+			phys = <&atcphy1 PHY_TYPE_USB2>, <&atcphy1 PHY_TYPE_USB3>;
+			phy-names = "usb2-phy", "usb3-phy";
 		};
 
 		dwc3_1_dart_0: iommu@502f00000 {
@@ -1115,6 +1160,44 @@
 			power-domains = <&ps_atc1_usb>;
 		};
 
+		atcphy1: phy@503000000 {
+			compatible = "apple,t8112-atcphy", "apple,t8103-atcphy";
+			reg = <0x5 0x03000000 0x0 0x4c000>,
+				<0x5 0x03050000 0x0 0x8000>,
+				<0x5 0x0 0x0 0x4000>,
+				<0x5 0x02a90000 0x0 0x4000>,
+				<0x5 0x02a84000 0x0 0x4000>;
+			reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+				"pipehandler";
+
+			nvmem-cells = <&atcphy1_aus_cmn_shm_vreg_trim>,
+				<&atcphy1_auspll_rodco_encap>,
+				<&atcphy1_auspll_rodco_bias_adjust>,
+				<&atcphy1_auspll_fracn_dll_start_capcode>,
+				<&atcphy1_auspll_dtc_vreg_adjust>,
+				<&atcphy1_cio3pll_dco_coarsebin0>,
+				<&atcphy1_cio3pll_dco_coarsebin1>,
+				<&atcphy1_cio3pll_dll_start_capcode>,
+				<&atcphy1_cio3pll_dtc_vreg_adjust>;
+			nvmem-cell-names =  "aus_cmn_shm_vreg_trim",
+				"auspll_rodco_encap",
+				"auspll_rodco_bias_adjust",
+				"auspll_fracn_dll_start_capcode",
+				"auspll_dtc_vreg_adjust",
+				"cio3pll_dco_coarsebin0",
+				"cio3pll_dco_coarsebin1",
+				"cio3pll_dll_start_capcode",
+				"cio3pll_dtc_vreg_adjust";
+
+			#phy-cells = <1>;
+			#reset-cells = <0>;
+
+			orientation-switch;
+			mode-switch;
+			svid = <0xff01>, <0x8087>;
+			power-domains = <&ps_atc1_usb>;
+		};
+
 		pcie0_dart: iommu@681008000 {
 			compatible = "apple,t8110-dart";
 			reg = <0x6 0x81008000 0x0 0x4000>;

From 1e21ad209491a97cbb349c64137bf93af4bddaa9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 20 Nov 2022 20:22:57 +0100
Subject: [PATCH 0045/1027] arm64: dts: apple: t8112: Add dcp/disp0 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts  | 10 +++
 arch/arm64/boot/dts/apple/t8112-j493.dts  |  9 +++
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 10 +++
 arch/arm64/boot/dts/apple/t8112.dtsi      | 79 +++++++++++++++++++++++
 4 files changed, 108 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index c4ae6b5a6c7519..de9c98c6fac0a2 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -35,6 +35,16 @@
 	};
 };
 
+&dcp {
+	panel: panel {
+		compatible = "apple,panel-j413", "apple,panel";
+		width-mm = <290>;
+		height-mm = <189>;
+		adj-height-mm = <181>;
+		apple,max-brightness = <525>;
+	};
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 45302227cf4be7..42705711c6928d 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -35,6 +35,15 @@
 	};
 };
 
+&dcp {
+	panel: panel {
+		compatible = "apple,panel-j493", "apple,panel";
+		width-mm = <286>;
+		height-mm = <179>;
+		apple,max-brightness = <525>;
+	};
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 1d0d1ab4a97cc9..7edabecc862e46 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -13,6 +13,9 @@
 	aliases {
 		atcphy0 = &atcphy0;
 		atcphy1 = &atcphy1;
+		dcp = &dcp;
+		disp0 = &display;
+		disp0_piodma = &disp0_piodma;
 		serial0 = &serial0;
 		serial2 = &serial2;
 	};
@@ -32,6 +35,13 @@
 		};
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		/* To be filled by loader */
+	};
+
 	memory@800000000 {
 		device_type = "memory";
 		reg = <0x8 0 0x2 0>; /* To be filled by loader */
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index d5edb738cd3d61..ec99763b11cc35 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -360,6 +360,14 @@
 		clock-output-names = "nco_ref";
 	};
 
+	/* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
+	clk_disp0: clock-disp0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <533333328>;
+		clock-output-names = "clk_disp0";
+	};
+
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
@@ -380,6 +388,71 @@
 			#performance-domain-cells = <0>;
 		};
 
+		disp0_dart: iommu@231304000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x2 0x31304000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
+			status = "disabled";
+		};
+
+		dcp_dart: iommu@23130c000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x2 0x3130c000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
+		};
+
+		dcp_mbox: mbox@231c08000 {
+			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x31c08000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 535 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 536 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 537 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 538 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_disp0_cpu0>;
+			resets = <&ps_disp0_cpu0>;
+		};
+
+		dcp: dcp@231c00000 {
+			compatible = "apple,t8112-dcp", "apple,dcp";
+			mboxes = <&dcp_mbox>;
+			mbox-names = "mbox";
+			iommus = <&dcp_dart 5>;
+
+			/* the ADT has 2 additional regs which seems to be unused */
+			reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+			reg = <0x2 0x31c00000 0x0 0x4000>,
+				<0x2 0x30000000 0x0 0x61c000>,
+				<0x2 0x31320000 0x0 0x4000>,
+				<0x2 0x31344000 0x0 0x4000>,
+				<0x2 0x31800000 0x0 0x800000>;
+			apple,bw-scratch = <&pmgr_dcp 0 4 0x5d8>;
+			power-domains = <&ps_disp0_cpu0>;
+			clocks = <&clk_disp0>;
+			apple,asc-dram-mask = <0x0 0x0>;
+			phandle = <&dcp>;
+
+			disp0_piodma: piodma {
+				iommus = <&disp0_dart 4>;
+				phandle = <&disp0_piodma>;
+			};
+		};
+
+		display: display-subsystem {
+			compatible = "apple,display-subsystem";
+			/* disp_dart0 must be 1st since it is locked */
+			iommus = <&disp0_dart 0>;
+			/* generate phandle explicitly for use in loader */
+			phandle = <&display>;
+		};
+
 		sio_dart: iommu@235004000 {
 			compatible = "apple,t8110-dart";
 			reg = <0x2 0x35004000 0x0 0x4000>;
@@ -595,6 +668,12 @@
 			/* child nodes are added in t8103-pmgr.dtsi */
 		};
 
+		pmgr_dcp: power-management@23b3d0000 {
+			reg = <0x2 0x3b3d0000 0x0 0x4000>;
+			reg-names = "dcp-bw-scratch";
+			#apple,bw-scratch-cells = <3>;
+		};
+
 		pinctrl_ap: pinctrl@23c100000 {
 			compatible = "apple,t8112-pinctrl", "apple,pinctrl";
 			reg = <0x2 0x3c100000 0x0 0x100000>;

From b23d3e0dd2ba2dd561aebe9d560727d1982c16bc Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Nov 2022 15:58:07 +0900
Subject: [PATCH 0046/1027] scripts/dtc: Add support for floating-point
 literals

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 scripts/dtc/data.c       | 27 +++++++++++++++++++++++++++
 scripts/dtc/dtc-lexer.l  | 22 ++++++++++++++++++++++
 scripts/dtc/dtc-parser.y | 16 ++++++++++++++++
 scripts/dtc/dtc.h        |  1 +
 4 files changed, 66 insertions(+)

diff --git a/scripts/dtc/data.c b/scripts/dtc/data.c
index 14734233ad8b7e..d12c1f0146bedf 100644
--- a/scripts/dtc/data.c
+++ b/scripts/dtc/data.c
@@ -184,6 +184,33 @@ struct data data_append_integer(struct data d, uint64_t value, int bits)
 	}
 }
 
+struct data data_append_float(struct data d, double value, int bits)
+{
+	float f32;
+	uint32_t u32;
+	double f64;
+	uint64_t u64;
+	fdt32_t value_32;
+	fdt64_t value_64;
+
+	switch (bits) {
+	case 32:
+		f32 = value;
+		memcpy(&u32, &f32, sizeof(u32));
+		value_32 = cpu_to_fdt32(u32);
+		return data_append_data(d, &value_32, 4);
+
+	case 64:
+		f64 = value;
+		memcpy(&u64, &f64, sizeof(u64));
+		value_64 = cpu_to_fdt64(u64);
+		return data_append_data(d, &value_64, 8);
+
+	default:
+		die("Invalid literal size (%d)\n", bits);
+	}
+}
+
 struct data data_append_re(struct data d, uint64_t address, uint64_t size)
 {
 	struct fdt_reserve_entry re;
diff --git a/scripts/dtc/dtc-lexer.l b/scripts/dtc/dtc-lexer.l
index de60a70b6bdbcb..ac0fadff20802d 100644
--- a/scripts/dtc/dtc-lexer.l
+++ b/scripts/dtc/dtc-lexer.l
@@ -151,6 +151,28 @@ static void PRINTF(1, 2) lexical_error(const char *fmt, ...);
 			return DT_LABEL;
 		}
 
+<V1>[-+]?(([0-9]+\.[0-9]*)|([0-9]*\.[0-9]+))(e[-+]?[0-9]+)?f? {
+			char *e;
+			DPRINT("Floating-point Literal: '%s'\n", yytext);
+
+			errno = 0;
+			yylval.floating = strtod(yytext, &e);
+
+			if (*e && (*e != 'f' || e[1])) {
+				lexical_error("Bad floating-point literal '%s'",
+					      yytext);
+			}
+
+			if (errno == ERANGE)
+				lexical_error("Floating-point literal '%s' out of range",
+					      yytext);
+			else
+				/* ERANGE is the only strtod error triggerable
+				 *  by strings matching the pattern */
+				assert(errno == 0);
+			return DT_FP_LITERAL;
+		}
+
 <V1>([0-9]+|0[xX][0-9a-fA-F]+)(U|L|UL|LL|ULL)? {
 			char *e;
 			DPRINT("Integer Literal: '%s'\n", yytext);
diff --git a/scripts/dtc/dtc-parser.y b/scripts/dtc/dtc-parser.y
index 4d5eece5262434..225a6b41b14fcf 100644
--- a/scripts/dtc/dtc-parser.y
+++ b/scripts/dtc/dtc-parser.y
@@ -48,6 +48,7 @@ static bool is_ref_relative(const char *ref)
 	struct node *nodelist;
 	struct reserve_info *re;
 	uint64_t integer;
+	double floating;
 	unsigned int flags;
 }
 
@@ -61,6 +62,7 @@ static bool is_ref_relative(const char *ref)
 %token DT_OMIT_NO_REF
 %token <propnodename> DT_PROPNODENAME
 %token <integer> DT_LITERAL
+%token <floating> DT_FP_LITERAL
 %token <integer> DT_CHAR_LITERAL
 %token <byte> DT_BYTE
 %token <data> DT_STRING
@@ -86,6 +88,7 @@ static bool is_ref_relative(const char *ref)
 %type <node> subnode
 %type <nodelist> subnodes
 
+%type <floating> floating_prim
 %type <integer> integer_prim
 %type <integer> integer_unary
 %type <integer> integer_mul
@@ -395,6 +398,15 @@ arrayprefix:
 			$$.data = data_add_marker(empty_data, TYPE_UINT32, NULL);
 			$$.bits = 32;
 		}
+	| arrayprefix floating_prim
+		{
+			if ($1.bits < 32) {
+				ERROR(&@2, "Floating-point values must be"
+				      " 32-bit or 64-bit");
+			}
+
+			$$.data = data_append_float($1.data, $2, $1.bits);
+		}
 	| arrayprefix integer_prim
 		{
 			if ($1.bits < 64) {
@@ -439,6 +451,10 @@ arrayprefix:
 		}
 	;
 
+floating_prim:
+	DT_FP_LITERAL
+	;
+
 integer_prim:
 	  DT_LITERAL
 	| DT_CHAR_LITERAL
diff --git a/scripts/dtc/dtc.h b/scripts/dtc/dtc.h
index 4c4aaca1fc417c..8561e71ae45a74 100644
--- a/scripts/dtc/dtc.h
+++ b/scripts/dtc/dtc.h
@@ -177,6 +177,7 @@ struct data data_insert_at_marker(struct data d, struct marker *m,
 struct data data_merge(struct data d1, struct data d2);
 struct data data_append_cell(struct data d, cell_t word);
 struct data data_append_integer(struct data d, uint64_t word, int bits);
+struct data data_append_float(struct data d, double value, int bits);
 struct data data_append_re(struct data d, uint64_t address, uint64_t size);
 struct data data_append_addr(struct data d, uint64_t addr);
 struct data data_append_byte(struct data d, uint8_t byte);

From 18b6af5bc1f202aa409aec1795dc1d4b5df71063 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 18 Aug 2022 02:15:43 +0900
Subject: [PATCH 0047/1027] arm64: dts: apple: t8103*: Add GPU nodes

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 51 ++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 4c64d4a3cd9263..8839cace6d154c 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -353,6 +353,27 @@
 		clock-output-names = "nco_ref";
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+	};
+
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
@@ -361,6 +382,36 @@
 		ranges;
 		nonposted-mmio;
 
+		agx: gpu@206400000 {
+			compatible = "apple,agx-t8103", "apple,agx-g13g";
+			reg = <0x2 0x6400000 0 0x40000>,
+				<0x2 0x4000000 0 0x1000000>;
+			reg-names = "asc", "sgx";
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 563 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 564 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 565 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 566 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 579 IRQ_TYPE_LEVEL_HIGH>;
+			mboxes = <&agx_mbox>;
+			power-domains = <&ps_gfx>;
+			memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
+			memory-region-names = "ttbs", "pagetables", "handoff";
+		};
+
+		agx_mbox: mbox@206408000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x6408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 575 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 576 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 577 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 578 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+		};
+
 		cpufreq_e: performance-controller@210e20000 {
 			compatible = "apple,t8103-cluster-cpufreq", "apple,cluster-cpufreq";
 			reg = <0x2 0x10e20000 0 0x1000>;

From eecb283dd85a22f665152767371214fe2a594638 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Oct 2022 00:15:22 +0900
Subject: [PATCH 0048/1027] arm64: dts: Add GPU performance data to t8103.dts

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 52 +++++++++++++++++++++++++++-
 1 file changed, 51 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 8839cace6d154c..81a7d9513c91c0 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -20,6 +20,10 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	aliases {
+		gpu = &gpu;
+	};
+
 	cpus {
 		#address-cells = <2>;
 		#size-cells = <0>;
@@ -299,6 +303,50 @@
 #endif
 	};
 
+	gpu_opp: opp-table-gpu {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <0>;
+			opp-microvolt = <400000>;
+			apple,opp-rel-power = <0>;
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <396000000>;
+			opp-microvolt = <603000>;
+			apple,opp-rel-power = <19>;
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <528000000>;
+			opp-microvolt = <640000>;
+			apple,opp-rel-power = <26>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <720000000>;
+			opp-microvolt = <690000>;
+			apple,opp-rel-power = <38>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <924000000>;
+			opp-microvolt = <784000>;
+			apple,opp-rel-power = <60>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1128000000>;
+			opp-microvolt = <862000>;
+			apple,opp-rel-power = <87>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1278000000>;
+			opp-microvolt = <931000>;
+			apple,opp-rel-power = <100>;
+		};
+	};
+
 	timer {
 		compatible = "arm,armv8-timer";
 		interrupt-parent = <&aic>;
@@ -382,7 +430,7 @@
 		ranges;
 		nonposted-mmio;
 
-		agx: gpu@206400000 {
+		gpu: gpu@206400000 {
 			compatible = "apple,agx-t8103", "apple,agx-g13g";
 			reg = <0x2 0x6400000 0 0x40000>,
 				<0x2 0x4000000 0 0x1000000>;
@@ -397,6 +445,8 @@
 			power-domains = <&ps_gfx>;
 			memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
 			memory-region-names = "ttbs", "pagetables", "handoff";
+
+			operating-points-v2 = <&gpu_opp>;
 		};
 
 		agx_mbox: mbox@206408000 {

From b9fbc668b918e0c1614e625994a5d18b5d991ad9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Nov 2022 23:32:46 +0900
Subject: [PATCH 0049/1027] arm64: dts: Add power data for t8103

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts |  4 +++
 arch/arm64/boot/dts/apple/t8103-j456.dts |  4 +++
 arch/arm64/boot/dts/apple/t8103-j457.dts |  4 +++
 arch/arm64/boot/dts/apple/t8103.dtsi     | 40 +++++++++++++++++++-----
 4 files changed, 45 insertions(+), 7 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 7169563c1b351a..0a69e2962c7c7f 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -127,3 +127,7 @@
 
 	};
 };
+
+&gpu {
+	apple,perf-base-pstate = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index 19f14208a80e16..c16a0594b1d2ca 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -127,3 +127,7 @@
 		};
 	};
 };
+
+&gpu {
+	apple,perf-base-pstate = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 72a0a1b9e2db25..c1d1201ecbe2cc 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -100,3 +100,7 @@
 		};
 	};
 };
+
+&gpu {
+	apple,perf-base-pstate = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 81a7d9513c91c0..7a3c553f863f12 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -313,37 +313,37 @@
 		opp00 {
 			opp-hz = /bits/ 64 <0>;
 			opp-microvolt = <400000>;
-			apple,opp-rel-power = <0>;
+			opp-microwatt = <0>;
 		};
 		opp01 {
 			opp-hz = /bits/ 64 <396000000>;
 			opp-microvolt = <603000>;
-			apple,opp-rel-power = <19>;
+			opp-microwatt = <3714690>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <528000000>;
 			opp-microvolt = <640000>;
-			apple,opp-rel-power = <26>;
+			opp-microwatt = <5083260>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <720000000>;
 			opp-microvolt = <690000>;
-			apple,opp-rel-power = <38>;
+			opp-microwatt = <7429380>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <924000000>;
 			opp-microvolt = <784000>;
-			apple,opp-rel-power = <60>;
+			opp-microwatt = <11730600>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <1128000000>;
 			opp-microvolt = <862000>;
-			apple,opp-rel-power = <87>;
+			opp-microwatt = <17009370>;
 		};
 		opp06 {
 			opp-hz = /bits/ 64 <1278000000>;
 			opp-microvolt = <931000>;
-			apple,opp-rel-power = <100>;
+			opp-microwatt = <19551000>;
 		};
 	};
 
@@ -447,6 +447,32 @@
 			memory-region-names = "ttbs", "pagetables", "handoff";
 
 			operating-points-v2 = <&gpu_opp>;
+			apple,perf-base-pstate = <1>;
+			apple,min-sram-microvolt = <850000>;
+			apple,avg-power-filter-tc-ms = <1000>;
+			apple,avg-power-ki-only = <7.5>;
+			apple,avg-power-kp = <4.0>;
+			apple,avg-power-min-duty-cycle = <40>;
+			apple,avg-power-target-filter-tc = <125>;
+			apple,fast-die0-integral-gain = <200.0>;
+			apple,fast-die0-proportional-gain = <5.0>;
+			apple,perf-filter-drop-threshold = <0>;
+			apple,perf-filter-time-constant = <5>;
+			apple,perf-filter-time-constant2 = <50>;
+			apple,perf-integral-gain2 = <0.197392>;
+			apple,perf-integral-min-clamp = <0>;
+			apple,perf-proportional-gain2 = <6.853981>;
+			apple,perf-tgt-utilization = <85>;
+			apple,power-sample-period = <8>;
+			apple,power-zones = <30000 100 6875>;
+			apple,ppm-filter-time-constant-ms = <100>;
+			apple,ppm-ki = <91.5>;
+			apple,ppm-kp = <6.9>;
+			apple,pwr-filter-time-constant = <313>;
+			apple,pwr-integral-gain = <0.0202129>;
+			apple,pwr-integral-min-clamp = <0>;
+			apple,pwr-min-duty-cycle = <40>;
+			apple,pwr-proportional-gain = <5.2831855>;
 		};
 
 		agx_mbox: mbox@206408000 {

From dca0d97e534ba4c83188c04881679395440ca4d0 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 3 Nov 2022 01:03:44 +0900
Subject: [PATCH 0050/1027] arm64: dts: Add t600x GPU nodes

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t6000.dtsi        |  6 ++
 arch/arm64/boot/dts/apple/t6001.dtsi        | 26 ++++++++
 arch/arm64/boot/dts/apple/t6002.dtsi        |  8 +++
 arch/arm64/boot/dts/apple/t600x-common.dtsi | 66 +++++++++++++++++++++
 arch/arm64/boot/dts/apple/t600x-die0.dtsi   | 64 ++++++++++++++++++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi   |  9 +++
 6 files changed, 179 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6000.dtsi b/arch/arm64/boot/dts/apple/t6000.dtsi
index 89c3b211b116e9..c9e4e52d9aac92 100644
--- a/arch/arm64/boot/dts/apple/t6000.dtsi
+++ b/arch/arm64/boot/dts/apple/t6000.dtsi
@@ -9,6 +9,8 @@
 
 /* This chip is just a cut down version of t6001, so include it and disable the missing parts */
 
+#define GPU_REPEAT(x) <x x>
+
 #include "t6001.dtsi"
 
 / {
@@ -16,3 +18,7 @@
 };
 
 /delete-node/ &pmgr_south;
+
+&gpu {
+	compatible = "apple,agx-t6000", "apple,agx-g13x";
+};
diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi
index 0bdd1966f5302e..6e7e7cdeacf943 100644
--- a/arch/arm64/boot/dts/apple/t6001.dtsi
+++ b/arch/arm64/boot/dts/apple/t6001.dtsi
@@ -16,11 +16,33 @@
 
 #include "multi-die-cpp.h"
 
+#ifndef GPU_REPEAT
+# define GPU_REPEAT(x) <x x x x>
+#endif
+
 #include "t600x-common.dtsi"
 
 / {
 	compatible = "apple,t6001", "apple,arm-platform";
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0 0 0 0>;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0 0 0 0>;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0 0 0 0>;
+		};
+	};
+
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
@@ -63,3 +85,7 @@
 		};
 	};
 };
+
+&gpu {
+	compatible = "apple,agx-t6001", "apple,agx-g13x";
+};
diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi
index 8fa2d8dd72ff7f..f1164315be755a 100644
--- a/arch/arm64/boot/dts/apple/t6002.dtsi
+++ b/arch/arm64/boot/dts/apple/t6002.dtsi
@@ -16,6 +16,10 @@
 
 #include "multi-die-cpp.h"
 
+#ifndef GPU_REPEAT
+# define GPU_REPEAT(x) <x x x x x x x x>
+#endif
+
 #include "t600x-common.dtsi"
 
 / {
@@ -301,3 +305,7 @@
 	// On t6002, the die0 GPU power domain needs both AFR power domains
 	power-domains = <&ps_afr>, <&ps_afr_die1>;
 };
+
+&gpu {
+	compatible = "apple,agx-t6002", "apple,agx-g13x";
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index 01385ef831ca91..c76ab2fc8f96dd 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -11,6 +11,10 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	aliases {
+		gpu = &gpu;
+	};
+
 	cpus {
 		#address-cells = <2>;
 		#size-cells = <0>;
@@ -333,6 +337,50 @@
 		*/
 	};
 
+	gpu_opp: opp-table-gpu {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <0>;
+			opp-microvolt = GPU_REPEAT(400000);
+			opp-microwatt = <0>;
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <388800000>;
+			opp-microvolt = GPU_REPEAT(634000);
+			opp-microwatt = <25011450>;
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <486000000>;
+			opp-microvolt = GPU_REPEAT(650000);
+			opp-microwatt = <31681170>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <648000000>;
+			opp-microvolt = GPU_REPEAT(668000);
+			opp-microwatt = <41685750>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <777600000>;
+			opp-microvolt = GPU_REPEAT(715000);
+			opp-microwatt = <56692620>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <972000000>;
+			opp-microvolt = GPU_REPEAT(778000);
+			opp-microwatt = <83371500>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1296000000>;
+			opp-microvolt = GPU_REPEAT(903000);
+			opp-microwatt = <166743000>;
+		};
+	};
+
 	pmu-e {
 		compatible = "apple,icestorm-pmu";
 		interrupt-parent = <&aic>;
@@ -384,4 +432,22 @@
 		#clock-cells = <0>;
 		clock-output-names = "nco_ref";
 	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0 0 0 0>;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0 0 0 0>;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0 0 0 0>;
+		};
+	};
 };
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 1b795ddbe552d5..a313f8e3058645 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -426,6 +426,70 @@
 		#sound-dai-cells = <1>;
 	};
 
+	gpu: gpu@406400000 {
+		compatible = "apple,agx-g13x";
+		reg = <0x4 0x6400000 0 0x40000>,
+			<0x4 0x4000000 0 0x1000000>;
+		reg-names = "asc", "sgx";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1044 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1045 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1046 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1047 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1063 IRQ_TYPE_LEVEL_HIGH>;
+		mboxes = <&agx_mbox>;
+		power-domains = <&ps_gfx>;
+		memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
+		memory-region-names = "ttbs", "pagetables", "handoff";
+
+		operating-points-v2 = <&gpu_opp>;
+		apple,perf-base-pstate = <1>;
+		apple,min-sram-microvolt = <790000>;
+		apple,avg-power-filter-tc-ms = <1000>;
+		apple,avg-power-ki-only = <2.4>;
+		apple,avg-power-kp = <1.5>;
+		apple,avg-power-min-duty-cycle = <40>;
+		apple,avg-power-target-filter-tc = <125>;
+		apple,fast-die0-integral-gain = <500.0>;
+		apple,fast-die0-proportional-gain = <72.0>;
+		apple,perf-boost-ce-step = <50>;
+		apple,perf-boost-min-util = <90>;
+		apple,perf-filter-drop-threshold = <0>;
+		apple,perf-filter-time-constant = <5>;
+		apple,perf-filter-time-constant2 = <50>;
+		apple,perf-integral-gain = <6.3>;
+		apple,perf-integral-gain2 = <0.197392>;
+		apple,perf-integral-min-clamp = <0>;
+		apple,perf-proportional-gain = <15.75>;
+		apple,perf-proportional-gain2 = <6.853981>;
+		apple,perf-tgt-utilization = <85>;
+		apple,power-sample-period = <8>;
+		apple,ppm-filter-time-constant-ms = <100>;
+		apple,ppm-ki = <30>;
+		apple,ppm-kp = <1.5>;
+		apple,pwr-filter-time-constant = <313>;
+		apple,pwr-integral-gain = <0.0202129>;
+		apple,pwr-integral-min-clamp = <0>;
+		apple,pwr-min-duty-cycle = <40>;
+		apple,pwr-proportional-gain = <5.2831855>;
+
+		apple,core-leak-coef = GPU_REPEAT(1200.0);
+		apple,sram-leak-coef = GPU_REPEAT(20.0);
+	};
+
+	agx_mbox: mbox@406408000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x4 0x6408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1059 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1060 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1061 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1062 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+	};
+
 	pcie0_dart_0: iommu@581008000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x5 0x81008000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index c5400f298a123e..dad19cad7a99a0 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -381,3 +381,12 @@
 &pcie0_dart_3 {
 	status = "okay";
 };
+
+&gpu {
+	apple,avg-power-ki-only = <0.6375>;
+	apple,avg-power-kp = <0.58>;
+	apple,avg-power-target-filter-tc = <1>;
+	apple,perf-base-pstate = <3>;
+	apple,ppm-ki = <5.8>;
+	apple,ppm-kp = <0.355>;
+};

From 2485a3bd33bbd0e11afb24dbb21ea9ffa4005cef Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 4 Nov 2022 21:56:26 +0900
Subject: [PATCH 0051/1027] arm64: dts: t8103: Add GPU leak coefficients

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 7a3c553f863f12..c77ae26879090f 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -473,6 +473,9 @@
 			apple,pwr-integral-min-clamp = <0>;
 			apple,pwr-min-duty-cycle = <40>;
 			apple,pwr-proportional-gain = <5.2831855>;
+
+			apple,core-leak-coef = <1000.0>;
+			apple,sram-leak-coef = <45.0>;
 		};
 
 		agx_mbox: mbox@206408000 {

From 8715a6fb87d6316da179d378dbee8a9e3112c096 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Nov 2022 10:44:43 +0900
Subject: [PATCH 0052/1027] arm64: dts: apple: Add no-map to GPU
 reserved-memory nodes

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t600x-common.dtsi | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index c76ab2fc8f96dd..279ba91d8abacd 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -440,14 +440,17 @@
 
 		uat_handoff: uat-handoff {
 			reg = <0 0 0 0>;
+			no-map;
 		};
 
 		uat_pagetables: uat-pagetables {
 			reg = <0 0 0 0>;
+			no-map;
 		};
 
 		uat_ttbs: uat-ttbs {
 			reg = <0 0 0 0>;
+			no-map;
 		};
 	};
 };

From 1759a9f6d5275624d67731a2122eb2914f58fc92 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 25 Nov 2022 23:06:59 +0900
Subject: [PATCH 0053/1027] arm64: dts: apple: Add GPU nodes to T8112

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 141 +++++++++++++++++++++++++++
 1 file changed, 141 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index ec99763b11cc35..019d9f42250e45 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -20,6 +20,10 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	aliases {
+		gpu = &gpu;
+	};
+
 	cpus {
 		#address-cells = <2>;
 		#size-cells = <0>;
@@ -321,6 +325,60 @@
 #endif
 	};
 
+	gpu_opp: opp-table-gpu {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <0>;
+			opp-microvolt = <400000>;
+			opp-microwatt = <0>;
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <444000000>;
+			opp-microvolt = <603000>;
+			opp-microwatt = <4295000>;
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <612000000>;
+			opp-microvolt = <675000>;
+			opp-microwatt = <6251000>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <808000000>;
+			opp-microvolt = <710000>;
+			opp-microwatt = <8625000>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <968000000>;
+			opp-microvolt = <775000>;
+			opp-microwatt = <11948000>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1110000000>;
+			opp-microvolt = <820000>;
+			opp-microwatt = <15071000>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1236000000>;
+			opp-microvolt = <875000>;
+			opp-microwatt = <18891000>;
+		};
+		opp07 {
+			opp-hz = /bits/ 64 <1338000000>;
+			opp-microvolt = <915000>;
+			opp-microwatt = <21960000>;
+		};
+		opp08 {
+			opp-hz = /bits/ 64 <1398000000>;
+			opp-microvolt = <950000>;
+			opp-microwatt = <22800000>;
+		};
+	};
+
 	timer {
 		compatible = "arm,armv8-timer";
 		interrupt-parent = <&aic>;
@@ -368,6 +426,27 @@
 		clock-output-names = "clk_disp0";
 	};
 
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0x0 0 0 0>;
+			no-map;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0x0 0 0 0>;
+			no-map;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0x0 0 0 0>;
+			no-map;
+		};
+	};
+
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
@@ -376,6 +455,68 @@
 		ranges;
 		nonposted-mmio;
 
+		gpu: gpu@206400000 {
+			compatible = "apple,agx-t8112", "apple,agx-g14g";
+			reg = <0x2 0x6400000 0 0x40000>,
+				<0x2 0x4000000 0 0x1000000>;
+			reg-names = "asc", "sgx";
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 697 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 698 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 699 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 700 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 713 IRQ_TYPE_LEVEL_HIGH>;
+			mboxes = <&agx_mbox>;
+			power-domains = <&ps_gfx>;
+			memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
+			memory-region-names = "ttbs", "pagetables", "handoff";
+
+			apple,firmware-version = <12 4 0>;
+			apple,firmware-compat = <12 4 0>;
+
+			operating-points-v2 = <&gpu_opp>;
+			apple,perf-base-pstate = <1>;
+			apple,min-sram-microvolt = <780000>;
+			apple,avg-power-filter-tc-ms = <300>;
+			apple,avg-power-ki-only = <9.375>;
+			apple,avg-power-kp = <3.22>;
+			apple,avg-power-min-duty-cycle = <40>;
+			apple,avg-power-target-filter-tc = <1>;
+			apple,fast-die0-integral-gain = <200.0>;
+			apple,fast-die0-proportional-gain = <5.0>;
+			apple,perf-boost-ce-step = <50>;
+			apple,perf-boost-min-util = <90>;
+			apple,perf-filter-drop-threshold = <0>;
+			apple,perf-filter-time-constant = <5>;
+			apple,perf-filter-time-constant2 = <200>;
+			apple,perf-integral-gain = <5.94>;
+			apple,perf-integral-gain2 = <5.94>;
+			apple,perf-integral-min-clamp = <0>;
+			apple,perf-proportional-gain = <14.85>;
+			apple,perf-proportional-gain2 = <14.85>;
+			apple,perf-tgt-utilization = <85>;
+			apple,power-sample-period = <8>;
+			apple,ppm-filter-time-constant-ms = <34>;
+			apple,ppm-ki = <205.0>;
+			apple,ppm-kp = <0.75>;
+			apple,pwr-min-duty-cycle = <40>;
+			apple,core-leak-coef = <1920.0>;
+			apple,sram-leak-coef = <74.0>;
+		};
+
+		agx_mbox: mbox@206408000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x6408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 709 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 710 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 711 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 712 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+		};
+
 		cpufreq_e: cpufreq@210e20000 {
 			compatible = "apple,t8112-cluster-cpufreq", "apple,cluster-cpufreq";
 			reg = <0x2 0x10e20000 0 0x1000>;

From 9b735a298654b9660aa9eaaf0223c9cc0adbbe0a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 25 Nov 2022 23:07:22 +0900
Subject: [PATCH 0054/1027] arm64: dts: apple: Add GPU firmware versions to
 t8113/t600x

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 3 +++
 arch/arm64/boot/dts/apple/t8103.dtsi      | 3 +++
 2 files changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index a313f8e3058645..65df566d4baf1d 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -442,6 +442,9 @@
 		memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
 		memory-region-names = "ttbs", "pagetables", "handoff";
 
+		apple,firmware-version = <12 3 0>;
+		apple,firmware-compat = <12 3 0>;
+
 		operating-points-v2 = <&gpu_opp>;
 		apple,perf-base-pstate = <1>;
 		apple,min-sram-microvolt = <790000>;
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index c77ae26879090f..4abc94bdd2888c 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -446,6 +446,9 @@
 			memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
 			memory-region-names = "ttbs", "pagetables", "handoff";
 
+			apple,firmware-version = <12 3 0>;
+			apple,firmware-compat = <12 3 0>;
+
 			operating-points-v2 = <&gpu_opp>;
 			apple,perf-base-pstate = <1>;
 			apple,min-sram-microvolt = <850000>;

From 34bf1895d2a8a48dc64c5a866be025071d313f7e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 30 Sep 2023 21:38:03 +0200
Subject: [PATCH 0055/1027] arch: arm64: apple: Add spi1-nvram.dtsi

All devices use an identical SPI nor/nvram setup so move that to dtsi
file they all can include.
Since the nvram partition size depends on the iboot version disable the
nvram partition and have m1n1 enable it after it has filled the correct
values from the ADT.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/spi1-nvram.dtsi | 32 +++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 arch/arm64/boot/dts/apple/spi1-nvram.dtsi

diff --git a/arch/arm64/boot/dts/apple/spi1-nvram.dtsi b/arch/arm64/boot/dts/apple/spi1-nvram.dtsi
new file mode 100644
index 00000000000000..36bfef5cf81d43
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/spi1-nvram.dtsi
@@ -0,0 +1,32 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Common config for Apple's nvram using a SPI nor flash. This is common on all
+ * M1 and M2 devices.  identically set up identically on all M1 and M2 devicesspi1, spinor and nvram config identical on all devices
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+&spi1 {
+	status = "okay";
+
+	flash@0 {
+		compatible = "jedec,spi-nor";
+		reg = <0x0>;
+		spi-max-frequency = <25000000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		partitions {
+			compatible = "fixed-partitions";
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			nvram: partition@700000 {
+				label = "nvram";
+				/* To be filled by the loader */
+				reg = <0x0 0x0>;
+				status = "disabled";
+			};
+		};
+	};
+};

From afb98a949174676f02419fd2f4b39f40e2000fc7 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 29 Oct 2022 23:18:27 +0300
Subject: [PATCH 0056/1027] arm64: dts: apple: t600x: Add the NVRAM bindings

Add the SPI controller and the nvram partition bindings
for M1 Pro/Max/Ultra Macs

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi      |  1 -
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 15 +++------------
 2 files changed, 3 insertions(+), 13 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 65df566d4baf1d..4a5a275b706f5a 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -355,7 +355,6 @@
 		pinctrl-0 = <&spi1_pins>;
 		pinctrl-names = "default";
 		power-domains = <&ps_spi1>;
-		status = "disabled";
 	};
 
 	spi3: spi@39b10c000 {
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 39fdc7d3360f42..58d017253f4aa2 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -21,6 +21,7 @@
 		dcp = &dcp;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
+		nvram = &nvram;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -262,18 +263,6 @@
 	clock-frequency = <1068000000>;
 };
 
-&spi1 {
-	status = "disabled";
-
-	flash@0 {
-		compatible = "jedec,spi-nor";
-		reg = <0x0>;
-		spi-max-frequency = <25000000>;
-		#address-cells = <1>;
-		#size-cells = <1>;
-	};
-};
-
 &spi3 {
 	status = "okay";
 
@@ -436,3 +425,5 @@
 		};
 	};
 };
+
+#include "spi1-nvram.dtsi"

From af2530df30d7b5b5f10c96fff5bae226990ab6c4 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 29 Oct 2022 23:17:40 +0300
Subject: [PATCH 0057/1027] arm64: dts: apple: t8112: Add the NVRAM bindings

Add the SPI controller and the nvram partition bindings for M2 Macs

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi |  2 ++
 arch/arm64/boot/dts/apple/t8112.dtsi      | 28 +++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 7edabecc862e46..16daa5c5cde57d 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -171,3 +171,5 @@
 &nco_clkref {
 	clock-frequency = <900000000>;
 };
+
+#include "spi1-nvram.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 019d9f42250e45..754d06f868b4b8 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -447,6 +447,13 @@
 		};
 	};
 
+	clk_200m: clock-200m {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <200000000>;
+		clock-output-names = "clk_200m";
+	};
+
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
@@ -682,6 +689,20 @@
 			status = "disabled";
 		};
 
+		spi1: spi@235104000 {
+			compatible = "apple,t8112-spi", "apple,spi";
+			reg = <0x2 0x35104000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 749 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_200m>;
+			pinctrl-0 = <&spi1_pins>;
+			pinctrl-names = "default";
+			power-domains = <&ps_spi1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		spi3: spi@23510c000 {
 			compatible = "apple,t8112-spi", "apple,spi";
 			reg = <0x2 0x3510c000 0x0 0x4000>;
@@ -861,6 +882,13 @@
 					 <APPLE_PINMUX(130, 1)>;
 			};
 
+			spi1_pins: spi1-pins {
+				pinmux = <APPLE_PINMUX(46, 1)>,
+					<APPLE_PINMUX(47, 1)>,
+					<APPLE_PINMUX(48, 1)>,
+					<APPLE_PINMUX(49, 1)>;
+			};
+
 			spi3_pins: spi3-pins {
 				pinmux = <APPLE_PINMUX(93, 1)>,
 					<APPLE_PINMUX(94, 1)>,

From 4335e4ca08f169ccea5db1749e9cb43321d3bfc4 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 29 Oct 2022 23:14:39 +0300
Subject: [PATCH 0058/1027] arm64: dts: apple: t8103: Add the NVRAM bindings

Add the SPI controller and the nvram partition bindings for M1 Macs

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi |  2 ++
 arch/arm64/boot/dts/apple/t8103.dtsi      | 28 +++++++++++++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 1ad2a57698afb9..6b6332b5652732 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -183,3 +183,5 @@
 &nco_clkref {
 	clock-frequency = <900000000>;
 };
+
+#include "spi1-nvram.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 4abc94bdd2888c..3d547b5cf4465a 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -383,6 +383,13 @@
 		clock-output-names = "clk_120m";
 	};
 
+	clk_200m: clock-200m {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <200000000>;
+		clock-output-names = "clk_200m";
+	};
+
 	/* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
 	clk_disp0: clock-disp0 {
 		compatible = "fixed-clock";
@@ -657,6 +664,20 @@
 			status = "disabled";
 		};
 
+		spi1: spi@235104000 {
+			compatible = "apple,t8103-spi", "apple,spi";
+			reg = <0x2 0x35104000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 615 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_200m>;
+			pinctrl-0 = <&spi1_pins>;
+			pinctrl-names = "default";
+			power-domains = <&ps_spi1>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled";
+		};
+
 		spi3: spi@23510c000 {
 			compatible = "apple,t8103-spi", "apple,spi";
 			reg = <0x2 0x3510c000 0x0 0x4000>;
@@ -835,6 +856,13 @@
 					 <APPLE_PINMUX(134, 1)>;
 			};
 
+			spi1_pins: spi1-pins {
+				pinmux = <APPLE_PINMUX(42, 1)>,
+					<APPLE_PINMUX(43, 1)>,
+					<APPLE_PINMUX(44, 1)>,
+					<APPLE_PINMUX(45, 1)>;
+			};
+
 			spi3_pins: spi3-pins {
 				pinmux = <APPLE_PINMUX(46, 1)>,
 					<APPLE_PINMUX(47, 1)>,

From e126ec56ad123db61118bc8a988cda1c8b810d3a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Dec 2022 00:17:35 +0900
Subject: [PATCH 0059/1027] arm64: dts: apple: t600x: Add DCP power domain to
 missing devices

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi      | 2 ++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 1 +
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 1 +
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi      | 2 --
 4 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 4a5a275b706f5a..f34c5f5d315a8a 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -179,6 +179,7 @@
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
 		status = "disabled";
+		power-domains = <&ps_disp0_cpu0>;
 	};
 
 	dcp_dart: iommu@38b30c000 {
@@ -187,6 +188,7 @@
 		#iommu-cells = <1>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_disp0_cpu0>;
 	};
 
 	dcp_mbox: mbox@38bc08000 {
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 58d017253f4aa2..04c418aead3a4e 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -38,6 +38,7 @@
 			reg = <0 0 0 0>; /* To be filled by loader */
 			/* Format properties will be added by loader */
 			status = "disabled";
+			power-domains = <&ps_disp0_cpu0>;
 		};
 	};
 
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index dad19cad7a99a0..50dd882a7ad2e8 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -35,6 +35,7 @@
 			reg = <0 0 0 0>; /* To be filled by loader */
 			/* Format properties will be added by loader */
 			status = "disabled";
+			power-domains = <&ps_disp0_cpu0>;
 		};
 	};
 
diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 0bd44753b76a0c..00b317c2355b8c 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1293,7 +1293,6 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(disp0_fe);
 		power-domains = <&DIE_NODE(ps_afnc2_lw0)>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
 	DIE_NODE(ps_disp0_cpu0): power-controller@350 {
@@ -1303,7 +1302,6 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(disp0_cpu0);
 		power-domains = <&DIE_NODE(ps_disp0_fe)>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 		apple,min-state = <4>;
 	};
 

From dd6df0d8a8e2a23d3a95293583064da92bd94d86 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Dec 2022 00:17:35 +0900
Subject: [PATCH 0060/1027] arm64: dts: apple: t8103: Add DCP power domain to
 missing devices

Removes the "apple,always-on" property from ps_disp0_fe/cpu0.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 --
 arch/arm64/boot/dts/apple/t8103.dtsi      | 2 ++
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 6b6332b5652732..2b4136d6f77ee4 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -32,6 +32,7 @@
 		framebuffer0: framebuffer@0 {
 			compatible = "apple,simple-framebuffer", "simple-framebuffer";
 			reg = <0 0 0 0>; /* To be filled by loader */
+			power-domains = <&ps_disp0_cpu0>;
 			/* Format properties will be added by loader */
 			status = "disabled";
 		};
diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 0966322c5c8e3f..ea0ee0224b1cb1 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -646,7 +646,6 @@
 		#reset-cells = <0>;
 		label = "disp0_fe";
 		power-domains = <&ps_rmx>, <&ps_pmp>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
 	ps_dispext_fe: power-controller@368 {
@@ -1001,7 +1000,6 @@
 		#reset-cells = <0>;
 		label = "disp0_cpu0";
 		power-domains = <&ps_disp0_fe>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 		apple,min-state = <4>;
 	};
 };
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 3d547b5cf4465a..d1cef7837e1ff9 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -519,6 +519,7 @@
 			#iommu-cells = <1>;
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_disp0_cpu0>;
 			status = "disabled";
 		};
 
@@ -528,6 +529,7 @@
 			#iommu-cells = <1>;
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_disp0_cpu0>;
 		};
 
 		dcp_mbox: mbox@231c08000 {

From 474acd882bd958c98fb72ebc246395934ccf5952 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Dec 2022 00:17:35 +0900
Subject: [PATCH 0061/1027] arm64: dts: apple: t8112: Add DCP power domain to
 missing devices

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 2 --
 arch/arm64/boot/dts/apple/t8112.dtsi      | 2 ++
 3 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 16daa5c5cde57d..5fec625bf5c2a6 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -30,6 +30,7 @@
 		framebuffer0: framebuffer@0 {
 			compatible = "apple,simple-framebuffer", "simple-framebuffer";
 			reg = <0 0 0 0>; /* To be filled by loader */
+			power-domains = <&ps_disp0_cpu0>;
 			/* Format properties will be added by loader */
 			status = "disabled";
 		};
diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 118694dd9b5f06..d1711155f84686 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -663,7 +663,6 @@
 		#reset-cells = <0>;
 		label = "disp0_sys";
 		power-domains = <&ps_rmx1>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
 	ps_disp0_fe: power-controller@378 {
@@ -673,7 +672,6 @@
 		#reset-cells = <0>;
 		label = "disp0_fe";
 		power-domains = <&ps_disp0_sys>, <&ps_pmp>;
-		apple,always-on; /* TODO: figure out if we can enable PM here */
 	};
 
 	ps_dispext_sys: power-controller@380 {
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 754d06f868b4b8..199214ce73db73 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -542,6 +542,7 @@
 			#iommu-cells = <1>;
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_disp0_cpu0>;
 			status = "disabled";
 		};
 
@@ -551,6 +552,7 @@
 			#iommu-cells = <1>;
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_disp0_cpu0>;
 		};
 
 		dcp_mbox: mbox@231c08000 {

From ebefb56ce84d787da4763255f798b9c95dc412a0 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 30 Dec 2022 01:15:57 +0100
Subject: [PATCH 0062/1027] arm64: dts: apple: t8103: Add missing ps_pmp
 dependency to ps_gfx

AGX' ASC crashes shortly after ps_pmp is powered down due to dcp
runtime PM suspend.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index ea0ee0224b1cb1..724e7fd559e7a1 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -733,6 +733,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "gfx";
+		power-domains = <&ps_pmp>;
 	};
 
 	ps_dcs4: power-controller@320 {

From ef323361aed8e815604afa5b9249ec530ed3d3c9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 9 Jan 2023 00:03:49 +0100
Subject: [PATCH 0063/1027] arm64: dts: apple: t600x: Add "ps_disp0_cpu0" as
 resets for dcp

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index f34c5f5d315a8a..6421f085465060 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -219,6 +219,7 @@
 			<0x3 0x8b800000 0x0 0x800000>;
 		apple,bw-scratch = <&pmgr_dcp 0 4 0x988>;
 		power-domains = <&ps_disp0_cpu0>;
+		resets = <&ps_disp0_cpu0>;
 		clocks = <&clk_disp0>;
 		apple,asc-dram-mask = <0x1f0 0x00000000>;
 		phandle = <&dcp>;

From 501fe38b5e5e8dcacf5310e3f46353258a6e21d5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 9 Jan 2023 00:05:06 +0100
Subject: [PATCH 0064/1027] arm64: dts: apple: t8103: Add "ps_disp0_cpu0" as
 resets for dcp

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index d1cef7837e1ff9..65e532c8198841 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -564,6 +564,7 @@
 			apple,bw-scratch = <&pmgr_dcp 0 5 0x14>;
 			apple,bw-doorbell = <&pmgr_dcp 1 6>;
 			power-domains = <&ps_disp0_cpu0>;
+			resets = <&ps_disp0_cpu0>;
 			clocks = <&clk_disp0>;
 			apple,asc-dram-mask = <0xf 0x00000000>;
 			phandle = <&dcp>;

From 5b7c4a6f5742e2b0d14037f94a646c5feef924e8 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 9 Jan 2023 00:05:30 +0100
Subject: [PATCH 0065/1027] arm64: dts: apple: t8112: Add "ps_disp0_cpu0" as
 resets for dcp

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 199214ce73db73..9ef423b5fde4c5 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -585,6 +585,7 @@
 				<0x2 0x31800000 0x0 0x800000>;
 			apple,bw-scratch = <&pmgr_dcp 0 4 0x5d8>;
 			power-domains = <&ps_disp0_cpu0>;
+			resets = <&ps_disp0_cpu0>;
 			clocks = <&clk_disp0>;
 			apple,asc-dram-mask = <0x0 0x0>;
 			phandle = <&dcp>;

From 054f7af8687d3f0a8d2a86cc97293ff1ff31edfa Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 11 Mar 2023 21:45:11 +0900
Subject: [PATCH 0066/1027] arm64: dts: apple: j314/j316: Disable ATC3_USB_AON
 power domain

These power domains are normally always on for real Thunderbolt ports
(or else dwc3 breaks), but not for the port that's hardwired to the HDMI
bridge.

Fixes some dmesg spam:

apple-pmgr-pwrstate 292280000.power-management:power-controller@a0:
    always-on domain atc3_usb_aon is not on at boot

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 04c418aead3a4e..7c5bbbed9a183d 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -385,6 +385,10 @@
 	status = "disabled";
 };
 
+&ps_atc3_usb_aon {
+	/delete-property/ apple,always-on;
+};
+
 / {
 	sound: sound {
 		compatible = "apple,j314-macaudio", "apple,macaudio";

From 2f527113e7a92f8a97f847736002e0077e7d51d5 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 19 Mar 2023 19:12:52 +0900
Subject: [PATCH 0067/1027] arm64: dts: apple: Add keyboard alias & layout
 props for t8112 laptops

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 5 ++++-
 arch/arm64/boot/dts/apple/t8112-j493.dts | 5 ++++-
 2 files changed, 8 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index de9c98c6fac0a2..ceb93965cb4d44 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -20,6 +20,7 @@
 	aliases {
 		bluetooth0 = &bluetooth0;
 		wifi0 = &wifi0;
+		keyboard = &keyboard;
 	};
 
 	led-controller {
@@ -206,7 +207,9 @@
 		firmware-name = "apple/tpmtfw-j413.bin";
 	};
 
-	keyboard {
+	keyboard: keyboard {
+		hid-country-code = <0>;
+		apple,keyboard-layout-id = <0>;
 	};
 
 	stm {
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 42705711c6928d..d34acd0ee2f203 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -20,6 +20,7 @@
 	aliases {
 		bluetooth0 = &bluetooth0;
 		wifi0 = &wifi0;
+		keyboard = &keyboard;
 	};
 
 	led-controller {
@@ -194,7 +195,9 @@
 		firmware-name = "apple/tpmtfw-j493.bin";
 	};
 
-	keyboard {
+	keyboard: keyboard {
+		hid-country-code = <0>;
+		apple,keyboard-layout-id = <0>;
 	};
 
 	stm {

From f606320fcb70a6a03054cb55b6f0b313df4a9c41 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 9 Apr 2023 23:49:01 +0900
Subject: [PATCH 0068/1027] arm64: dts: apple: Fix t600x mca IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 6421f085465060..d5f83226dda558 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -418,10 +418,10 @@
 			    "tx2a", "rx2a", "tx2b", "rx2b",
 			    "tx3a", "rx3a", "tx3b", "rx3b";
 		interrupt-parent = <&aic>;
-		interrupts = <AIC_IRQ 0 1112 IRQ_TYPE_LEVEL_HIGH>,
+		interrupts = <AIC_IRQ 0 1111 IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_IRQ 0 1112 IRQ_TYPE_LEVEL_HIGH>,
 			     <AIC_IRQ 0 1113 IRQ_TYPE_LEVEL_HIGH>,
-			     <AIC_IRQ 0 1114 IRQ_TYPE_LEVEL_HIGH>,
-			     <AIC_IRQ 0 1115 IRQ_TYPE_LEVEL_HIGH>;
+			     <AIC_IRQ 0 1114 IRQ_TYPE_LEVEL_HIGH>;
 		power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>,
 				<&ps_mca2>, <&ps_mca3>;
 		resets = <&ps_audio_p>;

From a05111c04cb93e55b6023660e2441df92ebbcd8a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 9 Apr 2023 23:48:38 +0900
Subject: [PATCH 0069/1027] arm64: dts: apple: Add initial t602x device trees

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/Makefile            |    5 +
 arch/arm64/boot/dts/apple/t600x-die0.dtsi     |    2 +-
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi |    7 +
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     |    4 +
 arch/arm64/boot/dts/apple/t6020-j414s.dts     |   38 +
 arch/arm64/boot/dts/apple/t6020-j416s.dts     |   38 +
 arch/arm64/boot/dts/apple/t6020-j474s.dts     |   98 +
 arch/arm64/boot/dts/apple/t6020.dtsi          |   24 +
 arch/arm64/boot/dts/apple/t6021-j414c.dts     |   38 +
 arch/arm64/boot/dts/apple/t6021-j416c.dts     |   54 +
 arch/arm64/boot/dts/apple/t6021.dtsi          |   91 +
 arch/arm64/boot/dts/apple/t602x-common.dtsi   |  489 ++++
 arch/arm64/boot/dts/apple/t602x-die0.dtsi     |  702 +++++
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi     |  344 +++
 .../arm64/boot/dts/apple/t602x-gpio-pins.dtsi |   81 +
 .../arm64/boot/dts/apple/t602x-j414-j416.dtsi |   88 +
 arch/arm64/boot/dts/apple/t602x-nvme.dtsi     |   42 +
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi     | 2262 +++++++++++++++++
 18 files changed, 4406 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm64/boot/dts/apple/t6020-j414s.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6020-j416s.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6020-j474s.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6020.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t6021-j414c.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6021-j416c.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6021.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-common.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-die0.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-dieX.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-nvme.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi

diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile
index aec5e29cdfb737..e3e62c672d53b7 100644
--- a/arch/arm64/boot/dts/apple/Makefile
+++ b/arch/arm64/boot/dts/apple/Makefile
@@ -13,3 +13,8 @@ dtb-$(CONFIG_ARCH_APPLE) += t6002-j375d.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j493.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6020-j414s.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6021-j414c.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6020-j416s.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6021-j416c.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6020-j474s.dtb
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index d5f83226dda558..3c991c9bdc529a 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -221,7 +221,7 @@
 		power-domains = <&ps_disp0_cpu0>;
 		resets = <&ps_disp0_cpu0>;
 		clocks = <&clk_disp0>;
-		apple,asc-dram-mask = <0x1f0 0x00000000>;
+		apple,asc-dram-mask = <0>;
 		phandle = <&dcp>;
 
 		disp0_piodma: piodma {
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 7c5bbbed9a183d..9475dbd2f2047e 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -264,6 +264,7 @@
 	clock-frequency = <1068000000>;
 };
 
+#ifndef NO_SPI_TRACKPAD
 &spi3 {
 	status = "okay";
 
@@ -284,6 +285,7 @@
 		interrupts-extended = <&pinctrl_nub 6 IRQ_TYPE_LEVEL_LOW>;
 	};
 };
+#endif
 
 /* PCIe devices */
 &port00 {
@@ -310,6 +312,7 @@
 	/* SD card reader */
 	bus-range = <2 2>;
 	pwren-gpios = <&smc_gpio 26 GPIO_ACTIVE_HIGH>;
+	status = "okay";
 	sdhci0: mmc@0,0 {
 		compatible = "pci17a0,9755";
 		reg = <0x20000 0x0 0x0 0x0 0x0>;
@@ -322,6 +325,10 @@
 	status = "okay";
 };
 
+&pcie0_dart_1 {
+	status = "okay";
+};
+
 /* USB controllers */
 &dwc3_0 {
 	port {
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 50dd882a7ad2e8..0564c8cae687ab 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -344,6 +344,7 @@
 	};
 };
 
+#ifndef NO_PCIE_SDHC
 &port01 {
 	/* SD card reader */
 	bus-range = <2 2>;
@@ -355,6 +356,7 @@
 		wp-inverted;
 	};
 };
+#endif
 
 &port02 {
 	/* 10 Gbit Ethernet */
@@ -383,6 +385,7 @@
 	status = "okay";
 };
 
+#ifndef NO_GPU
 &gpu {
 	apple,avg-power-ki-only = <0.6375>;
 	apple,avg-power-kp = <0.58>;
@@ -391,3 +394,4 @@
 	apple,ppm-ki = <5.8>;
 	apple,ppm-kp = <0.355>;
 };
+#endif
diff --git a/arch/arm64/boot/dts/apple/t6020-j414s.dts b/arch/arm64/boot/dts/apple/t6020-j414s.dts
new file mode 100644
index 00000000000000..18cc67a3076def
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6020-j414s.dts
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * MacBook Pro (14-inch, M2 Pro, 2023)
+ *
+ * target-type: J414s
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6020.dtsi"
+#include "t602x-j414-j416.dtsi"
+
+/ {
+	compatible = "apple,j414s", "apple,t6020", "apple,arm-platform";
+	model = "Apple MacBook Pro (14-inch, M2 Pro, 2023)";
+};
+
+&wifi0 {
+	brcm,board-type = "apple,tokara";
+};
+
+&bluetooth0 {
+	brcm,board-type = "apple,tokara";
+};
+
+&panel {
+	compatible = "apple,panel-j414", "apple,panel-mini-led", "apple,panel";
+	width-mm = <302>;
+	height-mm = <196>;
+	adj-height-mm = <189>;
+};
+
+&sound {
+	compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio";
+	model = "MacBook Pro J414";
+};
diff --git a/arch/arm64/boot/dts/apple/t6020-j416s.dts b/arch/arm64/boot/dts/apple/t6020-j416s.dts
new file mode 100644
index 00000000000000..b9e0973ba37c30
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6020-j416s.dts
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * MacBook Pro (16-inch, M2 Pro, 2023)
+ *
+ * target-type: J416s
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6020.dtsi"
+#include "t602x-j414-j416.dtsi"
+
+/ {
+	compatible = "apple,j416s", "apple,t6020", "apple,arm-platform";
+	model = "Apple MacBook Pro (16-inch, M2 Pro, 2023)";
+};
+
+&wifi0 {
+	brcm,board-type = "apple,amami";
+};
+
+&bluetooth0 {
+	brcm,board-type = "apple,amami";
+};
+
+&panel {
+	compatible = "apple,panel-j416", "apple,panel-mini-led", "apple,panel";
+	width-mm = <346>;
+	height-mm = <223>;
+	adj-height-mm = <216>;
+};
+
+&sound {
+	compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio";
+	model = "MacBook Pro J416";
+};
diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
new file mode 100644
index 00000000000000..9b61a7bb9d6ce4
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Mini (M2 Pro, 2023)
+ *
+ * target-type: J474s
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6020.dtsi"
+
+/*
+ * These model is very similar to the previous generation Mac Studio, other than
+ * the GPIO indices.
+ */
+
+#define NO_PCIE_SDHC
+#define NO_GPU
+#include "t600x-j375.dtsi"
+
+/ {
+	compatible = "apple,j474s", "apple,t6020", "apple,arm-platform";
+	model = "Apple Mac Mini (M2 Pro, 2023)";
+
+	aliases {
+		ethernet0 = &ethernet0;
+	};
+};
+
+&hpm0 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm1 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm2 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm3  {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&wifi0 {
+	compatible = "pci14e4,4434";
+	brcm,board-type = "apple,tasmania";
+};
+
+&bluetooth0 {
+	compatible = "pci14e4,5f72";
+	brcm,board-type = "apple,tasmania";
+};
+
+/* PCIe devices */
+&port00 {
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
+};
+
+&port02 {
+	/* 10 Gbit Ethernet */
+	bus-range = <3 3>;
+	status = "okay";
+	ethernet0: ethernet@0,0 {
+		reg = <0x30000 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-mac-address = [00 10 18 00 00 00];
+	};
+};
+
+&port03 {
+	/* USB xHCI */
+	pwren-gpios = <&smc_gpio 19 GPIO_ACTIVE_HIGH>;
+};
+
+
+&speaker {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&jack_codec {
+	reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&sound {
+	compatible = "apple,j474-macaudio", "apple,j473-macaudio", "apple,macaudio";
+	model = "Mac mini J474";
+};
+
+&gpu {
+	/* Apple does not do this, but they probably should */
+	apple,perf-base-pstate = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t6020.dtsi b/arch/arm64/boot/dts/apple/t6020.dtsi
new file mode 100644
index 00000000000000..3a864ebd91bb2f
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6020.dtsi
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Apple T6020 "M2 Pro" SoC
+ *
+ * Other names: H14J, "Rhodes Chop"
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/* This chip is just a cut down version of t6021, so include it and disable the missing parts */
+
+#define GPU_REPEAT(x) <x x>
+
+#include "t6021.dtsi"
+
+/ {
+	compatible = "apple,t6020", "apple,arm-platform";
+};
+
+/delete-node/ &pmgr_south;
+
+&gpu {
+	compatible = "apple,agx-t6020", "apple,agx-g14x";
+};
diff --git a/arch/arm64/boot/dts/apple/t6021-j414c.dts b/arch/arm64/boot/dts/apple/t6021-j414c.dts
new file mode 100644
index 00000000000000..b173caf0df0fce
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6021-j414c.dts
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * MacBook Pro (14-inch, M2 Max, 2023)
+ *
+ * target-type: J414c
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6021.dtsi"
+#include "t602x-j414-j416.dtsi"
+
+/ {
+	compatible = "apple,j414c", "apple,t6021", "apple,arm-platform";
+	model = "Apple MacBook Pro (14-inch, M2 Max, 2023)";
+};
+
+&wifi0 {
+	brcm,board-type = "apple,tokara";
+};
+
+&bluetooth0 {
+	brcm,board-type = "apple,tokara";
+};
+
+&panel {
+	compatible = "apple,panel-j414", "apple,panel-mini-led", "apple,panel";
+	width-mm = <302>;
+	height-mm = <196>;
+	adj-height-mm = <189>;
+};
+
+&sound {
+	compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio";
+	model = "MacBook Pro J414";
+};
diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts
new file mode 100644
index 00000000000000..36a57890c25d72
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * MacBook Pro (16-inch, M2 Max, 2022)
+ *
+ * target-type: J416c
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6021.dtsi"
+#include "t602x-j414-j416.dtsi"
+
+/ {
+	compatible = "apple,j416c", "apple,t6021", "apple,arm-platform";
+	model = "Apple MacBook Pro (16-inch, M2 Max, 2023)";
+};
+
+/* This machine model (only) has two extra boost CPU P-states */
+&avalanche_opp {
+	opp18 {
+		opp-hz = /bits/ 64 <3528000000>;
+		opp-level = <18>;
+		clock-latency-ns = <67000>;
+		turbo-mode;
+	};
+	opp19 {
+		opp-hz = /bits/ 64 <3696000000>;
+		opp-level = <19>;
+		clock-latency-ns = <67000>;
+		turbo-mode;
+	};
+};
+
+&wifi0 {
+	brcm,board-type = "apple,amami";
+};
+
+&bluetooth0 {
+	brcm,board-type = "apple,amami";
+};
+
+&panel {
+	compatible = "apple,panel-j416", "apple,panel-mini-led", "apple,panel";
+	width-mm = <346>;
+	height-mm = <223>;
+	adj-height-mm = <216>;
+};
+
+&sound {
+	compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio";
+	model = "MacBook Pro J416";
+};
diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi
new file mode 100644
index 00000000000000..d907c4753f67dd
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6021.dtsi
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Apple T6021 "M2 Max" SoC
+ *
+ * Other names: H14J, "Rhodes"
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/apple-aic.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/phy/phy.h>
+#include <dt-bindings/spmi/spmi.h>
+
+#include "multi-die-cpp.h"
+
+#ifndef GPU_REPEAT
+# define GPU_REPEAT(x) <x x x x>
+#endif
+
+#include "t602x-common.dtsi"
+
+/ {
+	compatible = "apple,t6001", "apple,arm-platform";
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0 0 0 0>;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0 0 0 0>;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0 0 0 0>;
+		};
+	};
+
+	soc {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+
+		ranges;
+		nonposted-mmio;
+
+		// filled via templated includes at the end of the file
+	};
+};
+
+#define DIE
+#define DIE_NO 0
+
+&{/soc} {
+	#include "t602x-die0.dtsi"
+	#include "t602x-dieX.dtsi"
+	#include "t602x-nvme.dtsi"
+};
+
+#include "t602x-gpio-pins.dtsi"
+#include "t602x-pmgr.dtsi"
+
+#undef DIE
+#undef DIE_NO
+
+
+&aic {
+	affinities {
+		e-core-pmu-affinity {
+			apple,fiq-index = <AIC_CPU_PMU_E>;
+			cpus = <&cpu_e00 &cpu_e01 &cpu_e02 &cpu_e03>;
+		};
+
+		p-core-pmu-affinity {
+			apple,fiq-index = <AIC_CPU_PMU_P>;
+			cpus = <&cpu_p00 &cpu_p01 &cpu_p02 &cpu_p03
+				&cpu_p10 &cpu_p11 &cpu_p12 &cpu_p13>;
+		};
+	};
+};
+
+&gpu {
+	compatible = "apple,agx-t6021", "apple,agx-g14x";
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi
new file mode 100644
index 00000000000000..1224e4f12a5347
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi
@@ -0,0 +1,489 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Nodes common to all T602x family SoCs (M2 Pro/Max/Ultra)
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+ / {
+	#address-cells = <2>;
+	#size-cells = <2>;
+
+	cpus {
+		#address-cells = <2>;
+		#size-cells = <0>;
+
+		cpu-map {
+			cluster0 {
+				core0 {
+					cpu = <&cpu_e00>;
+				};
+				core1 {
+					cpu = <&cpu_e01>;
+				};
+				core2 {
+					cpu = <&cpu_e02>;
+				};
+				core3 {
+					cpu = <&cpu_e03>;
+				};
+			};
+			cluster1 {
+				core0 {
+					cpu = <&cpu_p00>;
+				};
+				core1 {
+					cpu = <&cpu_p01>;
+				};
+				core2 {
+					cpu = <&cpu_p02>;
+				};
+				core3 {
+					cpu = <&cpu_p03>;
+				};
+			};
+
+			cluster2 {
+				core0 {
+					cpu = <&cpu_p10>;
+				};
+				core1 {
+					cpu = <&cpu_p11>;
+				};
+				core2 {
+					cpu = <&cpu_p12>;
+				};
+				core3 {
+					cpu = <&cpu_p13>;
+				};
+			};
+		};
+
+		cpu_e00: cpu@0 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x0>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_0>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e>;
+		};
+
+		cpu_e01: cpu@1 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x1>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_0>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e>;
+		};
+
+		cpu_e02: cpu@2 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x2>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_0>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e>;
+		};
+
+		cpu_e03: cpu@3 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x3>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_0>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e>;
+		};
+
+		cpu_p00: cpu@10100 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10100>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_1>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0>;
+		};
+
+		cpu_p01: cpu@10101 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10101>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_1>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0>;
+		};
+
+		cpu_p02: cpu@10102 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10102>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_1>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0>;
+		};
+
+		cpu_p03: cpu@10103 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10103>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_1>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0>;
+		};
+
+		cpu_p10: cpu@10200 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10200>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_2>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1>;
+		};
+
+		cpu_p11: cpu@10201 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10201>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_2>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1>;
+		};
+
+		cpu_p12: cpu@10202 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10202>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_2>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1>;
+		};
+
+		cpu_p13: cpu@10203 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10203>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_2>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1>;
+		};
+
+		l2_cache_0: l2-cache-0 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x400000>;
+		};
+
+		l2_cache_1: l2-cache-1 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x1000000>;
+		};
+
+		l2_cache_2: l2-cache-2 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x1000000>;
+		};
+	 };
+
+	blizzard_opp: opp-table-0 {
+		compatible = "operating-points-v2";
+		opp-shared;
+
+		/* pstate #1 is a dummy clone of #2 */
+		opp02 {
+			opp-hz = /bits/ 64 <912000000>;
+			opp-level = <2>;
+			clock-latency-ns = <7700>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <1284000000>;
+			opp-level = <3>;
+			clock-latency-ns = <25000>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <1752000000>;
+			opp-level = <4>;
+			clock-latency-ns = <33000>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <2004000000>;
+			opp-level = <5>;
+			clock-latency-ns = <38000>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <2256000000>;
+			opp-level = <6>;
+			clock-latency-ns = <44000>;
+		};
+		opp07 {
+			opp-hz = /bits/ 64 <2424000000>;
+			opp-level = <7>;
+			clock-latency-ns = <48000>;
+		};
+	};
+
+	avalanche_opp: opp-table-1 {
+		compatible = "operating-points-v2";
+		opp-shared;
+
+		opp01 {
+			opp-hz = /bits/ 64 <702000000>;
+			opp-level = <1>;
+			clock-latency-ns = <7400>;
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <948000000>;
+			opp-level = <2>;
+			clock-latency-ns = <18000>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <1188000000>;
+			opp-level = <3>;
+			clock-latency-ns = <21000>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <1452000000>;
+			opp-level = <4>;
+			clock-latency-ns = <24000>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1704000000>;
+			opp-level = <5>;
+			clock-latency-ns = <28000>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1968000000>;
+			opp-level = <6>;
+			clock-latency-ns = <31000>;
+		};
+		opp07 {
+			opp-hz = /bits/ 64 <2208000000>;
+			opp-level = <7>;
+			clock-latency-ns = <33000>;
+		};
+		opp08 {
+			opp-hz = /bits/ 64 <2400000000>;
+			opp-level = <8>;
+			clock-latency-ns = <45000>;
+		};
+		opp09 {
+			opp-hz = /bits/ 64 <2568000000>;
+			opp-level = <9>;
+			clock-latency-ns = <47000>;
+		};
+		opp10 {
+			opp-hz = /bits/ 64 <2724000000>;
+			opp-level = <10>;
+			clock-latency-ns = <50000>;
+		};
+		opp11 {
+			opp-hz = /bits/ 64 <2868000000>;
+			opp-level = <11>;
+			clock-latency-ns = <52000>;
+		};
+		opp12 {
+			opp-hz = /bits/ 64 <3000000000>;
+			opp-level = <12>;
+			clock-latency-ns = <57000>;
+		};
+		opp13 {
+			opp-hz = /bits/ 64 <3132000000>;
+			opp-level = <13>;
+			clock-latency-ns = <60000>;
+		};
+		opp14 {
+			opp-hz = /bits/ 64 <3264000000>;
+			opp-level = <14>;
+			clock-latency-ns = <64000>;
+		};
+		opp15 {
+			opp-hz = /bits/ 64 <3360000000>;
+			opp-level = <15>;
+			clock-latency-ns = <64000>;
+			turbo-mode;
+		};
+		opp16 {
+			opp-hz = /bits/ 64 <3408000000>;
+			opp-level = <16>;
+			clock-latency-ns = <64000>;
+			turbo-mode;
+		};
+		opp17 {
+			opp-hz = /bits/ 64 <3504000000>;
+			opp-level = <17>;
+			clock-latency-ns = <64000>;
+			turbo-mode;
+		};
+	};
+
+	gpu_opp: opp-table-gpu {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <0>;
+			opp-microvolt = GPU_REPEAT(400000);
+			opp-microwatt = <0>;
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <444000000>;
+			opp-microvolt = GPU_REPEAT(637000);
+			opp-microwatt = <4295000>;
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <612000000>;
+			opp-microvolt = GPU_REPEAT(656000);
+			opp-microwatt = <6251000>;
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <808000000>;
+			opp-microvolt = GPU_REPEAT(687000);
+			opp-microwatt = <8625000>;
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <968000000>;
+			opp-microvolt = GPU_REPEAT(725000);
+			opp-microwatt = <11948000>;
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1110000000>;
+			opp-microvolt = GPU_REPEAT(790000);
+			opp-microwatt = <15071000>;
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1236000000>;
+			opp-microvolt = GPU_REPEAT(843000);
+			opp-microwatt = <18891000>;
+		};
+		opp07 {
+			opp-hz = /bits/ 64 <1338000000>;
+			opp-microvolt = GPU_REPEAT(887000);
+			opp-microwatt = <21960000>;
+		};
+		opp08 {
+			opp-hz = /bits/ 64 <1398000000>;
+			opp-microvolt = GPU_REPEAT(918000);
+			opp-microwatt = <22800000>;
+		};
+	};
+
+	pmu-e {
+		compatible = "apple,blizzard-pmu";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_FIQ 0 AIC_CPU_PMU_E IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	pmu-p {
+		compatible = "apple,avalanche-pmu";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_FIQ 0 AIC_CPU_PMU_P IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	timer {
+		compatible = "arm,armv8-timer";
+		interrupt-parent = <&aic>;
+		interrupt-names = "phys", "virt", "hyp-phys", "hyp-virt";
+		interrupts = <AIC_FIQ 0 AIC_TMR_GUEST_PHYS IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_FIQ 0 AIC_TMR_GUEST_VIRT IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_FIQ 0 AIC_TMR_HV_PHYS IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_FIQ 0 AIC_TMR_HV_VIRT IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	clkref: clock-ref {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <24000000>;
+		clock-output-names = "clkref";
+	};
+
+	clk_200m: clock-200m {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <200000000>;
+		clock-output-names = "clk_200m";
+	};
+
+	clk_disp0: clock-disp0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <257142848>; /* TODO: check */
+		clock-output-names = "clk_disp0";
+	};
+
+	/*
+	 * This is a fabulated representation of the input clock
+	 * to NCO since we don't know the true clock tree.
+	 */
+	nco_clkref: clock-ref-nco {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-output-names = "nco_ref";
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
new file mode 100644
index 00000000000000..794abdacfb01dc
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -0,0 +1,702 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * In anticipation of an M2 Ultra. Inspired by T600x.
+ *
+ * Obviously needs filling out, just the bare bones required
+ * to boot to a console in the HV.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+	nco: clock-controller@28e03c000 {
+		compatible = "apple,t6020-nco", "apple,nco";
+		reg = <0x2 0x8e03c000 0x0 0x14000>;
+		clocks = <&nco_clkref>;
+		#clock-cells = <1>;
+	};
+
+	aic: interrupt-controller@28e100000 {
+		compatible = "apple,t6020-aic", "apple,aic2";
+		#interrupt-cells = <4>;
+		interrupt-controller;
+		reg = <0x2 0x8e100000 0x0 0xc000>,
+                <0x2 0x8e10c000 0x0 0x1000>;
+		reg-names = "core", "event";
+		power-domains = <&ps_aic>;
+	};
+
+	pmgr_dcp: power-management@28e3d0000 {
+		reg = <0x2 0x8e3d0000 0x0 0x4000>;
+		reg-names = "dcp-fw-pmgr";
+		#apple,bw-scratch-cells = <3>;
+	};
+
+	wdt: watchdog@29e2c4000 {
+		compatible = "apple,t6020-wdt", "apple,wdt";
+		reg = <0x2 0x9e2c4000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 719 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	nub_spmi0: spmi@29e114000 {
+		compatible = "apple,t6020-spmi", "apple,spmi";
+		reg = <0x2 0x9e114000 0x0 0x100>;
+		#address-cells = <2>;
+		#size-cells = <0>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 256 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 724 IRQ_TYPE_LEVEL_HIGH>;
+
+		pmu1: pmu@f {
+			compatible = "apple,maverick-pmu", "apple,spmi-pmu";
+			reg = <0xb SPMI_USID>;
+			#address-cells = <1>;
+			#size-cells = <1>;
+
+			rtc_nvmem@1400 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x1400 0x20>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				pm_setting: pm-setting@5 {
+					reg = <0x5 0x1>;
+				};
+
+				rtc_offset: rtc-offset@11 {
+					reg = <0x11 0x6>;
+				};
+			};
+
+			legacy_nvmem@6000 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x6000 0x20>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				boot_stage: boot-stage@1 {
+					reg = <0x1 0x1>;
+				};
+
+				boot_error_count: boot-error-count@2 {
+					reg = <0x2 0x1>;
+					bits = <0 4>;
+				};
+
+				panic_count: panic-count@2 {
+					reg = <0x2 0x1>;
+					bits = <4 4>;
+				};
+
+				boot_error_stage: boot-error-stage@3 {
+					reg = <0x3 0x1>;
+				};
+
+				shutdown_flag: shutdown-flag@f {
+					reg = <0xf 0x1>;
+					bits = <3 1>;
+				};
+			};
+
+			scrpad_nvmem@8000 {
+				compatible = "apple,spmi-pmu-nvmem";
+				reg = <0x8000 0x1000>;
+				#address-cells = <1>;
+				#size-cells = <1>;
+
+				fault_shadow: fault-shadow@67b {
+					reg = <0x67b 0x10>;
+				};
+
+				socd: socd@b00 {
+					reg = <0xb00 0x400>;
+				};
+			};
+
+		};
+	};
+
+	smc_mbox: mbox@2a2408000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0xa2408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 862 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 863 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 864 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 865 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+	};
+
+	smc: smc@2a2400000 {
+		compatible = "apple,t6020-smc", "apple,smc";
+		reg = <0x2 0xa2400000 0x0 0x4000>,
+			<0x2 0xa3e00000 0x0 0x100000>;
+		reg-names = "smc", "sram";
+		mboxes = <&smc_mbox>;
+
+		smc_gpio: gpio {
+			gpio-controller;
+			#gpio-cells = <2>;
+		};
+
+		smc_rtc: rtc {
+			nvmem-cells = <&rtc_offset>;
+			nvmem-cell-names = "rtc_offset";
+		};
+
+		smc_reboot: reboot {
+			nvmem-cells = <&shutdown_flag>, <&boot_stage>,
+				<&boot_error_count>, <&panic_count>, <&pm_setting>;
+			nvmem-cell-names = "shutdown_flag", "boot_stage",
+				"boot_error_count", "panic_count", "pm_setting";
+		};
+	};
+
+	pinctrl_smc: pinctrl@2a2820000 {
+		compatible = "apple,t6020-pinctrl", "apple,pinctrl";
+		reg = <0x2 0xa2820000 0x0 0x4000>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-ranges = <&pinctrl_smc 0 0 30>;
+		apple,npins = <30>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 851 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 852 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 853 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 854 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 855 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 856 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 857 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	disp0_dart: iommu@389304000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x89304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 911 IRQ_TYPE_LEVEL_HIGH>;
+		status = "disabled";
+		power-domains = <&ps_disp0_cpu0>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+	};
+
+	dcp_dart: iommu@38930c000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x8930c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 911 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_disp0_cpu0>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+	};
+
+	dcp_mbox: mbox@389c08000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x89c08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 932 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 933 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 934 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 935 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&ps_disp0_cpu0>;
+	};
+
+	dcp: dcp@389c00000 {
+		compatible = "apple,t6020-dcp", "apple,dcp";
+		mboxes = <&dcp_mbox>;
+		mbox-names = "mbox";
+		iommus = <&dcp_dart 5>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x3 0x89c00000 0x0 0x4000>, // check?
+			<0x3 0x88000000 0x0 0x61c000>,
+			<0x3 0x89320000 0x0 0x4000>,
+			<0x3 0x89344000 0x0 0x4000>,
+			<0x3 0x89800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x1208>;
+		power-domains = <&ps_disp0_cpu0>;
+		resets = <&ps_disp0_cpu0>;
+		clocks = <&clk_disp0>;
+		phandle = <&dcp>;
+
+		disp0_piodma: piodma {
+			iommus = <&disp0_dart 4>;
+			phandle = <&disp0_piodma>;
+		};
+	};
+
+	display: display-subsystem {
+		compatible = "apple,display-subsystem";
+		iommus = <&disp0_dart 0>;
+		/* generate phandle explicitly for use in loader */
+		phandle = <&display>;
+	};
+
+	sio_dart: iommu@39b008000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x9b008000 0x0 0x8000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1231 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&ps_sio_cpu>;
+	};
+
+	fpwm0: pwm@39b030000 {
+		compatible = "apple,t6020-fpwm", "apple,s5l-fpwm";
+		reg = <0x3 0x9b030000 0x0 0x4000>;
+		power-domains = <&ps_fpwm0>;
+		clocks = <&clkref>;
+		#pwm-cells = <2>;
+		status = "disabled";
+	};
+
+	i2c0: i2c@39b040000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b040000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1219 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c0_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c0>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+	};
+
+	i2c1: i2c@39b044000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b044000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1220 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c1_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c1>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c2: i2c@39b048000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b048000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1221 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c2_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c2>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c3: i2c@39b04c000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b04c000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1222 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c3_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c3>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c4: i2c@39b050000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b050000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1223 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c4_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c4>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c5: i2c@39b054000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b054000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1224 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c5_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c5>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c6: i2c@39b054000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b054000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1225 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c6_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c6>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c7: i2c@39b054000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b054000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1226 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c7_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c7>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	i2c8: i2c@39b054000 {
+		compatible = "apple,t6020-i2c", "apple,i2c";
+		reg = <0x3 0x9b054000 0x0 0x4000>;
+		clocks = <&clkref>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1227 IRQ_TYPE_LEVEL_HIGH>;
+		pinctrl-0 = <&i2c8_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_i2c8>;
+		#address-cells = <0x1>;
+		#size-cells = <0x0>;
+		status = "disabled";
+	};
+
+	spi1: spi@39b104000 {
+		compatible = "apple,t6020-spi", "apple,spi";
+		reg = <0x3 0x9b104000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1206 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clk_200m>;
+		pinctrl-0 = <&spi1_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_spi1>;
+		status = "disabled";
+	};
+
+	spi2: spi@39b108000 {
+		compatible = "apple,t6020-spi", "apple,spi";
+		reg = <0x3 0x9b108000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1207 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clkref>;
+		pinctrl-0 = <&spi2_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_spi2>;
+		status = "disabled";
+	};
+
+	spi4: spi@39b110000 {
+		compatible = "apple,t6020-spi", "apple,spi";
+		reg = <0x3 0x9b110000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1209 IRQ_TYPE_LEVEL_HIGH>;
+		#address-cells = <1>;
+		#size-cells = <0>;
+		clocks = <&clkref>;
+		pinctrl-0 = <&spi4_pins>;
+		pinctrl-names = "default";
+		power-domains = <&ps_spi4>;
+		status = "disabled";
+	};
+
+	serial0: serial@39b200000 {
+		compatible = "apple,s5l-uart";
+		reg = <0x3 0x9b200000 0x0 0x4000>;
+		reg-io-width = <4>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1198 IRQ_TYPE_LEVEL_HIGH>;
+		/*
+		 * TODO: figure out the clocking properly, there may
+		 * be a third selectable clock.
+		 */
+		clocks = <&clkref>, <&clkref>;
+		clock-names = "uart", "clk_uart_baud0";
+		power-domains = <&ps_uart0>;
+		status = "disabled";
+	};
+
+	admac: dma-controller@39b400000 {
+		compatible = "apple,t6020-admac", "apple,admac";
+		reg = <0x3 0x9b400000 0x0 0x34000>;
+		#dma-cells = <1>;
+		dma-channels = <16>;
+		interrupts-extended = <0>,
+				      <&aic AIC_IRQ 0 1218 IRQ_TYPE_LEVEL_HIGH>,
+				      <0>,
+				      <0>;
+		iommus = <&sio_dart 2>;
+		power-domains = <&ps_sio_adma>;
+		resets = <&ps_audio_p>;
+	};
+
+	mca: mca@39b600000 {
+		compatible = "apple,t6020-mca", "apple,mca";
+		reg = <0x3 0x9b600000 0x0 0x10000>,
+		      <0x3 0x9b500000 0x0 0x20000>;
+		clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>;
+		dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>,
+		       <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>,
+		       <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>,
+		       <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>;
+		dma-names = "tx0a", "rx0a", "tx0b", "rx0b",
+			    "tx1a", "rx1a", "tx1b", "rx1b",
+			    "tx2a", "rx2a", "tx2b", "rx2b",
+			    "tx3a", "rx3a", "tx3b", "rx3b";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1211 IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_IRQ 0 1212 IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_IRQ 0 1213 IRQ_TYPE_LEVEL_HIGH>,
+			     <AIC_IRQ 0 1214 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>,
+				<&ps_mca2>, <&ps_mca3>;
+		resets = <&ps_audio_p>;
+		#sound-dai-cells = <1>;
+	};
+
+	pmgr_gfx: power-management@404e80000 {
+		compatible = "apple,t6021-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		reg = <0x4 0x4e80000 0 0x4000>;
+	};
+
+	gpu: gpu@406400000 {
+		compatible = "apple,agx-g14x";
+		reg = <0x4 0x6400000 0 0x40000>,
+			<0x4 0x4000000 0 0x1000000>;
+		reg-names = "asc", "sgx";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1127 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1128 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1129 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1130 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1147 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1149 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1142 IRQ_TYPE_LEVEL_HIGH>;
+		mboxes = <&agx_mbox>;
+		power-domains = <&ps_gfx>;
+		memory-region = <&uat_ttbs>, <&uat_pagetables>, <&uat_handoff>;
+		memory-region-names = "ttbs", "pagetables", "handoff";
+
+		apple,firmware-version = <0 0 0>;
+		apple,firmware-compat = <0 0 0>;
+
+		operating-points-v2 = <&gpu_opp>;
+		/* TODO perf stuff */
+		apple,perf-base-pstate = <1>;
+	};
+
+	agx_mbox: mbox@406408000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x4 0x6408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1143 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1144 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1145 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 1146 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+	};
+
+	pcie0: pcie@580000000 {
+		compatible = "apple,t6020-pcie";
+		device_type = "pci";
+
+		reg = <0x5 0x80000000 0x0 0x1000000>,	/* config */
+			<0x5 0x91000000 0x0 0x4000>,	/* rc */
+			<0x5 0x94008000 0x0 0x4000>,	/* port0 */
+			<0x5 0x95008000 0x0 0x4000>,	/* port1 */
+			<0x5 0x96008000 0x0 0x4000>,	/* port2 */
+			<0x5 0x97008000 0x0 0x4000>,	/* port3 */
+			<0x5 0x9e00c000 0x0 0x4000>,	/* phy0 */
+			<0x5 0x9e010000 0x0 0x4000>,	/* phy1 */
+			<0x5 0x9e014000 0x0 0x4000>,	/* phy2 */
+			<0x5 0x9e018000 0x0 0x4000>,	/* phy3 */
+			<0x5 0x9401c000 0x0 0x1000>,	/* ltssm0 */
+			<0x5 0x9501c000 0x0 0x1000>,	/* ltssm1 */
+			<0x5 0x9601c000 0x0 0x1000>,	/* ltssm2 */
+			<0x5 0x9701c000 0x0 0x1000>;	/* ltssm3 */
+		reg-names = "config", "rc",
+			"port0", "port1", "port2", "port3",
+			"phy0", "phy1", "phy2", "phy3",
+			"ltssm0", "ltssm1", "ltssm2", "ltssm3";
+
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1340 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 1344 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 1348 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 0 1352 IRQ_TYPE_LEVEL_HIGH>;
+
+		msi-controller;
+		msi-parent = <&pcie0>;
+		msi-ranges = <&aic AIC_IRQ 0 1672 IRQ_TYPE_EDGE_RISING 32>;
+
+
+		iommu-map = <0x100 &pcie0_dart_0 1 1>,
+				<0x200 &pcie0_dart_1 1 1>,
+				<0x300 &pcie0_dart_2 1 1>,
+				<0x400 &pcie0_dart_3 1 1>;
+		iommu-map-mask = <0xff00>;
+
+		bus-range = <0 4>;
+		#address-cells = <3>;
+		#size-cells = <2>;
+		ranges = <0x43000000 0x5 0xa0000000 0x5 0xa0000000 0x0 0x20000000>,
+				<0x02000000 0x0 0xc0000000 0x5 0xc0000000 0x0 0x40000000>;
+
+		power-domains = <&ps_apcie_gp_sys>;
+		pinctrl-0 = <&pcie_pins>;
+		pinctrl-names = "default";
+
+		dma-coherent;
+
+		port00: pci@0,0 {
+			device_type = "pci";
+			reg = <0x0 0x0 0x0 0x0 0x0>;
+			reset-gpios = <&pinctrl_ap 4 GPIO_ACTIVE_LOW>;
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			interrupt-controller;
+			#interrupt-cells = <1>;
+
+			interrupt-map-mask = <0 0 0 7>;
+			interrupt-map = <0 0 0 1 &port00 0 0 0 0>,
+					<0 0 0 2 &port00 0 0 0 1>,
+					<0 0 0 3 &port00 0 0 0 2>,
+					<0 0 0 4 &port00 0 0 0 3>;
+		};
+
+		port01: pci@1,0 {
+			device_type = "pci";
+			reg = <0x800 0x0 0x0 0x0 0x0>;
+			reset-gpios = <&pinctrl_ap 5 GPIO_ACTIVE_LOW>;
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			interrupt-controller;
+			#interrupt-cells = <1>;
+
+			interrupt-map-mask = <0 0 0 7>;
+			interrupt-map = <0 0 0 1 &port01 0 0 0 0>,
+					<0 0 0 2 &port01 0 0 0 1>,
+					<0 0 0 3 &port01 0 0 0 2>,
+					<0 0 0 4 &port01 0 0 0 3>;
+			status = "disabled";
+		};
+
+		port02: pci@2,0 {
+			device_type = "pci";
+			reg = <0x1000 0x0 0x0 0x0 0x0>;
+			reset-gpios = <&pinctrl_ap 6 GPIO_ACTIVE_LOW>;
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			interrupt-controller;
+			#interrupt-cells = <1>;
+
+			interrupt-map-mask = <0 0 0 7>;
+			interrupt-map = <0 0 0 1 &port02 0 0 0 0>,
+					<0 0 0 2 &port02 0 0 0 1>,
+					<0 0 0 3 &port02 0 0 0 2>,
+					<0 0 0 4 &port02 0 0 0 3>;
+			status = "disabled";
+		};
+
+		port03: pci@3,0 {
+			device_type = "pci";
+			reg = <0x1800 0x0 0x0 0x0 0x0>;
+			reset-gpios = <&pinctrl_ap 7 GPIO_ACTIVE_LOW>;
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			interrupt-controller;
+			#interrupt-cells = <1>;
+
+			interrupt-map-mask = <0 0 0 7>;
+			interrupt-map = <0 0 0 1 &port03 0 0 0 0>,
+					<0 0 0 2 &port03 0 0 0 1>,
+					<0 0 0 3 &port03 0 0 0 2>,
+					<0 0 0 4 &port03 0 0 0 3>;
+			status = "disabled";
+		};
+	};
+
+	pcie0_dart_0: iommu@594000000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x5 0x94000000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1341 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_apcie_gp_sys>;
+	};
+
+	pcie0_dart_1: iommu@595000000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x5 0x95000000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1345 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_apcie_gp_sys>;
+		status = "disabled";
+	};
+
+	pcie0_dart_2: iommu@596000000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x5 0x96000000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1349 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_apcie_gp_sys>;
+		status = "disabled";
+	};
+
+	pcie0_dart_3: iommu@597000000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x5 0x97000000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 1353 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_apcie_gp_sys>;
+		status = "disabled";
+	};
+
+
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
new file mode 100644
index 00000000000000..203316df0d06f0
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -0,0 +1,344 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Nodes present on both dies of a hypothetical T6022 (M2 Ultra)
+ * and present on M2 Pro/Max.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+	DIE_NODE(cpufreq_e): cpufreq@210e20000 {
+		compatible = "apple,t6020-cluster-cpufreq", "apple,t8112-cluster-cpufreq", "apple,cluster-cpufreq";
+		reg = <0x2 0x10e20000 0 0x1000>;
+		#performance-domain-cells = <0>;
+	};
+
+	DIE_NODE(cpufreq_p0): cpufreq@211e20000 {
+		compatible = "apple,t6020-cluster-cpufreq", "apple,t8112-cluster-cpufreq", "apple,cluster-cpufreq";
+		reg = <0x2 0x11e20000 0 0x1000>;
+		#performance-domain-cells = <0>;
+	};
+
+	DIE_NODE(cpufreq_p1): cpufreq@212e20000 {
+		compatible = "apple,t6020-cluster-cpufreq", "apple,t8112-cluster-cpufreq", "apple,cluster-cpufreq";
+		reg = <0x2 0x12e20000 0 0x1000>;
+		#performance-domain-cells = <0>;
+	};
+
+	DIE_NODE(pmgr): power-management@28e080000 {
+		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x8e080000 0 0x8000>;
+	};
+
+	DIE_NODE(pmgr_south): power-management@28e680000 {
+		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x8e680000 0 0x8000>;
+	};
+
+	DIE_NODE(pmgr_east): power-management@290280000 {
+		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x90280000 0 0xc000>;
+	};
+
+	DIE_NODE(pinctrl_nub): pinctrl@29e1f0000 {
+		compatible = "apple,t6000-pinctrl", "apple,pinctrl";
+		reg = <0x2 0x9e1f0000 0x0 0x4000>;
+		power-domains = <&DIE_NODE(ps_nub_gpio)>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-ranges = <&DIE_NODE(pinctrl_nub) 0 0 30>;
+		apple,npins = <30>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 711 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 712 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 713 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 714 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 715 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 716 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 717 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	DIE_NODE(pmgr_mini): power-management@29e280000 {
+		compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x9e280000 0 0x4000>;
+	};
+
+	DIE_NODE(efuse): efuse@29e2cc000 {
+		compatible = "apple,t6020-efuses", "apple,efuses";
+		reg = <0x2 0x9e2cc000 0x0 0x2000>;
+		#address-cells = <1>;
+		#size-cells = <1>;
+	};
+
+	DIE_NODE(pinctrl_aop): pinctrl@2a6820000 {
+		compatible = "apple,t6020-pinctrl", "apple,pinctrl";
+		reg = <0x2 0xa6820000 0x0 0x4000>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-ranges = <&DIE_NODE(pinctrl_aop) 0 0 72>;
+		apple,npins = <72>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 598 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 599 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 600 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 601 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 602 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 603 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 604 IRQ_TYPE_LEVEL_HIGH>;
+	};
+
+	DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
+		compatible = "apple,t6020-pinctrl", "apple,pinctrl";
+		reg = <0x3 0x9b028000 0x0 0x4000>;
+
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 458 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 459 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 460 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 461 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 462 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 463 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ DIE_NO 464 IRQ_TYPE_LEVEL_HIGH>;
+
+		clocks = <&clkref>;
+		power-domains = <&DIE_NODE(ps_gpio)>;
+
+		gpio-controller;
+		#gpio-cells = <2>;
+		gpio-ranges = <&DIE_NODE(pinctrl_ap) 0 0 255>;
+		apple,npins = <255>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+	};
+
+	DIE_NODE(dwc3_0_dart_0): iommu@702f00000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x7 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1260 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_0_dart_1): iommu@702f80000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x7 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1260 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_0): usb@702280000 {
+		compatible = "apple,t6020-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0x7 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1256 IRQ_TYPE_LEVEL_HIGH>;
+		dr_mode = "otg";
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_0_dart_0) 0>,
+			<&DIE_NODE(dwc3_0_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		dma-coherent;
+		resets = <&DIE_NODE(atcphy0)>;
+		phys = <&DIE_NODE(atcphy0) PHY_TYPE_USB2>, <&DIE_NODE(atcphy0) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy0): phy@703000000 {
+		compatible = "apple,t6020-atcphy", "apple,t8103-atcphy";
+		reg = <0x7 0x03000000 0x0 0x4c000>,
+			<0x7 0x03050000 0x0 0x8000>,
+			<0x7 0x00000000 0x0 0x4000>,
+			<0x7 0x02a90000 0x0 0x4000>,
+			<0x7 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+	};
+
+	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0xb 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1278 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_1_dart_1): iommu@b02f80000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0xb 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1278 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_1): usb@b02280000 {
+		compatible = "apple,t6020-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0xb 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1274 IRQ_TYPE_LEVEL_HIGH>;
+		dr_mode = "otg";
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_1_dart_0) 0>,
+			<&DIE_NODE(dwc3_1_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		dma-coherent;
+		resets = <&DIE_NODE(atcphy1)>;
+		phys = <&DIE_NODE(atcphy1) PHY_TYPE_USB2>, <&DIE_NODE(atcphy1) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy1): phy@b03000000 {
+		compatible = "apple,t6020-atcphy", "apple,t8103-atcphy";
+		reg = <0xb 0x03000000 0x0 0x4c000>,
+			<0xb 0x03050000 0x0 0x8000>,
+			<0xb 0x00000000 0x0 0x4000>,
+			<0xb 0x02a90000 0x0 0x4000>,
+			<0xb 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+	};
+
+	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0xf 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1296 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_2_dart_1): iommu@f02f80000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0xf 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1296 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_2): usb@f02280000 {
+		compatible = "apple,t6020-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0xf 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1292 IRQ_TYPE_LEVEL_HIGH>;
+		dr_mode = "otg";
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_2_dart_0) 0>,
+			<&DIE_NODE(dwc3_2_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		dma-coherent;
+		resets = <&DIE_NODE(atcphy2)>;
+		phys = <&DIE_NODE(atcphy2) PHY_TYPE_USB2>, <&DIE_NODE(atcphy2) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy2): phy@f03000000 {
+		compatible = "apple,t6020-atcphy", "apple,t8103-atcphy";
+		reg = <0xf 0x03000000 0x0 0x4c000>,
+			<0xf 0x03050000 0x0 0x8000>,
+			<0xf 0x00000000 0x0 0x4000>,
+			<0xf 0x02a90000 0x0 0x4000>,
+			<0xf 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+	};
+
+	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x13 0x02f00000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1314 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_3_dart_1): iommu@1302f80000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x13 0x02f80000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1314 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		#iommu-cells = <1>;
+	};
+
+	DIE_NODE(dwc3_3): usb@1302280000 {
+		compatible = "apple,t6020-dwc3", "apple,dwc3", "snps,dwc3";
+		reg = <0x13 0x02280000 0x0 0x100000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1310 IRQ_TYPE_LEVEL_HIGH>;
+		dr_mode = "otg";
+		usb-role-switch;
+		role-switch-default-mode = "host";
+		iommus = <&DIE_NODE(dwc3_3_dart_0) 0>,
+			<&DIE_NODE(dwc3_3_dart_1) 1>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		dma-coherent;
+		resets = <&DIE_NODE(atcphy3)>;
+		phys = <&DIE_NODE(atcphy3) PHY_TYPE_USB2>, <&DIE_NODE(atcphy3) PHY_TYPE_USB3>;
+		phy-names = "usb2-phy", "usb3-phy";
+	};
+
+	DIE_NODE(atcphy3): phy@1303000000 {
+		compatible = "apple,t6020-atcphy", "apple,t8103-atcphy";
+		reg = <0x13 0x03000000 0x0 0x4c000>,
+			<0x13 0x03050000 0x0 0x8000>,
+			<0x13 0x00000000 0x0 0x4000>,
+			<0x13 0x02a90000 0x0 0x4000>,
+			<0x13 0x02a84000 0x0 0x4000>;
+		reg-names = "core", "lpdptx", "axi2af", "usb2phy",
+			"pipehandler";
+
+		#phy-cells = <1>;
+		#reset-cells = <0>;
+
+		orientation-switch;
+		mode-switch;
+		svid = <0xff01>, <0x8087>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+	};
diff --git a/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
new file mode 100644
index 00000000000000..acb133d1723d03
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
@@ -0,0 +1,81 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * GPIO pin mappings for Apple T600x SoCs.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+&pinctrl_ap {
+	i2c0_pins: i2c0-pins {
+		pinmux = <APPLE_PINMUX(63, 1)>,
+			<APPLE_PINMUX(64, 1)>;
+	};
+
+	i2c1_pins: i2c1-pins {
+		pinmux = <APPLE_PINMUX(65, 1)>,
+			<APPLE_PINMUX(66, 1)>;
+	};
+
+	i2c2_pins: i2c2-pins {
+		pinmux = <APPLE_PINMUX(67, 1)>,
+			<APPLE_PINMUX(68, 1)>;
+	};
+
+	i2c3_pins: i2c3-pins {
+		pinmux = <APPLE_PINMUX(69, 1)>,
+			<APPLE_PINMUX(70, 1)>;
+	};
+
+	i2c4_pins: i2c4-pins {
+		pinmux = <APPLE_PINMUX(71, 1)>,
+			<APPLE_PINMUX(72, 1)>;
+	};
+
+	i2c5_pins: i2c5-pins {
+		pinmux = <APPLE_PINMUX(73, 1)>,
+			<APPLE_PINMUX(74, 1)>;
+	};
+
+	i2c6_pins: i2c6-pins {
+		pinmux = <APPLE_PINMUX(75, 1)>,
+			<APPLE_PINMUX(76, 1)>;
+	};
+
+	i2c7_pins: i2c7-pins {
+		pinmux = <APPLE_PINMUX(77, 1)>,
+			<APPLE_PINMUX(78, 1)>;
+	};
+
+	i2c8_pins: i2c8-pins {
+		pinmux = <APPLE_PINMUX(79, 1)>,
+			<APPLE_PINMUX(80, 1)>;
+	};
+
+	spi1_pins: spi1-pins {
+		pinmux = <APPLE_PINMUX(155, 1)>, /* SDI */
+			<APPLE_PINMUX(156, 1)>,  /* SDO */
+			<APPLE_PINMUX(157, 1)>,  /* SCK */
+			<APPLE_PINMUX(158, 1)>;  /* CS */
+	};
+
+	spi2_pins: spi2-pins {
+		pinmux = <APPLE_PINMUX(159, 1)>, /* SDI */
+			<APPLE_PINMUX(160, 1)>,  /* SDO */
+			<APPLE_PINMUX(161, 1)>,  /* SCK */
+			<APPLE_PINMUX(162, 1)>;  /* CS */
+	};
+
+	spi4_pins: spi4-pins {
+		pinmux = <APPLE_PINMUX(167, 1)>, /* SDI */
+			<APPLE_PINMUX(168, 1)>,  /* SDO */
+			<APPLE_PINMUX(169, 1)>,  /* SCK */
+			<APPLE_PINMUX(170, 1)>;  /* CS */
+	};
+
+	pcie_pins: pcie-pins {
+		pinmux = <APPLE_PINMUX(0, 1)>,
+				<APPLE_PINMUX(1, 1)>,
+				<APPLE_PINMUX(2, 1)>,
+				<APPLE_PINMUX(3, 1)>;
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
new file mode 100644
index 00000000000000..c1f45b8114c92d
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * MacBook Pro (14/16-inch, 2022)
+ *
+ * This file contains the parts common to J414 and J416 devices with both t6020 and t6021.
+ *
+ * target-type: J414s / J414c / J416s / J416c
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/*
+ * These models are essentially identical to the previous generation, other than
+ * the GPIO indices.
+ */
+
+#define NO_SPI_TRACKPAD
+#include "t600x-j314-j316.dtsi"
+
+/ {
+	aliases {
+		keyboard = &keyboard;
+	};
+};
+
+&hpm0 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm1 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm2 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm5 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_left_tweet {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_left_woof1 {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_left_woof2 {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_right_tweet {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_right_woof1 {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&speaker_right_woof2 {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&jack_codec {
+	reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&wifi0 {
+	compatible = "pci14e4,4434";
+};
+
+&bluetooth0 {
+	compatible = "pci14e4,5f72";
+};
+
+&port01 {
+	pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>;
+};
+
diff --git a/arch/arm64/boot/dts/apple/t602x-nvme.dtsi b/arch/arm64/boot/dts/apple/t602x-nvme.dtsi
new file mode 100644
index 00000000000000..756a971bde48ae
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-nvme.dtsi
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * NVMe related devices for Apple T602x SoCs.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+	DIE_NODE(ans_mbox): mbox@347408000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x47408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1169 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1170 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1171 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1172 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		power-domains = <&DIE_NODE(ps_ans2)>;
+		#mbox-cells = <0>;
+	};
+
+	DIE_NODE(sart): sart@34bc50000 {
+		compatible = "apple,t6020-sart", "apple,t6000-sart";
+		reg = <0x3 0x4bc50000 0x0 0x10000>;
+		power-domains = <&DIE_NODE(ps_ans2)>;
+	};
+
+	DIE_NODE(nvme): nvme@34bcc0000 {
+		compatible = "apple,t6020-nvme-ans2", "apple,nvme-ans2";
+		reg = <0x3 0x4bcc0000 0x0 0x40000>, <0x3 0x47400000 0x0 0x4000>;
+		reg-names = "nvme", "ans";
+		interrupt-parent = <&aic>;
+		/* The NVME interrupt is always routed to die 0 */
+		interrupts = <AIC_IRQ 0 1832 IRQ_TYPE_LEVEL_HIGH>;
+		mboxes = <&DIE_NODE(ans_mbox)>;
+		apple,sart = <&DIE_NODE(sart)>;
+		power-domains = <&DIE_NODE(ps_ans2)>,
+			<&DIE_NODE(ps_apcie_st_sys)>,
+			<&DIE_NODE(ps_apcie_st1_sys)>;
+		power-domain-names = "ans", "apcie0", "apcie1";
+		resets = <&DIE_NODE(ps_ans2)>;
+	};
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
new file mode 100644
index 00000000000000..50d79ab1ed1298
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -0,0 +1,2262 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * PMGR Power domains for the Apple T6001 "M1 Max" SoC
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+&pmgr {
+	DIE_NODE(ps_afi): power-controller@100 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x100 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afi);
+		apple,always-on; /* Apple Fabric, CPU interface is here */
+	};
+
+	DIE_NODE(ps_aic): power-controller@108 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x108 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(aic);
+		apple,always-on; /* Core device */
+	};
+
+	DIE_NODE(ps_dwi): power-controller@110 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x110 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dwi);
+	};
+
+	DIE_NODE(ps_pms): power-controller@118 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x118 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pms);
+		apple,always-on; /* Core device */
+	};
+
+	DIE_NODE(ps_gpio): power-controller@120 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x120 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(gpio);
+		power-domains = <&DIE_NODE(ps_sio)>, <&DIE_NODE(ps_pms)>;
+	};
+
+	DIE_NODE(ps_soc_dpe): power-controller@128 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x128 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(soc_dpe);
+		apple,always-on; /* Core device */
+	};
+
+	DIE_NODE(ps_pms_c1ppt): power-controller@130 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x130 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pms_c1ppt);
+		apple,always-on; /* Core device */
+	};
+
+	DIE_NODE(ps_pmgr_soc_ocla): power-controller@138 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x138 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pmgr_soc_ocla);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_amcc0): power-controller@168 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x168 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc0);
+		apple,always-on; /* Memory controller */
+	};
+
+	DIE_NODE(ps_amcc2): power-controller@170 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x170 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc2);
+		apple,always-on; /* Memory controller */
+	};
+
+	DIE_NODE(ps_dcs_00): power-controller@178 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x178 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_00);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_01): power-controller@180 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x180 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_01);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_02): power-controller@188 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x188 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_02);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_03): power-controller@190 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x190 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_03);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_08): power-controller@198 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x198 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_08);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_09): power-controller@1a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_09);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_10): power-controller@1a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_10);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_11): power-controller@1b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_11);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_afnc1_ioa): power-controller@1b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc1_ioa);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afc): power-controller@1d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1d0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afc);
+		apple,always-on; /* Apple Fabric, CPU interface is here */
+	};
+
+	DIE_NODE(ps_afnc0_ioa): power-controller@1e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc0_ioa);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afnc1_ls): power-controller@1f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc1_ls);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc1_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc0_ls): power-controller@1f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1f8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc0_ls);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc0_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc1_lw0): power-controller@200 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x200 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc1_lw0);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc1_ls)>;
+	};
+
+	DIE_NODE(ps_afnc1_lw1): power-controller@208 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x208 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc1_lw1);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc1_ls)>;
+	};
+
+	DIE_NODE(ps_afnc1_lw2): power-controller@210 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x210 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc1_lw2);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc1_ls)>;
+	};
+
+	DIE_NODE(ps_afnc0_lw0): power-controller@218 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x218 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc0_lw0);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc0_ls)>;
+	};
+
+	DIE_NODE(ps_scodec): power-controller@220 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x220 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(scodec);
+		power-domains = <&DIE_NODE(ps_afnc1_lw0)>;
+	};
+
+	DIE_NODE(ps_atc0_common): power-controller@228 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x228 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_common);
+		power-domains = <&DIE_NODE(ps_afnc1_lw1)>;
+	};
+
+	DIE_NODE(ps_atc1_common): power-controller@230 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x230 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_common);
+		power-domains = <&DIE_NODE(ps_afnc1_lw1)>;
+	};
+
+	DIE_NODE(ps_atc2_common): power-controller@238 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x238 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_common);
+		power-domains = <&DIE_NODE(ps_afnc1_lw1)>;
+	};
+
+	DIE_NODE(ps_atc3_common): power-controller@240 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x240 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_common);
+		power-domains = <&DIE_NODE(ps_afnc1_lw1)>;
+	};
+
+	DIE_NODE(ps_dispext1_sys): power-controller@248 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x248 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext1_sys);
+		power-domains = <&DIE_NODE(ps_afnc1_lw2)>;
+	};
+
+	DIE_NODE(ps_pms_bridge): power-controller@250 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x250 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pms_bridge);
+		apple,always-on; /* Core device */
+		power-domains = <&DIE_NODE(ps_afnc0_lw0)>;
+	};
+
+	DIE_NODE(ps_dispext0_sys): power-controller@258 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x258 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext0_sys);
+		power-domains = <&DIE_NODE(ps_afnc0_lw0)>, <&DIE_NODE(ps_afr)>;
+	};
+
+	DIE_NODE(ps_ane_sys): power-controller@260 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x260 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_sys);
+		power-domains = <&DIE_NODE(ps_afnc0_lw0)>;
+	};
+
+	DIE_NODE(ps_avd_sys): power-controller@268 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x268 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(avd_sys);
+		power-domains = <&DIE_NODE(ps_afnc0_lw0)>;
+	};
+
+	DIE_NODE(ps_atc0_cio): power-controller@270 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x270 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_cio);
+		power-domains = <&DIE_NODE(ps_atc0_common)>;
+	};
+
+	DIE_NODE(ps_atc0_pcie): power-controller@278 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x278 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_pcie);
+		power-domains = <&DIE_NODE(ps_atc0_common)>;
+	};
+
+	DIE_NODE(ps_atc1_cio): power-controller@280 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x280 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_cio);
+		power-domains = <&DIE_NODE(ps_atc1_common)>;
+	};
+
+	DIE_NODE(ps_atc1_pcie): power-controller@288 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x288 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_pcie);
+		power-domains = <&DIE_NODE(ps_atc1_common)>;
+	};
+
+	DIE_NODE(ps_atc2_cio): power-controller@290 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x290 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_cio);
+		power-domains = <&DIE_NODE(ps_atc2_common)>;
+	};
+
+	DIE_NODE(ps_atc2_pcie): power-controller@298 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x298 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_pcie);
+		power-domains = <&DIE_NODE(ps_atc2_common)>;
+	};
+
+	DIE_NODE(ps_atc3_cio): power-controller@2a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2a0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_cio);
+		power-domains = <&DIE_NODE(ps_atc3_common)>;
+	};
+
+	DIE_NODE(ps_atc3_pcie): power-controller@2a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2a8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_pcie);
+		power-domains = <&DIE_NODE(ps_atc3_common)>;
+	};
+
+	DIE_NODE(ps_dispext1_fe): power-controller@2b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2b0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext1_fe);
+		power-domains = <&DIE_NODE(ps_dispext1_sys)>;
+	};
+
+	DIE_NODE(ps_dispext1_cpu0): power-controller@2b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2b8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext1_cpu0);
+		power-domains = <&DIE_NODE(ps_dispext1_fe)>;
+	};
+
+	DIE_NODE(ps_dispext0_fe): power-controller@2c0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2c0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext0_fe);
+		power-domains = <&DIE_NODE(ps_dispext0_sys)>;
+	};
+
+#if DIE_NO == 0
+	/* PMP is only present on die 0 of the M1 Ultra */
+	DIE_NODE(ps_pmp): power-controller@2c8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2c8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pmp);
+	};
+#endif
+
+	DIE_NODE(ps_pms_sram): power-controller@2d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2d0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(pms_sram);
+	};
+
+	DIE_NODE(ps_dispext0_cpu0): power-controller@2d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2d8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext0_cpu0);
+		power-domains = <&DIE_NODE(ps_dispext0_fe)>;
+	};
+
+	DIE_NODE(ps_ane_cpu): power-controller@2e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2e0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_cpu);
+		power-domains = <&DIE_NODE(ps_ane_sys)>;
+	};
+
+	DIE_NODE(ps_atc0_cio_pcie): power-controller@2e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_cio_pcie);
+		power-domains = <&DIE_NODE(ps_atc0_cio)>;
+	};
+
+	DIE_NODE(ps_atc0_cio_usb): power-controller@2f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_cio_usb);
+		power-domains = <&DIE_NODE(ps_atc0_cio)>;
+	};
+
+	DIE_NODE(ps_atc1_cio_pcie): power-controller@2f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2f8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_cio_pcie);
+		power-domains = <&DIE_NODE(ps_atc1_cio)>;
+	};
+
+	DIE_NODE(ps_atc1_cio_usb): power-controller@300 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x300 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_cio_usb);
+		power-domains = <&DIE_NODE(ps_atc1_cio)>;
+	};
+
+	DIE_NODE(ps_atc2_cio_pcie): power-controller@308 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x308 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_cio_pcie);
+		power-domains = <&DIE_NODE(ps_atc2_cio)>;
+	};
+
+	DIE_NODE(ps_atc2_cio_usb): power-controller@310 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x310 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_cio_usb);
+		power-domains = <&DIE_NODE(ps_atc2_cio)>;
+	};
+
+	DIE_NODE(ps_atc3_cio_pcie): power-controller@318 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x318 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_cio_pcie);
+		power-domains = <&DIE_NODE(ps_atc3_cio)>;
+	};
+
+	DIE_NODE(ps_atc3_cio_usb): power-controller@320 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x320 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_cio_usb);
+		power-domains = <&DIE_NODE(ps_atc3_cio)>;
+	};
+
+	DIE_NODE(ps_trace_fab): power-controller@390 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x390 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(trace_fab);
+	};
+
+	DIE_NODE(ps_ane_sys_mpm): power-controller@4000 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_sys_mpm);
+		power-domains = <&DIE_NODE(ps_ane_sys)>;
+	};
+
+	DIE_NODE(ps_ane_td): power-controller@4008 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_td);
+		power-domains = <&DIE_NODE(ps_ane_sys)>;
+	};
+
+	DIE_NODE(ps_ane_base): power-controller@4010 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_base);
+		power-domains = <&DIE_NODE(ps_ane_td)>;
+	};
+
+	DIE_NODE(ps_ane_set1): power-controller@4018 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_set1);
+		power-domains = <&DIE_NODE(ps_ane_base)>;
+	};
+
+	DIE_NODE(ps_ane_set2): power-controller@4020 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_set2);
+		power-domains = <&DIE_NODE(ps_ane_set1)>;
+	};
+
+	DIE_NODE(ps_ane_set3): power-controller@4028 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4028 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_set3);
+		power-domains = <&DIE_NODE(ps_ane_set2)>;
+	};
+
+	DIE_NODE(ps_ane_set4): power-controller@4030 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4030 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ane_set4);
+		power-domains = <&DIE_NODE(ps_ane_set3)>;
+	};
+};
+
+&pmgr_south {
+	DIE_NODE(ps_amcc4): power-controller@100 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x100 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc4);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_amcc5): power-controller@108 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x108 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc5);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_amcc6): power-controller@110 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x110 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc6);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_amcc7): power-controller@118 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x118 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc7);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_dcs_16): power-controller@120 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x120 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_16);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_17): power-controller@128 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x128 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_17);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_18): power-controller@130 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x130 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_18);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_19): power-controller@138 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x138 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_19);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_20): power-controller@140 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x140 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_20);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_21): power-controller@148 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x148 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_21);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_22): power-controller@150 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x150 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_22);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_23): power-controller@158 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x158 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_23);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_24): power-controller@160 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x160 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_24);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_25): power-controller@168 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x168 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_25);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_26): power-controller@170 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x170 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_26);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_27): power-controller@178 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x178 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_27);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_28): power-controller@180 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x180 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_28);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_29): power-controller@188 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x188 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_29);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_30): power-controller@190 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x190 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_30);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_31): power-controller@198 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x198 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_31);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_afnc4_ioa): power-controller@1a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc4_ioa);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afnc4_ls): power-controller@1a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc4_ls);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc4_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc4_lw0): power-controller@1b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc4_lw0);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc4_ls)>;
+	};
+
+	DIE_NODE(ps_afnc5_ioa): power-controller@1b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc5_ioa);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afnc5_ls): power-controller@1c0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1c0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc5_ls);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc5_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc5_lw0): power-controller@1c8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1c8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc5_lw0);
+		apple,always-on; /* Apple Fabric */
+		power-domains = <&DIE_NODE(ps_afnc5_ls)>;
+	};
+
+	DIE_NODE(ps_dispext2_sys): power-controller@1d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1d0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext2_sys);
+	};
+
+	DIE_NODE(ps_msr1): power-controller@1d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1d8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(msr1);
+	};
+
+	DIE_NODE(ps_dispext2_fe): power-controller@1e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1e0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext2_fe);
+		power-domains = <&DIE_NODE(ps_dispext2_sys)>;
+	};
+
+	DIE_NODE(ps_dispext2_cpu0): power-controller@1e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext2_cpu0);
+		power-domains = <&DIE_NODE(ps_dispext2_fe)>;
+	};
+
+	DIE_NODE(ps_msr1_ase_core): power-controller@1f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(msr1_ase_core);
+		power-domains = <&DIE_NODE(ps_msr1)>;
+	};
+
+	DIE_NODE(ps_dispext3_sys): power-controller@220 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x220 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext3_sys);
+	};
+
+	DIE_NODE(ps_venc1_sys): power-controller@228 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x228 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_sys);
+	};
+
+	DIE_NODE(ps_dispext3_fe): power-controller@230 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x230 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext3_fe);
+		power-domains = <&DIE_NODE(ps_dispext3_sys)>;
+	};
+
+	DIE_NODE(ps_dispext3_cpu0): power-controller@238 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x238 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dispext3_cpu0);
+		power-domains = <&DIE_NODE(ps_dispext3_fe)>;
+	};
+
+	DIE_NODE(ps_venc1_dma): power-controller@4000 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_dma);
+		power-domains = <&DIE_NODE(ps_venc1_sys)>;
+	};
+
+	DIE_NODE(ps_venc1_pipe4): power-controller@4008 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_pipe4);
+		power-domains = <&DIE_NODE(ps_venc1_dma)>;
+	};
+
+	DIE_NODE(ps_venc1_pipe5): power-controller@4010 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_pipe5);
+		power-domains = <&DIE_NODE(ps_venc1_dma)>;
+	};
+
+	DIE_NODE(ps_venc1_me0): power-controller@4018 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_me0);
+		power-domains = <&DIE_NODE(ps_venc1_pipe5)>, <&DIE_NODE(ps_venc1_pipe4)>;
+	};
+
+	DIE_NODE(ps_venc1_me1): power-controller@4020 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc1_me1);
+		power-domains = <&DIE_NODE(ps_venc1_me0)>;
+	};
+};
+
+&pmgr_east {
+	DIE_NODE(ps_clvr_spmi0): power-controller@100 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x100 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(clvr_spmi0);
+		apple,always-on; /* PCPU voltage regulator interface (used by SMC) */
+	};
+
+	DIE_NODE(ps_clvr_spmi1): power-controller@108 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x108 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(clvr_spmi1);
+		apple,always-on; /* GPU voltage regulator interface (used by SMC) */
+	};
+
+	DIE_NODE(ps_clvr_spmi2): power-controller@110 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x110 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(clvr_spmi2);
+		apple,always-on; /* ANE, fabric, AFR voltage regulator interface (used by SMC) */
+	};
+
+	DIE_NODE(ps_clvr_spmi3): power-controller@118 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x118 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(clvr_spmi3);
+		apple,always-on; /* Additional voltage regulator, probably used on T6021 (SMC) */
+	};
+
+	DIE_NODE(ps_clvr_spmi4): power-controller@120 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x120 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(clvr_spmi4);
+		apple,always-on; /* Additional voltage regulator, probably used on T6021 (SMC) */
+	};
+
+	DIE_NODE(ps_ispsens0): power-controller@128 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x128 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ispsens0);
+	};
+
+	DIE_NODE(ps_ispsens1): power-controller@130 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x130 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ispsens1);
+	};
+
+	DIE_NODE(ps_ispsens2): power-controller@138 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x138 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ispsens2);
+	};
+
+	DIE_NODE(ps_ispsens3): power-controller@140 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x140 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ispsens3);
+	};
+
+	DIE_NODE(ps_afnc6_ioa): power-controller@148 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x148 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc6_ioa);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afnc6_ls): power-controller@150 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x150 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc6_ls);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc6_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc6_lw0): power-controller@158 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x158 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc6_lw0);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc6_ls)>;
+	};
+
+	DIE_NODE(ps_afnc2_ioa): power-controller@160 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x160 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc2_ioa);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_dcs_10)>;
+	};
+
+	DIE_NODE(ps_afnc2_ls): power-controller@168 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x168 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc2_ls);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc2_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc2_lw0): power-controller@170 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x170 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc2_lw0);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc2_ls)>;
+	};
+
+	DIE_NODE(ps_afnc2_lw1): power-controller@178 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x178 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc2_lw1);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc2_ls)>;
+	};
+
+	DIE_NODE(ps_afnc3_ioa): power-controller@180 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x180 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc3_ioa);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afi)>;
+	};
+
+	DIE_NODE(ps_afnc3_ls): power-controller@188 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x188 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc3_ls);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc3_ioa)>;
+	};
+
+	DIE_NODE(ps_afnc3_lw0): power-controller@190 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x190 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afnc3_lw0);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_afnc3_ls)>;
+	};
+
+	DIE_NODE(ps_apcie_gp): power-controller@198 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x198 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_gp);
+		power-domains = <&DIE_NODE(ps_afnc6_lw0)>;
+	};
+
+	DIE_NODE(ps_apcie_st): power-controller@1a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_st);
+		power-domains = <&DIE_NODE(ps_afnc6_lw0)>;
+	};
+
+	DIE_NODE(ps_ans2): power-controller@1a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1a8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(ans2);
+		power-domains = <&DIE_NODE(ps_afnc6_lw0)>;
+	};
+
+	DIE_NODE(ps_disp0_sys): power-controller@1b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(disp0_sys);
+		power-domains = <&DIE_NODE(ps_afnc2_lw0)>;
+	};
+
+	DIE_NODE(ps_jpg): power-controller@1b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1b8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(jpg);
+		power-domains = <&DIE_NODE(ps_afnc2_lw0)>;
+	};
+
+	DIE_NODE(ps_sio): power-controller@1c0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1c0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio);
+		power-domains = <&DIE_NODE(ps_afnc2_lw1)>;
+	};
+
+	DIE_NODE(ps_isp_sys): power-controller@1c8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1c8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_sys);
+		power-domains = <&DIE_NODE(ps_afnc2_lw1)>;
+	};
+
+	DIE_NODE(ps_disp0_fe): power-controller@1d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1d0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(disp0_fe);
+		power-domains = <&DIE_NODE(ps_disp0_sys)>;
+	};
+
+	DIE_NODE(ps_disp0_cpu0): power-controller@1d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1d8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(disp0_cpu0);
+		power-domains = <&DIE_NODE(ps_disp0_fe)>;
+		apple,min-state = <4>;
+	};
+
+	DIE_NODE(ps_sio_cpu): power-controller@1e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1e0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio_cpu);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_fpwm0): power-controller@1e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(fpwm0);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_fpwm1): power-controller@1f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(fpwm1);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_fpwm2): power-controller@1f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x1f8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(fpwm2);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c0): power-controller@200 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x200 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c0);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c1): power-controller@208 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x208 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c1);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c2): power-controller@210 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x210 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c2);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c3): power-controller@218 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x218 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c3);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c4): power-controller@220 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x220 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c4);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c5): power-controller@228 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x228 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c5);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c6): power-controller@230 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x230 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c6);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c7): power-controller@238 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x238 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c7);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_i2c8): power-controller@240 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x240 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(i2c8);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_spi_p): power-controller@248 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x248 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi_p);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_sio_spmi0): power-controller@250 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x250 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio_spmi0);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_sio_spmi1): power-controller@258 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x258 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio_spmi1);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_sio_spmi2): power-controller@260 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x260 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio_spmi2);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_uart_p): power-controller@268 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x268 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart_p);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_audio_p): power-controller@270 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x270 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(audio_p);
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_sio_adma): power-controller@278 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x278 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sio_adma);
+		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_aes): power-controller@280 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x280 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(aes);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_dptx_phy_ps): power-controller@288 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x288 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dptx_phy_ps);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_sio)>;
+	};
+
+	DIE_NODE(ps_spi0): power-controller@2d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2d8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi0);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_spi1): power-controller@2e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2e0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi1);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_spi2): power-controller@2e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi2);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_spi3): power-controller@2f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi3);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_spi4): power-controller@2f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x2f8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi4);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_spi5): power-controller@300 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x300 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(spi5);
+		power-domains = <&DIE_NODE(ps_spi_p)>;
+	};
+
+	DIE_NODE(ps_uart_n): power-controller@308 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x308 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart_n);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart0): power-controller@310 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x310 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart0);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_amcc1): power-controller@318 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x318 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc1);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_amcc3): power-controller@320 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x320 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(amcc3);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_dcs_04): power-controller@328 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x328 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_04);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_05): power-controller@330 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x330 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_05);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_06): power-controller@338 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x338 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_06);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_07): power-controller@340 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x340 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_07);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_12): power-controller@348 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x348 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_12);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_13): power-controller@350 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x350 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_13);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_14): power-controller@358 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x358 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_14);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_dcs_15): power-controller@360 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x360 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dcs_15);
+		apple,always-on; /* LPDDR5 interface */
+	};
+
+	DIE_NODE(ps_uart1): power-controller@368 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x368 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart1);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart2): power-controller@370 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x370 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart2);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart3): power-controller@378 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x378 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart3);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart4): power-controller@380 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x380 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart4);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart5): power-controller@388 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x388 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart5);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_uart6): power-controller@390 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x390 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(uart6);
+		power-domains = <&DIE_NODE(ps_uart_p)>;
+	};
+
+	DIE_NODE(ps_mca0): power-controller@398 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x398 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mca0);
+		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+	};
+
+	DIE_NODE(ps_mca1): power-controller@3a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3a0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mca1);
+		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+	};
+
+	DIE_NODE(ps_mca2): power-controller@3a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3a8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mca2);
+		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+	};
+
+	DIE_NODE(ps_mca3): power-controller@3b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3b0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mca3);
+		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+	};
+
+	DIE_NODE(ps_dpa0): power-controller@3b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3b8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dpa0);
+		power-domains = <&DIE_NODE(ps_audio_p)>;
+	};
+
+	DIE_NODE(ps_dpa1): power-controller@3c0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3c0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dpa1);
+		power-domains = <&DIE_NODE(ps_audio_p)>;
+	};
+
+	DIE_NODE(ps_dpa2): power-controller@3c8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3c8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dpa2);
+		power-domains = <&DIE_NODE(ps_audio_p)>;
+	};
+
+	DIE_NODE(ps_dpa3): power-controller@3d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3d0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dpa3);
+		power-domains = <&DIE_NODE(ps_audio_p)>;
+	};
+
+	DIE_NODE(ps_msr0): power-controller@3d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3d8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(msr0);
+	};
+
+	DIE_NODE(ps_venc_sys): power-controller@3e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3e0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_sys);
+	};
+
+	DIE_NODE(ps_dpa4): power-controller@3e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3e8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dpa4);
+		power-domains = <&DIE_NODE(ps_audio_p)>;
+	};
+
+	DIE_NODE(ps_msr0_ase_core): power-controller@3f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3f0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(msr0_ase_core);
+		power-domains = <&DIE_NODE(ps_msr0)>;
+	};
+
+	DIE_NODE(ps_apcie_gpshr_sys): power-controller@3f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x3f8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_gpshr_sys);
+		power-domains = <&DIE_NODE(ps_apcie_gp)>;
+	};
+
+	DIE_NODE(ps_apcie_st_sys): power-controller@408 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x408 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_st_sys);
+		power-domains = <&DIE_NODE(ps_apcie_st)>, <&DIE_NODE(ps_ans2)>;
+	};
+
+	DIE_NODE(ps_apcie_st1_sys): power-controller@410 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x410 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_st1_sys);
+		power-domains = <&DIE_NODE(ps_apcie_st_sys)>;
+	};
+
+	DIE_NODE(ps_apcie_gp_sys): power-controller@418 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x418 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_gp_sys);
+		power-domains = <&DIE_NODE(ps_apcie_gpshr_sys)>;
+		apple,always-on; /* Breaks things if shut down */
+	};
+
+	DIE_NODE(ps_apcie_ge_sys): power-controller@420 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x420 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_ge_sys);
+		power-domains = <&DIE_NODE(ps_apcie_gpshr_sys)>;
+	};
+
+	DIE_NODE(ps_apcie_phy_sw): power-controller@428 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x428 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(apcie_phy_sw);
+		apple,always-on; /* macOS does not turn this off */
+	};
+
+	DIE_NODE(ps_sep): power-controller@c00 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xc00 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(sep);
+		apple,always-on; /* Locked on */
+	};
+
+	/* There is a dependency tree involved with these PDs,
+	 * but we do not express it here since the ISP driver
+	 * is supposed to sequence them in the right order anyway.
+	 *
+	 * This also works around spurious parent PD activation
+	 * on machines with ISP disabled (desktops), so we don't
+	 * have to enable/disable everything in the per-model DTs.
+	 */
+	DIE_NODE(ps_isp_cpu): power-controller@4000 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_cpu);
+		/* power-domains = <&DIE_NODE(ps_isp_sys)>; */
+		apple,force-disable;
+	};
+
+	DIE_NODE(ps_isp_fe): power-controller@4008 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_fe);
+		/* power-domains = <&DIE_NODE(ps_isp_sys)>; */
+	};
+
+	DIE_NODE(ps_dprx): power-controller@4010 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(dprx);
+		/* power-domains = <&DIE_NODE(ps_isp_sys)>; */
+	};
+
+	DIE_NODE(ps_isp_vis): power-controller@4018 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_vis);
+		/* power-domains = <&DIE_NODE(ps_isp_fe)>; */
+	};
+
+	DIE_NODE(ps_isp_be): power-controller@4020 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_be);
+		/* power-domains = <&DIE_NODE(ps_isp_fe)>; */
+	};
+
+	DIE_NODE(ps_isp_raw): power-controller@4028 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4028 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_raw);
+		/* power-domains = <&DIE_NODE(ps_isp_fe)>; */
+	};
+
+	DIE_NODE(ps_isp_clr): power-controller@4030 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4030 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(isp_clr);
+		/* power-domains = <&DIE_NODE(ps_isp_be)>; */
+	};
+
+	DIE_NODE(ps_venc_dma): power-controller@8000 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x8000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_dma);
+		power-domains = <&DIE_NODE(ps_venc_sys)>;
+	};
+
+	DIE_NODE(ps_venc_pipe4): power-controller@8008 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x8008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_pipe4);
+		power-domains = <&DIE_NODE(ps_venc_dma)>;
+	};
+
+	DIE_NODE(ps_venc_pipe5): power-controller@8010 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x8010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_pipe5);
+		power-domains = <&DIE_NODE(ps_venc_dma)>;
+	};
+
+	DIE_NODE(ps_venc_me0): power-controller@8018 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x8018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_me0);
+		power-domains = <&DIE_NODE(ps_venc_pipe5)>, <&DIE_NODE(ps_venc_pipe4)>;
+	};
+
+	DIE_NODE(ps_venc_me1): power-controller@8020 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x8020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(venc_me1);
+		power-domains = <&DIE_NODE(ps_venc_me0)>;
+	};
+
+	DIE_NODE(ps_prores): power-controller@c000 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xc000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(prores);
+		power-domains = <&DIE_NODE(ps_afnc3_lw0)>;
+	};
+};
+
+&pmgr_mini {
+	DIE_NODE(ps_debug): power-controller@58 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x58 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(debug);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_nub_spmi0): power-controller@60 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x60 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_spmi0);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_nub_spmi1): power-controller@68 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x68 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_spmi1);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_nub_aon): power-controller@70 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x70 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_aon);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_msg): power-controller@78 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x78 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(msg);
+	};
+
+	DIE_NODE(ps_nub_gpio): power-controller@80 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x80 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_gpio);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_nub_fabric): power-controller@88 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x88 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_fabric);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_atc0_usb_aon): power-controller@90 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x90 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_usb_aon);
+		apple,always-on; /* Needs to stay on for dwc3 to work */
+	};
+
+	DIE_NODE(ps_atc1_usb_aon): power-controller@98 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x98 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_usb_aon);
+		apple,always-on; /* Needs to stay on for dwc3 to work */
+	};
+
+	DIE_NODE(ps_atc2_usb_aon): power-controller@a0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xa0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_usb_aon);
+		apple,always-on; /* Needs to stay on for dwc3 to work */
+	};
+
+	DIE_NODE(ps_atc3_usb_aon): power-controller@a8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xa8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_usb_aon);
+		apple,always-on; /* Needs to stay on for dwc3 to work */
+	};
+
+	DIE_NODE(ps_mtp_fabric): power-controller@b0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xb0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_fabric);
+		apple,always-on;
+		power-domains = <&DIE_NODE(ps_nub_fabric)>;
+	};
+
+	DIE_NODE(ps_nub_sram): power-controller@b8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xb8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(nub_sram);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_debug_switch): power-controller@c0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xc0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(debug_switch);
+		apple,always-on; /* Core AON device */
+	};
+
+	DIE_NODE(ps_atc0_usb): power-controller@c8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xc8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc0_usb);
+		power-domains = <&DIE_NODE(ps_atc0_common)>;
+	};
+
+	DIE_NODE(ps_atc1_usb): power-controller@d0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xd0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc1_usb);
+		power-domains = <&DIE_NODE(ps_atc1_common)>;
+	};
+
+	DIE_NODE(ps_atc2_usb): power-controller@d8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xd8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc2_usb);
+		power-domains = <&DIE_NODE(ps_atc2_common)>;
+	};
+
+	DIE_NODE(ps_atc3_usb): power-controller@e0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xe0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(atc3_usb);
+		power-domains = <&DIE_NODE(ps_atc3_common)>;
+	};
+
+#if 0
+	/* MTP stuff is self-managed */
+	DIE_NODE(ps_mtp_gpio): power-controller@e8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xe8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_gpio);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_base): power-controller@f0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xf0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_base);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_periph): power-controller@f8 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0xf8 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_periph);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_spi0): power-controller@100 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x100 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_spi0);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_i2cm0): power-controller@108 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x108 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_i2cm0);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_uart0): power-controller@110 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x110 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_uart0);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_cpu): power-controller@118 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x118 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_cpu);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_fabric)>;
+	};
+
+	DIE_NODE(ps_mtp_scm_fabric): power-controller@120 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x120 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_scm_fabric);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_periph)>;
+	};
+
+	DIE_NODE(ps_mtp_sram): power-controller@128 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x128 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_sram);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_scm_fabric)>, <&DIE_NODE(ps_mtp_cpu)>;
+	};
+
+	DIE_NODE(ps_mtp_dma): power-controller@130 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x130 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(mtp_dma);
+		apple,always-on; /* MTP always stays on */
+		power-domains = <&DIE_NODE(ps_mtp_sram)>;
+	};
+#endif
+};
+
+&pmgr_gfx {
+	DIE_NODE(ps_gpx): power-controller@0 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x0 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(gpx);
+		apple,always-on;
+	};
+
+	DIE_NODE(ps_afr): power-controller@100 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x100 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(afr);
+		/* Apple Fabric, media stuff: this can power down */
+	};
+
+	DIE_NODE(ps_gfx): power-controller@108 {
+		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x108 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = DIE_LABEL(gfx);
+		power-domains = <&DIE_NODE(ps_afr)>, <&DIE_NODE(ps_gpx)>;
+	};
+};
+

From b9759e8448c5ea99d99d75c0ad737c2d392111c9 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 18:15:33 +0900
Subject: [PATCH 0070/1027] arm64: dts: apple: Add MTP nodes to t6020x

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6020-j414s.dts     |  4 +
 arch/arm64/boot/dts/apple/t6020-j416s.dts     |  4 +
 arch/arm64/boot/dts/apple/t6021-j414c.dts     |  4 +
 arch/arm64/boot/dts/apple/t6021-j416c.dts     |  4 +
 arch/arm64/boot/dts/apple/t602x-die0.dtsi     | 76 +++++++++++++++++++
 .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 41 ++++++++++
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi     |  1 +
 arch/arm64/boot/dts/apple/t8112.dtsi          |  1 +
 8 files changed, 135 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6020-j414s.dts b/arch/arm64/boot/dts/apple/t6020-j414s.dts
index 18cc67a3076def..5dd97df71efc4b 100644
--- a/arch/arm64/boot/dts/apple/t6020-j414s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j414s.dts
@@ -36,3 +36,7 @@
 	compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio";
 	model = "MacBook Pro J414";
 };
+
+&mtp_mt {
+	firmware-name = "apple/tpmtfw-j414s.bin";
+};
diff --git a/arch/arm64/boot/dts/apple/t6020-j416s.dts b/arch/arm64/boot/dts/apple/t6020-j416s.dts
index b9e0973ba37c30..56ddf7c61de634 100644
--- a/arch/arm64/boot/dts/apple/t6020-j416s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j416s.dts
@@ -36,3 +36,7 @@
 	compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio";
 	model = "MacBook Pro J416";
 };
+
+&mtp_mt {
+	firmware-name = "apple/tpmtfw-j416s.bin";
+};
diff --git a/arch/arm64/boot/dts/apple/t6021-j414c.dts b/arch/arm64/boot/dts/apple/t6021-j414c.dts
index b173caf0df0fce..6905c7d39db0ce 100644
--- a/arch/arm64/boot/dts/apple/t6021-j414c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j414c.dts
@@ -36,3 +36,7 @@
 	compatible = "apple,j414-macaudio", "apple,j314-macaudio", "apple,macaudio";
 	model = "MacBook Pro J414";
 };
+
+&mtp_mt {
+	firmware-name = "apple/tpmtfw-j414c.bin";
+};
diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts
index 36a57890c25d72..331a1e93e7f352 100644
--- a/arch/arm64/boot/dts/apple/t6021-j416c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts
@@ -52,3 +52,7 @@
 	compatible = "apple,j416-macaudio", "apple,j316-macaudio", "apple,macaudio";
 	model = "MacBook Pro J416";
 };
+
+&mtp_mt {
+	firmware-name = "apple/tpmtfw-j416c.bin";
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 794abdacfb01dc..443326b4f3ffec 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -176,6 +176,82 @@
 				<AIC_IRQ 0 857 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
+	mtp: mtp@2a9400000 {
+		compatible = "apple,t6020-mtp", "apple,t6020-rtk-helper-asc4", "apple,mtp", "apple,rtk-helper-asc4";
+		reg = <0x2 0xa9400000 0x0 0x4000>,
+			<0x2 0xa9c00000 0x0 0x100000>;
+		reg-names = "asc", "sram";
+		mboxes = <&mtp_mbox>;
+		iommus = <&mtp_dart 1>;
+		#helper-cells = <0>;
+
+		status = "disabled";
+	};
+
+	mtp_mbox: mbox@2a9408000 {
+		compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0xa9408000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 693 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 694 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 695 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 0 696 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+
+		status = "disabled";
+	};
+
+	mtp_dart: iommu@2a9808000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x2 0xa9808000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 676 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+
+		apple,dma-range = <0x100 0x0 0x1 0x0>;
+
+		status = "disabled";
+	};
+
+	mtp_dockchannel: fifo@2a9b14000 {
+		compatible = "apple,t6020-dockchannel", "apple,dockchannel";
+		reg = <0x2 0xa9b14000 0x0 0x4000>;
+		reg-names = "irq";
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 677 IRQ_TYPE_LEVEL_HIGH>;
+
+		ranges = <0 0x2 0xa9b28000 0x20000>;
+		nonposted-mmio;
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		interrupt-controller;
+		#interrupt-cells = <2>;
+
+		status = "disabled";
+
+		mtp_hid: input@8000 {
+			compatible = "apple,dockchannel-hid";
+			reg = <0x8000 0x4000>,
+				<0xc000 0x4000>,
+				<0x0000 0x4000>,
+				<0x4000 0x4000>;
+			reg-names = "config", "data",
+				"rmt-config", "rmt-data";
+			iommus = <&mtp_dart 1>;
+			interrupt-parent = <&mtp_dockchannel>;
+			interrupts = <2 IRQ_TYPE_LEVEL_HIGH>,
+				<3 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "tx", "rx";
+
+			apple,fifo-size = <0x800>;
+			apple,helper-cpu = <&mtp>;
+		};
+
+	};
+
 	disp0_dart: iommu@389304000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0x3 0x89304000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index c1f45b8114c92d..dd1ea2b5dd95a8 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -86,3 +86,44 @@
 	pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>;
 };
 
+&ps_mtp_fabric {
+	status = "okay";
+};
+
+&mtp {
+	status = "okay";
+};
+
+&mtp_mbox {
+	status = "okay";
+};
+
+&mtp_dart {
+	status = "okay";
+};
+
+&mtp_dockchannel {
+	status = "okay";
+};
+
+&mtp_hid {
+	apple,afe-reset-gpios = <&smc_gpio 25 GPIO_ACTIVE_LOW>;
+	apple,stm-reset-gpios = <&smc_gpio 26 GPIO_ACTIVE_LOW>;
+
+	mtp_mt: multi-touch {
+	};
+
+	keyboard: keyboard {
+		hid-country-code = <0>;
+		apple,keyboard-layout-id = <0>;
+	};
+
+	stm {
+	};
+
+	actuator {
+	};
+
+	tp_accel {
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index 50d79ab1ed1298..facbcc1260f4f9 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -2071,6 +2071,7 @@
 		label = DIE_LABEL(mtp_fabric);
 		apple,always-on;
 		power-domains = <&DIE_NODE(ps_nub_fabric)>;
+		status = "disabled";
 	};
 
 	DIE_NODE(ps_nub_sram): power-controller@b8 {
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 9ef423b5fde4c5..e09e35ee96337b 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -1245,6 +1245,7 @@
 			interrupts = <AIC_IRQ 850 IRQ_TYPE_LEVEL_HIGH>;
 
 			ranges = <0 0x2 0x4eb28000 0x20000>;
+			nonposted-mmio;
 			#address-cells = <1>;
 			#size-cells = <1>;
 

From 4a606d84483111d453f96739a6b60e7ae6d16555 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 02:34:01 +0900
Subject: [PATCH 0071/1027] arm64: dts: apple: Add identity dma-ranges mapping

Without this, the OF core ends up limiting all DMA masks to the default
32-bit, since that runs before drivers set up the proper DMA mask.

Skipping the highest page because it is impossible to express a full
64-bit range in the DT.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6001.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t6002.dtsi | 4 ++++
 arch/arm64/boot/dts/apple/t6021.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t8103.dtsi | 2 ++
 arch/arm64/boot/dts/apple/t8112.dtsi | 2 ++
 5 files changed, 12 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6001.dtsi b/arch/arm64/boot/dts/apple/t6001.dtsi
index 6e7e7cdeacf943..316deb8a95be63 100644
--- a/arch/arm64/boot/dts/apple/t6001.dtsi
+++ b/arch/arm64/boot/dts/apple/t6001.dtsi
@@ -50,6 +50,8 @@
 
 		ranges;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		// filled via templated includes at the end of the file
 	};
diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi
index f1164315be755a..a7dfc6196fa724 100644
--- a/arch/arm64/boot/dts/apple/t6002.dtsi
+++ b/arch/arm64/boot/dts/apple/t6002.dtsi
@@ -240,6 +240,8 @@
 			 <0x5 0x80000000 0x5 0x80000000 0x1 0x80000000>,
 			 <0x7 0x0 0x7 0x0 0xf 0x80000000>;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		// filled via templated includes at the end of the file
 	};
@@ -251,6 +253,8 @@
 		ranges = <0x2 0x0 0x22 0x0 0x4 0x0>,
 			 <0x7 0x0 0x27 0x0 0xf 0x80000000>;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		// filled via templated includes at the end of the file
 	};
diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi
index d907c4753f67dd..ec3cfde14722a6 100644
--- a/arch/arm64/boot/dts/apple/t6021.dtsi
+++ b/arch/arm64/boot/dts/apple/t6021.dtsi
@@ -50,6 +50,8 @@
 
 		ranges;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		// filled via templated includes at the end of the file
 	};
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 65e532c8198841..a7cdb3ae1ad11d 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -436,6 +436,8 @@
 
 		ranges;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		gpu: gpu@206400000 {
 			compatible = "apple,agx-t8103", "apple,agx-g13g";
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index e09e35ee96337b..41eafdeddd9007 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -461,6 +461,8 @@
 
 		ranges;
 		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
 
 		gpu: gpu@206400000 {
 			compatible = "apple,agx-t8112", "apple,agx-g14g";

From 16e04fdee547a9238292e4859dc1523221a8a8b6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 11 Apr 2023 21:39:50 +0200
Subject: [PATCH 0072/1027] DO NOT SUBMIT: arm64: dts: apple: t6020-j474s: Add
 unused PCIe port01

This works around a Linux bug which results in mismatched iommus on
gaps in PCI(e) ports / bus numbers.

Remove as soon as the bug is identified.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index 9b61a7bb9d6ce4..653af803551c76 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -60,6 +60,16 @@
 	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
 };
 
+&port01 {
+	/*
+	 * TODO: do not enable port without device. This works around a Linux
+	 * bug which results in mismatched iommus on gaps in PCI(e) ports / bus
+	 * numbers.
+	 */
+	bus-range = <2 2>;
+	status = "okay";
+};
+
 &port02 {
 	/* 10 Gbit Ethernet */
 	bus-range = <3 3>;

From ee89f66555c200c76848b91abb86f817387ad331 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Apr 2023 05:05:57 +0900
Subject: [PATCH 0073/1027] arm64: dts: apple: Add pmgr-misc nodes to t60xx

---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 10 ++++++++++
 arch/arm64/boot/dts/apple/t602x-die0.dtsi |  9 +++++++++
 2 files changed, 19 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 3c991c9bdc529a..6a43db684ed461 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -24,6 +24,16 @@
 		power-domains = <&ps_aic>;
 	};
 
+	pmgr_misc: power-management@28e20c000 {
+		compatible = "apple,t6000-pmgr-misc";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x8e20c000 0 0x400>,
+			<0x2 0x8e20c800 0 0x400>;
+		reg-names = "fabric-ps", "dcs-ps";
+		apple,dcs-min-ps = <7>;
+	};
+
 	pmgr_dcp: power-management@28e3d0000 {
 		reg = <0x2 0x8e3d0000 0x0 0x4000>;
 		reg-names = "dcp-fw-pmgr";
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 443326b4f3ffec..71dd21c5136f54 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -25,6 +25,15 @@
 		power-domains = <&ps_aic>;
 	};
 
+	pmgr_misc: power-management@28e20c000 {
+		compatible = "apple,t6020-pmgr-misc", "apple,t6000-pmgr-misc";
+		#address-cells = <1>;
+		#size-cells = <1>;
+		reg = <0x2 0x8e20c000 0 0x400>,
+			<0x2 0x8e20c400 0 0x400>;
+		reg-names = "fabric-ps", "dcs-ps";
+	};
+
 	pmgr_dcp: power-management@28e3d0000 {
 		reg = <0x2 0x8e3d0000 0x0 0x4000>;
 		reg-names = "dcp-fw-pmgr";

From dc6fba3bbfb37cc6c2d620cb059dc476a5265268 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 24 Apr 2023 23:27:52 +0900
Subject: [PATCH 0074/1027] arm64: dts: apple: t600x: Remove obsolete comment
 in ans2 power domain

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 00b317c2355b8c..0555ba2eb3918c 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1383,12 +1383,6 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(ans2);
-		/*
-		 * The ADT makes ps_apcie_st[1]_sys depend on ps_ans2 instead,
-		 * but we'd rather have a single power domain for the downstream
-		 * device to depend on, so use this node as the child.
-		 * This makes more sense anyway (since ANS2 uses APCIE_ST).
-		 */
 		power-domains = <&DIE_NODE(ps_afnc2_lw0)>;
 	};
 

From 6d96b6591e2ec8c9e674d18d21c0a354c33d9e3b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 26 Apr 2023 02:17:26 +0900
Subject: [PATCH 0075/1027] arm64: dts: apple: Make ps_msg always-on

Apple has it that way, and it might be important. Let's not risk it.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 1 +
 4 files changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 0555ba2eb3918c..af3baf871b22ee 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1873,6 +1873,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(msg);
+		apple,always-on; /* Core AON device? */
 	};
 
 	DIE_NODE(ps_nub_gpio): power-controller@80 {
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index facbcc1260f4f9..33641648f2ae02 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -2007,6 +2007,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(msg);
+		apple,always-on; /* Core AON device? */
 	};
 
 	DIE_NODE(ps_nub_gpio): power-controller@80 {
diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 724e7fd559e7a1..5fb8c8601a9dcb 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -1095,6 +1095,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "msg";
+		apple,always-on; /* Core AON device? */
 	};
 
 	ps_atc0_usb_aon: power-controller@88 {
diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index d1711155f84686..3828a1333dacae 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -1061,6 +1061,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "msg";
+		apple,always-on; /* Core AON device? */
 	};
 
 	ps_nub_gpio: power-controller@80 {

From 50e60c40308952b471f6f66aa52523bd44a025a2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 27 Apr 2023 13:53:35 +0900
Subject: [PATCH 0076/1027] arm64: dts: apple: t600x: Enable turbo CPU p-states

These should work now that we have cpuidle.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-common.dtsi | 2 --
 1 file changed, 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index 279ba91d8abacd..667c02724b8646 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -315,7 +315,6 @@
 			opp-level = <12>;
 			clock-latency-ns = <56000>;
 		};
-		/* Not available until CPU deep sleep is implemented
 		opp13 {
 			opp-hz = /bits/ 64 <3132000000>;
 			opp-level = <13>;
@@ -334,7 +333,6 @@
 			clock-latency-ns = <56000>;
 			turbo-mode;
 		};
-		*/
 	};
 
 	gpu_opp: opp-table-gpu {

From d6876805a6c5362d8eb35bec2febdf8b03ad0276 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 27 Apr 2023 13:54:17 +0900
Subject: [PATCH 0077/1027] arm64: dts: apple: t8103: Enable turbo CPU p-states

These should work now that we have cpuidle.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 2 --
 1 file changed, 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index a7cdb3ae1ad11d..130e240f59a08c 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -280,7 +280,6 @@
 			opp-level = <12>;
 			clock-latency-ns = <55000>;
 		};
-#if 0
 		/* Not available until CPU deep sleep is implemented */
 		opp13 {
 			opp-hz = /bits/ 64 <3096000000>;
@@ -300,7 +299,6 @@
 			clock-latency-ns = <56000>;
 			turbo-mode;
 		};
-#endif
 	};
 
 	gpu_opp: opp-table-gpu {

From c1192576d76e835e71c64505643deedac41575b2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 27 Apr 2023 13:54:30 +0900
Subject: [PATCH 0078/1027] arm64: dts: apple: t8112: Enable turbo CPU p-states

These should work now that we have cpuidle.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 41eafdeddd9007..cabf3a23104afc 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -302,8 +302,6 @@
 			opp-level = <14>;
 			clock-latency-ns = <46000>;
 		};
-		/* Not available until CPU deep sleep is implemented */
-#if 0
 		opp15 {
 			opp-hz = /bits/ 64 <3324000000>;
 			opp-level = <15>;
@@ -322,7 +320,6 @@
 			clock-latency-ns = <62000>;
 			turbo-mode;
 		};
-#endif
 	};
 
 	gpu_opp: opp-table-gpu {

From 17d83ec5c6377fdd4d2e0deccce83b9a6ec45014 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 19 Apr 2023 21:28:29 +0900
Subject: [PATCH 0079/1027] arm64: dts: apple: Add T602x GPU node

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t6020.dtsi        |  7 ++
 arch/arm64/boot/dts/apple/t6021.dtsi        | 10 +++
 arch/arm64/boot/dts/apple/t602x-common.dtsi | 78 +++++++++++++++++++++
 arch/arm64/boot/dts/apple/t602x-die0.dtsi   | 45 +++++++++++-
 4 files changed, 139 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t6020.dtsi b/arch/arm64/boot/dts/apple/t6020.dtsi
index 3a864ebd91bb2f..77affcd3aa0d1c 100644
--- a/arch/arm64/boot/dts/apple/t6020.dtsi
+++ b/arch/arm64/boot/dts/apple/t6020.dtsi
@@ -21,4 +21,11 @@
 
 &gpu {
 	compatible = "apple,agx-t6020", "apple,agx-g14x";
+
+	apple,avg-power-filter-tc-ms = <302>;
+	apple,avg-power-ki-only = <2.6375>;
+	apple,avg-power-kp = <0.18>;
+	apple,fast-die0-integral-gain = <1350.0>;
+	apple,ppm-filter-time-constant-ms = <32>;
+	apple,ppm-ki = <28.0>;
 };
diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi
index ec3cfde14722a6..102f2915b9e8ad 100644
--- a/arch/arm64/boot/dts/apple/t6021.dtsi
+++ b/arch/arm64/boot/dts/apple/t6021.dtsi
@@ -19,6 +19,9 @@
 #ifndef GPU_REPEAT
 # define GPU_REPEAT(x) <x x x x>
 #endif
+#ifndef GPU_DIE_REPEAT
+# define GPU_DIE_REPEAT(x) <x>
+#endif
 
 #include "t602x-common.dtsi"
 
@@ -90,4 +93,11 @@
 
 &gpu {
 	compatible = "apple,agx-t6021", "apple,agx-g14x";
+
+	apple,avg-power-filter-tc-ms = <300>;
+	apple,avg-power-ki-only = <1.5125>;
+	apple,avg-power-kp = <0.38>;
+	apple,fast-die0-integral-gain = <700.0>;
+	apple,ppm-filter-time-constant-ms = <34>;
+	apple,ppm-ki = <18.0>;
 };
diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi
index 1224e4f12a5347..80ba81c47a3424 100644
--- a/arch/arm64/boot/dts/apple/t602x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi
@@ -9,6 +9,10 @@
 	#address-cells = <2>;
 	#size-cells = <2>;
 
+	aliases {
+		gpu = &gpu;
+	};
+
 	cpus {
 		#address-cells = <2>;
 		#size-cells = <0>;
@@ -434,6 +438,80 @@
 		};
 	};
 
+	gpu_cs_opp: opp-table-gpu-cs {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <24>;
+			opp-microvolt = GPU_DIE_REPEAT(668000);
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <444000000>;
+			opp-microvolt = GPU_DIE_REPEAT(668000);
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <612000000>;
+			opp-microvolt = GPU_DIE_REPEAT(678000);
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <808000000>;
+			opp-microvolt = GPU_DIE_REPEAT(737000);
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <1024000000>;
+			opp-microvolt = GPU_DIE_REPEAT(815000);
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1140000000>;
+			opp-microvolt = GPU_DIE_REPEAT(862000);
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1236000000>;
+			opp-microvolt = GPU_DIE_REPEAT(893000);
+		};
+	};
+
+	gpu_afr_opp: opp-table-gpu-afr {
+		compatible = "operating-points-v2";
+
+		/*
+		 * NOTE: The voltage and power values are device-specific and
+		 * must be filled in by the bootloader.
+		 */
+		opp00 {
+			opp-hz = /bits/ 64 <24>;
+			opp-microvolt = GPU_DIE_REPEAT(668000);
+		};
+		opp01 {
+			opp-hz = /bits/ 64 <400000000>;
+			opp-microvolt = GPU_DIE_REPEAT(668000);
+		};
+		opp02 {
+			opp-hz = /bits/ 64 <552000000>;
+			opp-microvolt = GPU_DIE_REPEAT(678000);
+		};
+		opp03 {
+			opp-hz = /bits/ 64 <760000000>;
+			opp-microvolt = GPU_DIE_REPEAT(737000);
+		};
+		opp04 {
+			opp-hz = /bits/ 64 <980000000>;
+			opp-microvolt = GPU_DIE_REPEAT(815000);
+		};
+		opp05 {
+			opp-hz = /bits/ 64 <1098000000>;
+			opp-microvolt = GPU_DIE_REPEAT(862000);
+		};
+		opp06 {
+			opp-hz = /bits/ 64 <1200000000>;
+			opp-microvolt = GPU_DIE_REPEAT(893000);
+		};
+	};
+
 	pmu-e {
 		compatible = "apple,blizzard-pmu";
 		interrupt-parent = <&aic>;
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 71dd21c5136f54..6bccfb60e50b3b 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -596,8 +596,51 @@
 		apple,firmware-compat = <0 0 0>;
 
 		operating-points-v2 = <&gpu_opp>;
-		/* TODO perf stuff */
+		apple,cs-opp = <&gpu_cs_opp>;
+		apple,afr-opp = <&gpu_afr_opp>;
+
+		apple,min-sram-microvolt = <790000>;
+		apple,csafr-min-sram-microvolt = <812000>;
 		apple,perf-base-pstate = <1>;
+
+		apple,avg-power-min-duty-cycle = <40>;
+		apple,avg-power-target-filter-tc = <1>;
+		apple,fast-die0-proportional-gain = <34.0>;
+		apple,perf-boost-ce-step = <50>;
+		apple,perf-boost-min-util = <90>;
+		apple,perf-filter-drop-threshold = <0>;
+		apple,perf-filter-time-constant = <5>;
+		apple,perf-filter-time-constant2 = <200>;
+		apple,perf-integral-gain = <1.62>;
+		apple,perf-integral-gain2 = <1.62>;
+		apple,perf-integral-min-clamp = <0>;
+		apple,perf-proportional-gain2 = <5.4>;
+		apple,perf-proportional-gain = <5.4>;
+		apple,perf-tgt-utilization = <85>;
+		apple,power-sample-period = <8>;
+		apple,ppm-filter-time-constant-ms = <34>;
+		apple,ppm-ki = <18.0>;
+		apple,ppm-kp = <0.1>;
+		apple,pwr-filter-time-constant = <313>;
+		apple,pwr-integral-gain = <0.0202129>;
+		apple,pwr-integral-min-clamp = <0>;
+		apple,pwr-min-duty-cycle = <40>;
+		apple,pwr-proportional-gain = <5.2831855>;
+		apple,pwr-sample-period-aic-clks = <200000>;
+		apple,se-engagement-criteria = <700>;
+		apple,se-filter-time-constant = <9>;
+		apple,se-filter-time-constant-1 = <3>;
+		apple,se-inactive-threshold = <2500>;
+		apple,se-ki = <-50.0>;
+		apple,se-ki-1 = <-100.0>;
+		apple,se-kp = <-5.0>;
+		apple,se-kp-1 = <-10.0>;
+		apple,se-reset-criteria = <50>;
+
+		apple,core-leak-coef = GPU_REPEAT(1200.0);
+		apple,sram-leak-coef = GPU_REPEAT(20.0);
+		apple,cs-leak-coef = GPU_DIE_REPEAT(400.0);
+		apple,afr-leak-coef = GPU_DIE_REPEAT(200.0);
 	};
 
 	agx_mbox: mbox@406408000 {

From e483e67d24f2d2b0c1db6addaaa3e861ec7eda6e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Jul 2023 13:47:13 +0900
Subject: [PATCH 0080/1027] arm64: dts: apple: t600x-j375.dtsi: Add missing
 etherhet0 alias

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j375.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 0564c8cae687ab..c31b9798d2617c 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -19,6 +19,7 @@
 		dcp = &dcp;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
+		ethernet0 = &ethernet0;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};

From b22bda302e6abcc553a0a5145fcab2cff7410ed8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 7 Aug 2023 18:08:35 +0900
Subject: [PATCH 0081/1027] arm64: dts: apple: Add initial t6022 support

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/Makefile          |   1 +
 arch/arm64/boot/dts/apple/t6021.dtsi        |  18 -
 arch/arm64/boot/dts/apple/t6022.dtsi        | 360 ++++++++++++++++++++
 arch/arm64/boot/dts/apple/t602x-common.dtsi |  21 ++
 arch/arm64/boot/dts/apple/t602x-die0.dtsi   |   8 -
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi   |   8 +
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi   |  12 +-
 7 files changed, 397 insertions(+), 31 deletions(-)
 create mode 100644 arch/arm64/boot/dts/apple/t6022.dtsi

diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile
index e3e62c672d53b7..a55d7097b7fbbb 100644
--- a/arch/arm64/boot/dts/apple/Makefile
+++ b/arch/arm64/boot/dts/apple/Makefile
@@ -18,3 +18,4 @@ dtb-$(CONFIG_ARCH_APPLE) += t6021-j414c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6020-j416s.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6021-j416c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6020-j474s.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6022-j180d.dtb
diff --git a/arch/arm64/boot/dts/apple/t6021.dtsi b/arch/arm64/boot/dts/apple/t6021.dtsi
index 102f2915b9e8ad..95298973624f1d 100644
--- a/arch/arm64/boot/dts/apple/t6021.dtsi
+++ b/arch/arm64/boot/dts/apple/t6021.dtsi
@@ -28,24 +28,6 @@
 / {
 	compatible = "apple,t6001", "apple,arm-platform";
 
-	reserved-memory {
-		#address-cells = <2>;
-		#size-cells = <2>;
-		ranges;
-
-		uat_handoff: uat-handoff {
-			reg = <0 0 0 0>;
-		};
-
-		uat_pagetables: uat-pagetables {
-			reg = <0 0 0 0>;
-		};
-
-		uat_ttbs: uat-ttbs {
-			reg = <0 0 0 0>;
-		};
-	};
-
 	soc {
 		compatible = "simple-bus";
 		#address-cells = <2>;
diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi
new file mode 100644
index 00000000000000..b7c19be04c72a3
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6022.dtsi
@@ -0,0 +1,360 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Apple T6022 "M2 Ultra" SoC
+ *
+ * Other names: H14J, "Rhodes 2C"
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <dt-bindings/gpio/gpio.h>
+#include <dt-bindings/interrupt-controller/apple-aic.h>
+#include <dt-bindings/interrupt-controller/irq.h>
+#include <dt-bindings/pinctrl/apple.h>
+#include <dt-bindings/phy/phy.h>
+#include <dt-bindings/spmi/spmi.h>
+
+#include "multi-die-cpp.h"
+
+#ifndef GPU_REPEAT
+# define GPU_REPEAT(x) <x x x x x x x x>
+#endif
+#ifndef GPU_DIE_REPEAT
+# define GPU_DIE_REPEAT(x) <x x>
+#endif
+
+#include "t602x-common.dtsi"
+
+/ {
+	compatible = "apple,t6022", "apple,arm-platform";
+
+	#address-cells = <2>;
+	#size-cells = <2>;
+
+	cpus {
+		cpu-map {
+			cluster3 {
+				core0 {
+					cpu = <&cpu_e10>;
+				};
+				core1 {
+					cpu = <&cpu_e11>;
+				};
+				core2 {
+					cpu = <&cpu_e12>;
+				};
+				core3 {
+					cpu = <&cpu_e13>;
+				};
+			};
+
+			cluster4 {
+				core0 {
+					cpu = <&cpu_p20>;
+				};
+				core1 {
+					cpu = <&cpu_p21>;
+				};
+				core2 {
+					cpu = <&cpu_p22>;
+				};
+				core3 {
+					cpu = <&cpu_p23>;
+				};
+			};
+
+			cluster5 {
+				core0 {
+					cpu = <&cpu_p30>;
+				};
+				core1 {
+					cpu = <&cpu_p31>;
+				};
+				core2 {
+					cpu = <&cpu_p32>;
+				};
+				core3 {
+					cpu = <&cpu_p33>;
+				};
+			};
+		};
+
+		cpu_e10: cpu@800 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x800>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_3>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e_die1>;
+		};
+
+		cpu_e11: cpu@801 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x801>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_3>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e_die1>;
+		};
+
+		cpu_e12: cpu@802 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x802>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_3>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e_die1>;
+		};
+
+		cpu_e13: cpu@803 {
+			compatible = "apple,blizzard";
+			device_type = "cpu";
+			reg = <0x0 0x803>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* to be filled by loader */
+			next-level-cache = <&l2_cache_3>;
+			i-cache-size  = <0x20000>;
+			d-cache-size = <0x10000>;
+			operating-points-v2 = <&blizzard_opp>;
+			capacity-dmips-mhz = <756>;
+			performance-domains = <&cpufreq_e_die1>;
+		};
+
+		cpu_p20: cpu@10900 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10900>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_4>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0_die1>;
+		};
+
+		cpu_p21: cpu@10901 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10901>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_4>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0_die1>;
+		};
+
+		cpu_p22: cpu@10902 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10902>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_4>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0_die1>;
+		};
+
+		cpu_p23: cpu@10903 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10903>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_4>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p0_die1>;
+		};
+
+		cpu_p30: cpu@10a00 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10a00>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_5>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1_die1>;
+		};
+
+		cpu_p31: cpu@10a01 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10a01>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_5>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1_die1>;
+		};
+
+		cpu_p32: cpu@10a02 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10a02>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_5>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1_die1>;
+		};
+
+		cpu_p33: cpu@10a03 {
+			compatible = "apple,avalanche";
+			device_type = "cpu";
+			reg = <0x0 0x10a03>;
+			enable-method = "spin-table";
+			cpu-release-addr = <0 0>; /* To be filled by loader */
+			next-level-cache = <&l2_cache_5>;
+			i-cache-size = <0x30000>;
+			d-cache-size = <0x20000>;
+			operating-points-v2 = <&avalanche_opp>;
+			capacity-dmips-mhz = <1024>;
+			performance-domains = <&cpufreq_p1_die1>;
+		};
+
+		l2_cache_3: l2-cache-3 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x400000>;
+		};
+
+		l2_cache_4: l2-cache-4 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x1000000>;
+		};
+
+		l2_cache_5: l2-cache-5 {
+			compatible = "cache";
+			cache-level = <2>;
+			cache-unified;
+			cache-size = <0x1000000>;
+		};
+	};
+
+	die0: soc@200000000 {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges = <0x2 0x0 0x2 0x0 0x4 0x0>,
+			 <0x5 0x80000000 0x5 0x80000000 0x1 0x80000000>,
+			 <0x7 0x0 0x7 0x0 0xf 0x80000000>;
+		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
+
+		// filled via templated includes at the end of the file
+	};
+
+	die1: soc@2200000000 {
+		compatible = "simple-bus";
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges = <0x2 0x0 0x22 0x0 0x4 0x0>,
+			 <0x7 0x0 0x27 0x0 0xf 0x80000000>;
+		nonposted-mmio;
+		/* Required to get >32-bit DMA via DARTs */
+		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
+
+		// filled via templated includes at the end of the file
+	};
+};
+
+#define DIE
+#define DIE_NO 0
+
+&die0 {
+	#include "t602x-die0.dtsi"
+	#include "t602x-dieX.dtsi"
+};
+
+#include "t602x-pmgr.dtsi"
+#include "t602x-gpio-pins.dtsi"
+
+#undef DIE
+#undef DIE_NO
+
+#define DIE _die1
+#define DIE_NO 1
+
+&die1 {
+	#include "t602x-dieX.dtsi"
+	#include "t602x-nvme.dtsi"
+};
+
+#include "t602x-pmgr.dtsi"
+
+#undef DIE
+#undef DIE_NO
+
+&aic {
+	affinities {
+		e-core-pmu-affinity {
+			apple,fiq-index = <AIC_CPU_PMU_E>;
+			cpus = <&cpu_e00 &cpu_e01 &cpu_e02 &cpu_e03
+				&cpu_e10 &cpu_e11 &cpu_e12 &cpu_e13>;
+		};
+
+		p-core-pmu-affinity {
+			apple,fiq-index = <AIC_CPU_PMU_P>;
+			cpus = <&cpu_p00 &cpu_p01 &cpu_p02 &cpu_p03
+				&cpu_p10 &cpu_p11 &cpu_p12 &cpu_p13
+				&cpu_p20 &cpu_p21 &cpu_p22 &cpu_p23
+				&cpu_p30 &cpu_p31 &cpu_p32 &cpu_p33>;
+		};
+	};
+};
+
+&ps_gfx {
+	// On t6022, the die0 GPU power domain needs both AFR power domains
+	power-domains = <&ps_afr>, <&ps_afr_die1>;
+};
+
+&gpu {
+	compatible = "apple,agx-t6022", "apple,agx-g14x";
+
+	apple,avg-power-filter-tc-ms = <302>;
+	apple,avg-power-ki-only = <1.0125>;
+	apple,avg-power-kp = <0.15>;
+	apple,fast-die0-integral-gain = <9.6>;
+	apple,fast-die0-proportional-gain = <24.0>;
+	apple,ppm-ki = <11.0>;
+	apple,ppm-kp = <0.15>;
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi
index 80ba81c47a3424..79a2afc1b39268 100644
--- a/arch/arm64/boot/dts/apple/t602x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi
@@ -564,4 +564,25 @@
 		#clock-cells = <0>;
 		clock-output-names = "nco_ref";
 	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		uat_handoff: uat-handoff {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+
+		uat_pagetables: uat-pagetables {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+
+		uat_ttbs: uat-ttbs {
+			reg = <0 0 0 0>;
+			no-map;
+		};
+	};
 };
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 6bccfb60e50b3b..be873359b11677 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -566,14 +566,6 @@
 		#sound-dai-cells = <1>;
 	};
 
-	pmgr_gfx: power-management@404e80000 {
-		compatible = "apple,t6021-pmgr", "apple,pmgr", "syscon", "simple-mfd";
-		#address-cells = <1>;
-		#size-cells = <1>;
-
-		reg = <0x4 0x4e80000 0 0x4000>;
-	};
-
 	gpu: gpu@406400000 {
 		compatible = "apple,agx-g14x";
 		reg = <0x4 0x6400000 0 0x40000>,
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index 203316df0d06f0..0ac6c258884187 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -127,6 +127,14 @@
 		#interrupt-cells = <2>;
 	};
 
+	DIE_NODE(pmgr_gfx): power-management@404e80000 {
+		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
+		#address-cells = <1>;
+		#size-cells = <1>;
+
+		reg = <0x4 0x4e80000 0 0x4000>;
+	};
+
 	DIE_NODE(dwc3_0_dart_0): iommu@702f00000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0x7 0x02f00000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index 33641648f2ae02..ea4372739e4fff 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -5,7 +5,7 @@
  * Copyright The Asahi Linux Contributors
  */
 
-&pmgr {
+&DIE_NODE(pmgr) {
 	DIE_NODE(ps_afi): power-controller@100 {
 		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x100 4>;
@@ -627,7 +627,7 @@
 	};
 };
 
-&pmgr_south {
+&DIE_NODE(pmgr_south) {
 	DIE_NODE(ps_amcc4): power-controller@100 {
 		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x100 4>;
@@ -991,7 +991,7 @@
 	};
 };
 
-&pmgr_east {
+&DIE_NODE(pmgr_east) {
 	DIE_NODE(ps_clvr_spmi0): power-controller@100 {
 		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x100 4>;
@@ -1964,7 +1964,7 @@
 	};
 };
 
-&pmgr_mini {
+&DIE_NODE(pmgr_mini) {
 	DIE_NODE(ps_debug): power-controller@58 {
 		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x58 4>;
@@ -2233,13 +2233,14 @@
 #endif
 };
 
-&pmgr_gfx {
+&DIE_NODE(pmgr_gfx) {
 	DIE_NODE(ps_gpx): power-controller@0 {
 		compatible = "apple,t6020-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x0 4>;
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(gpx);
+		apple,min-state = <4>;
 		apple,always-on;
 	};
 
@@ -2250,6 +2251,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(afr);
 		/* Apple Fabric, media stuff: this can power down */
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_gfx): power-controller@108 {

From 853d15a060540fa3a64c41f9349b668a7c51e131 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 7 Aug 2023 18:09:41 +0900
Subject: [PATCH 0082/1027] arm64: dts: apple: Add j180d (Mac Pro 2023) device
 tree

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts | 554 ++++++++++++++++++++++
 1 file changed, 554 insertions(+)
 create mode 100644 arch/arm64/boot/dts/apple/t6022-j180d.dts

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
new file mode 100644
index 00000000000000..62882f6bfd5eb5
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Pro (M2 Ultra, 2023)
+ *
+ * target-type: J180d
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6022.dtsi"
+
+/ {
+	compatible = "apple,j180d", "apple,t6022", "apple,arm-platform";
+	model = "Apple Mac Pro (M2 Ultra, 2023)";
+	aliases {
+		atcphy0 = &atcphy0;
+		atcphy1 = &atcphy1;
+		atcphy2 = &atcphy2;
+		atcphy3 = &atcphy3;
+		atcphy4 = &atcphy0_die1;
+		atcphy5 = &atcphy1_die1;
+		atcphy6 = &atcphy2_die1;
+		atcphy7 = &atcphy3_die1;
+		//bluetooth0 = &bluetooth0;
+		//ethernet0 = &ethernet0;
+		//ethernet1 = &ethernet1;
+		serial0 = &serial0;
+		//wifi0 = &wifi0;
+	};
+
+	chosen {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		stdout-path = "serial0";
+
+		framebuffer0: framebuffer@0 {
+			compatible = "apple,simple-framebuffer", "simple-framebuffer";
+			reg = <0 0 0 0>; /* To be filled by loader */
+			/* Format properties will be added by loader */
+			status = "disabled";
+			power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>;
+		};
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+		/* To be filled by loader */
+	};
+
+	memory@10000000000 {
+		device_type = "memory";
+		reg = <0x100 0 0x2 0>; /* To be filled by loader */
+	};
+};
+
+&serial0 {
+	status = "okay";
+};
+
+/* USB Type C Rear */
+&i2c0 {
+	hpm2: usb-pd@3b {
+		compatible = "apple,cd321x";
+		reg = <0x3b>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec2: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 1";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec2_con_hs: endpoint {
+						remote-endpoint = <&typec2_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec2_con_ss: endpoint {
+						remote-endpoint = <&typec2_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm3: usb-pd@3c {
+		compatible = "apple,cd321x";
+		reg = <0x3c>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec3: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 2";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec3_con_hs: endpoint {
+						remote-endpoint = <&typec3_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec3_con_ss: endpoint {
+						remote-endpoint = <&typec3_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm4: usb-pd@39 {
+		compatible = "apple,cd321x";
+		reg = <0x39>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec4: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 3";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec4_con_hs: endpoint {
+						remote-endpoint = <&typec4_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec4_con_ss: endpoint {
+						remote-endpoint = <&typec4_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm5: usb-pd@3a {
+		compatible = "apple,cd321x";
+		reg = <0x3a>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec5: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 4";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec5_con_hs: endpoint {
+						remote-endpoint = <&typec5_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec5_con_ss: endpoint {
+						remote-endpoint = <&typec5_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm6: usb-pd@3d {
+		compatible = "apple,cd321x";
+		reg = <0x3d>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec6: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 5";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec6_con_hs: endpoint {
+						remote-endpoint = <&typec6_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec6_con_ss: endpoint {
+						remote-endpoint = <&typec6_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm7: usb-pd@3e {
+		compatible = "apple,cd321x";
+		reg = <0x3e>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec7: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Back 6";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec7_con_hs: endpoint {
+						remote-endpoint = <&typec7_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec7_con_ss: endpoint {
+						remote-endpoint = <&typec7_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB Type C Front */
+&i2c3 {
+	status = "okay";
+
+	hpm0: usb-pd@38 {
+		compatible = "apple,cd321x";
+		reg = <0x38>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <60 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec0: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Top Right";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec0_con_hs: endpoint {
+						remote-endpoint = <&typec0_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec0_con_ss: endpoint {
+						remote-endpoint = <&typec0_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	hpm1: usb-pd@3f {
+		compatible = "apple,cd321x";
+		reg = <0x3f>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <60 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec1: connector {
+			compatible = "usb-c-connector";
+			label = "USB-C Top Left";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec1_con_hs: endpoint {
+						remote-endpoint = <&typec1_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec1_con_ss: endpoint {
+						remote-endpoint = <&typec1_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers */
+&dwc3_0 {
+	port {
+		typec0_usb_hs: endpoint {
+			remote-endpoint = <&typec0_con_hs>;
+		};
+	};
+};
+
+&dwc3_1 {
+	port {
+		typec1_usb_hs: endpoint {
+			remote-endpoint = <&typec1_con_hs>;
+		};
+	};
+};
+
+&dwc3_2 {
+	port {
+		typec2_usb_hs: endpoint {
+			remote-endpoint = <&typec2_con_hs>;
+		};
+	};
+};
+
+&dwc3_3 {
+	port {
+		typec3_usb_hs: endpoint {
+			remote-endpoint = <&typec3_con_hs>;
+		};
+	};
+};
+
+&dwc3_0_die1 {
+	port {
+		typec4_usb_hs: endpoint {
+			remote-endpoint = <&typec4_con_hs>;
+		};
+	};
+};
+
+&dwc3_1_die1 {
+	port {
+		typec5_usb_hs: endpoint {
+			remote-endpoint = <&typec5_con_hs>;
+		};
+	};
+};
+
+&dwc3_2_die1 {
+	port {
+		typec6_usb_hs: endpoint {
+			remote-endpoint = <&typec6_con_hs>;
+		};
+	};
+};
+
+&dwc3_3_die1 {
+	port {
+		typec7_usb_hs: endpoint {
+			remote-endpoint = <&typec7_con_hs>;
+		};
+	};
+};
+
+/* Type-C PHYs */
+&atcphy0 {
+	port {
+		typec0_usb_ss: endpoint {
+			remote-endpoint = <&typec0_con_ss>;
+		};
+	};
+};
+
+&atcphy1 {
+	port {
+		typec1_usb_ss: endpoint {
+			remote-endpoint = <&typec1_con_ss>;
+		};
+	};
+};
+
+&atcphy2 {
+	port {
+		typec2_usb_ss: endpoint {
+			remote-endpoint = <&typec2_con_ss>;
+		};
+	};
+};
+
+&atcphy3 {
+	port {
+		typec3_usb_ss: endpoint {
+			remote-endpoint = <&typec3_con_ss>;
+		};
+	};
+};
+
+&atcphy0_die1 {
+	port {
+		typec4_usb_ss: endpoint {
+			remote-endpoint = <&typec4_con_ss>;
+		};
+	};
+};
+
+&atcphy1_die1 {
+	port {
+		typec5_usb_ss: endpoint {
+			remote-endpoint = <&typec5_con_ss>;
+		};
+	};
+};
+
+&atcphy2_die1 {
+	port {
+		typec6_usb_ss: endpoint {
+			remote-endpoint = <&typec6_con_ss>;
+		};
+	};
+};
+
+&atcphy3_die1 {
+	port {
+		typec7_usb_ss: endpoint {
+			remote-endpoint = <&typec7_con_ss>;
+		};
+	};
+};
+
+/* Audio */
+&i2c1 {
+	status = "okay";
+
+	speaker_tweeter: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Tweeter";
+		interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+	};
+
+	speaker_woofer: codec@39 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x39>;
+		shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Woofer";
+		interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+	};
+};
+
+&i2c2 {
+	status = "okay";
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+&nco_clkref {
+	clock-frequency = <1068000000>;
+};
+
+/ {
+	sound: sound {
+		compatible = "apple,j180-macaudio", "apple,macaudio";
+		model = "Mac Pro J180";
+
+		dai-link@0 {
+			link-name = "Speakers";
+			/*
+			* DANGER ZONE: You can blow your speakers!
+			*
+			* The drivers are not ready, and unless you are careful
+			* to attenuate the audio stream, you run the risk of
+			* blowing your speakers.
+			*/
+			status = "disabled";
+			cpu {
+				sound-dai = <&mca 0>;
+			};
+			codec {
+				sound-dai = <&speaker_woofer>, <&speaker_tweeter>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
+
+&pcie0 {
+	status = "disabled";
+};
+
+&pcie0_dart_0 {
+	status = "disabled";
+};
+
+/* delete unused always-on power-domains on die 1 */
+/delete-node/ &ps_disp0_cpu0_die1;
+/delete-node/ &ps_disp0_fe_die1;
+
+&gpu {
+	apple,idleoff-standby-timer = <3000>;
+	apple,perf-base-pstate = <5>;
+	apple,perf-boost-ce-step = <100>;
+	apple,perf-boost-min-util = <75>;
+	apple,perf-tgt-utilization = <70>;
+};

From 6d3115b96263bbbd89273afc3517d5ed5699cc39 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 7 Aug 2023 19:53:50 +0900
Subject: [PATCH 0083/1027] arm64: dts: apple: t6022: Add APCIE-GE nodes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts     | 16 +++++
 arch/arm64/boot/dts/apple/t6022.dtsi          | 12 +++-
 arch/arm64/boot/dts/apple/t602x-die0.dtsi     |  2 -
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi     | 64 +++++++++++++++++++
 .../arm64/boot/dts/apple/t602x-gpio-pins.dtsi |  4 ++
 5 files changed, 94 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index 62882f6bfd5eb5..7775cbf8698d06 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -541,6 +541,22 @@
 	status = "disabled";
 };
 
+&pcie_ge {
+	status = "ok";
+};
+
+&pcie_ge_dart {
+	status = "ok";
+};
+
+&pcie_ge_die1 {
+	status = "ok";
+};
+
+&pcie_ge_dart_die1 {
+	status = "ok";
+};
+
 /* delete unused always-on power-domains on die 1 */
 /delete-node/ &ps_disp0_cpu0_die1;
 /delete-node/ &ps_disp0_fe_die1;
diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi
index b7c19be04c72a3..ebf8e5bf53e86c 100644
--- a/arch/arm64/boot/dts/apple/t6022.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022.dtsi
@@ -275,7 +275,8 @@
 		#size-cells = <2>;
 		ranges = <0x2 0x0 0x2 0x0 0x4 0x0>,
 			 <0x5 0x80000000 0x5 0x80000000 0x1 0x80000000>,
-			 <0x7 0x0 0x7 0x0 0xf 0x80000000>;
+			 <0x7 0x0 0x7 0x0 0xf 0x80000000>,
+			 <0x16 0x80000000 0x16 0x80000000 0x5 0x80000000>;
 		nonposted-mmio;
 		/* Required to get >32-bit DMA via DARTs */
 		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
@@ -288,7 +289,8 @@
 		#address-cells = <2>;
 		#size-cells = <2>;
 		ranges = <0x2 0x0 0x22 0x0 0x4 0x0>,
-			 <0x7 0x0 0x27 0x0 0xf 0x80000000>;
+			 <0x7 0x0 0x27 0x0 0xf 0x80000000>,
+			 <0x16 0x80000000 0x36 0x80000000 0x5 0x80000000>;
 		nonposted-mmio;
 		/* Required to get >32-bit DMA via DARTs */
 		dma-ranges = <0 0 0 0 0xffffffff 0xffffc000>;
@@ -358,3 +360,9 @@
 	apple,ppm-ki = <11.0>;
 	apple,ppm-kp = <0.15>;
 };
+
+&pinctrl_ap_die1 {
+	pcie_ge_pins_die1: pcie-ge1-pins {
+		pinmux = <APPLE_PINMUX(8, 1)>;
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index be873359b11677..332df752013e10 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -818,5 +818,3 @@
 		power-domains = <&ps_apcie_gp_sys>;
 		status = "disabled";
 	};
-
-
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index 0ac6c258884187..da891047c5db7a 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -350,3 +350,67 @@
 		svid = <0xff01>, <0x8087>;
 		power-domains = <&DIE_NODE(ps_atc3_usb)>;
 	};
+
+	DIE_NODE(pcie_ge): pcie@1680000000 {
+		compatible = "apple,t6020-pcie-ge", "apple,t6020-pcie";
+		device_type = "pci";
+
+		reg = <0x16 0x80000000 0x0 0x1000000>,	/* config */
+			<0x16 0x91000000 0x0 0x4000>,	/* rc */
+			<0x16 0x94008000 0x0 0x4000>,	/* port0 */
+			<0x16 0x9e01c000 0x0 0x4000>,	/* phy0 */
+			<0x16 0x9401c000 0x0 0x1000>;	/* ltssm0 */
+		reg-names = "config", "rc", "port0", "phy0", "ltssm0";
+
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1356 IRQ_TYPE_LEVEL_HIGH>;
+
+		msi-controller;
+		msi-parent = <&pcie0>;
+		msi-ranges = <&aic AIC_IRQ DIE_NO 1672 IRQ_TYPE_EDGE_RISING 32>;
+
+
+		iommu-map = <0x100 &pcie_ge_dart 1 1>;
+		iommu-map-mask = <0xff00>;
+
+		bus-range = <0 1>;
+		#address-cells = <3>;
+		#size-cells = <2>;
+		ranges = <0x43000000 0x18 0x00000000 0x18 0x00000000 0x4 0x00000000>,
+				<0x02000000 0x0 0x80000000 0x17 0x80000000 0x0 0x80000000>;
+
+		power-domains = <&ps_apcie_ge_sys>;
+		pinctrl-0 = <&DIE_NODE(pcie_ge_pins)>;
+		pinctrl-names = "default";
+
+		dma-coherent;
+
+		status = "disabled";
+
+		DIE_NODE(port_ge00): pci@0,0 {
+			device_type = "pci";
+			reg = <0x0 0x0 0x0 0x0 0x0>;
+			reset-gpios = <&DIE_NODE(pinctrl_ap) 9 GPIO_ACTIVE_LOW>;
+
+			#address-cells = <3>;
+			#size-cells = <2>;
+			ranges;
+
+			interrupt-controller;
+			#interrupt-cells = <1>;
+
+			interrupt-map-mask = <0 0 0 7>;
+			interrupt-map = <0 0 0 1 &port00 0 0 0 0>;
+		};
+	};
+
+	DIE_NODE(pcie_ge_dart): iommu@1694000000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x16 0x94000000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1357 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_apcie_ge_sys>;
+		status = "disabled";
+	};
+
diff --git a/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
index acb133d1723d03..9b24832ba26abe 100644
--- a/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-gpio-pins.dtsi
@@ -78,4 +78,8 @@
 				<APPLE_PINMUX(2, 1)>,
 				<APPLE_PINMUX(3, 1)>;
 	};
+
+	pcie_ge_pins: pcie-ge-pins {
+		pinmux = <APPLE_PINMUX(8, 1)>;
+	};
 };

From 9a4abdf8000792ff6177f1bc5e8a4a893d3410b2 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Tue, 18 Apr 2023 23:06:49 +0300
Subject: [PATCH 0084/1027] arm64: dts: apple: t8103: Add touchbar screen
 bindings

Adds device tree entries for the touchbar screen

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts |  9 ++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi     | 26 ++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index a845d92ee10c25..3351cf4228b052 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -138,6 +138,15 @@
 &fpwm1 {
 	status = "okay";
 };
+
+&display_dfr {
+	status = "okay";
+	dfr_panel: panel@0 {
+		compatible = "apple,summit";
+		reg = <0>;
+	};
+};
+
 / {
 	sound {
 		compatible = "apple,j293-macaudio", "apple,macaudio";
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 130e240f59a08c..29bc5c61150124 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -513,6 +513,32 @@
 			#performance-domain-cells = <0>;
 		};
 
+		display_dfr: display-pipe@228200000 {
+			compatible = "apple,t8103-display-pipe", "apple,h7-display-pipe";
+			reg-names = "be", "fe", "mipi";
+			reg = <0x2 0x28200000 0x0 0xc000>,
+				<0x2 0x28400000 0x0 0x4000>,
+				<0x2 0x28600000 0x0 0x100000>;
+			power-domains = <&ps_dispdfr_fe>, <&ps_dispdfr_be>, <&ps_mipi_dsi>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 502 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 506 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "be", "fe";
+			status = "disabled";
+			iommus = <&displaydfr_dart 0>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+		};
+
+		displaydfr_dart: iommu@228304000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x28304000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 504 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_dispdfr_fe>;
+		};
+
 		disp0_dart: iommu@231304000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x2 0x31304000 0x0 0x4000>;

From f526517becab4b32b39ba19c50dbbe351ef25b0b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 15 Apr 2023 16:42:41 +0200
Subject: [PATCH 0085/1027] arm64: dts: apple: Add touchbar display nodes for
 t8112-j493

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j493.dts | 15 ++++++++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi     | 25 ++++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index d34acd0ee2f203..0234102dbe8462 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -45,6 +45,21 @@
 	};
 };
 
+&display_dfr {
+	status = "okay";
+	#address-cells = <1>;
+	#size-cells = <0>;
+
+	dfr_panel: panel@0 {
+		compatible = "apple,summit";
+		reg = <0>;
+	};
+};
+
+&displaydfr_dart {
+	status = "okay";
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index cabf3a23104afc..4f92fb33b77110 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -535,6 +535,31 @@
 			#performance-domain-cells = <0>;
 		};
 
+		display_dfr: display-pipe@228200000 {
+			compatible = "apple,t8112-display-pipe", "apple,h7-display-pipe";
+			reg-names = "be", "fe", "mipi";
+			reg = <0x2 0x28200000 0x0 0xc000>,
+				<0x2 0x28400000 0x0 0x4000>,
+				<0x2 0x28600000 0x0 0x100000>;
+			power-domains = <&ps_dispdfr_fe>, <&ps_dispdfr_be>, <&ps_mipi_dsi>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 614 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 618 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "be", "fe";
+			status = "disabled";
+			iommus = <&displaydfr_dart 0>;
+		};
+
+		displaydfr_dart: iommu@228304000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x28304000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 616 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_dispdfr_fe>;
+			status = "disabled";
+		};
+
 		disp0_dart: iommu@231304000 {
 			compatible = "apple,t8112-dart", "apple,t8110-dart";
 			reg = <0x2 0x31304000 0x0 0x4000>;

From b0b9cc53468ed4492886eb1fe99c879711cdf895 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 21 Jan 2023 19:47:32 +0300
Subject: [PATCH 0086/1027] arm64: dts: apple: t8103: Add touchbar bindings

Adds device tree entries for the touchbar digitizer

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 24 ++++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi     | 12 ++++++++++++
 2 files changed, 36 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 3351cf4228b052..c519a8975d95fa 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -28,6 +28,10 @@
 			default-state = "keep";
 		};
 	};
+
+	aliases {
+		touchbar0 = &touchbar0;
+	};
 };
 
 &dcp {
@@ -59,6 +63,26 @@
 	label = "USB-C Left-front";
 };
 
+&spi0 {
+	status = "okay";
+
+	touchbar0: touchbar@0 {
+		compatible = "apple,j293-touchbar",
+			"apple,z2-touchbar", "apple,z2-multitouch";
+		reg = <0>;
+		spi-max-frequency = <11500000>;
+		spi-cs-setup-delay-ns = <2000>;
+		spi-cs-hold-delay-ns = <2000>;
+		reset-gpios = <&pinctrl_ap 139 GPIO_ACTIVE_LOW>;
+		cs-gpios = <&pinctrl_ap 109 0>;
+		interrupts-extended = <&pinctrl_ap 194 IRQ_TYPE_EDGE_FALLING>;
+		firmware-name = "apple/dfrmtfw-j293.bin";
+		touchscreen-size-x = <23045>;
+		touchscreen-size-y = <640>;
+		label = "MacBookPro17,1 Touch Bar";
+	};
+};
+
 &spi3 {
 	status = "okay";
 
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 29bc5c61150124..762aaa2661dd5a 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -693,6 +693,18 @@
 			status = "disabled";
 		};
 
+		spi0: spi@235100000 {
+			compatible = "apple,t8103-spi", "apple,spi";
+			reg = <0x2 0x35100000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 614 IRQ_TYPE_LEVEL_HIGH>;
+			clocks = <&clk_200m>;
+			power-domains = <&ps_spi0>;
+			#address-cells = <1>;
+			#size-cells = <0>;
+			status = "disabled"; /* only used in J293 */
+		};
+
 		spi1: spi@235104000 {
 			compatible = "apple,t8103-spi", "apple,spi";
 			reg = <0x2 0x35104000 0x0 0x4000>;

From 4755c30c54ccca0c9be101cc970dc6c7d315a0b7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 21 Jan 2023 19:47:32 +0300
Subject: [PATCH 0087/1027] arm64: dts: apple: t8112: Add touchbar digitizer
 node

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j493.dts | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 0234102dbe8462..8b629e564c5019 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -21,6 +21,7 @@
 		bluetooth0 = &bluetooth0;
 		wifi0 = &wifi0;
 		keyboard = &keyboard;
+		touchbar0 = &touchbar0;
 	};
 
 	led-controller {
@@ -190,6 +191,25 @@
 	};
 };
 
+&spi3 {
+	status = "okay";
+
+	touchbar0: touchbar@0 {
+		compatible = "apple,j493-touchbar", "apple,z2-touchbar", "apple,z2-multitouch";
+		reg = <0>;
+		label = "Mac14,7 Touch Bar";
+		spi-max-frequency = <8000000>;
+		spi-cs-setup-delay-ns = <2000>;
+		spi-cs-hold-delay-ns = <2000>;
+
+		reset-gpios = <&pinctrl_ap 170 GPIO_ACTIVE_LOW>;
+		interrupts-extended = <&pinctrl_ap 174 IRQ_TYPE_EDGE_FALLING>;
+		firmware-name = "apple/dfrmtfw-j493.bin";
+		touchscreen-size-x = <23045>;
+		touchscreen-size-y = <640>;
+       };
+};
+
 &mtp {
 	status = "okay";
 };

From dc0a5e6ddc287c87ed1c5d56557ff55be98d042b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 29 Jul 2023 16:14:11 +0200
Subject: [PATCH 0088/1027] arm64: dts: apple: Add devicetree for Macbook Air
 (15-inch, M2, 2023)

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/Makefile       |   1 +
 arch/arm64/boot/dts/apple/t8112-j415.dts | 239 +++++++++++++++++++++++
 2 files changed, 240 insertions(+)
 create mode 100644 arch/arm64/boot/dts/apple/t8112-j415.dts

diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile
index a55d7097b7fbbb..02fab071d0acd4 100644
--- a/arch/arm64/boot/dts/apple/Makefile
+++ b/arch/arm64/boot/dts/apple/Makefile
@@ -11,6 +11,7 @@ dtb-$(CONFIG_ARCH_APPLE) += t6001-j316c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6001-j375c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6002-j375d.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j413.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t8112-j415.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j473.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t8112-j493.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6020-j414s.dtb
diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
new file mode 100644
index 00000000000000..c502bed5f96224
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -0,0 +1,239 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Apple MacBook Air (15-inchl, M2, 2023)
+ *
+ * target-type: J415
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t8112.dtsi"
+#include "t8112-jxxx.dtsi"
+#include <dt-bindings/leds/common.h>
+
+/ {
+	compatible = "apple,j415", "apple,t8112", "apple,arm-platform";
+	model = "Apple MacBook Air (15-inch, M2, 2023)";
+
+	aliases {
+		bluetooth0 = &bluetooth0;
+		wifi0 = &wifi0;
+		keyboard = &keyboard;
+	};
+
+	led-controller {
+		compatible = "pwm-leds";
+		led-0 {
+			pwms = <&fpwm1 0 40000>;
+			label = "kbd_backlight";
+			function = LED_FUNCTION_KBD_BACKLIGHT;
+			color = <LED_COLOR_ID_WHITE>;
+			max-brightness = <255>;
+			default-state = "keep";
+		};
+	};
+};
+
+&dcp {
+	panel: panel {
+		compatible = "apple,panel-j415", "apple,panel";
+		width-mm = <327>;
+		height-mm = <211>;
+		adj-height-mm = <204>;
+		apple,max-brightness = <500>;
+	};
+};
+
+/*
+ * Force the bus number assignments so that we can declare some of the
+ * on-board devices and properties that are populated by the bootloader
+ * (such as MAC addresses).
+ */
+&port00 {
+	bus-range = <1 1>;
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
+	wifi0: wifi@0,0 {
+		compatible = "pci14e4,4433";
+		reg = <0x10000 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-mac-address = [00 10 18 00 00 10];
+		apple,antenna-sku = "XX";
+		brcm,board-type = "apple,snake";
+	};
+
+	bluetooth0: bluetooth@0,1 {
+		compatible = "pci14e4,5f71";
+		reg = <0x10100 0x0 0x0 0x0 0x0>;
+		/* To be filled by the loader */
+		local-bd-address = [00 00 00 00 00 00];
+		brcm,board-type = "apple,snake";
+	};
+};
+
+/*
+ * Provide labels for the USB type C ports.
+ */
+
+&typec0 {
+	label = "USB-C Left-back";
+};
+
+&typec1 {
+	label = "USB-C Left-front";
+};
+
+&i2c0 {
+	/* MagSafe port */
+	hpm5: usb-pd@3a {
+		compatible = "apple,cd321x";
+		reg = <0x3a>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <8 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+	};
+};
+
+&i2c1 {
+	speaker_left_woof1: codec@38 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x38>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Woofer 1";
+	};
+
+	speaker_left_tweet: codec@39 {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x39>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Tweeter";
+	};
+
+	speaker_left_woof2: codec@3a {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3a>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Left Woofer 2";
+	};
+};
+
+&i2c3 {
+	speaker_right_woof1: codec@3b {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3b>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Woofer 1";
+	};
+
+	speaker_right_tweet: codec@3c {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3c>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Tweeter";
+	};
+
+	speaker_right_woof2: codec@3d {
+		compatible = "ti,sn012776", "ti,tas2764";
+		reg = <0x3d>;
+		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		sound-name-prefix = "Right Woofer 2";
+	};
+
+	jack_codec: codec@4b {
+		compatible = "cirrus,cs42l84";
+		reg = <0x4b>;
+		reset-gpios = <&pinctrl_nub 12 GPIO_ACTIVE_HIGH>;
+		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 149 IRQ_TYPE_LEVEL_LOW>;
+		sound-name-prefix = "Jack";
+	};
+};
+
+&fpwm1 {
+	status = "okay";
+};
+
+/ {
+	sound {
+		compatible = "apple,j415-macaudio", "apple,macaudio";
+		model = "MacBook Air J415";
+
+		dai-link@0 {
+			link-name = "Speakers";
+
+			/*
+			 * DANGER ZONE: You can blow your speakers!
+			 *
+			 * The drivers are not ready, and unless you are careful
+			 * to attenuate the audio stream, you run the risk of
+			 * blowing your speakers.
+			 */
+			status = "disabled";
+
+			cpu {
+				sound-dai = <&mca 0>, <&mca 1>;
+			};
+			codec {
+				sound-dai = <&speaker_left_woof1>,
+					    <&speaker_left_tweet>,
+					    <&speaker_left_woof2>,
+					    <&speaker_right_woof1>,
+					    <&speaker_right_tweet>,
+					    <&speaker_right_woof2>;
+			};
+		};
+
+		dai-link@1 {
+			link-name = "Headphone Jack";
+
+			cpu {
+				sound-dai = <&mca 2>;
+			};
+			codec {
+				sound-dai = <&jack_codec>;
+			};
+		};
+	};
+};
+
+&mtp {
+	status = "okay";
+};
+&mtp_mbox {
+	status = "okay";
+};
+&mtp_dart {
+	status = "okay";
+};
+&mtp_dockchannel {
+	status = "okay";
+};
+&mtp_hid {
+	apple,afe-reset-gpios = <&smc_gpio 8 GPIO_ACTIVE_LOW>;
+	apple,stm-reset-gpios = <&smc_gpio 24 GPIO_ACTIVE_LOW>;
+
+	multi-touch {
+		firmware-name = "apple/tpmtfw-j415.bin";
+	};
+
+	keyboard: keyboard {
+		hid-country-code = <0>;
+		apple,keyboard-layout-id = <0>;
+	};
+
+	stm {
+	};
+
+	actuator {
+	};
+
+	tp_accel {
+	};
+};

From 1c0a697ee2d0753ec1a9c0582490bbe6f528f394 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:08:43 +0900
Subject: [PATCH 0089/1027] arm64: dts: apple: t8112-j473: Set GPU base pstate

This should help performance/responsiveness (used on most desktops).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 4705ec980211c4..92efe72b18461b 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -136,3 +136,7 @@
 
 	};
 };
+
+&gpu {
+	apple,perf-base-pstate = <3>;
+};

From 2935dca38175eda11e53955a9e420420c6cea0a5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 4 Sep 2023 20:57:11 +0200
Subject: [PATCH 0090/1027] arm64: dts: apple: Share USB-C port node on t6022
 devices

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts  | 105 ++-----------------
 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 111 +++++++++++++++++++++
 2 files changed, 122 insertions(+), 94 deletions(-)
 create mode 100644 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index 7775cbf8698d06..499e5ad7e8c658 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -10,6 +10,7 @@
 /dts-v1/;
 
 #include "t6022.dtsi"
+#include "t6022-jxxxd.dtsi"
 
 / {
 	compatible = "apple,j180d", "apple,t6022", "apple,arm-platform";
@@ -129,69 +130,9 @@
 		};
 	};
 
-	hpm4: usb-pd@39 {
-		compatible = "apple,cd321x";
-		reg = <0x39>;
-		interrupt-parent = <&pinctrl_ap>;
-		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
-		interrupt-names = "irq";
-
-		typec4: connector {
-			compatible = "usb-c-connector";
-			label = "USB-C Back 3";
-			power-role = "dual";
-			data-role = "dual";
-
-			ports {
-				#address-cells = <1>;
-				#size-cells = <0>;
-				port@0 {
-					reg = <0>;
-					typec4_con_hs: endpoint {
-						remote-endpoint = <&typec4_usb_hs>;
-					};
-				};
-				port@1 {
-					reg = <1>;
-					typec4_con_ss: endpoint {
-						remote-endpoint = <&typec4_usb_ss>;
-					};
-				};
-			};
-		};
-	};
-
-	hpm5: usb-pd@3a {
-		compatible = "apple,cd321x";
-		reg = <0x3a>;
-		interrupt-parent = <&pinctrl_ap>;
-		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
-		interrupt-names = "irq";
+	/* hpm4 included from t6022-jxxxd.dtsi */
 
-		typec5: connector {
-			compatible = "usb-c-connector";
-			label = "USB-C Back 4";
-			power-role = "dual";
-			data-role = "dual";
-
-			ports {
-				#address-cells = <1>;
-				#size-cells = <0>;
-				port@0 {
-					reg = <0>;
-					typec5_con_hs: endpoint {
-						remote-endpoint = <&typec5_usb_hs>;
-					};
-				};
-				port@1 {
-					reg = <1>;
-					typec5_con_ss: endpoint {
-						remote-endpoint = <&typec5_usb_ss>;
-					};
-				};
-			};
-		};
-	};
+	/* hpm5 included from t6022-jxxxd.dtsi */
 
 	hpm6: usb-pd@3d {
 		compatible = "apple,cd321x";
@@ -258,6 +199,14 @@
 	};
 };
 
+&hpm4 {
+	label = "USB-C Back 3";
+};
+
+&hpm5 {
+	label = "USB-C Back 4";
+};
+
 /* USB Type C Front */
 &i2c3 {
 	status = "okay";
@@ -360,22 +309,6 @@
 	};
 };
 
-&dwc3_0_die1 {
-	port {
-		typec4_usb_hs: endpoint {
-			remote-endpoint = <&typec4_con_hs>;
-		};
-	};
-};
-
-&dwc3_1_die1 {
-	port {
-		typec5_usb_hs: endpoint {
-			remote-endpoint = <&typec5_con_hs>;
-		};
-	};
-};
-
 &dwc3_2_die1 {
 	port {
 		typec6_usb_hs: endpoint {
@@ -425,22 +358,6 @@
 	};
 };
 
-&atcphy0_die1 {
-	port {
-		typec4_usb_ss: endpoint {
-			remote-endpoint = <&typec4_con_ss>;
-		};
-	};
-};
-
-&atcphy1_die1 {
-	port {
-		typec5_usb_ss: endpoint {
-			remote-endpoint = <&typec5_con_ss>;
-		};
-	};
-};
-
 &atcphy2_die1 {
 	port {
 		typec6_usb_ss: endpoint {
diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
new file mode 100644
index 00000000000000..f8fbdca4105fb7
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Pro (M2 Ultra, 2023) and Mac Studio (M2 Ultra, 2023)
+ *
+ * This file contains the parts common to J180 and J475 devices with t6022.
+ *
+ * target-type: J180d / J475d
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/* USB Type C */
+&i2c0 {
+	/* front-right */
+	hpm4: usb-pd@39 {
+		compatible = "apple,cd321x";
+		reg = <0x39>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec4: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec4_con_hs: endpoint {
+						remote-endpoint = <&typec4_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec4_con_ss: endpoint {
+						remote-endpoint = <&typec4_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+
+	/* front-left */
+	hpm5: usb-pd@3a {
+		compatible = "apple,cd321x";
+		reg = <0x3a>;
+		interrupt-parent = <&pinctrl_ap>;
+		interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+		interrupt-names = "irq";
+
+		typec5: connector {
+			compatible = "usb-c-connector";
+			power-role = "dual";
+			data-role = "dual";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					typec5_con_hs: endpoint {
+						remote-endpoint = <&typec5_usb_hs>;
+					};
+				};
+				port@1 {
+					reg = <1>;
+					typec5_con_ss: endpoint {
+						remote-endpoint = <&typec5_usb_ss>;
+					};
+				};
+			};
+		};
+	};
+};
+
+/* USB controllers on die 1 */
+&dwc3_0_die1 {
+	port {
+		typec4_usb_hs: endpoint {
+			remote-endpoint = <&typec4_con_hs>;
+		};
+	};
+};
+
+&dwc3_1_die1 {
+	port {
+		typec5_usb_hs: endpoint {
+			remote-endpoint = <&typec5_con_hs>;
+		};
+	};
+};
+
+/* Type-C PHYs */
+&atcphy0_die1 {
+	port {
+		typec4_usb_ss: endpoint {
+			remote-endpoint = <&typec4_con_ss>;
+		};
+	};
+};
+
+&atcphy1_die1 {
+	port {
+		typec5_usb_ss: endpoint {
+			remote-endpoint = <&typec5_con_ss>;
+		};
+	};
+};

From dc31937c32e405cbb4a1f49ea01ad85909e11231 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 4 Sep 2023 21:48:34 +0200
Subject: [PATCH 0091/1027] arm64: dts: apple: t6022: Disable dcp thouroughly

Also disables "display" until it can be supported via dispext*.\

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts  |  4 ----
 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 22 ++++++++++++++++++++++
 2 files changed, 22 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index 499e5ad7e8c658..a2cb2c5b86bc10 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -474,10 +474,6 @@
 	status = "ok";
 };
 
-/* delete unused always-on power-domains on die 1 */
-/delete-node/ &ps_disp0_cpu0_die1;
-/delete-node/ &ps_disp0_fe_die1;
-
 &gpu {
 	apple,idleoff-standby-timer = <3000>;
 	apple,perf-base-pstate = <5>;
diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
index f8fbdca4105fb7..4f552c2530aa7a 100644
--- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -9,6 +9,28 @@
  * Copyright The Asahi Linux Contributors
  */
 
+/* disable unused display node */
+
+&display {
+	status = "disabled";
+	iommus = <>; /* <&dispext0_dart_die1 0>; */
+};
+
+/* delete missing dcp0/disp0 */
+
+/delete-node/ &disp0_dart;
+/delete-node/ &dcp_dart;
+/delete-node/ &dcp_mbox;
+/delete-node/ &dcp;
+
+/* delete unused always-on power-domains */
+/delete-node/ &ps_disp0_cpu0;
+/delete-node/ &ps_disp0_fe;
+
+/delete-node/ &ps_disp0_cpu0_die1;
+/delete-node/ &ps_disp0_fe_die1;
+
+
 /* USB Type C */
 &i2c0 {
 	/* front-right */

From 9d7ec674702ee46558b8b2bf14e5fc572fd5f020 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 23:25:58 +0200
Subject: [PATCH 0092/1027] arm64: dts: apple: t6020-j474s: Disable dcp until
 lpdpphy is supported

This emulates the M2 Ultra Mac Studio and Pro.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index 653af803551c76..557226c04deed9 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -29,6 +29,10 @@
 	};
 };
 
+&dcp {
+	status = "disabled";
+};
+
 &hpm0 {
 	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };

From 91f3fb120e5e830e8bb143769d20944cc54f52a5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 21 Aug 2023 00:50:07 +0200
Subject: [PATCH 0093/1027] arm64: dts: apple: t602x: Add initial Mac Studio
 (2023) device trees

They use the same GPIO pins and interrupts as the Mac Mini (M2 Pro, 2023)
so use a common .dtsi for those definitions.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/Makefile            |  2 +
 arch/arm64/boot/dts/apple/t600x-j375.dtsi     |  2 +
 arch/arm64/boot/dts/apple/t6020-j474s.dts     | 63 +---------------
 arch/arm64/boot/dts/apple/t6021-j475c.dts     | 49 ++++++++++++
 arch/arm64/boot/dts/apple/t6022-j475d.dts     | 73 ++++++++++++++++++
 .../arm64/boot/dts/apple/t602x-j474-j475.dtsi | 74 +++++++++++++++++++
 6 files changed, 201 insertions(+), 62 deletions(-)
 create mode 100644 arch/arm64/boot/dts/apple/t6021-j475c.dts
 create mode 100644 arch/arm64/boot/dts/apple/t6022-j475d.dts
 create mode 100644 arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi

diff --git a/arch/arm64/boot/dts/apple/Makefile b/arch/arm64/boot/dts/apple/Makefile
index 02fab071d0acd4..b974842cb1500b 100644
--- a/arch/arm64/boot/dts/apple/Makefile
+++ b/arch/arm64/boot/dts/apple/Makefile
@@ -19,4 +19,6 @@ dtb-$(CONFIG_ARCH_APPLE) += t6021-j414c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6020-j416s.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6021-j416c.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6020-j474s.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6021-j475c.dtb
+dtb-$(CONFIG_ARCH_APPLE) += t6022-j475d.dtb
 dtb-$(CONFIG_ARCH_APPLE) += t6022-j180d.dtb
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index c31b9798d2617c..a41ca38a476814 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -16,9 +16,11 @@
 		atcphy2 = &atcphy2;
 		atcphy3 = &atcphy3;
 		bluetooth0 = &bluetooth0;
+		#ifndef NO_DCP
 		dcp = &dcp;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
+		#endif
 		ethernet0 = &ethernet0;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index 557226c04deed9..ab0e50bbd49dd0 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -11,42 +11,12 @@
 
 #include "t6020.dtsi"
 
-/*
- * These model is very similar to the previous generation Mac Studio, other than
- * the GPIO indices.
- */
-
 #define NO_PCIE_SDHC
-#define NO_GPU
-#include "t600x-j375.dtsi"
+#include "t602x-j474-j475.dtsi"
 
 / {
 	compatible = "apple,j474s", "apple,t6020", "apple,arm-platform";
 	model = "Apple Mac Mini (M2 Pro, 2023)";
-
-	aliases {
-		ethernet0 = &ethernet0;
-	};
-};
-
-&dcp {
-	status = "disabled";
-};
-
-&hpm0 {
-	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
-};
-
-&hpm1 {
-	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
-};
-
-&hpm2 {
-	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
-};
-
-&hpm3  {
-	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &wifi0 {
@@ -60,10 +30,6 @@
 };
 
 /* PCIe devices */
-&port00 {
-	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
-};
-
 &port01 {
 	/*
 	 * TODO: do not enable port without device. This works around a Linux
@@ -74,33 +40,6 @@
 	status = "okay";
 };
 
-&port02 {
-	/* 10 Gbit Ethernet */
-	bus-range = <3 3>;
-	status = "okay";
-	ethernet0: ethernet@0,0 {
-		reg = <0x30000 0x0 0x0 0x0 0x0>;
-		/* To be filled by the loader */
-		local-mac-address = [00 10 18 00 00 00];
-	};
-};
-
-&port03 {
-	/* USB xHCI */
-	pwren-gpios = <&smc_gpio 19 GPIO_ACTIVE_HIGH>;
-};
-
-
-&speaker {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
-	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
-};
-
-&jack_codec {
-	reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>;
-	interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>;
-};
-
 &sound {
 	compatible = "apple,j474-macaudio", "apple,j473-macaudio", "apple,macaudio";
 	model = "Mac mini J474";
diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts
new file mode 100644
index 00000000000000..591f637c4a6a98
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts
@@ -0,0 +1,49 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Studio (M2 Max, 2023)
+ *
+ * target-type: J475c
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#include "t6021.dtsi"
+#include "t602x-j474-j475.dtsi"
+
+/ {
+	compatible = "apple,j475c", "apple,t6021", "apple,arm-platform";
+	model = "Apple Mac Studio (M2 Max, 2023)";
+};
+
+&wifi0 {
+	compatible = "pci14e4,4434";
+	brcm,board-type = "apple,canary";
+};
+
+&bluetooth0 {
+	compatible = "pci14e4,5f72";
+	brcm,board-type = "apple,canary";
+};
+
+&pinctrl_ap {
+	usb_hub_oe-hog {
+		gpio-hog;
+		gpios = <231 0>;
+		input;
+		line-name = "usb-hub-oe";
+	};
+
+	usb_hub_rst-hog {
+		gpio-hog;
+		gpios = <232 GPIO_ACTIVE_LOW>;
+		output-low;
+		line-name = "usb-hub-rst";
+	};
+};
+
+&sound {
+	compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio";
+	model = "Mac Studio J475";
+};
diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
new file mode 100644
index 00000000000000..43dba036456159
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Studio (M2 Ultra, 2023)
+ *
+ * target-type: J475d
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/dts-v1/;
+
+#define NO_DCP
+
+#include "t6022.dtsi"
+#include "t602x-j474-j475.dtsi"
+#include "t6022-jxxxd.dtsi"
+
+/ {
+	compatible = "apple,j475d", "apple,t6022", "apple,arm-platform";
+	model = "Apple Mac Studio (M2 Ultra, 2023)";
+	aliases {
+		atcphy4 = &atcphy0_die1;
+		atcphy5 = &atcphy1_die1;
+	};
+};
+
+&framebuffer0 {
+	power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>;
+};
+
+&typec4 {
+	label = "USB-C Front Right";
+};
+
+&typec5 {
+	label = "USB-C Front Left";
+};
+
+/* delete unused USB nodes on die 1 */
+
+/delete-node/ &dwc3_2_dart_0_die1;
+/delete-node/ &dwc3_2_dart_1_die1;
+/delete-node/ &dwc3_2_die1;
+/delete-node/ &atcphy2_die1;
+
+/delete-node/ &dwc3_3_dart_0_die1;
+/delete-node/ &dwc3_3_dart_1_die1;
+/delete-node/ &dwc3_3_die1;
+/delete-node/ &atcphy3_die1;
+
+
+/* delete unused always-on power-domains on die 1 */
+
+/delete-node/ &ps_atc2_usb_aon_die1;
+/delete-node/ &ps_atc2_usb_die1;
+
+/delete-node/ &ps_atc3_usb_aon_die1;
+/delete-node/ &ps_atc3_usb_die1;
+
+&wifi0 {
+	compatible = "pci14e4,4434";
+	brcm,board-type = "apple,canary";
+};
+
+&bluetooth0 {
+	compatible = "pci14e4,5f72";
+	brcm,board-type = "apple,canary";
+};
+
+&sound {
+	compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio";
+	model = "Mac Studio J475";
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
new file mode 100644
index 00000000000000..0553e557d8becb
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
@@ -0,0 +1,74 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Mac Mini (M2 Pro, 2023) and Mac Studio (2023)
+ *
+ * This file contains the parts common to J474 and J475 devices with t6020,
+ * t6021 and t6022.
+ *
+ * target-type: J474s / J375c / J375d
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/*
+ * These model is very similar to the previous generation Mac Studio, other than
+ * the GPIO indices.
+ */
+
+#include "t600x-j375.dtsi"
+
+&framebuffer0 {
+	power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>;
+};
+
+/* disable dcp until it is supported */
+&dcp {
+	status = "disabled";
+};
+
+&hpm0 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm1 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm2 {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&hpm3  {
+	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
+};
+
+/* PCIe devices */
+&port00 {
+	pwren-gpios = <&smc_gpio 13 GPIO_ACTIVE_HIGH>;
+};
+
+#ifndef NO_PCIE_SDHC
+&port01 {
+	pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>;
+	status = "okay";
+};
+
+&pcie0_dart_1 {
+	status = "okay";
+};
+#endif
+
+&port03 {
+	/* USB xHCI */
+	pwren-gpios = <&smc_gpio 19 GPIO_ACTIVE_HIGH>;
+};
+
+&speaker {
+	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+};
+
+&jack_codec {
+	reset-gpios = <&pinctrl_nub 8 GPIO_ACTIVE_HIGH>;
+	interrupts-extended = <&pinctrl_ap 59 IRQ_TYPE_LEVEL_LOW>;
+};

From c82baaaead205d0190421ba904276420595127ac Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 6 Sep 2023 12:47:36 +0200
Subject: [PATCH 0094/1027] arm64: dts: apple: t8112-j473: Add dptx-phy
 power-domain

The HDMI output used by framebuffer0 requires the display controller and
external DP phy power-domains to remain active.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 92efe72b18461b..fa26d1d4be7ded 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -23,6 +23,15 @@
 	};
 };
 
+&framebuffer0 {
+	power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>;
+};
+
+/* disable dcp until it is supported */
+&dcp {
+	status = "disabled";
+};
+
 /*
  * Provide labels for the USB type C ports.
  */

From f3a078fe9a82185ce50da4728946e2d46fe409db Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 25 Sep 2023 19:55:58 +0200
Subject: [PATCH 0095/1027] arm64: dts: apple: t6020x: Mark dptx_phy_ps only on
 laptops always-on

The desktops will need to handle this on their own. On laptops it is a
little weird since dcp seems to handle the programming of the phy which
is apparently used for the internal display. It might be possible to
move this to the panel node once dcp is upstream ready. The
chosen.framebuffer node should reference the panel then.

In the meantime keep it always-on on notebooks.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi | 7 +++++++
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi      | 1 -
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index dd1ea2b5dd95a8..280dc15f5a3b6c 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -23,6 +23,13 @@
 	};
 };
 
+/* HACK: keep dptx_phy_ps power-domain always-on
+ *       it is unclear how to sequence with dcp for the integrated display
+ */
+&ps_dptx_phy_ps {
+	apple,always-on;
+};
+
 &hpm0 {
 	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index ea4372739e4fff..47b02b76bb1523 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -1447,7 +1447,6 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(dptx_phy_ps);
-		apple,always-on;
 		power-domains = <&DIE_NODE(ps_sio)>;
 	};
 

From c489784c8040785f50703864e5852a07f2ac70e5 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 3 Sep 2023 16:41:27 +1000
Subject: [PATCH 0096/1027] arm64: dts: apple: t8112: add opp-microwatt props
 to avalanche/blizzard

Enable energy-aware scheduling on devices with the Apple M2
SoC (T8112) by adding experimentally measured opp-microwatt
values to the application core OPP tables.

Values are an approximation calculated by the System Management Controller,
and collected using freqbench.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 4f92fb33b77110..4d3f10fe0e02e2 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -195,36 +195,43 @@
 			opp-hz = /bits/ 64 <600000000>;
 			opp-level = <1>;
 			clock-latency-ns = <7500>;
+			opp-microwatt = <26000>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <912000000>;
 			opp-level = <2>;
 			clock-latency-ns = <20000>;
+			opp-microwatt = <56000>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1284000000>;
 			opp-level = <3>;
 			clock-latency-ns = <22000>;
+			opp-microwatt = <88000>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1752000000>;
 			opp-level = <4>;
 			clock-latency-ns = <30000>;
+			opp-microwatt = <155000>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <2004000000>;
 			opp-level = <5>;
 			clock-latency-ns = <35000>;
+			opp-microwatt = <231000>;
 		};
 		opp06 {
 			opp-hz = /bits/ 64 <2256000000>;
 			opp-level = <6>;
 			clock-latency-ns = <39000>;
+			opp-microwatt = <254000>;
 		};
 		opp07 {
 			opp-hz = /bits/ 64 <2424000000>;
 			opp-level = <7>;
 			clock-latency-ns = <53000>;
+			opp-microwatt = <351000>;
 		};
 	};
 
@@ -236,88 +243,105 @@
 			opp-hz = /bits/ 64 <660000000>;
 			opp-level = <1>;
 			clock-latency-ns = <9000>;
+			opp-microwatt = <133000>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <924000000>;
 			opp-level = <2>;
 			clock-latency-ns = <19000>;
+			opp-microwatt = <212000>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1188000000>;
 			opp-level = <3>;
 			clock-latency-ns = <22000>;
+			opp-microwatt = <261000>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1452000000>;
 			opp-level = <4>;
 			clock-latency-ns = <24000>;
+			opp-microwatt = <345000>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <1704000000>;
 			opp-level = <5>;
 			clock-latency-ns = <26000>;
+			opp-microwatt = <441000>;
 		};
 		opp06 {
 			opp-hz = /bits/ 64 <1968000000>;
 			opp-level = <6>;
 			clock-latency-ns = <28000>;
+			opp-microwatt = <619000>;
 		};
 		opp07 {
 			opp-hz = /bits/ 64 <2208000000>;
 			opp-level = <7>;
 			clock-latency-ns = <30000>;
+			opp-microwatt = <740000>;
 		};
 		opp08 {
 			opp-hz = /bits/ 64 <2400000000>;
 			opp-level = <8>;
 			clock-latency-ns = <33000>;
+			opp-microwatt = <855000>;
 		};
 		opp09 {
 			opp-hz = /bits/ 64 <2568000000>;
 			opp-level = <9>;
 			clock-latency-ns = <34000>;
+			opp-microwatt = <1006000>;
 		};
 		opp10 {
 			opp-hz = /bits/ 64 <2724000000>;
 			opp-level = <10>;
 			clock-latency-ns = <36000>;
+			opp-microwatt = <1217000>;
 		};
 		opp11 {
 			opp-hz = /bits/ 64 <2868000000>;
 			opp-level = <11>;
 			clock-latency-ns = <41000>;
+			opp-microwatt = <1534000>;
 		};
 		opp12 {
 			opp-hz = /bits/ 64 <2988000000>;
 			opp-level = <12>;
 			clock-latency-ns = <42000>;
+			opp-microwatt = <1714000>;
 		};
 		opp13 {
 			opp-hz = /bits/ 64 <3096000000>;
 			opp-level = <13>;
 			clock-latency-ns = <44000>;
+			opp-microwatt = <1877000>;
 		};
 		opp14 {
 			opp-hz = /bits/ 64 <3204000000>;
 			opp-level = <14>;
 			clock-latency-ns = <46000>;
+			opp-microwatt = <2159000>;
 		};
 		opp15 {
 			opp-hz = /bits/ 64 <3324000000>;
 			opp-level = <15>;
 			clock-latency-ns = <62000>;
+			opp-microwatt = <2393000>;
 			turbo-mode;
 		};
 		opp16 {
 			opp-hz = /bits/ 64 <3408000000>;
 			opp-level = <16>;
 			clock-latency-ns = <62000>;
+			opp-microwatt = <2497000>;
 			turbo-mode;
 		};
 		opp17 {
 			opp-hz = /bits/ 64 <3504000000>;
 			opp-level = <17>;
 			clock-latency-ns = <62000>;
+			opp-microwatt = <2648000>;
 			turbo-mode;
 		};
 	};

From 6b50ca28dc8ca3f15d8629bf1c49dec6561fba39 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 19:21:26 +0200
Subject: [PATCH 0097/1027] arm64: dts: apple: t600x-j375.dtsi: Add spi nor
 flash and nvram partition

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-j375.dtsi | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index a41ca38a476814..fcacd7c74af110 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -22,6 +22,7 @@
 		disp0_piodma = &disp0_piodma;
 		#endif
 		ethernet0 = &ethernet0;
+		nvram = &nvram;
 		serial0 = &serial0;
 		wifi0 = &wifi0;
 	};
@@ -398,3 +399,5 @@
 	apple,ppm-kp = <0.355>;
 };
 #endif
+
+#include "spi1-nvram.dtsi"

From 3e93dbf70c42e7f03cbeff92efcd50271f8e6f2b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 19:21:26 +0200
Subject: [PATCH 0098/1027] arm64: dts: apple: t6022-j180.dtsi: Add spi nor
 flash and nvram partition

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index a2cb2c5b86bc10..be93805ee0417a 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -27,6 +27,7 @@
 		//bluetooth0 = &bluetooth0;
 		//ethernet0 = &ethernet0;
 		//ethernet1 = &ethernet1;
+		nvram = &nvram;
 		serial0 = &serial0;
 		//wifi0 = &wifi0;
 	};
@@ -481,3 +482,5 @@
 	apple,perf-boost-min-util = <75>;
 	apple,perf-tgt-utilization = <70>;
 };
+
+#include "spi1-nvram.dtsi"

From 2c19acea35b7648402f9f6ea2e3edf6b46ca5c05 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 12:36:01 +0900
Subject: [PATCH 0099/1027] arm64: dts: apple: t8103: Add nvram alias

This is used by m1n1 to populate the nvram size automatically, since
that turned out to be firmware-dependent.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 2b4136d6f77ee4..09ae9aa4f1e550 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -15,6 +15,7 @@
 		dcp = &dcp;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
+		nvram = &nvram;
 		serial0 = &serial0;
 		serial2 = &serial2;
 		wifi0 = &wifi0;

From 19ea7d36c1dd48757898e368ec16c21a92245784 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 12:36:01 +0900
Subject: [PATCH 0100/1027] arm64: dts: apple: t8112: Add nvram alias

This is used by m1n1 to populate the nvram size automatically, since
that turned out to be firmware-dependent.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index 5fec625bf5c2a6..fb93cedeb24a44 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -16,6 +16,7 @@
 		dcp = &dcp;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
+		nvram = &nvram;
 		serial0 = &serial0;
 		serial2 = &serial2;
 	};

From fda227c946972eb4bd08ec8e5c1475cdcf541a1d Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 31 Aug 2023 19:10:27 +0900
Subject: [PATCH 0101/1027] arm64: dts: apple: t8103: Add ISP nodes

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 117 ++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi      |  55 ++++++++++
 2 files changed, 172 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 5fb8c8601a9dcb..fa989987866837 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -1003,6 +1003,123 @@
 		power-domains = <&ps_disp0_fe>;
 		apple,min-state = <4>;
 	};
+
+	/* There is a dependency tree involved with these PDs,
+	 * but we do not express it here since the ISP driver
+	 * is supposed to sequence them in the right order anyway
+	 * (and we do not know the exact tree structure).
+	 *
+	 * This also works around spurious parent PD activation
+	 * on machines with ISP disabled (desktops).
+	 */
+	ps_isp_set0: power-controller@4000 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set0";
+		apple,force-disable;
+	};
+
+	ps_isp_set1: power-controller@4008 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set1";
+		apple,force-disable;
+		apple,force-reset;
+	};
+
+	ps_isp_set2: power-controller@4010 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set2";
+		apple,force-disable;
+		apple,force-reset;
+	};
+
+	ps_isp_fe: power-controller@4018 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_fe";
+	};
+
+	ps_isp_set4: power-controller@4020 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set4";
+	};
+
+	ps_isp_set5: power-controller@4028 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4028 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set5";
+	};
+
+	ps_isp_set6: power-controller@4030 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4030 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set6";
+	};
+
+	ps_isp_set7: power-controller@4038 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4038 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set7";
+	};
+
+	ps_isp_set8: power-controller@4040 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4040 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set8";
+	};
+
+	ps_isp_set9: power-controller@4048 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4048 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set9";
+	};
+
+	ps_isp_set10: power-controller@4050 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4050 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set10";
+	};
+
+	ps_isp_set11: power-controller@4058 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4058 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set11";
+	};
+
+	ps_isp_set12: power-controller@4060 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4060 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set12";
+	};
 };
 
 &pmgr_mini {
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 762aaa2661dd5a..932b3fca3e1f72 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -608,6 +608,61 @@
 			phandle = <&display>;
 		};
 
+		isp_dart0: iommu@22c0e8000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0e8000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+
+			status = "disabled";
+		};
+
+		isp_dart1: iommu@22c0f4000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0f4000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+
+			status = "disabled";
+		};
+
+		isp_dart2: iommu@22c0fc000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0fc000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+
+			status = "disabled";
+		};
+
+		isp: isp@22a000000 {
+			compatible = "apple,t8103-isp", "apple,isp";
+			iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+			reg-names = "coproc", "mbox", "gpio", "mbox2";
+			reg = <0x2 0x2a000000 0x0 0x2000000>,
+				<0x2 0x2c104000 0x0 0x100>,
+				<0x2 0x2c104170 0x0 0x100>,
+				<0x2 0x2c1043f0 0x0 0x100>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 246 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>, <&ps_isp_set0>,
+				<&ps_isp_set1>, <&ps_isp_set2>, <&ps_isp_fe>,
+				<&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>,
+				<&ps_isp_set7>, <&ps_isp_set8>, <&ps_isp_set9>,
+				<&ps_isp_set10>, <&ps_isp_set11>,
+				<&ps_isp_set12>;
+
+			apple,dart-vm-size = <0x0 0xa0000000>;
+
+			status = "disabled";
+		};
+
 		sio_dart: iommu@235004000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x2 0x35004000 0x0 0x4000>;

From 5141ef1ce4095025c50416061f4a526cb9041b01 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Sat, 2 Sep 2023 01:39:10 +0900
Subject: [PATCH 0102/1027] arm64: dts: apple: t6000: Add ISP nodes

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 49 ++++++++++++++
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 80 +++++++++++++++++++++++
 2 files changed, 129 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 6a43db684ed461..94b832b68b9226 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -505,6 +505,55 @@
 		#mbox-cells = <0>;
 	};
 
+	isp_dart0: iommu@3860e8000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860e8000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
+	isp_dart1: iommu@3860f4000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860f4000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
+	isp_dart2: iommu@3860fc000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860fc000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
+	isp: isp@384000000 {
+		compatible = "apple,t6000-isp", "apple,isp";
+		iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+		reg-names = "coproc", "mbox", "gpio", "mbox2";
+		reg = <0x3 0x84000000 0x0 0x2000000>,
+			<0x3 0x86104000 0x0 0x100>,
+			<0x3 0x86104170 0x0 0x100>,
+			<0x3 0x861043f0 0x0 0x100>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 538 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>, <&ps_isp_set0>,
+			<&ps_isp_set1>, <&ps_isp_fe>, <&ps_isp_set3>,
+			<&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>,
+			<&ps_isp_set7>, <&ps_isp_set8>;
+		apple,dart-vm-size = <0x0 0xa0000000>;
+
+		status = "disabled";
+	};
+
 	pcie0_dart_0: iommu@581008000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x5 0x81008000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index af3baf871b22ee..429ab2969d048e 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1448,6 +1448,86 @@
 		label = DIE_LABEL(venc_me1);
 		power-domains = <&DIE_NODE(ps_venc_me0)>;
 	};
+
+	/* There is a dependency tree involved with these PDs,
+	 * but we do not express it here since the ISP driver
+	 * is supposed to sequence them in the right order anyway
+	 * (and we do not know the exact tree structure).
+	 *
+	 * This also works around spurious parent PD activation
+	 * on machines with ISP disabled (desktops).
+	 */
+	DIE_NODE(ps_isp_set0): power-controller@4000 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set0";
+	};
+
+	DIE_NODE(ps_isp_set1): power-controller@4010 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set1";
+	};
+
+	DIE_NODE(ps_isp_fe): power-controller@4008 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set2";
+	};
+
+	DIE_NODE(ps_isp_set3): power-controller@4028 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4028 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set3";
+	};
+
+	DIE_NODE(ps_isp_set4): power-controller@4020 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set4";
+	};
+
+	DIE_NODE(ps_isp_set5): power-controller@4030 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4030 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set5";
+	};
+
+	DIE_NODE(ps_isp_set6): power-controller@4018 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set6";
+	};
+
+	DIE_NODE(ps_isp_set7): power-controller@4038 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4038 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set7";
+	};
+
+	DIE_NODE(ps_isp_set8): power-controller@4040 {
+		compatible = "apple,t6000-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4040 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set8";
+	};
 };
 
 &DIE_NODE(pmgr_south) {

From 6a10a8d9c35701a3f3bba59b0db27b1f0409f6fa Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Sep 2023 00:46:11 +0900
Subject: [PATCH 0103/1027] arm64: dts: apple: t8112: Add ISP nodes

Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 117 ++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi      |  51 ++++++++++
 2 files changed, 168 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 3828a1333dacae..c82436067c3c56 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -961,6 +961,123 @@
 		apple,always-on;
 	};
 
+	/* There is a dependency tree involved with these PDs,
+	 * but we do not express it here since the ISP driver
+	 * is supposed to sequence them in the right order anyway
+	 * (and we do not know the exact tree structure).
+	 *
+	 * This also works around spurious parent PD activation
+	 * on machines with ISP disabled (desktops).
+	 */
+	ps_isp_set0: power-controller@4000 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4000 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set0";
+		apple,force-disable;
+	};
+
+	ps_isp_set1: power-controller@4008 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4008 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set1";
+		apple,force-disable;
+		apple,force-reset;
+	};
+
+	ps_isp_set2: power-controller@4010 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4010 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set2";
+		apple,force-disable;
+		apple,force-reset;
+	};
+
+	ps_isp_fe: power-controller@4018 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4018 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_fe";
+	};
+
+	ps_isp_set4: power-controller@4020 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4020 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set4";
+	};
+
+	ps_isp_set5: power-controller@4028 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4028 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set5";
+	};
+
+	ps_isp_set6: power-controller@4030 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4030 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set6";
+	};
+
+	ps_isp_set7: power-controller@4038 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4038 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set7";
+	};
+
+	ps_isp_set8: power-controller@4040 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4040 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set8";
+	};
+
+	ps_isp_set9: power-controller@4048 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4048 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set9";
+	};
+
+	ps_isp_set12: power-controller@4050 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4050 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set10";
+	};
+
+	ps_isp_set10: power-controller@4058 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4058 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set11";
+	};
+
+	ps_isp_set11: power-controller@4060 {
+		compatible = "apple,t8103-pmgr-pwrstate", "apple,pmgr-pwrstate";
+		reg = <0x4060 4>;
+		#power-domain-cells = <0>;
+		#reset-cells = <0>;
+		label = "isp_set12";
+	};
+
 	ps_venc_dma: power-controller@8000 {
 		compatible = "apple,t8112-pmgr-pwrstate", "apple,pmgr-pwrstate";
 		reg = <0x8000 4>;
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 4d3f10fe0e02e2..62128ef1f966ad 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -584,6 +584,57 @@
 			status = "disabled";
 		};
 
+		isp_dart0: iommu@22c4a8000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4a8000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			status = "disabled";
+		};
+
+		isp_dart1: iommu@22c4b4000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4b4000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			status = "disabled";
+		};
+
+		isp_dart2: iommu@22c4bc000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4bc000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			status = "disabled";
+		};
+
+		isp: isp@22a000000 {
+			compatible = "apple,t8112-isp", "apple,isp";
+			iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+			reg-names = "coproc", "mbox", "gpio", "mbox2";
+			reg = <0x2 0x2a000000 0x0 0x2000000>,
+				<0x2 0x2c4c4000 0x0 0x100>,
+				<0x2 0x2c4c41b0 0x0 0x100>,
+				<0x2 0x2c4c4430 0x0 0x100>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 269 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>, <&ps_isp_set0>,
+				<&ps_isp_set1>, <&ps_isp_set2>, <&ps_isp_fe>,
+				<&ps_isp_set4>, <&ps_isp_set5>, <&ps_isp_set6>,
+				<&ps_isp_set7>, <&ps_isp_set8>, <&ps_isp_set9>,
+				<&ps_isp_set10>, <&ps_isp_set11>,
+				<&ps_isp_set12>;
+
+			apple,dart-vm-size = <0x0 0xa0000000>;
+			status = "disabled";
+		};
+
 		disp0_dart: iommu@231304000 {
 			compatible = "apple,t8112-dart", "apple,t8110-dart";
 			reg = <0x2 0x31304000 0x0 0x4000>;

From 5eed2954cdb0cebf5183da7f6809654b6f305ca4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 24 Sep 2023 01:01:10 +0900
Subject: [PATCH 0104/1027] arm64: dts: apple: t602x: Add ISP nodes

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/t602x-die0.dtsi | 54 +++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 332df752013e10..63040483d9e2c6 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -261,6 +261,60 @@
 
 	};
 
+	isp_dart0: iommu@3860e8000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x860e8000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 574 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+
+		apple,dma-range = <0x100 0x0 0x1 0x0>;
+		status = "disabled";
+	};
+
+	isp_dart1: iommu@3860f4000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x860f4000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 574 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+
+		apple,dma-range = <0x100 0x0 0x1 0x0>;
+		status = "disabled";
+	};
+
+	isp_dart2: iommu@3860fc000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x860fc000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 574 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_sys>;
+
+		apple,dma-range = <0x100 0x0 0x1 0x0>;
+		status = "disabled";
+	};
+
+	isp: isp@384000000 {
+		compatible = "apple,t6020-isp", "apple,isp";
+		iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+		reg-names = "coproc", "mbox", "gpio", "mbox2";
+		reg = <0x3 0x84000000 0x0 0x2000000>,
+			<0x3 0x86104000 0x0 0x100>,
+			<0x3 0x86104170 0x0 0x100>,
+			<0x3 0x861043f0 0x0 0x100>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 569 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&ps_isp_cpu>, <&ps_isp_fe>,
+			<&ps_dprx>, <&ps_isp_vis>, <&ps_isp_be>,
+			<&ps_isp_clr>, <&ps_isp_raw>;
+		apple,dart-vm-size = <0x0 0xa0000000>;
+
+		status = "disabled";
+	};
+
 	disp0_dart: iommu@389304000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0x3 0x89304000 0x0 0x4000>;

From dd6a2404a0b317d159742f85686397aa128c5c0d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 28 Sep 2023 02:02:43 +0900
Subject: [PATCH 0105/1027] arm64: dts: ISP platform configs

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/boot/dts/apple/isp-common.dtsi     | 43 +++++++++
 arch/arm64/boot/dts/apple/isp-imx248.dtsi     | 22 +++++
 arch/arm64/boot/dts/apple/isp-imx364.dtsi     | 71 ++++++++++++++
 .../arm64/boot/dts/apple/isp-imx558-cfg0.dtsi | 92 +++++++++++++++++++
 arch/arm64/boot/dts/apple/isp-imx558.dtsi     | 50 ++++++++++
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi |  6 ++
 .../arm64/boot/dts/apple/t602x-j414-j416.dtsi |  7 ++
 arch/arm64/boot/dts/apple/t8103-j293.dts      |  6 ++
 arch/arm64/boot/dts/apple/t8103-j313.dts      |  6 ++
 arch/arm64/boot/dts/apple/t8103-j456.dts      |  6 ++
 arch/arm64/boot/dts/apple/t8103-j457.dts      |  6 ++
 arch/arm64/boot/dts/apple/t8112-j413.dts      |  7 ++
 arch/arm64/boot/dts/apple/t8112-j415.dts      |  7 ++
 arch/arm64/boot/dts/apple/t8112-j493.dts      |  6 ++
 14 files changed, 335 insertions(+)
 create mode 100644 arch/arm64/boot/dts/apple/isp-common.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/isp-imx248.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/isp-imx364.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/isp-imx558.dtsi

diff --git a/arch/arm64/boot/dts/apple/isp-common.dtsi b/arch/arm64/boot/dts/apple/isp-common.dtsi
new file mode 100644
index 00000000000000..bf406772469b67
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-common.dtsi
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Common ISP configuration for Apple silicon platforms.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+/ {
+	aliases {
+		isp = &isp;
+	};
+
+	reserved-memory {
+		#address-cells = <2>;
+		#size-cells = <2>;
+		ranges;
+
+		isp_heap: isp-heap {
+			compatible = "apple,asc-mem";
+			/* Filled in by bootloder */
+			reg = <0 0 0 0>;
+			no-map;
+		};
+	};
+};
+
+&isp {
+	memory-region = <&isp_heap>;
+	memory-region-names = "heap";
+	status = "okay";
+};
+
+&isp_dart0 {
+	status = "okay";
+};
+
+&isp_dart1 {
+	status = "okay";
+};
+
+&isp_dart2 {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/apple/isp-imx248.dtsi b/arch/arm64/boot/dts/apple/isp-imx248.dtsi
new file mode 100644
index 00000000000000..acad3ecf0331ef
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx248.dtsi
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * ISP configuration for platforms with IMX248 sensor.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include "isp-common.dtsi"
+
+&isp {
+	apple,temporal-filter = <0>;
+
+	sensor-presets {
+		/* 1280x720 */
+		preset0 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <1280 720>;
+			apple,crop = <8 8 1280 720>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/isp-imx364.dtsi b/arch/arm64/boot/dts/apple/isp-imx364.dtsi
new file mode 100644
index 00000000000000..55484d86523657
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx364.dtsi
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * ISP configuration for platforms with IMX364 sensor.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include "isp-common.dtsi"
+
+&isp {
+	apple,temporal-filter = <0>;
+
+	sensor-presets {
+		/* 1920x1080 */
+		preset0 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <1920 1080>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 1440x720 (4:3) */
+		preset1 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <1440 1080>;
+			apple,crop = <240 0 1440 1080>;
+		};
+		/* 1280x720 (16:9) */
+		preset2 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <1280 720>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 960x720 (4:3) */
+		preset3{
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <960 720>;
+			apple,crop = <240 0 1440 1080>;
+		};
+		/* 960x540 (16:9) */
+		preset4 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <960 540>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 640x480 (4:3) */
+		preset5 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <640 480>;
+			apple,crop = <240 0 1440 1080>;
+		};
+		/* 640x360 (16:9) */
+		preset6 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <640 360>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 320x180 (16:9) */
+		preset7 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <320 180>;
+			apple,crop = <0 0 1920 1080>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi b/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi
new file mode 100644
index 00000000000000..729b97829cbb7e
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi
@@ -0,0 +1,92 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * ISP configuration for platforms with IMX558 sensor in
+ * config #0 mode.
+ *
+ * These platforms enable MLVNR for all configs except
+ * #0, which we don't support. Config #0 is an uncropped
+ * square 1920x1920 sensor, with dark corners.
+ * Therefore, we synthesize common resolutions by using
+ * crop/scale while always choosing config #0.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include "isp-common.dtsi"
+
+&isp {
+	apple,temporal-filter = <0>;
+
+	sensor-presets {
+		/* 1920x1080 */
+		preset0 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1920 1080>;
+			apple,crop = <0 420 1920 1080>;
+		};
+		/* 1080x1920 */
+		preset1 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1080 1920>;
+			apple,crop = <420 0 1080 1920>;
+		};
+		/* 1920x1440 */
+		preset2 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1920 1440>;
+			apple,crop = <0 240 1920 1440>;
+		};
+		/* 1440x1920 */
+		preset3 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1440 1920>;
+			apple,crop = <240 0 1440 1920>;
+		};
+		/* 1280x720 */
+		preset4 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1280 720>;
+			apple,crop = <0 420 1920 1080>;
+		};
+		/* 720x1280 */
+		preset5 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <720 1280>;
+			apple,crop = <420 0 1080 1920>;
+		};
+		/* 1280x960 */
+		preset6 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <1280 960>;
+			apple,crop = <0 240 1920 1440>;
+		};
+		/* 960x1280 */
+		preset7 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <960 1280>;
+			apple,crop = <240 0 1440 1920>;
+		};
+		/* 640x480 */
+		preset8 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <640 480>;
+			apple,crop = <0 240 1920 1440>;
+		};
+		/* 480x640 */
+		preset9 {
+			apple,config-index = <0>;
+			apple,input-size = <1920 1920>;
+			apple,output-size = <480 640>;
+			apple,crop = <240 0 1440 1920>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/isp-imx558.dtsi b/arch/arm64/boot/dts/apple/isp-imx558.dtsi
new file mode 100644
index 00000000000000..a23785b7d5e65a
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx558.dtsi
@@ -0,0 +1,50 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * ISP configuration for platforms with IMX558 sensor.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include "isp-common.dtsi"
+
+&isp {
+	apple,temporal-filter = <0>;
+
+	sensor-presets {
+		/* 1920x1080 */
+		preset0 {
+			apple,config-index = <1>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <1920 1080>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 1080x1920 */
+		preset1 {
+			apple,config-index = <2>;
+			apple,input-size = <1080 1920>;
+			apple,output-size = <1080 1920>;
+			apple,crop = <0 0 1080 1920>;
+		};
+		/* 1760x1328 */
+		preset2 {
+			apple,config-index = <3>;
+			apple,input-size = <1760 1328>;
+			apple,output-size = <1760 1328>;
+			apple,crop = <0 0 1760 1328>;
+		};
+		/* 1328x1760 */
+		preset3 {
+			apple,config-index = <4>;
+			apple,input-size = <1328 1760>;
+			apple,output-size = < 1328 1760>;
+			apple,crop = <0 0 1328 1760>;
+		};
+		/* 1152x1152 */
+		preset4 {
+			apple,config-index = <5>;
+			apple,input-size = <1152 1152>;
+			apple,output-size = <1152 1152>;
+			apple,crop = <0 0 1152 1152>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 9475dbd2f2047e..ad282b663e3ac0 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -439,3 +439,9 @@
 };
 
 #include "spi1-nvram.dtsi"
+
+#include "isp-imx558.dtsi"
+
+&isp {
+	apple,platform-id = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index 280dc15f5a3b6c..9c2b5b7ce2beb3 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -134,3 +134,10 @@
 	tp_accel {
 	};
 };
+
+&isp {
+	apple,platform-id = <7>;
+	/delete-node/ sensor-presets; /* Override j31[46] below */
+};
+
+#include "isp-imx558-cfg0.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index c519a8975d95fa..e7dd28737e001c 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -209,3 +209,9 @@
 		};
 	};
 };
+
+#include "isp-imx248.dtsi"
+
+&isp {
+	apple,platform-id = <1>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index cc05341b24def4..6e1478f5827ca0 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -151,3 +151,9 @@
 		};
 	};
 };
+
+#include "isp-imx248.dtsi"
+
+&isp {
+	apple,platform-id = <1>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index c16a0594b1d2ca..b7cc5cb8a60af1 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -131,3 +131,9 @@
 &gpu {
 	apple,perf-base-pstate = <3>;
 };
+
+#include "isp-imx364.dtsi"
+
+&isp {
+	apple,platform-id = <2>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index c1d1201ecbe2cc..10742637efc2f3 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -104,3 +104,9 @@
 &gpu {
 	apple,perf-base-pstate = <3>;
 };
+
+#include "isp-imx364.dtsi"
+
+&isp {
+	apple,platform-id = <2>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index ceb93965cb4d44..920602617ade05 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -221,3 +221,10 @@
 	tp_accel {
 	};
 };
+
+#include "isp-imx558-cfg0.dtsi"
+
+&isp {
+	apple,platform-id = <14>;
+	apple,temporal-filter = <1>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index c502bed5f96224..8d7b700ce62607 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -237,3 +237,10 @@
 	tp_accel {
 	};
 };
+
+#include "isp-imx558-cfg0.dtsi"
+
+&isp {
+	apple,platform-id = <15>;
+	apple,temporal-filter = <1>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 8b629e564c5019..dcf11e6ddb1ff5 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -244,3 +244,9 @@
 	tp_accel {
 	};
 };
+
+#include "isp-imx248.dtsi"
+
+&isp {
+	apple,platform-id = <6>;
+};

From 8020fa9965dd4955e2e7d906a47cfe82abc7f3d2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 7 Oct 2023 00:38:24 +0200
Subject: [PATCH 0106/1027] arm64: dts: apple: imx248: Add scaled and cropped
 presets

Adds following resolution presets:
 - 960x720 (4:3)
 - 960x540 (16:9)
 - 640x480 (4:3)
 - 640x360 (16:9)
 - 320x180 (16:9)

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/isp-imx248.dtsi | 35 +++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/isp-imx248.dtsi b/arch/arm64/boot/dts/apple/isp-imx248.dtsi
index acad3ecf0331ef..0a4ac1a0152c2c 100644
--- a/arch/arm64/boot/dts/apple/isp-imx248.dtsi
+++ b/arch/arm64/boot/dts/apple/isp-imx248.dtsi
@@ -18,5 +18,40 @@
 			apple,output-size = <1280 720>;
 			apple,crop = <8 8 1280 720>;
 		};
+		/* 960x720 (4:3) */
+		preset1 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <960 720>;
+			apple,crop = <168 8 960 720>;
+		};
+		/* 960x540 (16:9) */
+		preset2 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <960 540>;
+			apple,crop = <8 8 1280 720>;
+		};
+		/* 640x480 (4:3) */
+		preset3 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <640 480>;
+			apple,crop = <168 8 960 720>;
+		};
+		/* 640x360 (16:9) */
+		preset4 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <640 360>;
+			apple,crop = <8 8 1280 720>;
+		};
+		/* 320x180 (16:9) */
+		preset5 {
+			apple,config-index = <0>;
+			apple,input-size = <1296 736>;
+			apple,output-size = <320 180>;
+			apple,crop = <8 8 1280 720>;
+		};
 	};
 };

From e48e469348c17fb7a8cc764217b30cdc05967adc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 8 Oct 2023 19:53:27 +0900
Subject: [PATCH 0107/1027] arm64: dts: apple: imx558: Add downscaled
 resolution presets

To match those from cfg0. The 4:3 crops are different and this also has
a 1:1 config, so we might want to unify things at some point...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/isp-imx558.dtsi | 42 +++++++++++++++++++++++
 1 file changed, 42 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/isp-imx558.dtsi b/arch/arm64/boot/dts/apple/isp-imx558.dtsi
index a23785b7d5e65a..d55854c883f5b6 100644
--- a/arch/arm64/boot/dts/apple/isp-imx558.dtsi
+++ b/arch/arm64/boot/dts/apple/isp-imx558.dtsi
@@ -46,5 +46,47 @@
 			apple,output-size = <1152 1152>;
 			apple,crop = <0 0 1152 1152>;
 		};
+		/* 1280x720 */
+		preset5 {
+			apple,config-index = <1>;
+			apple,input-size = <1920 1080>;
+			apple,output-size = <1280 720>;
+			apple,crop = <0 0 1920 1080>;
+		};
+		/* 720x1280 */
+		preset6 {
+			apple,config-index = <2>;
+			apple,input-size = <1080 1920>;
+			apple,output-size = <720 1280>;
+			apple,crop = <0 0 1080 1920>;
+		};
+		/* 1280x960 */
+		preset7 {
+			apple,config-index = <3>;
+			apple,input-size = <1760 1328>;
+			apple,output-size = <1280 960>;
+			apple,crop = <0 4 1760 1320>;
+		};
+		/* 960x1280 */
+		preset8 {
+			apple,config-index = <4>;
+			apple,input-size = <1328 1760>;
+			apple,output-size = <960 1280>;
+			apple,crop = <4 0 1320 1760>;
+		};
+		/* 640x480 */
+		preset9 {
+			apple,config-index = <3>;
+			apple,input-size = <1760 1328>;
+			apple,output-size = <640 480>;
+			apple,crop = <0 4 1760 1320>;
+		};
+		/* 480x640 */
+		preset10 {
+			apple,config-index = <4>;
+			apple,input-size = <1328 1760>;
+			apple,output-size = <480 640>;
+			apple,crop = <4 0 1320 1760>;
+		};
 	};
 };

From 2491a07a6829d55e4c61219b0f6e248bd5f0a728 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 20 Jan 2023 11:40:29 +0100
Subject: [PATCH 0108/1027] arm64: dts: apple: t8103-j274: Add speaker I/V
 sense slots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Specify TDM slots for the speaker amp IC to transmit I/V sense
measurements in.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 0a69e2962c7c7f..b20300a5b18306 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -80,6 +80,9 @@
 		reg = <0x31>;
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,sdout-zero-fill;
 	};
 };
 

From 1b7d926b3b70904138cc451caef63988c7cc9961 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 20 Jan 2023 11:43:08 +0100
Subject: [PATCH 0109/1027] arm64: dts: apple: t600x-j314-j316: Add speaker I/V
 sense slots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Specify TDM slots for the speaker amp IC to transmit I/V sense
measurements in. Make sure the channel order mirrors that of the
playback PCM.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index ad282b663e3ac0..fb1649062a2b8c 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -195,6 +195,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <8>;
+		ti,vmon-slot-no = <10>;
 	};
 
 	speaker_left_woof1: codec@38 {
@@ -204,6 +206,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 1";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
 	};
 
 	speaker_left_woof2: codec@39 {
@@ -213,6 +217,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 2";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <16>;
+		ti,vmon-slot-no = <18>;
 	};
 };
 
@@ -239,6 +245,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <12>;
+		ti,vmon-slot-no = <14>;
 	};
 
 	speaker_right_woof1: codec@3b {
@@ -248,6 +256,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 1";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
 	};
 
 	speaker_right_woof2: codec@3c {
@@ -257,6 +267,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 2";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <20>;
+		ti,vmon-slot-no = <22>;
 	};
 };
 

From 13465eadfaa1fca8b8c0b827baac839f1305fe72 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 24 Jan 2023 15:30:36 +0100
Subject: [PATCH 0110/1027] arm64: dts: apple: t600x-j314-j316: Zero out unused
 speaker sense slots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Make one left codec and one right codec zero out the unused slots on
their respective speaker sense buses. Internally, inside the SoC, the
left and right sense buses are ORed, and zeroing-out the unused slots
on one bus is required so as not to corrupt the data on the other.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index fb1649062a2b8c..8d67b6d5b2bfd6 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -208,6 +208,7 @@
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
+		ti,sdout-force-zero-mask = <0xf0f0f0>;
 	};
 
 	speaker_left_woof2: codec@39 {
@@ -258,6 +259,7 @@
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
+		ti,sdout-force-zero-mask = <0x0f0f0f>;
 	};
 
 	speaker_right_woof2: codec@3c {

From 6817a7933d23b9e7698ab2be475cdf777f7a6a70 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:20:26 +0900
Subject: [PATCH 0111/1027] arm64: dts: apple: t600x: Mark MCA power states as
 externally-clocked

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 429ab2969d048e..b8957a4c6359f1 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1113,6 +1113,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca0);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca1): power-controller@290 {
@@ -1122,6 +1123,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca1);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca2): power-controller@298 {
@@ -1131,6 +1133,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca2);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca3): power-controller@2a0 {
@@ -1140,6 +1143,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca3);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_dpa0): power-controller@2a8 {

From f9d19e37034e5fee52f997dc90f95ae7d909fa58 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:20:39 +0900
Subject: [PATCH 0112/1027] arm64: dts: apple: t602x: Mark MCA power states as
 externally-clocked

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index 47b02b76bb1523..c3b1a1f50fff84 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -1673,6 +1673,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca0);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca1): power-controller@3a0 {
@@ -1682,6 +1683,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca1);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca2): power-controller@3a8 {
@@ -1691,6 +1693,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca2);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_mca3): power-controller@3b0 {
@@ -1700,6 +1703,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(mca3);
 		power-domains = <&DIE_NODE(ps_audio_p)>, <&DIE_NODE(ps_sio_adma)>;
+		apple,externally-clocked;
 	};
 
 	DIE_NODE(ps_dpa0): power-controller@3b8 {

From 787794789d0003bd70a7ac2f80b443b2be6e2f8e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:20:47 +0900
Subject: [PATCH 0113/1027] arm64: dts: apple: t8103: Mark MCA power states as
 externally-clocked

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index fa989987866837..4d1422d7e8b5b4 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -493,6 +493,7 @@
 		#reset-cells = <0>;
 		label = "mca0";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_mca1: power-controller@2c0 {
@@ -502,6 +503,7 @@
 		#reset-cells = <0>;
 		label = "mca1";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_mca2: power-controller@2c8 {
@@ -511,6 +513,7 @@
 		#reset-cells = <0>;
 		label = "mca2";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_mca3: power-controller@2d0 {
@@ -520,6 +523,7 @@
 		#reset-cells = <0>;
 		label = "mca3";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_mca4: power-controller@2d8 {
@@ -529,6 +533,7 @@
 		#reset-cells = <0>;
 		label = "mca4";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_mca5: power-controller@2e0 {
@@ -538,6 +543,7 @@
 		#reset-cells = <0>;
 		label = "mca5";
 		power-domains = <&ps_audio_p>, <&ps_sio_adma>;
+		apple,externally-clocked;
 	};
 
 	ps_dpa0: power-controller@2e8 {

From e8172c8bebf54b3a7745abfc5be61283b4237278 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:20:56 +0900
Subject: [PATCH 0114/1027] arm64: dts: apple: t8112: Mark MCA power states as
 externally-clocked

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index c82436067c3c56..9ed831031ae6f0 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -465,6 +465,7 @@
 		#reset-cells = <0>;
 		label = "mca0";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mca1: power-controller@2c8 {
@@ -474,6 +475,7 @@
 		#reset-cells = <0>;
 		label = "mca1";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mca2: power-controller@2d0 {
@@ -483,6 +485,7 @@
 		#reset-cells = <0>;
 		label = "mca2";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mca3: power-controller@2d8 {
@@ -492,6 +495,7 @@
 		#reset-cells = <0>;
 		label = "mca3";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mca4: power-controller@2e0 {
@@ -501,6 +505,7 @@
 		#reset-cells = <0>;
 		label = "mca4";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mca5: power-controller@2e8 {
@@ -510,6 +515,7 @@
 		#reset-cells = <0>;
 		label = "mca5";
 		power-domains = <&ps_sio_adma>, <&ps_audio_p>;
+		apple,externally-clocked;
 	};
 
 	ps_mcc: power-controller@2f0 {

From 0a64f893d9071b1efb35e1a8b7b1a0fe0c2b57b4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:36:54 +0900
Subject: [PATCH 0115/1027] arm64: dts: apple: t600x-j375: Add I/VMON slots to
 amp

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j375.dtsi | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index fcacd7c74af110..86bb83aa8b6c2b 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -270,6 +270,8 @@
 		reg = <0x38>;
 		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
 	};
 };
 

From b4754727c7e83c9f4e0e4562080622dac4ca3edc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:37:23 +0900
Subject: [PATCH 0116/1027] arm64: dts: apple: t600x-j180d: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6022-j180d.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6022-j180d.dts b/arch/arm64/boot/dts/apple/t6022-j180d.dts
index be93805ee0417a..9afc14ae04a290 100644
--- a/arch/arm64/boot/dts/apple/t6022-j180d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j180d.dts
@@ -386,6 +386,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Tweeter";
 		interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
 	};
 
 	speaker_woofer: codec@39 {
@@ -395,6 +397,8 @@
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Woofer";
 		interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
 	};
 };
 

From af5343ec98a4e11291202d47e18ee525bbe56e46 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:38:24 +0900
Subject: [PATCH 0117/1027] arm64: dts: apple: t8112-j413: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 920602617ade05..c056956d508c2e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -102,6 +102,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer";
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,sdout-force-zero-mask = <0xf0f0>;
 	};
 
 	speaker_left_tweet: codec@39 {
@@ -110,6 +113,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
+		ti,imon-slot-no = <8>;
+		ti,vmon-slot-no = <10>;
 	};
 };
 
@@ -120,6 +125,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer";
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
+		ti,sdout-force-zero-mask = <0x0f0f>;
 	};
 
 	speaker_right_tweet: codec@3c {
@@ -128,6 +136,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
+		ti,imon-slot-no = <12>;
+		ti,vmon-slot-no = <14>;
 	};
 
 	jack_codec: codec@4b {

From 10915d82c57c1f266eeccf5217202b0a0db610c2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:39:10 +0900
Subject: [PATCH 0118/1027] arm64: dts: apple: t8112-j415: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j415.dts | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index 8d7b700ce62607..296c8075846f86 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -102,6 +102,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 1";
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,sdout-force-zero-mask = <0xf0f0f0>;
 	};
 
 	speaker_left_tweet: codec@39 {
@@ -110,6 +113,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
+		ti,imon-slot-no = <8>;
+		ti,vmon-slot-no = <10>;
 	};
 
 	speaker_left_woof2: codec@3a {
@@ -118,6 +123,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 2";
+		ti,imon-slot-no = <16>;
+		ti,vmon-slot-no = <18>;
 	};
 };
 
@@ -128,6 +135,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 1";
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
+		ti,sdout-force-zero-mask = <0x0f0f0f>;
 	};
 
 	speaker_right_tweet: codec@3c {
@@ -136,6 +146,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
+		ti,imon-slot-no = <12>;
+		ti,vmon-slot-no = <14>;
 	};
 
 	speaker_right_woof2: codec@3d {
@@ -144,6 +156,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 2";
+		ti,imon-slot-no = <20>;
+		ti,vmon-slot-no = <22>;
 	};
 
 	jack_codec: codec@4b {

From aca9311e1bdd766d6da2e3796597a1ecc61dc092 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:39:19 +0900
Subject: [PATCH 0119/1027] arm64: dts: apple: t8112-j473: Add I/VMON slots to
 amp

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index fa26d1d4be7ded..686426f9468d18 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -102,6 +102,8 @@
 		#sound-dai-cells = <0>;
 		interrupt-parent = <&pinctrl_ap>;
 		interrupts = <11 IRQ_TYPE_LEVEL_LOW>;
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
 	};
 
 	jack_codec: codec@4b {

From c79deeab0677b48246a7e196fec67f5c8837a496 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:42:30 +0900
Subject: [PATCH 0120/1027] arm64: dts: apple: t8112-j493: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j493.dts | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index dcf11e6ddb1ff5..0f8a17439075a0 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -106,6 +106,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
+		ti,imon-slot-no = <8>;
+		ti,vmon-slot-no = <10>;
 	};
 
 	speaker_left_front: codec@39 {
@@ -114,6 +116,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,sdout-force-zero-mask = <0xf0f0>;
 	};
 };
 
@@ -124,6 +129,8 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
+		ti,imon-slot-no = <12>;
+		ti,vmon-slot-no = <14>;
 	};
 
 	speaker_right_front: codec@3c {
@@ -132,6 +139,9 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
+		ti,sdout-force-zero-mask = <0x0f0f>;
 	};
 
 	jack_codec: codec@4b {

From 41ff06fa42662dd7af4db784f62bdf7db9f2582f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:43:09 +0900
Subject: [PATCH 0121/1027] arm64: dts: apple: t8103-j293: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index e7dd28737e001c..42014a5763b384 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -111,6 +111,9 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
+		ti,imon-slot-no = <8>;
+		ti,vmon-slot-no = <10>;
+		ti,pdm-slot-no = <12>;
 	};
 
 	speaker_left_front: codec@32 {
@@ -119,6 +122,10 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,pdm-slot-no = <4>;
+		ti,sdout-pull-down;
 	};
 };
 
@@ -144,6 +151,9 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
+		ti,imon-slot-no = <12>;
+		ti,vmon-slot-no = <14>;
+		ti,pdm-slot-no = <16>;
 	};
 
 	speaker_right_front: codec@35 {
@@ -152,6 +162,10 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
+		ti,pdm-slot-no = <8>;
+		ti,sdout-pull-down;
 	};
 };
 

From 1abbdea80c5656297456ba50a02aa70fa89b72b8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:43:20 +0900
Subject: [PATCH 0122/1027] arm64: dts: apple: t8103-j313: Add I/VMON slots to
 amps

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j313.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 6e1478f5827ca0..a35fa16186fc09 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -91,6 +91,9 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left";
+		ti,imon-slot-no = <0>;
+		ti,vmon-slot-no = <2>;
+		ti,sdout-zero-fill;
 	};
 };
 
@@ -101,6 +104,9 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right";
+		ti,imon-slot-no = <4>;
+		ti,vmon-slot-no = <6>;
+		ti,sdout-zero-fill;
 	};
 
 	jack_codec: codec@48 {

From 1f8a201ca65083877760e1e982f894ebe7bfcaf2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 02:44:48 +0900
Subject: [PATCH 0123/1027] arm64: dts: apple: j314/j316: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 8d67b6d5b2bfd6..f258e4a6e39baf 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -417,15 +417,6 @@
 		dai-link@0 {
 			link-name = "Speakers";
 
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
-
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;
 			};

From a3eeae6778d51daa6b8d0060cd4c789d1036b005 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 02:48:03 +0900
Subject: [PATCH 0124/1027] arm64: dts: apple: j413: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index c056956d508c2e..ec5dbb69b3e850 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -166,15 +166,6 @@
 		dai-link@0 {
 			link-name = "Speakers";
 
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
-
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;
 			};

From 6c083e3453928362dc9736a8a233f369ab79a25b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 02:48:19 +0900
Subject: [PATCH 0125/1027] arm64: dts: apple: j415: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j415.dts | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index 296c8075846f86..6f25131813a445 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -182,15 +182,6 @@
 		dai-link@0 {
 			link-name = "Speakers";
 
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
-
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;
 			};

From 6efbe4e8e8a01a5236a5b2276a0c818759365b89 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:40:34 +0900
Subject: [PATCH 0126/1027] arm64: dts: apple: j375: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j375.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index 86bb83aa8b6c2b..db3b5fe0b4f32a 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -270,6 +270,7 @@
 		reg = <0x38>;
 		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
+		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 	};

From 7e69ad45c82d7c7da77037cdb61eac839b243fca Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:42:00 +0900
Subject: [PATCH 0127/1027] arm64: dts: apple: j293: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 42014a5763b384..865d7e56aba6bb 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -111,6 +111,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <8>;
 		ti,vmon-slot-no = <10>;
 		ti,pdm-slot-no = <12>;
@@ -122,6 +123,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 		ti,pdm-slot-no = <4>;
@@ -151,6 +153,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <12>;
 		ti,vmon-slot-no = <14>;
 		ti,pdm-slot-no = <16>;
@@ -162,6 +165,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
 		ti,pdm-slot-no = <8>;

From edeb3d231d868e6f1de42d08b3eaa7b0443771c1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:42:13 +0900
Subject: [PATCH 0128/1027] arm64: dts: apple: j313: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j313.dts | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index a35fa16186fc09..94a621666767aa 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -91,6 +91,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 		ti,sdout-zero-fill;
@@ -104,6 +105,7 @@
 		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right";
+		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
 		ti,sdout-zero-fill;

From 95787013d29e91ed709d249edcbe3d5b2de9c968 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:42:38 +0900
Subject: [PATCH 0129/1027] arm64: dts: apple: j413: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index ec5dbb69b3e850..b3ac6c8c3f6261 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -102,6 +102,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 		ti,sdout-force-zero-mask = <0xf0f0>;
@@ -113,6 +114,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <8>;
 		ti,vmon-slot-no = <10>;
 	};
@@ -125,6 +127,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
 		ti,sdout-force-zero-mask = <0x0f0f>;
@@ -136,6 +139,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <12>;
 		ti,vmon-slot-no = <14>;
 	};

From cbefcb059199dedb141a0cf77980debe80ae4f82 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:43:13 +0900
Subject: [PATCH 0130/1027] arm64: dts: apple: j415: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j415.dts | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index 6f25131813a445..be75466a2b5788 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -102,6 +102,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 1";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 		ti,sdout-force-zero-mask = <0xf0f0f0>;
@@ -113,6 +114,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <8>;
 		ti,vmon-slot-no = <10>;
 	};
@@ -123,6 +125,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 2";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <16>;
 		ti,vmon-slot-no = <18>;
 	};
@@ -135,6 +138,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 1";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
 		ti,sdout-force-zero-mask = <0x0f0f0f>;
@@ -146,6 +150,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <12>;
 		ti,vmon-slot-no = <14>;
 	};
@@ -156,6 +161,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 2";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <20>;
 		ti,vmon-slot-no = <22>;
 	};

From bede342585a1d4f03593bfc2f26137904efe0134 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Oct 2023 23:43:37 +0900
Subject: [PATCH 0131/1027] arm64: dts: apple: j493: Add missing speaker amp
 IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j493.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 0f8a17439075a0..81fd99cc9b452b 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -106,6 +106,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <8>;
 		ti,vmon-slot-no = <10>;
 	};
@@ -116,6 +117,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <0>;
 		ti,vmon-slot-no = <2>;
 		ti,sdout-force-zero-mask = <0xf0f0>;
@@ -129,6 +131,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <12>;
 		ti,vmon-slot-no = <14>;
 	};
@@ -139,6 +142,7 @@
 		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
+		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
 		ti,imon-slot-no = <4>;
 		ti,vmon-slot-no = <6>;
 		ti,sdout-force-zero-mask = <0x0f0f>;

From a0d903710d6816f563a3057347df7896a902a79f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 21 Oct 2023 22:18:06 +0900
Subject: [PATCH 0132/1027] arm64: dts: apple: j413: Model SDZ GPIO as a
 regulator

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index b3ac6c8c3f6261..d5104c24d2e4e3 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -95,11 +95,22 @@
 	};
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-sn012776-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "sn012776-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	speaker_left_woof: codec@38 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x38>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -111,7 +122,7 @@
 	speaker_left_tweet: codec@39 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x39>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -124,7 +135,7 @@
 	speaker_right_woof: codec@3b {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3b>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -136,7 +147,7 @@
 	speaker_right_tweet: codec@3c {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3c>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;

From 5a40a88bdc1967fe14e70ab922ae2e793402080a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:52:20 +0900
Subject: [PATCH 0133/1027] arm64: dts: apple: t600x-j314-j316: Set sound
 compatibles per device

j314 and j316 have different speakers, so should have different
compatibles. In addition, j414 and j416 will include this config but
should have their own compatibles. Move the compatibles to the
individual device files.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t6000-j314s.dts      | 1 +
 arch/arm64/boot/dts/apple/t6000-j316s.dts      | 1 +
 arch/arm64/boot/dts/apple/t6001-j314c.dts      | 1 +
 arch/arm64/boot/dts/apple/t6001-j316c.dts      | 1 +
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 2 +-
 5 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t6000-j314s.dts b/arch/arm64/boot/dts/apple/t6000-j314s.dts
index a2ccc654216164..ae79e3236614be 100644
--- a/arch/arm64/boot/dts/apple/t6000-j314s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j314s.dts
@@ -33,5 +33,6 @@
 };
 
 &sound {
+	compatible = "apple,j314-macaudio", "apple,macaudio";
 	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6000-j316s.dts b/arch/arm64/boot/dts/apple/t6000-j316s.dts
index 99a76392b3b791..272fa1c1712479 100644
--- a/arch/arm64/boot/dts/apple/t6000-j316s.dts
+++ b/arch/arm64/boot/dts/apple/t6000-j316s.dts
@@ -33,5 +33,6 @@
 };
 
 &sound {
+	compatible = "apple,j316-macaudio", "apple,macaudio";
 	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j314c.dts b/arch/arm64/boot/dts/apple/t6001-j314c.dts
index 82d851d4cd3857..81d34507ed81ff 100644
--- a/arch/arm64/boot/dts/apple/t6001-j314c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j314c.dts
@@ -33,5 +33,6 @@
 };
 
 &sound {
+	compatible = "apple,j314-macaudio", "apple,macaudio";
 	model = "MacBook Pro J314";
 };
diff --git a/arch/arm64/boot/dts/apple/t6001-j316c.dts b/arch/arm64/boot/dts/apple/t6001-j316c.dts
index a6987c8324dbd7..564d927f2fecbd 100644
--- a/arch/arm64/boot/dts/apple/t6001-j316c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j316c.dts
@@ -33,5 +33,6 @@
 };
 
 &sound {
+	compatible = "apple,j316-macaudio", "apple,macaudio";
 	model = "MacBook Pro J316";
 };
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index f258e4a6e39baf..ac49a38917432b 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -412,7 +412,7 @@
 
 / {
 	sound: sound {
-		compatible = "apple,j314-macaudio", "apple,macaudio";
+		/* compatible is set per machine */
 
 		dai-link@0 {
 			link-name = "Speakers";

From 7ea4766f48ab902b42bf246af3b7d80268a9ade0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:58:36 +0900
Subject: [PATCH 0134/1027] arm64: dts: apple: j375 & friends: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t600x-j375.dtsi | 9 +--------
 1 file changed, 1 insertion(+), 8 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index db3b5fe0b4f32a..d8db13fc02e3f6 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -299,14 +299,7 @@
 
 		dai-link@0 {
 			link-name = "Speaker";
-			/*
-			* DANGER ZONE: You can blow your speakers!
-			*
-			* The drivers are not ready, and unless you are careful
-			* to attenuate the audio stream, you run the risk of
-			* blowing your speakers.
-			*/
-			status = "disabled";
+
 			cpu {
 				sound-dai = <&mca 0>;
 			};

From 8e40ccd5413fb757cca8ef41daa8fbbb697b0714 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:58:49 +0900
Subject: [PATCH 0135/1027] arm64: dts: apple: j293: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 865d7e56aba6bb..c6ef45fad387c2 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -197,15 +197,6 @@
 		dai-link@0 {
 			link-name = "Speakers";
 
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
-
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;
 			};

From b43264e0b74eb5797c6e9c5d72b52e1d2e827db1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:58:58 +0900
Subject: [PATCH 0136/1027] arm64: dts: apple: j313: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j313.dts | 8 --------
 1 file changed, 8 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 94a621666767aa..bc2ec8c7cb6862 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -130,14 +130,6 @@
 
 		dai-link@0 {
 			link-name = "Speakers";
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
 
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;

From 5afc9511723deda1a28fca4731cc73e1186135a8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:59:05 +0900
Subject: [PATCH 0137/1027] arm64: dts: apple: j493: Enable speakers

Still gated in macaudio by the command line argument

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8112-j493.dts | 9 ---------
 1 file changed, 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 81fd99cc9b452b..f1589366d48631 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -174,15 +174,6 @@
 		dai-link@0 {
 			link-name = "Speakers";
 
-			/*
-			 * DANGER ZONE: You can blow your speakers!
-			 *
-			 * The drivers are not ready, and unless you are careful
-			 * to attenuate the audio stream, you run the risk of
-			 * blowing your speakers.
-			 */
-			status = "disabled";
-
 			cpu {
 				sound-dai = <&mca 0>, <&mca 1>;
 			};

From ab501823e78946963536ec3b71d3b2d7aa25b807 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 29 Oct 2023 08:55:40 +1000
Subject: [PATCH 0138/1027] arm64: dts: apple: describe shared SDZ GPIO for
 tas2764

machines with the tas2764 amp codec share a GPIO line for
asserting/deasserting the SDZ pin on the chips. describe
this as a regulator to facilitate chip reset on suspend/resume

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 23 ++++++++++++++-----
 .../arm64/boot/dts/apple/t602x-j414-j416.dtsi | 11 ++++-----
 arch/arm64/boot/dts/apple/t8112-j415.dts      | 23 ++++++++++++++-----
 arch/arm64/boot/dts/apple/t8112-j493.dts      | 19 +++++++++++----
 4 files changed, 54 insertions(+), 22 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index ac49a38917432b..2631daf667e14e 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -185,13 +185,24 @@
 	};
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-sn012776-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "sn012776-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	status = "okay";
 
 	speaker_left_tweet: codec@3a {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3a>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
@@ -202,7 +213,7 @@
 	speaker_left_woof1: codec@38 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x38>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 1";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
@@ -214,7 +225,7 @@
 	speaker_left_woof2: codec@39 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x39>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 2";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
@@ -242,7 +253,7 @@
 	speaker_right_tweet: codec@3d {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3d>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
@@ -253,7 +264,7 @@
 	speaker_right_woof1: codec@3b {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3b>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 1";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
@@ -265,7 +276,7 @@
 	speaker_right_woof2: codec@3c {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3c>;
-		shutdown-gpios = <&pinctrl_ap 178 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 2";
 		interrupts-extended = <&pinctrl_ap 179 IRQ_TYPE_LEVEL_LOW>;
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index 9c2b5b7ce2beb3..9eb19bfef4171a 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -46,33 +46,32 @@
 	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };
 
+/* Redefine GPIO for SDZ */
+&speaker_sdz {
+	gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
+};
+
 &speaker_left_tweet {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &speaker_left_woof1 {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &speaker_left_woof2 {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &speaker_right_tweet {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &speaker_right_woof1 {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
 &speaker_right_woof2 {
-	shutdown-gpios = <&pinctrl_ap 57 GPIO_ACTIVE_HIGH>;
 	interrupts-extended = <&pinctrl_ap 58 IRQ_TYPE_LEVEL_LOW>;
 };
 
diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index be75466a2b5788..82f86859efdbea 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -95,11 +95,22 @@
 	};
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-sn012776-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "sn012776-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	speaker_left_woof1: codec@38 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x38>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 1";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -111,7 +122,7 @@
 	speaker_left_tweet: codec@39 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x39>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Tweeter";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -122,7 +133,7 @@
 	speaker_left_woof2: codec@3a {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3a>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Woofer 2";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -135,7 +146,7 @@
 	speaker_right_woof1: codec@3b {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3b>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 1";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -147,7 +158,7 @@
 	speaker_right_tweet: codec@3c {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3c>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Tweeter";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -158,7 +169,7 @@
 	speaker_right_woof2: codec@3d {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3d>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Woofer 2";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index f1589366d48631..0624d854b5542e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -99,11 +99,22 @@
 	label = "USB-C Left-front";
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-sn012776-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "sn012776-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	speaker_left_rear: codec@38 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x38>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -114,7 +125,7 @@
 	speaker_left_front: codec@39 {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x39>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -128,7 +139,7 @@
 	speaker_right_rear: codec@3b {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3b>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;
@@ -139,7 +150,7 @@
 	speaker_right_front: codec@3c {
 		compatible = "ti,sn012776", "ti,tas2764";
 		reg = <0x3c>;
-		shutdown-gpios = <&pinctrl_ap 88 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
 		interrupts-extended = <&pinctrl_ap 11 IRQ_TYPE_LEVEL_LOW>;

From fbad85ee8d8a4113eb4450daa36a2e561d594c66 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 17:35:00 +0900
Subject: [PATCH 0139/1027] arm64: dts: apple: j293: Model SDZ GPIO as a
 regulator

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index c6ef45fad387c2..bb8b878630bac1 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -104,11 +104,22 @@
 	};
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-tas5770-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "tas5770-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	speaker_left_rear: codec@31 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x31>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Rear";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
@@ -120,7 +131,7 @@
 	speaker_left_front: codec@32 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x32>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left Front";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
@@ -150,7 +161,7 @@
 	speaker_right_rear: codec@34 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x34>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Rear";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
@@ -162,7 +173,7 @@
 	speaker_right_front: codec@35 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x35>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right Front";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;

From ad5935ef50232052907579f2f432c17cfcafc971 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 17:35:16 +0900
Subject: [PATCH 0140/1027] arm64: dts: apple: j313: Model SDZ GPIO as a
 regulator

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 arch/arm64/boot/dts/apple/t8103-j313.dts | 15 +++++++++++++--
 1 file changed, 13 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index bc2ec8c7cb6862..dc96f17aea2fed 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -84,11 +84,22 @@
 	};
 };
 
+/* Virtual regulator representing the shared shutdown GPIO */
+/ {
+	speaker_sdz: fixed-regulator-tas5770-sdz {
+		compatible = "regulator-fixed";
+		regulator-name = "tas5770-sdz";
+		startup-delay-us = <5000>;
+		gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		enable-active-high;
+	};
+};
+
 &i2c1 {
 	speaker_left: codec@31 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x31>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Left";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;
@@ -102,7 +113,7 @@
 	speaker_right: codec@34 {
 		compatible = "ti,tas5770l", "ti,tas2770";
 		reg = <0x34>;
-		shutdown-gpios = <&pinctrl_ap 181 GPIO_ACTIVE_HIGH>;
+		SDZ-supply = <&speaker_sdz>;
 		#sound-dai-cells = <0>;
 		sound-name-prefix = "Right";
 		interrupts-extended = <&pinctrl_ap 182 IRQ_TYPE_LEVEL_LOW>;

From 5478f685058c293bd49c5df93f656556a15be82c Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Mon, 6 Nov 2023 20:33:51 +1000
Subject: [PATCH 0141/1027] arm64: dts: apple: add opp-microwatt to t8103/t600x

This patch adds measured opp-microwatt values for the
Firestorm and Icestorm application cores found in Apple's
T8103 (M1), T6000 (M1 Pro), T6001 (M1 Max) and T6002 (M1 Ultra) SoCs.

Values were measured from the System Management Controller's
core cluster power meter. A version of freqbench modified
to read this power meter was used to orchestrate testing,
running 1,000,000 iterations of coremark on a single core
from each cluster at each operating point. The bulk of the
testing was done on a T6000.

Note that Apple calibrates voltage regulator settings for
each SoC as they come off the assembly line, introducing some
natural variance between machines. Testing across multiple
machines with identical SoCs reveals no measurable impact
on the accuracy of the EM subsystem's cost calculations.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 arch/arm64/boot/dts/apple/t600x-common.dtsi | 20 ++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi        | 20 ++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index 667c02724b8646..5f62329b628a39 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -229,26 +229,31 @@
 			opp-hz = /bits/ 64 <600000000>;
 			opp-level = <1>;
 			clock-latency-ns = <7500>;
+			opp-microwatt = <47296>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <972000000>;
 			opp-level = <2>;
 			clock-latency-ns = <23000>;
+			opp-microwatt = <99715>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1332000000>;
 			opp-level = <3>;
 			clock-latency-ns = <29000>;
+			opp-microwatt = <188860>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1704000000>;
 			opp-level = <4>;
 			clock-latency-ns = <40000>;
+			opp-microwatt = <288891>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <2064000000>;
 			opp-level = <5>;
 			clock-latency-ns = <50000>;
+			opp-microwatt = <412979>;
 		};
 	};
 
@@ -259,78 +264,93 @@
 			opp-hz = /bits/ 64 <600000000>;
 			opp-level = <1>;
 			clock-latency-ns = <8000>;
+			opp-microwatt = <290230>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <828000000>;
 			opp-level = <2>;
 			clock-latency-ns = <18000>;
+			opp-microwatt = <449013>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1056000000>;
 			opp-level = <3>;
 			clock-latency-ns = <19000>;
+			opp-microwatt = <647097>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1296000000>;
 			opp-level = <4>;
 			clock-latency-ns = <23000>;
+			opp-microwatt = <865620>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <1524000000>;
 			opp-level = <5>;
 			clock-latency-ns = <24000>;
+			opp-microwatt = <1112838>;
 		};
 		opp06 {
 			opp-hz = /bits/ 64 <1752000000>;
 			opp-level = <6>;
 			clock-latency-ns = <28000>;
+			opp-microwatt = <1453271>;
 		};
 		opp07 {
 			opp-hz = /bits/ 64 <1980000000>;
 			opp-level = <7>;
 			clock-latency-ns = <31000>;
+			opp-microwatt = <1776667>;
 		};
 		opp08 {
 			opp-hz = /bits/ 64 <2208000000>;
 			opp-level = <8>;
 			clock-latency-ns = <45000>;
+			opp-microwatt = <2366690>;
 		};
 		opp09 {
 			opp-hz = /bits/ 64 <2448000000>;
 			opp-level = <9>;
 			clock-latency-ns = <49000>;
+			opp-microwatt = <2892193>;
 		};
 		opp10 {
 			opp-hz = /bits/ 64 <2676000000>;
 			opp-level = <10>;
 			clock-latency-ns = <53000>;
+			opp-microwatt = <3475417>;
 		};
 		opp11 {
 			opp-hz = /bits/ 64 <2904000000>;
 			opp-level = <11>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <3959410>;
 		};
 		opp12 {
 			opp-hz = /bits/ 64 <3036000000>;
 			opp-level = <12>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4540620>;
 		};
 		opp13 {
 			opp-hz = /bits/ 64 <3132000000>;
 			opp-level = <13>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4745031>;
 			turbo-mode;
 		};
 		opp14 {
 			opp-hz = /bits/ 64 <3168000000>;
 			opp-level = <14>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4822390>;
 			turbo-mode;
 		};
 		opp15 {
 			opp-hz = /bits/ 64 <3228000000>;
 			opp-level = <15>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4951324>;
 			turbo-mode;
 		};
 	};
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 932b3fca3e1f72..30fe535b4b308c 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -194,26 +194,31 @@
 			opp-hz = /bits/ 64 <600000000>;
 			opp-level = <1>;
 			clock-latency-ns = <7500>;
+			opp-microwatt = <47296>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <972000000>;
 			opp-level = <2>;
 			clock-latency-ns = <22000>;
+			opp-microwatt = <99715>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1332000000>;
 			opp-level = <3>;
 			clock-latency-ns = <27000>;
+			opp-microwatt = <188860>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1704000000>;
 			opp-level = <4>;
 			clock-latency-ns = <33000>;
+			opp-microwatt = <288891>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <2064000000>;
 			opp-level = <5>;
 			clock-latency-ns = <50000>;
+			opp-microwatt = <412979>;
 		};
 	};
 
@@ -224,79 +229,94 @@
 			opp-hz = /bits/ 64 <600000000>;
 			opp-level = <1>;
 			clock-latency-ns = <8000>;
+			opp-microwatt = <290230>;
 		};
 		opp02 {
 			opp-hz = /bits/ 64 <828000000>;
 			opp-level = <2>;
 			clock-latency-ns = <19000>;
+			opp-microwatt = <449013>;
 		};
 		opp03 {
 			opp-hz = /bits/ 64 <1056000000>;
 			opp-level = <3>;
 			clock-latency-ns = <21000>;
+			opp-microwatt = <647097>;
 		};
 		opp04 {
 			opp-hz = /bits/ 64 <1284000000>;
 			opp-level = <4>;
 			clock-latency-ns = <23000>;
+			opp-microwatt = <865620>;
 		};
 		opp05 {
 			opp-hz = /bits/ 64 <1500000000>;
 			opp-level = <5>;
 			clock-latency-ns = <24000>;
+			opp-microwatt = <1112838>;
 		};
 		opp06 {
 			opp-hz = /bits/ 64 <1728000000>;
 			opp-level = <6>;
 			clock-latency-ns = <29000>;
+			opp-microwatt = <1453271>;
 		};
 		opp07 {
 			opp-hz = /bits/ 64 <1956000000>;
 			opp-level = <7>;
 			clock-latency-ns = <31000>;
+			opp-microwatt = <1776667>;
 		};
 		opp08 {
 			opp-hz = /bits/ 64 <2184000000>;
 			opp-level = <8>;
 			clock-latency-ns = <34000>;
+			opp-microwatt = <2366690>;
 		};
 		opp09 {
 			opp-hz = /bits/ 64 <2388000000>;
 			opp-level = <9>;
 			clock-latency-ns = <36000>;
+			opp-microwatt = <2892193>;
 		};
 		opp10 {
 			opp-hz = /bits/ 64 <2592000000>;
 			opp-level = <10>;
 			clock-latency-ns = <51000>;
+			opp-microwatt = <3475417>;
 		};
 		opp11 {
 			opp-hz = /bits/ 64 <2772000000>;
 			opp-level = <11>;
 			clock-latency-ns = <54000>;
+			opp-microwatt = <3959410>;
 		};
 		opp12 {
 			opp-hz = /bits/ 64 <2988000000>;
 			opp-level = <12>;
 			clock-latency-ns = <55000>;
+			opp-microwatt = <4540620>;
 		};
 		/* Not available until CPU deep sleep is implemented */
 		opp13 {
 			opp-hz = /bits/ 64 <3096000000>;
 			opp-level = <13>;
 			clock-latency-ns = <55000>;
+			opp-microwatt = <4745031>;
 			turbo-mode;
 		};
 		opp14 {
 			opp-hz = /bits/ 64 <3144000000>;
 			opp-level = <14>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4822390>;
 			turbo-mode;
 		};
 		opp15 {
 			opp-hz = /bits/ 64 <3204000000>;
 			opp-level = <15>;
 			clock-latency-ns = <56000>;
+			opp-microwatt = <4951324>;
 			turbo-mode;
 		};
 	};

From 9da10aa7ddebb9ace76df9773b6998a7fc8fc5cf Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 Nov 2023 22:44:17 +0100
Subject: [PATCH 0142/1027] arm64: dts: apple: Disable ps_isp_sys unless it is
 used

Seems to be fuxed off on t602x devices without camera and causes
annoying kernel log splat.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/isp-common.dtsi | 4 ++++
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 1 +
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 1 +
 5 files changed, 8 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/isp-common.dtsi b/arch/arm64/boot/dts/apple/isp-common.dtsi
index bf406772469b67..739e6e9e66e740 100644
--- a/arch/arm64/boot/dts/apple/isp-common.dtsi
+++ b/arch/arm64/boot/dts/apple/isp-common.dtsi
@@ -41,3 +41,7 @@
 &isp_dart2 {
 	status = "okay";
 };
+
+&ps_isp_sys {
+	status = "okay";
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index b8957a4c6359f1..607ae7697973c3 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1370,6 +1370,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(isp_sys);
 		power-domains = <&DIE_NODE(ps_afnc2_lw1)>;
+		status = "disabled";
 	};
 
 	DIE_NODE(ps_venc_sys): power-controller@3b0 {
diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index c3b1a1f50fff84..07c50156149562 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -1230,6 +1230,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(isp_sys);
 		power-domains = <&DIE_NODE(ps_afnc2_lw1)>;
+		status = "disabled";
 	};
 
 	DIE_NODE(ps_disp0_fe): power-controller@1d0 {
diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 4d1422d7e8b5b4..10facd0c01e420 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -812,6 +812,7 @@
 		#reset-cells = <0>;
 		label = "isp_sys";
 		power-domains = <&ps_rmx>;
+		status = "disabled";
 	};
 
 	ps_venc_sys: power-controller@408 {
diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 9ed831031ae6f0..102ff3ad0535d0 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -821,6 +821,7 @@
 		#reset-cells = <0>;
 		label = "isp_sys";
 		power-domains = <&ps_rmx1>;
+		status = "disabled";
 	};
 
 	ps_venc_sys: power-controller@440 {

From 18e1af7309b86032fcc9e1abc404471eb9514cb8 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 00:51:53 +0100
Subject: [PATCH 0143/1027] arm64: dts: apple: t600x: Switch to apple,dma-range

Obsoletes the use of "apple,asc-dram-mask" in the device tree and the
dcp driver.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 94b832b68b9226..89357b619363c2 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -190,6 +190,7 @@
 		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
 		status = "disabled";
 		power-domains = <&ps_disp0_cpu0>;
+		apple,dma-range = <0x0 0x0 0x0 0xfc000000>;
 	};
 
 	dcp_dart: iommu@38b30c000 {
@@ -199,6 +200,7 @@
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ 0 821 IRQ_TYPE_LEVEL_HIGH>;
 		power-domains = <&ps_disp0_cpu0>;
+		apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>;
 	};
 
 	dcp_mbox: mbox@38bc08000 {
@@ -231,7 +233,6 @@
 		power-domains = <&ps_disp0_cpu0>;
 		resets = <&ps_disp0_cpu0>;
 		clocks = <&clk_disp0>;
-		apple,asc-dram-mask = <0>;
 		phandle = <&dcp>;
 
 		disp0_piodma: piodma {

From ff313c02becfd6aabc4db93ca38a419c9d916140 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 00:51:53 +0100
Subject: [PATCH 0144/1027] arm64: dts: apple: t8103: Switch to apple,dma-range

Obsoletes the use of "apple,asc-dram-mask" in the device tree and the
dcp driver.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index 30fe535b4b308c..b6ef5c71cb02a1 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -566,6 +566,7 @@
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
 			power-domains = <&ps_disp0_cpu0>;
+			apple,dma-range = <0x0 0x0 0x0 0xfc000000>;
 			status = "disabled";
 		};
 
@@ -575,6 +576,7 @@
 			#iommu-cells = <1>;
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 445 IRQ_TYPE_LEVEL_HIGH>;
+			apple,dma-range = <0xf 0x00000000 0x0 0xfc000000>;
 			power-domains = <&ps_disp0_cpu0>;
 		};
 
@@ -612,7 +614,6 @@
 			power-domains = <&ps_disp0_cpu0>;
 			resets = <&ps_disp0_cpu0>;
 			clocks = <&clk_disp0>;
-			apple,asc-dram-mask = <0xf 0x00000000>;
 			phandle = <&dcp>;
 
 			disp0_piodma: piodma {

From b0f4bc5efce01053937662276fe4adb2ba0e9f1a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 20:04:09 +0100
Subject: [PATCH 0145/1027] arm64: dts: apple: t8112: Switch to apple,dma-range

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 62128ef1f966ad..9c78063f3a2338 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -642,6 +642,7 @@
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
 			power-domains = <&ps_disp0_cpu0>;
+			apple,dma-range = <0x0 0x0 0xf 0xffff0000>;
 			status = "disabled";
 		};
 
@@ -652,6 +653,7 @@
 			interrupt-parent = <&aic>;
 			interrupts = <AIC_IRQ 553 IRQ_TYPE_LEVEL_HIGH>;
 			power-domains = <&ps_disp0_cpu0>;
+			apple,dma-range = <0x8 0x00000000 0x7 0xffff0000>;
 		};
 
 		dcp_mbox: mbox@231c08000 {
@@ -686,7 +688,6 @@
 			power-domains = <&ps_disp0_cpu0>;
 			resets = <&ps_disp0_cpu0>;
 			clocks = <&clk_disp0>;
-			apple,asc-dram-mask = <0x0 0x0>;
 			phandle = <&dcp>;
 
 			disp0_piodma: piodma {

From b6b37d04a7c50f2a929e27dcdf330b14635503f7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 00:46:53 +0100
Subject: [PATCH 0146/1027] arm64: dts: apple: t600x: Add "apple,min-state" to
 ps_dispextN_cpu0

DCP ASC co-processors do not come back up from lower power states.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 607ae7697973c3..88bd7a760f370f 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -396,6 +396,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext0_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext0_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_dispext1_cpu0): power-controller@2a8 {
@@ -405,6 +406,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext1_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext1_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_ane_sys_cpu): power-controller@2c8 {
@@ -1792,6 +1794,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext2_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext2_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_dispext3_fe): power-controller@210 {
@@ -1810,6 +1813,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext3_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext3_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_msr1): power-controller@250 {

From 0cb4f5566ea7699123e8e6a654c6d29cfbecd8d4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 00:46:53 +0100
Subject: [PATCH 0147/1027] arm64: dts: apple: t602x: Add "apple,min-state" to
 ps_dispextN_cpu0

DCP ASC co-processors do not come back up from lower power states.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index 07c50156149562..063181e44412b5 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -435,6 +435,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext1_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext1_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_dispext0_fe): power-controller@2c0 {
@@ -472,6 +473,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext0_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext0_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_ane_cpu): power-controller@2e0 {
@@ -900,6 +902,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext2_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext2_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_msr1_ase_core): power-controller@1f0 {
@@ -943,6 +946,7 @@
 		#reset-cells = <0>;
 		label = DIE_LABEL(dispext3_cpu0);
 		power-domains = <&DIE_NODE(ps_dispext3_fe)>;
+		apple,min-state = <4>;
 	};
 
 	DIE_NODE(ps_venc1_dma): power-controller@4000 {

From a57c236c7e2d9615e7efd0f794cee51b2a5a04cb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 30 Sep 2022 22:30:13 +0200
Subject: [PATCH 0148/1027] arm64: dts: apple: t8103: Add dcpext/dispext0 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103.dtsi | 73 ++++++++++++++++++++++++++++
 1 file changed, 73 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index b6ef5c71cb02a1..e970db75042fd1 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -416,6 +416,14 @@
 		clock-output-names = "clk_disp0";
 	};
 
+	/* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
+	clk_dispext0: clock-dispext0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0";
+	};
+
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
@@ -624,6 +632,7 @@
 
 		display: display-subsystem {
 			compatible = "apple,display-subsystem";
+			/* disp_dart0 must be 1st since it is locked */
 			iommus = <&disp0_dart 0>;
 			/* generate phandle explicitly for use in loader */
 			phandle = <&display>;
@@ -1186,6 +1195,70 @@
 				     <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
 		};
 
+		dispext0_dart: iommu@271304000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x71304000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 481 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext_dart: iommu@27130c000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x7130c000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 481 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext_mbox: mbox@271c08000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x71c08000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 466 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 467 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 468 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 469 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_dispext_cpu0>;
+			resets = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext: dcp@271c00000 {
+			compatible = "apple,t8103-dcpext", "apple,dcpext";
+			mboxes = <&dcpext_mbox>;
+			mbox-names = "mbox";
+			iommus = <&dcpext_dart 0>;
+			phandle = <&dcpext>;
+
+			reg-names = "coproc", "disp-0", "disp-1", "disp-2",
+			"disp-3", "disp-4";
+			reg = <0x2 0x71c00000 0x0 0x4000>,
+			      <0x2 0x70000000 0x0 0x118000>,
+			      <0x2 0x71320000 0x0 0x4000>,
+			      <0x2 0x71344000 0x0 0x4000>,
+			      <0x2 0x71800000 0x0 0x800000>,
+			      <0x2 0x3b3d0000 0x0 0x4000>;
+			apple,bw-scratch = <&pmgr_dcp 0 5 0x18>;
+			apple,bw-doorbell = <&pmgr_dcp 1 6>;
+			power-domains = <&ps_dispext_cpu0>;
+			resets = <&ps_dispext_cpu0>;
+			clocks = <&clk_dispext0>;
+			apple,asc-dram-mask = <0xf 0x00000000>;
+			status = "disabled";
+
+			piodma {
+				iommus = <&dispext0_dart 4>;
+			};
+		};
+
 		ans_mbox: mbox@277408000 {
 			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
 			reg = <0x2 0x77408000 0x0 0x4000>;

From 4a7cc94666fadd3da916288a6eee6449858f14b2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 3 Dec 2022 22:12:25 +0100
Subject: [PATCH 0149/1027] arm64: dts: apple: t8112: Add dcpext/dispext0 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 72 ++++++++++++++++++++++++++++
 1 file changed, 72 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 9c78063f3a2338..bd8b3e288facfa 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -447,6 +447,14 @@
 		clock-output-names = "clk_disp0";
 	};
 
+	/* Pixel clock? frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
+	clk_dispext0: clock-dispext0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0";
+	};
+
 	reserved-memory {
 		#address-cells = <2>;
 		#size-cells = <2>;
@@ -1374,6 +1382,70 @@
 
 		};
 
+		dispext0_dart: iommu@271304000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x2 0x71304000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			apple,dma-range = <0x0 0x0 0xf 0xffff0000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 593 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext_dart: iommu@27130c000 {
+			compatible = "apple,t8112-dart", "apple,t8110-dart";
+			reg = <0x2 0x7130c000 0x0 0x4000>;
+			#iommu-cells = <1>;
+			apple,dma-range = <0x8 0x0 0x7 0xffff0000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 593 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext_mbox: mbox@271c08000 {
+			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x71c08000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 578 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 579 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 580 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ 581 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_dispext_cpu0>;
+			resets = <&ps_dispext_cpu0>;
+			status = "disabled";
+		};
+
+		dcpext: dcp@271c00000 {
+			compatible = "apple,t8112-dcpext", "apple,dcpext";
+			mboxes = <&dcpext_mbox>;
+			mbox-names = "mbox";
+			iommus = <&dcpext_dart 5>;
+			phandle = <&dcpext>;
+
+			/* the ADT has 2 additional regs which seems to be unused */
+			reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+			reg = <0x2 0x71c00000 0x0 0x4000>,
+			      <0x2 0x70000000 0x0 0x61C000>,
+			      <0x2 0x71320000 0x0 0x4000>,
+			      <0x2 0x71344000 0x0 0x4000>,
+			      <0x2 0x71800000 0x0 0x800000>;
+			apple,bw-scratch = <&pmgr_dcp 0 4 0x5e0>;
+			power-domains = <&ps_dispext_cpu0>;
+			resets = <&ps_dispext_cpu0>;
+			clocks = <&clk_dispext0>;
+			apple,dcp-index = <1>;
+			status = "disabled";
+
+			piodma {
+				iommus = <&dispext0_dart 4>;
+			};
+		};
+
 		ans_mbox: mbox@277408000 {
 			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
 			reg = <0x2 0x77408000 0x0 0x4000>;

From febf2b995e3948ed9d63315bc4c967938629e7f1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 20 Oct 2022 20:44:02 +0200
Subject: [PATCH 0150/1027] arm64: dts: apple: t600x: Add t6000 dispext device
 nodes

While thunderbolt and DP-altmode are not working 2 dispext/dcpext
devices are enough. "dispext0" will be used for the HDMI output and
dispext1 can be used for DP-altmopde experiments.
All nodes are disabled and have be enabled explicitly in device .dts
or .dtsi.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6002.dtsi        |  10 ++
 arch/arm64/boot/dts/apple/t600x-common.dtsi |  28 +++++
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi   | 126 ++++++++++++++++++++
 3 files changed, 164 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6002.dtsi b/arch/arm64/boot/dts/apple/t6002.dtsi
index a7dfc6196fa724..331cc49b42994d 100644
--- a/arch/arm64/boot/dts/apple/t6002.dtsi
+++ b/arch/arm64/boot/dts/apple/t6002.dtsi
@@ -305,6 +305,16 @@
 	};
 };
 
+&dcpext0_die1 {
+	// TODO: verify
+	apple,bw-scratch = <&pmgr_dcp 0 4 0x9c0>;
+};
+
+&dcpext1_die1 {
+	// TODO: verify
+	apple,bw-scratch = <&pmgr_dcp 0 4 0x9c8>;
+};
+
 &ps_gfx {
 	// On t6002, the die0 GPU power domain needs both AFR power domains
 	power-domains = <&ps_afr>, <&ps_afr_die1>;
diff --git a/arch/arm64/boot/dts/apple/t600x-common.dtsi b/arch/arm64/boot/dts/apple/t600x-common.dtsi
index 5f62329b628a39..b17018120e79fa 100644
--- a/arch/arm64/boot/dts/apple/t600x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-common.dtsi
@@ -441,6 +441,34 @@
 		clock-frequency = <237333328>;
 		clock-output-names = "clk_disp0";
 	};
+
+	clk_dispext0: clock-dispext0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0";
+	};
+
+	clk_dispext0_die1: clock-dispext0_die1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0_die1";
+	};
+
+	clk_dispext1: clock-dispext1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext1";
+	};
+
+	clk_dispext1_die1: clock-dispext1_die1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext1_die1";
+	};
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index 3fca8efb2dcf17..dbb426d3c2c883 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -24,6 +24,132 @@
 		#performance-domain-cells = <0>;
 	};
 
+	DIE_NODE(dispext0_dart): iommu@289304000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x2 0x89304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 873 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		apple,dma-range = <0x0 0x0 0x0 0xfc000000>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0_dart): iommu@28930c000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x2 0x8930c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 873 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0_mbox): mbox@289c08000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0x89c08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 894 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 895 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 896 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 897 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext0_cpu0)>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0):  dcp@289c00000 {
+		compatible = "apple,t6000-dcpext", "apple,dcpext";
+		mboxes = <&DIE_NODE(dcpext0_mbox)>;
+		mbox-names = "mbox";
+		iommus = <&DIE_NODE(dcpext0_dart) 0>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x2 0x89c00000 0x0 0x4000>,
+			<0x2 0x88000000 0x0 0x3000000>,
+			<0x2 0x89320000 0x0 0x4000>,
+			<0x2 0x89344000 0x0 0x4000>,
+			<0x2 0x89800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x990>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext0_cpu0)>;
+		clocks = <&DIE_NODE(clk_dispext0)>;
+		phandle = <&DIE_NODE(dcpext0)>;
+		apple,dcp-index = <1>;
+		status = "disabled";
+
+		piodma {
+			iommus = <&DIE_NODE(dispext0_dart) 4>;
+		};
+	};
+
+	DIE_NODE(dispext1_dart): iommu@28c304000 {
+		compatible = "apple,t6000-dart", "apple,t8110-dart";
+		reg = <0x2 0x8c304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 909 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		apple,dma-range = <0x0 0x0 0x0 0xfc000000>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1_dart): iommu@28c30c000 {
+		compatible = "apple,t6000-dart", "apple,t8110-dart";
+		reg = <0x2 0x8c30c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 909 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1_mbox): mbox@28cc08000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0x8cc08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 930 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 931 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 932 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 933 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext1_cpu0)>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1):  dcp@28cc00000 {
+		compatible = "apple,t6000-dcpext", "apple,dcpext";
+		mboxes = <&DIE_NODE(dcpext1_mbox)>;
+		mbox-names = "mbox";
+		iommus = <&DIE_NODE(dcpext1_dart) 0>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x2 0x8cc00000 0x0 0x4000>,
+			<0x2 0x8b000000 0x0 0x3000000>,
+			<0x2 0x8c320000 0x0 0x4000>,
+			<0x2 0x8c344000 0x0 0x4000>,
+			<0x2 0x8c800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x998>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext1_cpu0)>;
+		clocks = <&DIE_NODE(clk_dispext1)>;
+		phandle = <&DIE_NODE(dcpext1)>;
+		apple,dcp-index = <2>;
+		status = "disabled";
+
+		piodma {
+			iommus = <&DIE_NODE(dispext1_dart) 4>;
+		};
+	};
+
 	DIE_NODE(pmgr): power-management@28e080000 {
 		compatible = "apple,t6000-pmgr", "apple,pmgr", "syscon", "simple-mfd";
 		#address-cells = <1>;

From 813e9cbdf82d52ba82d011e154fc5f484c71918a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 19:45:46 +0200
Subject: [PATCH 0151/1027] arm64: dts: apple: t602x: Add t6020 dispext device
 nodes

While thunderbolt and DP-altmode are not working 2 dispext/dcpext
devices are enough. "dispext0" will be used for the HDMI output and
dispext1 can be used for DP-altmopde experiments.
All nodes are disabled and have be enabled explicitly in device .dts
or .dtsi.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022.dtsi        |   8 ++
 arch/arm64/boot/dts/apple/t602x-common.dtsi |  28 +++++
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi   | 126 ++++++++++++++++++++
 3 files changed, 162 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6022.dtsi b/arch/arm64/boot/dts/apple/t6022.dtsi
index ebf8e5bf53e86c..e9140440fb65e4 100644
--- a/arch/arm64/boot/dts/apple/t6022.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022.dtsi
@@ -344,6 +344,14 @@
 	};
 };
 
+&dcpext0_die1 {
+	apple,bw-scratch = <&pmgr_dcp 0 4 0x1240>;
+};
+
+&dcpext1_die1 {
+	apple,bw-scratch = <&pmgr_dcp 0 4 0x1248>;
+};
+
 &ps_gfx {
 	// On t6022, the die0 GPU power domain needs both AFR power domains
 	power-domains = <&ps_afr>, <&ps_afr_die1>;
diff --git a/arch/arm64/boot/dts/apple/t602x-common.dtsi b/arch/arm64/boot/dts/apple/t602x-common.dtsi
index 79a2afc1b39268..bc8f439b523a0d 100644
--- a/arch/arm64/boot/dts/apple/t602x-common.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-common.dtsi
@@ -555,6 +555,34 @@
 		clock-output-names = "clk_disp0";
 	};
 
+	clk_dispext0: clock-dispext0 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0";
+	};
+
+	clk_dispext0_die1: clock-dispext0_die1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext0_die1";
+	};
+
+	clk_dispext1: clock-dispext1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext1";
+	};
+
+	clk_dispext1_die1: clock-dispext1_die1 {
+		compatible = "fixed-clock";
+		#clock-cells = <0>;
+		clock-frequency = <0>;
+		clock-output-names = "clk_dispext1_die1";
+	};
+
 	/*
 	 * This is a fabulated representation of the input clock
 	 * to NCO since we don't know the true clock tree.
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index da891047c5db7a..46c24c71014f24 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -24,6 +24,69 @@
 		#performance-domain-cells = <0>;
 	};
 
+	DIE_NODE(dispext0_dart): iommu@289304000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x2 0x89304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 950 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0_dart): iommu@28930c000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x2 0x8930c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 950 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0_mbox): mbox@289c08000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x2 0x89c08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 971 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 972 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 973 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 974 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext0_cpu0)>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext0):  dcp@289c00000 {
+		compatible = "apple,t6020-dcpext", "apple,dcpext";
+		mboxes = <&DIE_NODE(dcpext0_mbox)>;
+		mbox-names = "mbox";
+		iommus = <&DIE_NODE(dcpext0_dart) 5>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x2 0x89c00000 0x0 0x4000>,
+			<0x2 0x88000000 0x0 0x4000000>,
+			<0x2 0x89320000 0x0 0x4000>,
+			<0x2 0x89344000 0x0 0x4000>,
+			<0x2 0x89800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x1210>;
+		power-domains = <&DIE_NODE(ps_dispext0_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext0_cpu0)>;
+		clocks = <&DIE_NODE(clk_dispext0)>;
+		phandle = <&DIE_NODE(dcpext0)>;
+		apple,dcp-index = <1>;
+		status = "disabled";
+
+		piodma {
+			iommus = <&DIE_NODE(dispext0_dart) 4>;
+		};
+	};
+
 	DIE_NODE(pmgr): power-management@28e080000 {
 		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
 		#address-cells = <1>;
@@ -102,6 +165,69 @@
 				<AIC_IRQ DIE_NO 604 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
+	DIE_NODE(dispext1_dart): iommu@315304000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x15304000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 986 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1_dart): iommu@31530c000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x1530c000 0x0 0x4000>;
+		#iommu-cells = <1>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 986 IRQ_TYPE_LEVEL_HIGH>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		apple,dma-range = <0x100 0x0 0x10 0x0>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1_mbox): mbox@315c08000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x15c08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1007 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1008 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1009 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1010 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext1_cpu0)>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dcpext1):  dcp@315c00000 {
+		compatible = "apple,t6020-dcpext", "apple,dcpext";
+		mboxes = <&DIE_NODE(dcpext1_mbox)>;
+		mbox-names = "mbox";
+		iommus = <&DIE_NODE(dcpext1_dart) 5>;
+
+		reg-names = "coproc", "disp-0", "disp-1", "disp-2", "disp-3";
+		reg = <0x3 0x15c00000 0x0 0x4000>,
+			<0x3 0x14000000 0x0 0x4000000>,
+			<0x3 0x15320000 0x0 0x4000>,
+			<0x3 0x15344000 0x0 0x4000>,
+			<0x3 0x15800000 0x0 0x800000>;
+		apple,bw-scratch = <&pmgr_dcp 0 4 0x1218>;
+		power-domains = <&DIE_NODE(ps_dispext1_cpu0)>;
+		resets = <&DIE_NODE(ps_dispext1_cpu0)>;
+		clocks = <&DIE_NODE(clk_dispext1)>;
+		phandle = <&DIE_NODE(dcpext1)>;
+		apple,dcp-index = <2>;
+		status = "disabled";
+
+		piodma {
+			iommus = <&DIE_NODE(dispext1_dart) 4>;
+		};
+	};
+
 	DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
 		compatible = "apple,t6020-pinctrl", "apple,pinctrl";
 		reg = <0x3 0x9b028000 0x0 0x4000>;

From 492e46613448de81c3c71150c087d2a66624dbaf Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 28 Oct 2023 23:12:33 +0200
Subject: [PATCH 0152/1027] arm64: dts: apple: t8112: Add dptx-phy node

On M2 desktop devices more parts of the HDMI output pipeline are under
the OS' control. One of this parts is the primary DPTX phy which drives
the the HDMI port through an integrated MCDP29XX DP to HDMI converter.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index bd8b3e288facfa..4071d8f83a3077 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -1015,6 +1015,17 @@
 			};
 		};
 
+		dptxphy: phy@23c500000 {
+			compatible = "apple,t8112-dptx-phy", "apple,dptx-phy";
+			reg = <0x2 0x3c500000 0x0 0x4000>,
+				<0x2 0x3c540000 0x0 0xc000>;
+			reg-names = "core", "dptx";
+			power-domains = <&ps_dptx_ext_phy>;
+			#phy-cells = <0>;
+			#reset-cells = <0>;
+			status = "disabled"; /* only used on j473 */
+               };
+
 		nub_spmi: spmi@23d0d9300 {
 			compatible = "apple,t8112-spmi", "apple,spmi";
 			reg = <0x2 0x3d714000 0x0 0x100>;

From 9f1d927a5ec27b17560becf1b3c2f19fb6af5c1c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 29 Oct 2023 10:39:17 +0100
Subject: [PATCH 0153/1027] arm64: dts: apple: t602x: Add lpdptx-phy node

On M2 desktop devices more parts of the HDMI output pipeline are under
the OS' control. One of this parts is the primary DPTX phy which drives
the the HDMI port through an integrated MCDP29XX DP to HDMI converter.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index 46c24c71014f24..896986f153afd2 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -253,6 +253,17 @@
 		#interrupt-cells = <2>;
 	};
 
+	DIE_NODE(lpdptxphy): phy@39c000000 {
+		compatible = "apple,t6020-dptx-phy", "apple,dptx-phy";
+		reg = <0x3 0x9c000000 0x0 0x4000>,
+			<0x3 0x9c040000 0x0 0xc000>;
+		reg-names = "core", "dptx";
+		power-domains = <&DIE_NODE(ps_dptx_phy_ps)>;
+		#phy-cells = <0>;
+		#reset-cells = <0>;
+		status = "disabled"; /* only exposed on desktop devices */
+	};
+
 	DIE_NODE(pmgr_gfx): power-management@404e80000 {
 		compatible = "apple,t6020-pmgr", "apple,pmgr", "syscon", "simple-mfd";
 		#address-cells = <1>;

From 8816fd7b07139288078483d146faee3e686a607d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 00:35:54 +0100
Subject: [PATCH 0154/1027] arm64: dts: apple: t600x: Add device nodes for atc
 DP crossbar

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6002-j375d.dts |  2 ++
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 32 +++++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index 95a783b9fb144a..062bfc72575ebf 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -135,11 +135,13 @@
 /delete-node/ &dwc3_2_dart_1_die1;
 /delete-node/ &dwc3_2_die1;
 /delete-node/ &atcphy2_die1;
+/delete-node/ &atcphy2_xbar_die1;
 
 /delete-node/ &dwc3_3_dart_0_die1;
 /delete-node/ &dwc3_3_dart_1_die1;
 /delete-node/ &dwc3_3_die1;
 /delete-node/ &atcphy3_die1;
+/delete-node/ &atcphy3_xbar_die1;
 
 
 /* delete unused always-on power-domains on die 1 */
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index dbb426d3c2c883..3b79fef7a8fc8d 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -506,6 +506,14 @@
 		power-domains = <&DIE_NODE(ps_atc0_usb)>;
 	};
 
+	DIE_NODE(atcphy0_xbar): mux@70304c000 {
+		compatible = "apple,t6000-display-crossbar";
+		reg = <0x7 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
 		compatible = "apple,t6000-dart";
 		reg = <0xb 0x02f00000 0x0 0x4000>;
@@ -579,6 +587,14 @@
 		power-domains = <&DIE_NODE(ps_atc1_usb)>;
 	};
 
+	DIE_NODE(atcphy1_xbar): mux@b0304c000 {
+		compatible = "apple,t6000-display-crossbar";
+		reg = <0xb 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
 		compatible = "apple,t6000-dart";
 		reg = <0xf 0x02f00000 0x0 0x4000>;
@@ -652,6 +668,14 @@
 		power-domains = <&DIE_NODE(ps_atc2_usb)>;
 	};
 
+	DIE_NODE(atcphy2_xbar): mux@f0304c000 {
+		compatible = "apple,t6000-display-crossbar";
+		reg = <0xf 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x13 0x02f00000 0x0 0x4000>;
@@ -724,3 +748,11 @@
 		svid = <0xff01>, <0x8087>;
 		power-domains = <&DIE_NODE(ps_atc3_usb)>;
 	};
+
+	DIE_NODE(atcphy3_xbar): mux@130304c000 {
+		compatible = "apple,t6000-display-crossbar";
+		reg = <0x13 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		status = "disabled";
+	};

From 540aa8e65819783dc99bea181b13ae0e07c2f494 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 23:17:13 +0200
Subject: [PATCH 0155/1027] arm64: dts: apple: t602x: Add device nodes for atc
 DP crossbar

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-j475d.dts |  2 ++
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi | 32 +++++++++++++++++++++++
 2 files changed, 34 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
index 43dba036456159..1ee076f229f916 100644
--- a/arch/arm64/boot/dts/apple/t6022-j475d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -42,11 +42,13 @@
 /delete-node/ &dwc3_2_dart_1_die1;
 /delete-node/ &dwc3_2_die1;
 /delete-node/ &atcphy2_die1;
+/delete-node/ &atcphy2_xbar_die1;
 
 /delete-node/ &dwc3_3_dart_0_die1;
 /delete-node/ &dwc3_3_dart_1_die1;
 /delete-node/ &dwc3_3_die1;
 /delete-node/ &atcphy3_die1;
+/delete-node/ &atcphy3_xbar_die1;
 
 
 /* delete unused always-on power-domains on die 1 */
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index 896986f153afd2..3a9580d48ab6e3 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -326,6 +326,14 @@
 		power-domains = <&DIE_NODE(ps_atc0_usb)>;
 	};
 
+	DIE_NODE(atcphy0_xbar): mux@70304c000 {
+		compatible = "apple,t6020-display-crossbar";
+		reg = <0x7 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc0_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_1_dart_0): iommu@b02f00000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0xb 0x02f00000 0x0 0x4000>;
@@ -380,6 +388,14 @@
 		power-domains = <&DIE_NODE(ps_atc1_usb)>;
 	};
 
+	DIE_NODE(atcphy1_xbar): mux@b0304c000 {
+		compatible = "apple,t6020-display-crossbar";
+		reg = <0xb 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc1_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_2_dart_0): iommu@f02f00000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0xf 0x02f00000 0x0 0x4000>;
@@ -434,6 +450,14 @@
 		power-domains = <&DIE_NODE(ps_atc2_usb)>;
 	};
 
+	DIE_NODE(atcphy2_xbar): mux@f0304c000 {
+		compatible = "apple,t6020-display-crossbar";
+		reg = <0xf 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc2_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(dwc3_3_dart_0): iommu@1302f00000 {
 		compatible = "apple,t6020-dart", "apple,t8110-dart";
 		reg = <0x13 0x02f00000 0x0 0x4000>;
@@ -488,6 +512,14 @@
 		power-domains = <&DIE_NODE(ps_atc3_usb)>;
 	};
 
+	DIE_NODE(atcphy3_xbar): mux@130304c000 {
+		compatible = "apple,t6020-display-crossbar";
+		reg = <0x13 0x0304c000 0x0 0x4000>;
+		#mux-control-cells = <1>;
+		power-domains = <&DIE_NODE(ps_atc3_usb)>;
+		status = "disabled";
+	};
+
 	DIE_NODE(pcie_ge): pcie@1680000000 {
 		compatible = "apple,t6020-pcie-ge", "apple,t6020-pcie";
 		device_type = "pci";

From 186dd9b2734ca5e1d612b0bfc939ac68e8814bd0 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 28 Oct 2023 23:40:47 +0200
Subject: [PATCH 0156/1027] arm64: dts: apple: t8112-j473: Enable
 dcp/dptx-phy/dp2hdmi

After all parts are in place enable DCP on the M2 Mac Mini for HDMI
output.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 18 ++++++++++++++++--
 1 file changed, 16 insertions(+), 2 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 686426f9468d18..dbd66483f2f56e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -27,9 +27,23 @@
 	power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>;
 };
 
-/* disable dcp until it is supported */
+&dptxphy {
+	status = "okay";
+};
+
 &dcp {
-	status = "disabled";
+	status = "okay";
+	apple,connector-type = "HDMI-A";
+
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_aop 49 GPIO_ACTIVE_HIGH>;
+
+	hdmi-pwren-gpios = <&smc_gpio 21 GPIO_ACTIVE_HIGH>;
+	dp2hdmi-pwren-gpios = <&smc_gpio 22 GPIO_ACTIVE_HIGH>;
+
+	phys = <&dptxphy>;
+	phy-names = "dp-phy";
+	apple,dptx-phy = <5>;
 };
 
 /*

From 414f0a0b8728b33af9181c02d8a36ec55b9a9820 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 21:47:19 +0100
Subject: [PATCH 0157/1027] arm64: dts: apple: t6020-j474,t6021-j475: Enable
 dcp/dptx-phy/dp2hdmi

After all parts are in place enable the DCP on the M2 Pro Mac Mini and
the M2 Max Mac Studio for HDMI output.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts     | 45 +++++++++++++++++++
 arch/arm64/boot/dts/apple/t6021-j475c.dts     | 19 ++++++++
 .../arm64/boot/dts/apple/t602x-j474-j475.dtsi |  5 ---
 3 files changed, 64 insertions(+), 5 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index ab0e50bbd49dd0..ea763d25b2e874 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -45,6 +45,51 @@
 	model = "Mac mini J474";
 };
 
+&lpdptxphy {
+	status = "okay";
+};
+
+#define USE_DCPEXT0 0
+
+#if USE_DCPEXT0
+/ {
+	aliases {
+		dcpext0 = &dcpext0;
+	};
+};
+&dcp {
+	status = "disabled";
+};
+&display {
+	iommus = <&dispext0_dart 0>;
+};
+&dispext0_dart {
+	status = "okay";
+};
+&dcpext0_dart {
+	status = "okay";
+};
+&dcpext0_mbox {
+	status = "okay";
+};
+&dcpext0 {
+#else
+&dcp {
+#endif
+	status = "okay";
+	apple,connector-type = "HDMI-A";
+
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>;
+
+	hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>;
+	dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>;
+
+	phys = <&lpdptxphy>;
+	phy-names = "dp-phy";
+	apple,dptx-phy = <4>;
+};
+
 &gpu {
 	/* Apple does not do this, but they probably should */
 	apple,perf-base-pstate = <3>;
diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts
index 591f637c4a6a98..46708998e86f96 100644
--- a/arch/arm64/boot/dts/apple/t6021-j475c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts
@@ -47,3 +47,22 @@
 	compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio";
 	model = "Mac Studio J475";
 };
+
+&lpdptxphy {
+	status = "okay";
+};
+
+&dcp {
+	status = "okay";
+	apple,connector-type = "HDMI-A";
+
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>;
+
+	hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>;
+	dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>;
+
+	phys = <&lpdptxphy>;
+	phy-names = "dp-phy";
+	apple,dptx-phy = <4>;
+};
diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
index 0553e557d8becb..b1390aefdbd7c1 100644
--- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
@@ -21,11 +21,6 @@
 	power-domains = <&ps_disp0_cpu0>, <&ps_dptx_phy_ps>;
 };
 
-/* disable dcp until it is supported */
-&dcp {
-	status = "disabled";
-};
-
 &hpm0 {
 	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };

From a77060c874a70fa1ab2735b0d3a8e86e59716dcc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 Nov 2023 22:33:29 +0100
Subject: [PATCH 0158/1027] arm64: dts: apple: t6022-{j180,j475}: Enable
 dcpext0/dptx-phy/dp2hdmi

After all parts are in place enable dcpext on M2 Ultra Mac Pro and
Studio. On the Mac Pro only the HDMI output connected to die1 is
enabled.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-j475d.dts  |  6 +++
 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi | 43 ++++++++++++++++++++--
 2 files changed, 46 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
index 1ee076f229f916..0d53f26b6f5352 100644
--- a/arch/arm64/boot/dts/apple/t6022-j475d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -21,6 +21,7 @@
 	aliases {
 		atcphy4 = &atcphy0_die1;
 		atcphy5 = &atcphy1_die1;
+		/delete-property/ dcp;
 	};
 };
 
@@ -28,6 +29,11 @@
 	power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>;
 };
 
+&dcpext0_die1 {
+	// J180 misses "function-dp2hdmi_pwr_en"
+	dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>;
+};
+
 &typec4 {
 	label = "USB-C Front Right";
 };
diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
index 4f552c2530aa7a..9b7391b922db54 100644
--- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -9,11 +9,48 @@
  * Copyright The Asahi Linux Contributors
  */
 
-/* disable unused display node */
+/ {
+	aliases {
+		dcpext4 = &dcpext0_die1;
+		disp0 = &display;
+	};
+};
+
+&lpdptxphy_die1 {
+	status = "okay";
+};
 
 &display {
-	status = "disabled";
-	iommus = <>; /* <&dispext0_dart_die1 0>; */
+	iommus = <&dispext0_dart_die1 0>;
+};
+
+&dispext0_dart_die1 {
+	status = "okay";
+};
+
+&dcpext0_dart_die1 {
+	status = "okay";
+};
+
+&dcpext0_mbox_die1 {
+	status = "okay";
+};
+
+&dcpext0_die1 {
+	status = "okay";
+	apple,connector-type = "HDMI-A";
+
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_aop 41 GPIO_ACTIVE_HIGH>;
+
+	hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>;
+	// J180 misses "function-dp2hdmi_pwr_en"
+	// dp2hdmi-pwren-gpios = <&smc_gpio 25 GPIO_ACTIVE_HIGH>;
+
+	phys = <&lpdptxphy_die1>;
+	phy-names = "dp-phy";
+	apple,dptx-phy = <4>;
+	apple,dptx-die = <1>;
 };
 
 /* delete missing dcp0/disp0 */

From 4ff29cace0f58623a5a3a10c18601a9538595a78 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 20:39:42 +0100
Subject: [PATCH 0159/1027] arm64: dts: apple: Fill device node for dp2hdmi on
 Macbook Pros

The HDMI output on the 14 and 16 inch Macbook Pros with M1/M2 Pro/Max is
driven by an unused ATC port using the phy and crossbar. The DP output
from any dcpext display controller is routed to a Kinetic DP2HDMI
converter (MCDP2920 and a unknown HDMI 2.1 capable variant).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 .../arm64/boot/dts/apple/t600x-j314-j316.dtsi | 48 +++++++++++++++++++
 .../arm64/boot/dts/apple/t602x-j414-j416.dtsi |  5 ++
 2 files changed, 53 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 2631daf667e14e..3f1ade2f9066ab 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -19,6 +19,7 @@
 		atcphy3 = &atcphy3;
 		bluetooth0 = &bluetooth0;
 		dcp = &dcp;
+		dcpext0 = &dcpext0;
 		disp0 = &display;
 		disp0_piodma = &disp0_piodma;
 		nvram = &nvram;
@@ -77,6 +78,49 @@
 	};
 };
 
+&display {
+	iommus = <&disp0_dart 0>, <&dispext0_dart 0>;
+};
+
+&dispext0_dart {
+	status = "okay";
+};
+
+&dcpext0_dart {
+	status = "okay";
+};
+
+&dcpext0_mbox {
+	status = "okay";
+};
+
+&dcpext0 {
+	/* enabled by the loader */
+	apple,connector-type = "HDMI-A";
+
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_nub 15 GPIO_ACTIVE_HIGH>;
+
+	hdmi-pwren-gpios = <&smc_gpio 23 GPIO_ACTIVE_HIGH>;
+	dp2hdmi-pwren-gpios = <&smc_gpio 6 GPIO_ACTIVE_HIGH>;
+
+	phy-names = "dp-phy";
+	phys = <&atcphy3 PHY_TYPE_DP>;
+	phy-names = "dp-phy";
+	mux-controls = <&atcphy3_xbar 0>;
+	mux-control-names = "dp-xbar";
+	mux-index = <0>;
+	apple,dptx-phy = <3>;
+};
+
+&atcphy3 {
+	apple,mode-fixed-dp;
+};
+
+&atcphy3_xbar {
+	status = "okay";
+};
+
 /* USB Type C */
 &i2c0 {
 	hpm0: usb-pd@38 {
@@ -416,6 +460,10 @@
 &dwc3_3 {
 	status = "disabled";
 };
+/* Delete unused dwc3_3 to prevent dt_disable_missing_devs() from disabling
+ * atcphy3 via phandle references from a disablecd device.
+ */
+/delete-node/ &dwc3_3;
 
 &ps_atc3_usb_aon {
 	/delete-property/ apple,always-on;
diff --git a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
index 9eb19bfef4171a..6e8df7750d2a43 100644
--- a/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j414-j416.dtsi
@@ -30,6 +30,11 @@
 	apple,always-on;
 };
 
+&dcpext0 {
+	/*  HDMI HPD gpio, used as interrupt*/
+	hdmi-hpd-gpios = <&pinctrl_aop 25 GPIO_ACTIVE_HIGH>;
+};
+
 &hpm0 {
 	interrupts = <44 IRQ_TYPE_LEVEL_LOW>;
 };

From f4ae65dc05c55359331987309d7ce136ad3c4dba Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 27 Nov 2023 00:35:55 +0100
Subject: [PATCH 0160/1027] arm64: dts: apple: j474s/j475c: Use dcpext0 for
 HDMI out

dcp on t8112 and t602x does not wake up after sleep + reset but dcpext*
does. Use dcpext0 for sharing the code with M1* devices.
My interpretation of the tea leaves from Apple's marketing department
suggests that dcpext is more capable (6k 60Hz vs 5k 60Hz) so use dcpext
as long as only one is used.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts |  8 +++++-
 arch/arm64/boot/dts/apple/t6021-j475c.dts | 33 +++++++++++++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index ea763d25b2e874..e4d9b580f2ef4d 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -49,14 +49,20 @@
 	status = "okay";
 };
 
-#define USE_DCPEXT0 0
+#define USE_DCPEXT0 1
 
 #if USE_DCPEXT0
 / {
 	aliases {
 		dcpext0 = &dcpext0;
+		/delete-property/ dcp;
 	};
 };
+
+&framebuffer0 {
+	power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>;
+};
+
 &dcp {
 	status = "disabled";
 };
diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts
index 46708998e86f96..c954d02a038d9f 100644
--- a/arch/arm64/boot/dts/apple/t6021-j475c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts
@@ -52,7 +52,40 @@
 	status = "okay";
 };
 
+
+#define USE_DCPEXT0 1
+
+#if USE_DCPEXT0
+/ {
+	aliases {
+		dcpext0 = &dcpext0;
+		/delete-property/ dcp;
+	};
+};
+
+&framebuffer0 {
+	power-domains = <&ps_dispext0_cpu0>, <&ps_dptx_phy_ps>;
+};
+
+&dcp {
+	status = "disabled";
+};
+&display {
+	iommus = <&dispext0_dart 0>;
+};
+&dispext0_dart {
+	status = "okay";
+};
+&dcpext0_dart {
+	status = "okay";
+};
+&dcpext0_mbox {
+	status = "okay";
+};
+&dcpext0 {
+#else
 &dcp {
+#endif
 	status = "okay";
 	apple,connector-type = "HDMI-A";
 

From 0454fb65c328726fb6b81e0699fde77a8142731b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 27 Nov 2023 00:45:27 +0100
Subject: [PATCH 0161/1027] arm64: dts: apple: t8112-j473: Use dcpext for HDMI
 out

dcp on t8112 and t602x does not wake up after sleep + reset but dcpext*
does. Use dcpext0 for sharing the code with M1* devices.
My interpretation of the tea leaves from Apple's marketing department
suggests that dcpext is more capable (6k 60Hz vs 5k 60Hz) so use dcpext
as long as only one is used.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index dbd66483f2f56e..8697eef5c7a3fc 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -18,13 +18,15 @@
 
 	aliases {
 		bluetooth0 = &bluetooth0;
+		/delete-property/ dcp;
+		dcpext = &dcpext;
 		ethernet0 = &ethernet0;
 		wifi0 = &wifi0;
 	};
 };
 
 &framebuffer0 {
-	power-domains = <&ps_disp0_cpu0>, <&ps_dptx_ext_phy>;
+	power-domains = <&ps_dispext_cpu0>, <&ps_dptx_ext_phy>;
 };
 
 &dptxphy {
@@ -32,6 +34,22 @@
 };
 
 &dcp {
+	status = "disabled";
+};
+
+&display {
+	iommus = <&dispext0_dart 0>;
+};
+&dispext0_dart {
+	status = "okay";
+};
+&dcpext_dart {
+	status = "okay";
+};
+&dcpext_mbox {
+	status = "okay";
+};
+&dcpext {
 	status = "okay";
 	apple,connector-type = "HDMI-A";
 

From 3b722f91f425f15ec39aa37fbab86c872354ca2b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 30 Nov 2023 22:16:15 +0900
Subject: [PATCH 0162/1027] fixup! arm64: dts: apple: Add initial t602x device
 trees

---
 arch/arm64/boot/dts/apple/t6021-j416c.dts | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t6021-j416c.dts b/arch/arm64/boot/dts/apple/t6021-j416c.dts
index 331a1e93e7f352..786ac2393d7535 100644
--- a/arch/arm64/boot/dts/apple/t6021-j416c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j416c.dts
@@ -17,7 +17,10 @@
 	model = "Apple MacBook Pro (16-inch, M2 Max, 2023)";
 };
 
-/* This machine model (only) has two extra boost CPU P-states */
+/* This machine model (only) has two extra boost CPU P-states *
+ * Disabled: Only the highest CPU bin (38 GPU cores) has this.
+ * Keep this disabled until m1n1 learns how to remove these OPPs
+ * for unsupported machines, otherwise it breaks cpufreq.
 &avalanche_opp {
 	opp18 {
 		opp-hz = /bits/ 64 <3528000000>;
@@ -32,6 +35,7 @@
 		turbo-mode;
 	};
 };
+*/
 
 &wifi0 {
 	brcm,board-type = "apple,amami";

From f9d692bc2ca9d9fb700c2f238990a9580fa2e171 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 5 Mar 2024 20:54:53 +0100
Subject: [PATCH 0163/1027] fixup! arch: arm64: apple: Add dcp panel node for
 t600x based laptops

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 3f1ade2f9066ab..19c270019c26d7 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -40,6 +40,7 @@
 			/* Format properties will be added by loader */
 			status = "disabled";
 			power-domains = <&ps_disp0_cpu0>;
+			panel = &panel;
 		};
 	};
 

From b50c6e07148410cce2582e7f5c85a751a3a0ca40 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 5 Mar 2024 20:59:32 +0100
Subject: [PATCH 0164/1027] fixup! arch: arm64: apple: Add dcp panel node for
 t8103 based laptops and imacs

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j293.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j313.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j456.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8103-j457.dts | 4 ++++
 4 files changed, 16 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index bb8b878630bac1..74f205e6662697 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -43,6 +43,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,honshu";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index dc96f17aea2fed..97facb88428fe6 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -39,6 +39,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,shikoku";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index b7cc5cb8a60af1..b77f998009fae3 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -30,6 +30,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,capri";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 10742637efc2f3..282dc912a49444 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -30,6 +30,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,santorini";
 };

From a1db7c0557d3ccf4bcf0e969d369db2fae7e01d9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 5 Mar 2024 21:00:32 +0100
Subject: [PATCH 0165/1027] fixup! arm64: dts: apple: t8112: Add dcp/disp0
 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts | 4 ++++
 arch/arm64/boot/dts/apple/t8112-j493.dts | 4 ++++
 2 files changed, 8 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index d5104c24d2e4e3..1f75df87a12525 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -46,6 +46,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 0624d854b5542e..059edbdca3ec40 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -46,6 +46,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 &display_dfr {
 	status = "okay";
 	#address-cells = <1>;

From eaab4279599a48763bb478954696bbdb5056cf68 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 5 Mar 2024 21:01:13 +0100
Subject: [PATCH 0166/1027] fixup! arm64: dts: apple: Add devicetree for
 Macbook Air (15-inch, M2, 2023)

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j415.dts | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index 82f86859efdbea..8aa576477837d8 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -46,6 +46,10 @@
 	};
 };
 
+&framebuffer0 {
+	panel = &panel;
+};
+
 /*
  * Force the bus number assignments so that we can declare some of the
  * on-board devices and properties that are populated by the bootloader

From eae1a5f0563745d01b5688c4245afecd1ac20e89 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 25 Dec 2023 21:34:28 +0100
Subject: [PATCH 0167/1027] fixup! arm64: dts: apple: Add MTP nodes to t6020x

---
 arch/arm64/boot/dts/apple/t602x-die0.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 63040483d9e2c6..9375e5777e7126 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -198,7 +198,7 @@
 	};
 
 	mtp_mbox: mbox@2a9408000 {
-		compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
 		reg = <0x2 0xa9408000 0x0 0x4000>;
 		interrupt-parent = <&aic>;
 		interrupts = <AIC_IRQ 0 693 IRQ_TYPE_LEVEL_HIGH>,

From 5e39f77d7066e2907927adf5fe34064ef7628e02 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 12 Feb 2023 15:26:30 +0100
Subject: [PATCH 0168/1027] arm64: apple: t8103-pmgr: SIO: Add audio, spi and
 uart power-domains
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC
firmware to run. This is not explicitly expressed in the ADT (probably
since the power-domains are implicitly turned on when macOS uses SIO).
Since we plan to use SIO only for DP/HDMI audio add the power-domains
explicitly as dependency of ps_sio_cpu.
They might be better placed directly into the SIO node but the SIO
driver doesn't support multiple power-domains.

Signed-off-by: Janne Grunau <j@jannau.net>
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t8103-pmgr.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 10facd0c01e420..5d3846d44e3578 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -234,7 +234,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "sio_cpu";
-		power-domains = <&ps_sio>;
+		power-domains = <&ps_sio &ps_uart_p &ps_spi_p &ps_dpa0>;
 	};
 
 	ps_fpwm0: power-controller@1d8 {

From eba8a7dfaf9046c66bd0240516a2d40bf9926119 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 Nov 2023 23:58:10 +0100
Subject: [PATCH 0169/1027] arm64: apple: t8112-pmgr: SIO: Add audio, spi and
 uart power-domains

The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC
firmware to run. This is not explicitly expressed in the ADT (probably
since the power-domains are implicitly turned on when macOS uses SIO).
Since we plan to use SIO only for DP/HDMI audio add the power-domains
explicitly as dependency of ps_sio_cpu.
They might be better placed directly into the SIO node but the SIO
driver doesn't support multiple power-domains.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 102ff3ad0535d0..ab8ec9bd4e4401 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -176,7 +176,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = "sio_cpu";
-		power-domains = <&ps_sio>;
+		power-domains = <&ps_sio &ps_uart_p &ps_spi_p &ps_dpa0>;
 	};
 
 	ps_fpwm0: power-controller@1c8 {

From b4465be3536cb998c1d79b2f61fe81dfd95ec44d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 21 Apr 2024 18:01:37 +0200
Subject: [PATCH 0170/1027] arm64: apple: t600x: pmgr: SIO: Add audio, spi and
 uart power-domains

The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC
firmware to run. This is not explicitly expressed in the ADT (probably
since the power-domains are implicitly turned on when macOS uses SIO).
Since we plan to use SIO only for DP/HDMI audio add the power-domains
explicitly as dependency of ps_sio_cpu.
They might be better placed directly into the SIO node butr the SIO
driver doesn't support multiple power-domains.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 88bd7a760f370f..3517b2aeb5f61f 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -826,7 +826,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(sio_cpu);
-		power-domains = <&DIE_NODE(ps_sio)>;
+		power-domains = <&DIE_NODE(ps_sio) &DIE_NODE(ps_uart_p) &DIE_NODE(ps_spi_p) &DIE_NODE(ps_audio_p)>;
 	};
 
 	DIE_NODE(ps_fpwm0): power-controller@190 {

From b0e9a258d97de80c4c863e07827486c874879ed0 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 21 Apr 2024 18:01:37 +0200
Subject: [PATCH 0171/1027] arm64: apple: t602x: pmgr: SIO: Add audio, spi and
 uart power-domains

The power-domains AUDIO_P, SPI_P and UART_P are necessary for SIO's ASC
firmware to run. This is not explicitly expressed in the ADT (probably
since the power-domains are implicitly turned on when macOS uses SIO).
Since we plan to use SIO only for DP/HDMI audio add the power-domains
explicitly as dependency of ps_sio_cpu.
They might be better placed directly into the SIO node butr the SIO
driver doesn't support multiple power-domains.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t602x-pmgr.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
index 063181e44412b5..d97287833f1bf3 100644
--- a/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-pmgr.dtsi
@@ -1262,7 +1262,7 @@
 		#power-domain-cells = <0>;
 		#reset-cells = <0>;
 		label = DIE_LABEL(sio_cpu);
-		power-domains = <&DIE_NODE(ps_sio)>;
+		power-domains = <&DIE_NODE(ps_sio) &DIE_NODE(ps_uart_p) &DIE_NODE(ps_spi_p) &DIE_NODE(ps_audio_p)>;
 	};
 
 	DIE_NODE(ps_fpwm0): power-controller@1e8 {

From daa89d86faf85ca2f3991e4e8f770a2bab5c7799 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 28 Nov 2022 17:10:01 +0100
Subject: [PATCH 0172/1027] arm64: apple: t8103: Add SIO, DPA nodes; hook up to
 DCP
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts |  5 ++
 arch/arm64/boot/dts/apple/t8103.dtsi     | 90 ++++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index b20300a5b18306..b12dbecb5f0cd7 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -18,6 +18,7 @@
 
 	aliases {
 		ethernet0 = &ethernet0;
+		sio = &sio;
 	};
 };
 
@@ -25,6 +26,10 @@
 	apple,connector-type = "HDMI-A";
 };
 
+&dpaudio0 {
+	status = "okay";
+};
+
 &bluetooth0 {
 	brcm,board-type = "apple,atlantisb";
 };
diff --git a/arch/arm64/boot/dts/apple/t8103.dtsi b/arch/arm64/boot/dts/apple/t8103.dtsi
index e970db75042fd1..e9c42d3a2f9658 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -628,6 +628,17 @@
 				iommus = <&disp0_dart 4>;
 				phandle = <&disp0_piodma>;
 			};
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dcp_audio: endpoint {
+						remote-endpoint = <&dpaudio0_dcp>;
+					};
+				};
+			};
 		};
 
 		display: display-subsystem {
@@ -846,6 +857,32 @@
 			status = "disabled";
 		};
 
+		sio_mbox: mbox@236408000 {
+			compatible = "apple,t8103-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x36408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 640 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 641 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 642 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 643 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_sio>;
+		};
+
+		sio: sio@236400000 {
+			compatible = "apple,t8103-sio", "apple,sio";
+			reg = <0x2 0x36400000 0x0 0x8000>;
+			dma-channels = <128>;
+			#dma-cells = <1>;
+			mboxes = <&sio_mbox>;
+			iommus = <&sio_dart 0>;
+			power-domains = <&ps_sio_cpu>;
+			resets = <&ps_sio>; /* TODO: verify reset does something */
+			status = "disabled";
+		};
+
 		admac: dma-controller@238200000 {
 			compatible = "apple,t8103-admac", "apple,admac";
 			reg = <0x2 0x38200000 0x0 0x34000>;
@@ -860,6 +897,48 @@
 			resets = <&ps_audio_p>;
 		};
 
+		dpaudio0: audio-controller@238330000 {
+			compatible = "apple,t8103-dpaudio", "apple,dpaudio";
+			reg = <0x2 0x38330000 0x0 0x4000>;
+			dmas = <&sio 0x64>;
+			dma-names = "tx";
+			power-domains = <&ps_dpa0>;
+			reset-domains = <&ps_dpa0>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dpaudio0_dcp: endpoint {
+						remote-endpoint = <&dcp_audio>;
+					};
+				};
+			};
+		};
+
+		dpaudio1: audio-controller@238334000 {
+			compatible = "apple,t8103-dpaudio", "apple,dpaudio";
+			reg = <0x2 0x38334000 0x0 0x4000>;
+			dmas = <&sio 0x66>;
+			dma-names = "tx";
+			power-domains = <&ps_dpa1>;
+			reset-domains = <&ps_dpa1>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dpaudio1_dcp: endpoint {
+						remote-endpoint = <&dcpext_audio>;
+					};
+				};
+			};
+		};
+
 		mca: i2s@238400000 {
 			compatible = "apple,t8103-mca", "apple,mca";
 			reg = <0x2 0x38400000 0x0 0x18000>,
@@ -1257,6 +1336,17 @@
 			piodma {
 				iommus = <&dispext0_dart 4>;
 			};
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dcpext_audio: endpoint {
+						remote-endpoint = <&dpaudio1_dcp>;
+					};
+				};
+			};
 		};
 
 		ans_mbox: mbox@277408000 {

From 1d0562a8587d264ded90157264edec557c6da067 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 Nov 2023 23:35:07 +0100
Subject: [PATCH 0173/1027] arm64: apple: t8112: Add SIO, DPA nodes; hook up to
 DCP

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts |  5 ++
 arch/arm64/boot/dts/apple/t8112.dtsi     | 90 ++++++++++++++++++++++++
 2 files changed, 95 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 8697eef5c7a3fc..5ad461092fbd7e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -21,6 +21,7 @@
 		/delete-property/ dcp;
 		dcpext = &dcpext;
 		ethernet0 = &ethernet0;
+		sio = &sio;
 		wifi0 = &wifi0;
 	};
 };
@@ -64,6 +65,10 @@
 	apple,dptx-phy = <5>;
 };
 
+&dpaudio1 {
+	status = "okay";
+};
+
 /*
  * Provide labels for the USB type C ports.
  */
diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index 4071d8f83a3077..a14e9dc3b4dbb7 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -702,6 +702,17 @@
 				iommus = <&disp0_dart 4>;
 				phandle = <&disp0_piodma>;
 			};
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dcp_audio: endpoint {
+						remote-endpoint = <&dpaudio0_dcp>;
+					};
+				};
+			};
 		};
 
 		display: display-subsystem {
@@ -856,6 +867,32 @@
 			status = "disabled";
 		};
 
+		sio_mbox: mbox@236408000 {
+			compatible = "apple,t8112-asc-mailbox", "apple,asc-mailbox-v4";
+			reg = <0x2 0x36408000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 774 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 775 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 776 IRQ_TYPE_LEVEL_HIGH>,
+				<AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>;
+			interrupt-names = "send-empty", "send-not-empty",
+				"recv-empty", "recv-not-empty";
+			#mbox-cells = <0>;
+			power-domains = <&ps_sio_cpu>;
+		};
+
+		sio: sio@236400000 {
+			compatible = "apple,t8112-sio", "apple,sio";
+			reg = <0x2 0x36400000 0x0 0x8000>;
+			dma-channels = <128>;
+			#dma-cells = <1>;
+			mboxes = <&sio_mbox>;
+			iommus = <&sio_dart 0>;
+			power-domains = <&ps_sio_cpu>;
+			resets = <&ps_sio>; /* TODO: verify reset does something */
+			status = "disabled";
+		};
+
 		admac: dma-controller@238200000 {
 			compatible = "apple,t8112-admac", "apple,admac";
 			reg = <0x2 0x38200000 0x0 0x34000>;
@@ -870,6 +907,48 @@
 			resets = <&ps_audio_p>;
 		};
 
+		dpaudio0: audio-controller@238330000 {
+			compatible = "apple,t8112-dpaudio", "apple,dpaudio";
+			reg = <0x2 0x38330000 0x0 0x4000>;
+			dmas = <&sio 0x64>;
+			dma-names = "tx";
+			power-domains = <&ps_dpa0>;
+			reset-domains = <&ps_dpa0>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dpaudio0_dcp: endpoint {
+						remote-endpoint = <&dcp_audio>;
+					};
+				};
+			};
+		};
+
+		dpaudio1: audio-controller@238334000 {
+			compatible = "apple,t8112-dpaudio", "apple,dpaudio";
+			reg = <0x2 0x38334000 0x0 0x4000>;
+			dmas = <&sio 0x66>;
+			dma-names = "tx";
+			power-domains = <&ps_dpa1>;
+			reset-domains = <&ps_dpa1>;
+			status = "disabled";
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dpaudio1_dcp: endpoint {
+						remote-endpoint = <&dcpext_audio>;
+					};
+				};
+			};
+		};
+
 		mca: i2s@238400000 {
 			compatible = "apple,t8112-mca", "apple,mca";
 			reg = <0x2 0x38400000 0x0 0x18000>,
@@ -1455,6 +1534,17 @@
 			piodma {
 				iommus = <&dispext0_dart 4>;
 			};
+
+			ports {
+				#address-cells = <1>;
+				#size-cells = <0>;
+				port@0 {
+					reg = <0>;
+					dcpext_audio: endpoint {
+						remote-endpoint = <&dpaudio1_dcp>;
+					};
+				};
+			};
 		};
 
 		ans_mbox: mbox@277408000 {

From eb0282e6874bb0427d7de4ce3cad4dd4cfa4d763 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 19 Nov 2023 14:55:00 +0100
Subject: [PATCH 0174/1027] arm64: apple: t600x: Move dart_sio* to dieX

j375d uses SIO on the second die for DP audio for its dcpexts.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi | 18 ------------------
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 18 ++++++++++++++++++
 2 files changed, 18 insertions(+), 18 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 89357b619363c2..252c05316d7186 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -248,24 +248,6 @@
 		phandle = <&display>;
 	};
 
-	sio_dart_0: iommu@39b004000 {
-		compatible = "apple,t6000-dart";
-		reg = <0x3 0x9b004000 0x0 0x4000>;
-		interrupt-parent = <&aic>;
-		interrupts = <AIC_IRQ 0 1130 IRQ_TYPE_LEVEL_HIGH>;
-		#iommu-cells = <1>;
-		power-domains = <&ps_sio_cpu>;
-	};
-
-	sio_dart_1: iommu@39b008000 {
-		compatible = "apple,t6000-dart";
-		reg = <0x3 0x9b008000 0x0 0x8000>;
-		interrupt-parent = <&aic>;
-		interrupts = <AIC_IRQ 0 1130 IRQ_TYPE_LEVEL_HIGH>;
-		#iommu-cells = <1>;
-		power-domains = <&ps_sio_cpu>;
-	};
-
 	fpwm0: pwm@39b030000 {
 		compatible = "apple,t6000-fpwm", "apple,s5l-fpwm";
 		reg = <0x3 0x9b030000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index 3b79fef7a8fc8d..041f9788fe8b09 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -408,6 +408,24 @@
 				<AIC_IRQ DIE_NO 573 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
+	DIE_NODE(sio_dart_0): iommu@39b004000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x9b004000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1130 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+	};
+
+	DIE_NODE(sio_dart_1): iommu@39b008000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x9b008000 0x0 0x8000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1130 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+	};
+
 	DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
 		compatible = "apple,t6000-pinctrl", "apple,pinctrl";
 		reg = <0x3 0x9b028000 0x0 0x4000>;

From 92008b65cdbc00541a0983f8ef64608fe31a6672 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 19 Nov 2023 14:57:09 +0100
Subject: [PATCH 0175/1027] arm64: apple: t600x: Add sio and dpaudio device
 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-die0.dtsi |  32 +++++
 arch/arm64/boot/dts/apple/t600x-dieX.dtsi | 136 ++++++++++++++++++++++
 2 files changed, 168 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index 252c05316d7186..a378f65f9126d9 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -203,6 +203,27 @@
 		apple,dma-range = <0x1f0 0x0 0x0 0xfc000000>;
 	};
 
+	dpaudio0: audio-controller@39b500000 {
+		compatible = "apple,t6000-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b500000 0x0 0x4000>;
+		dmas = <&sio 0x64>;
+		dma-names = "tx";
+		power-domains = <&ps_dpa0>;
+		reset-domains = <&ps_dpa0>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				dpaudio0_dcp: endpoint {
+					remote-endpoint = <&dcp_audio>;
+				};
+			};
+		};
+	};
+
 	dcp_mbox: mbox@38bc08000 {
 		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
 		reg = <0x3 0x8bc08000 0x0 0x4000>;
@@ -239,6 +260,17 @@
 			iommus = <&disp0_dart 4>;
 			phandle = <&disp0_piodma>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				dcp_audio: endpoint {
+					remote-endpoint = <&dpaudio0_dcp>;
+				};
+			};
+		};
 	};
 
 	display: display-subsystem {
diff --git a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
index 041f9788fe8b09..00e8ce85688507 100644
--- a/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-dieX.dtsi
@@ -85,6 +85,17 @@
 		piodma {
 			iommus = <&DIE_NODE(dispext0_dart) 4>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dcpext0_audio): endpoint {
+					remote-endpoint = <&DIE_NODE(dpaudio1_dcp)>;
+				};
+			};
+		};
 	};
 
 	DIE_NODE(dispext1_dart): iommu@28c304000 {
@@ -148,6 +159,17 @@
 		piodma {
 			iommus = <&DIE_NODE(dispext1_dart) 4>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dcpext1_audio): endpoint {
+					remote-endpoint = <&DIE_NODE(dpaudio2_dcp)>;
+				};
+			};
+		};
 	};
 
 	DIE_NODE(pmgr): power-management@28e080000 {
@@ -451,6 +473,120 @@
 		#interrupt-cells = <2>;
 	};
 
+	DIE_NODE(sio_mbox): mbox@39bc08000 {
+		compatible = "apple,t6000-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x9bc08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1147 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1148 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1149 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1150 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+	};
+
+	DIE_NODE(sio): sio@39bc00000 {
+		compatible = "apple,t6000-sio", "apple,sio";
+		reg = <0x3 0x9bc00000 0x0 0x8000>;
+		dma-channels = <128>;
+		#dma-cells = <1>;
+		mboxes = <&DIE_NODE(sio_mbox)>;
+		iommus = <&DIE_NODE(sio_dart_0) 0>, <&DIE_NODE(sio_dart_1) 0>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+		resets = <&DIE_NODE(ps_sio)>; /* TODO: verify reset does something */
+		status = "disabled";
+	};
+
+	DIE_NODE(dpaudio1): audio-controller@39b504000 {
+		compatible = "apple,t6000-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b540000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x66>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa1)>;
+		reset-domains = <&DIE_NODE(ps_dpa1)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio1_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext0_audio)>;
+				};
+			};
+		};
+	};
+
+	DIE_NODE(dpaudio2): audio-controller@39b508000 {
+		compatible = "apple,t6000-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b580000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x68>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa2)>;
+		reset-domains = <&DIE_NODE(ps_dpa2)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio2_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext1_audio)>;
+				};
+			};
+		};
+	};
+
+	/*
+	 * omit dpaudio3 / 4 as long as the linked dcpext nodes don't exist
+	 *
+	DIE_NODE(dpaudio3): audio-controller@39b50c000 {
+		compatible = "apple,t6000-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b5c0000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x6a>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa3)>;
+		reset-domains = <&DIE_NODE(ps_dpa3)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio3_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext2_audio)>;
+				};
+			};
+		};
+	};
+
+	DIE_NODE(dpaudio4): audio-controller@39b510000 {
+		compatible = "apple,t6000-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b500000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x6c>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa4)>;
+		reset-domains = <&DIE_NODE(ps_dpa4)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio4_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext3_audio)>;
+				};
+			};
+		};
+	};
+	*/
+
 	DIE_NODE(dwc3_0_dart_0): iommu@702f00000 {
 		compatible = "apple,t6000-dart";
 		reg = <0x7 0x02f00000 0x0 0x4000>;

From fbd4a97521cbf3cc2048360f7840bb5159f607a2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 21 Jan 2024 14:45:49 +0100
Subject: [PATCH 0176/1027] arm64: apple: t602x: Add sio and dpaudio device
 nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi |   1 +
 arch/arm64/boot/dts/apple/t602x-die0.dtsi  |  41 ++++--
 arch/arm64/boot/dts/apple/t602x-dieX.dtsi  | 146 +++++++++++++++++++++
 3 files changed, 179 insertions(+), 9 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
index 9b7391b922db54..33e439903080ad 100644
--- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -59,6 +59,7 @@
 /delete-node/ &dcp_dart;
 /delete-node/ &dcp_mbox;
 /delete-node/ &dcp;
+/delete-node/ &dpaudio0;
 
 /* delete unused always-on power-domains */
 /delete-node/ &ps_disp0_cpu0;
diff --git a/arch/arm64/boot/dts/apple/t602x-die0.dtsi b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
index 9375e5777e7126..fb50c02d6c39bf 100644
--- a/arch/arm64/boot/dts/apple/t602x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-die0.dtsi
@@ -372,6 +372,17 @@
 			iommus = <&disp0_dart 4>;
 			phandle = <&disp0_piodma>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				dcp_audio: endpoint {
+					remote-endpoint = <&dpaudio0_dcp>;
+				};
+			};
+		};
 	};
 
 	display: display-subsystem {
@@ -381,15 +392,6 @@
 		phandle = <&display>;
 	};
 
-	sio_dart: iommu@39b008000 {
-		compatible = "apple,t6020-dart", "apple,t8110-dart";
-		reg = <0x3 0x9b008000 0x0 0x8000>;
-		interrupt-parent = <&aic>;
-		interrupts = <AIC_IRQ 0 1231 IRQ_TYPE_LEVEL_HIGH>;
-		#iommu-cells = <1>;
-		power-domains = <&ps_sio_cpu>;
-	};
-
 	fpwm0: pwm@39b030000 {
 		compatible = "apple,t6020-fpwm", "apple,s5l-fpwm";
 		reg = <0x3 0x9b030000 0x0 0x4000>;
@@ -596,6 +598,27 @@
 		resets = <&ps_audio_p>;
 	};
 
+	dpaudio0: audio-controller@39b500000 {
+		compatible = "apple,t6020-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b500000 0x0 0x4000>;
+		dmas = <&sio 0x64>;
+		dma-names = "tx";
+		power-domains = <&ps_dpa0>;
+		reset-domains = <&ps_dpa0>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				dpaudio0_dcp: endpoint {
+					remote-endpoint = <&dcp_audio>;
+				};
+			};
+		};
+	};
+
 	mca: mca@39b600000 {
 		compatible = "apple,t6020-mca", "apple,mca";
 		reg = <0x3 0x9b600000 0x0 0x10000>,
diff --git a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
index 3a9580d48ab6e3..aeea9540208858 100644
--- a/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-dieX.dtsi
@@ -85,6 +85,17 @@
 		piodma {
 			iommus = <&DIE_NODE(dispext0_dart) 4>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dcpext0_audio): endpoint {
+					remote-endpoint = <&DIE_NODE(dpaudio1_dcp)>;
+				};
+			};
+		};
 	};
 
 	DIE_NODE(pmgr): power-management@28e080000 {
@@ -226,6 +237,27 @@
 		piodma {
 			iommus = <&DIE_NODE(dispext1_dart) 4>;
 		};
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dcpext1_audio): endpoint {
+					remote-endpoint = <&DIE_NODE(dpaudio2_dcp)>;
+				};
+			};
+		};
+	};
+
+	DIE_NODE(sio_dart): iommu@39b008000 {
+		compatible = "apple,t6020-dart", "apple,t8110-dart";
+		reg = <0x3 0x9b008000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1231 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&DIE_NODE(ps_sio)>;
+		//apple,dma-range = <0x100 0x0001c000 0x2ff 0xfffe4000>;
 	};
 
 	DIE_NODE(pinctrl_ap): pinctrl@39b028000 {
@@ -253,6 +285,120 @@
 		#interrupt-cells = <2>;
 	};
 
+	DIE_NODE(sio_mbox): mbox@39bc08000 {
+		compatible = "apple,t6020-asc-mailbox", "apple,asc-mailbox-v4";
+		reg = <0x3 0x9bc08000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ DIE_NO 1248 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1249 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1250 IRQ_TYPE_LEVEL_HIGH>,
+			<AIC_IRQ DIE_NO 1251 IRQ_TYPE_LEVEL_HIGH>;
+		interrupt-names = "send-empty", "send-not-empty",
+			"recv-empty", "recv-not-empty";
+		#mbox-cells = <0>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+	};
+
+	DIE_NODE(sio): sio@39bc00000 {
+		compatible = "apple,t6020-sio", "apple,sio";
+		reg = <0x3 0x9bc00000 0x0 0x8000>;
+		dma-channels = <128>;
+		#dma-cells = <1>;
+		mboxes = <&DIE_NODE(sio_mbox)>;
+		iommus = <&DIE_NODE(sio_dart) 0>;
+		power-domains = <&DIE_NODE(ps_sio_cpu)>;
+		resets = <&DIE_NODE(ps_sio_cpu)>;
+		status = "disabled";
+	};
+
+	DIE_NODE(dpaudio1): audio-controller@39b504000 {
+		compatible = "apple,t6020-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b540000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x66>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa1)>;
+		reset-domains = <&DIE_NODE(ps_dpa1)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio1_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext0_audio)>;
+				};
+			};
+		};
+	};
+
+	DIE_NODE(dpaudio2): audio-controller@39b508000 {
+		compatible = "apple,t6020-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b580000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x68>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa2)>;
+		reset-domains = <&DIE_NODE(ps_dpa2)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio2_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext1_audio)>;
+				};
+			};
+		};
+	};
+
+	/*
+	 * omit dpaudio3 / 4 as long as the linked dcpext nodes don't exist
+	 *
+	DIE_NODE(dpaudio3): audio-controller@39b50c000 {
+		compatible = "apple,t6020-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b5c0000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x6a>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa3)>;
+		reset-domains = <&DIE_NODE(ps_dpa3)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio3_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext2_audio)>;
+				};
+			};
+		};
+	};
+
+	DIE_NODE(dpaudio4): audio-controller@39b510000 {
+		compatible = "apple,t6020-dpaudio", "apple,dpaudio";
+		reg = <0x3 0x9b500000 0x0 0x4000>;
+		dmas = <&DIE_NODE(sio) 0x6c>;
+		dma-names = "tx";
+		power-domains = <&DIE_NODE(ps_dpa4)>;
+		reset-domains = <&DIE_NODE(ps_dpa4)>;
+		status = "disabled";
+
+		ports {
+			#address-cells = <1>;
+			#size-cells = <0>;
+			port@0 {
+				reg = <0>;
+				DIE_NODE(dpaudio4_dcp): endpoint {
+					remote-endpoint = <&DIE_NODE(dcpext3_audio)>;
+				};
+			};
+		};
+	};
+	*/
+
 	DIE_NODE(lpdptxphy): phy@39c000000 {
 		compatible = "apple,t6020-dptx-phy", "apple,dptx-phy";
 		reg = <0x3 0x9c000000 0x0 0x4000>,

From e326cdabab6cd9455635fa192bbe2afd44de51e5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 7 Apr 2024 23:10:55 +0200
Subject: [PATCH 0177/1027] arm64: apple: t60xx: Enable DP/HMI audio nodes on
 all devices

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6001-j375c.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t6002-j375d.dts      | 4 ++++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 5 +++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 1 +
 arch/arm64/boot/dts/apple/t6020-j474s.dts      | 6 ++++++
 arch/arm64/boot/dts/apple/t6021-j475c.dts      | 6 ++++++
 arch/arm64/boot/dts/apple/t6022-j475d.dts      | 5 +++++
 arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi     | 5 +++++
 8 files changed, 36 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts
index a71b1ebb29d956..f0c31f301dfef0 100644
--- a/arch/arm64/boot/dts/apple/t6001-j375c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts
@@ -17,6 +17,10 @@
 	model = "Apple Mac Studio (M1 Max, 2022)";
 };
 
+&dpaudio0 {
+	status = "okay";
+};
+
 &sound {
 	compatible = "apple,j375-macaudio", "apple,macaudio";
 	model = "Mac Studio J375";
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index 062bfc72575ebf..e16254b1d6819b 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -21,6 +21,10 @@
 	};
 };
 
+&dpaudio0 {
+	status = "okay";
+};
+
 &sound {
 	compatible = "apple,j375-macaudio", "apple,macaudio";
 	model = "Mac Studio J375";
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 19c270019c26d7..dc78f503bc245a 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -24,6 +24,7 @@
 		disp0_piodma = &disp0_piodma;
 		nvram = &nvram;
 		serial0 = &serial0;
+		sio = &sio;
 		wifi0 = &wifi0;
 	};
 
@@ -114,6 +115,10 @@
 	apple,dptx-phy = <3>;
 };
 
+&dpaudio1 {
+	status = "okay";
+};
+
 &atcphy3 {
 	apple,mode-fixed-dp;
 };
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index d8db13fc02e3f6..d740a06a995984 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -24,6 +24,7 @@
 		ethernet0 = &ethernet0;
 		nvram = &nvram;
 		serial0 = &serial0;
+		sio = &sio;
 		wifi0 = &wifi0;
 	};
 
diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index e4d9b580f2ef4d..180ea8aa7646bb 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -78,8 +78,14 @@
 &dcpext0_mbox {
 	status = "okay";
 };
+&dpaudio1 {
+	status = "okay";
+};
 &dcpext0 {
 #else
+&dpaudio0 {
+	status = "okay";
+};
 &dcp {
 #endif
 	status = "okay";
diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts
index c954d02a038d9f..aeca677cccaa7c 100644
--- a/arch/arm64/boot/dts/apple/t6021-j475c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts
@@ -82,8 +82,14 @@
 &dcpext0_mbox {
 	status = "okay";
 };
+&dpaudio1 {
+	status = "okay";
+};
 &dcpext0 {
 #else
+&dpaudio0 {
+	status = "okay";
+};
 &dcp {
 #endif
 	status = "okay";
diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
index 0d53f26b6f5352..b084ee8be12390 100644
--- a/arch/arm64/boot/dts/apple/t6022-j475d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -22,9 +22,14 @@
 		atcphy4 = &atcphy0_die1;
 		atcphy5 = &atcphy1_die1;
 		/delete-property/ dcp;
+		/delete-property/ sio;
 	};
 };
 
+&sio {
+        status = "disabled";
+};
+
 &framebuffer0 {
 	power-domains = <&ps_dispext0_cpu0_die1>, <&ps_dptx_phy_ps_die1>;
 };
diff --git a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
index 33e439903080ad..f8d2fcd485d1fc 100644
--- a/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
+++ b/arch/arm64/boot/dts/apple/t6022-jxxxd.dtsi
@@ -13,6 +13,7 @@
 	aliases {
 		dcpext4 = &dcpext0_die1;
 		disp0 = &display;
+		sio1 = &sio_die1;
 	};
 };
 
@@ -53,6 +54,10 @@
 	apple,dptx-die = <1>;
 };
 
+&dpaudio1_die1 {
+	status = "okay";
+};
+
 /* delete missing dcp0/disp0 */
 
 /delete-node/ &disp0_dart;

From 69f8209dc289bf46ec09972c2f1ccc63a577d7e6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 27 Apr 2024 12:57:52 +0200
Subject: [PATCH 0178/1027] arm64: apple: t60x0/t60x1: Enable sio explicitly

To be removed after m1n1 does this after proper setup.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 5 +++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 5 +++++
 2 files changed, 10 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index dc78f503bc245a..9723c6c1fb0138 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -115,6 +115,11 @@
 	apple,dptx-phy = <3>;
 };
 
+/* remove once m1n1 enables sio nodes after setup */
+&sio {
+        status = "okay";
+};
+
 &dpaudio1 {
 	status = "okay";
 };
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index d740a06a995984..e8c355ca0f21c5 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -65,6 +65,11 @@
 	apple,connector-type = "HDMI-A";
 };
 
+/* remove once m1n1 enables sio nodes after setup */
+&sio {
+        status = "okay";
+};
+
 /* USB Type C */
 &i2c0 {
 	hpm0: usb-pd@38 {

From 79f2cfc3fa6bfc70a9cdf13917541b9de5330be2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 27 Apr 2024 12:57:52 +0200
Subject: [PATCH 0179/1027] arm64: apple: t8103-j274: Enable sio explicitly

To be removed after m1n1 does this after proper setup.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index b12dbecb5f0cd7..9f8712b5b6accc 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -26,6 +26,11 @@
 	apple,connector-type = "HDMI-A";
 };
 
+/* remove once m1n1 enables sio nodes after setup */
+&sio {
+        status = "okay";
+};
+
 &dpaudio0 {
 	status = "okay";
 };

From 194dafce5b0134589b08a0d8ff5ccfdd0a3adf80 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 27 Apr 2024 12:57:52 +0200
Subject: [PATCH 0180/1027] arm64: apple: t8112-j473: Enable sio explicitly

To be removed after m1n1 does this after proper setup.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j473.dts | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index 5ad461092fbd7e..aa6be5e55e4ce9 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -65,6 +65,11 @@
 	apple,dptx-phy = <5>;
 };
 
+/* remove once m1n1 enables sio nodes after setup */
+&sio {
+        status = "okay";
+};
+
 &dpaudio1 {
 	status = "okay";
 };

From 261ed282f44ba4cff3ca7d7e8fc6b16f73550108 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 12 Jun 2024 21:48:27 +0200
Subject: [PATCH 0181/1027] fixup! arm64: dts: apple: t8112: Add PMU NVMEM and
 SMC RTC/reboot nodes

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112.dtsi | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/boot/dts/apple/t8112.dtsi b/arch/arm64/boot/dts/apple/t8112.dtsi
index a14e9dc3b4dbb7..bb2b039b89d29b 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -1105,7 +1105,7 @@
 			status = "disabled"; /* only used on j473 */
                };
 
-		nub_spmi: spmi@23d0d9300 {
+		nub_spmi: spmi@23d714000 {
 			compatible = "apple,t8112-spmi", "apple,spmi";
 			reg = <0x2 0x3d714000 0x0 0x100>;
 			#address-cells = <2>;

From cb526955b0f7798cc9db3b958289f0169c7a25c5 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Mon, 1 Jul 2024 10:07:12 +1000
Subject: [PATCH 0182/1027] arm64: dts: apple: add common hwmon keys and fans

Each SoC's SMC exposes a different set of hardware sensor keys,
however there are a number that are shared between all currently
supported SoCs. Describe these in a .dtsi so that we don't need to
duplicate them across every SoC.

Likewise, the fans on every machine are exposed as the same set of
keys on each. Add .dtsis for these too.

Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 arch/arm64/boot/dts/apple/hwmon-common.dtsi   | 43 +++++++++++++++++++
 arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi | 26 +++++++++++
 arch/arm64/boot/dts/apple/hwmon-fan.dtsi      | 21 +++++++++
 arch/arm64/boot/dts/apple/hwmon-laptop.dtsi   | 41 ++++++++++++++++++
 arch/arm64/boot/dts/apple/hwmon-mini.dtsi     | 20 +++++++++
 5 files changed, 151 insertions(+)
 create mode 100644 arch/arm64/boot/dts/apple/hwmon-common.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/hwmon-fan.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/hwmon-laptop.dtsi
 create mode 100644 arch/arm64/boot/dts/apple/hwmon-mini.dtsi

diff --git a/arch/arm64/boot/dts/apple/hwmon-common.dtsi b/arch/arm64/boot/dts/apple/hwmon-common.dtsi
new file mode 100644
index 00000000000000..1f9a2435e14cb7
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/hwmon-common.dtsi
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * hwmon sensors expected on all systems
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+&smc {
+	hwmon {
+		apple,power-keys {
+			power-PSTR {
+				apple,key-id = "PSTR";
+				label = "Total System Power";
+			};
+			power-PDTR {
+				apple,key-id = "PDTR";
+				label = "AC Input Power";
+			};
+			power-PMVR {
+				apple,key-id = "PMVR";
+				label = "3.8 V Rail Power";
+			};
+		};
+		apple,temp-keys {
+			temp-TH0x {
+				apple,key-id = "TH0x";
+				label = "NAND Flash Temperature";
+			};
+		};
+		apple,volt-keys {
+			volt-VD0R {
+				apple,key-id = "VD0R";
+				label = "AC Input Voltage";
+			};
+		};
+		apple,current-keys {
+			current-ID0R {
+				apple,key-id = "ID0R";
+				label = "AC Input Current";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi
new file mode 100644
index 00000000000000..782b6051a3866e
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/hwmon-fan-dual.dtsi
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Copyright The Asahi Linux Contributors
+ *
+ * Fan hwmon sensors for machines with 2 fan.
+ */
+
+#include "hwmon-fan.dtsi"
+
+&smc {
+	hwmon {
+		apple,fan-keys {
+			fan-F0Ac {
+				label = "Fan 1";
+			};
+			fan-F1Ac {
+				apple,key-id = "F1Ac";
+				label = "Fan 2";
+				apple,fan-minimum = "F1Mn";
+				apple,fan-maximum = "F1Mx";
+				apple,fan-target = "F1Tg";
+				apple,fan-mode = "F1Md";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/hwmon-fan.dtsi b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi
new file mode 100644
index 00000000000000..8f329ac4ff9fef
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/hwmon-fan.dtsi
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Copyright The Asahi Linux Contributors
+ *
+ * Fan hwmon sensors for machines with a single fan.
+ */
+
+&smc {
+	hwmon {
+		apple,fan-keys {
+			fan-F0Ac {
+				apple,key-id = "F0Ac";
+				label = "Fan";
+				apple,fan-minimum = "F0Mn";
+				apple,fan-maximum = "F0Mx";
+				apple,fan-target = "F0Tg";
+				apple,fan-mode = "F0Md";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi
new file mode 100644
index 00000000000000..2583ef379dfac9
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/hwmon-laptop.dtsi
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * hwmon sensors expected on all laptops
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+&smc {
+	hwmon {
+		apple,power-keys {
+			power-PHPC {
+				apple,key-id = "PHPC";
+				label = "Heatpipe Power";
+			};
+		};
+		apple,temp-keys {
+			temp-TB0T {
+				apple,key-id = "TB0T";
+				label = "Battery Hotspot";
+			};
+			temp-TCHP {
+				apple,key-id = "TCHP";
+				label = "Charge Regulator Temp";
+			};
+			temp-TW0P {
+				apple,key-id = "TW0P";
+				label = "WiFi/BT Module Temp";
+			};
+		};
+		apple,volt-keys {
+			volt-SBAV {
+				apple,key-id = "SBAV";
+				label = "Battery Voltage";
+			};
+			volt-VD0R {
+				apple,key-id = "VD0R";
+				label = "Charger Input Voltage";
+			};
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/hwmon-mini.dtsi b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi
new file mode 100644
index 00000000000000..bd0c22786d4226
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/hwmon-mini.dtsi
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * hwmon sensors common to the Mac mini desktop
+ * models, but not the Studio or Pro.
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include "hwmon-fan.dtsi"
+
+&smc {
+	hwmon {
+		apple,temp-keys {
+			temp-TW0P {
+				apple,key-id = "TW0P";
+				label = "WiFi/BT Module Temp";
+			};
+		};
+	};
+};

From 7d622909a000aabe113915493bab8d29489fa6d3 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Jul 2024 21:29:10 +0200
Subject: [PATCH 0183/1027] arm64: dts: apple: t8103: Add SMC hwmon sensors

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8103-j274.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8103-j293.dts  | 3 +++
 arch/arm64/boot/dts/apple/t8103-j313.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8103-j456.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8103-j457.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8103-jxxx.dtsi | 2 ++
 6 files changed, 13 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8103-j274.dts b/arch/arm64/boot/dts/apple/t8103-j274.dts
index 9f8712b5b6accc..d52a0b4525c041 100644
--- a/arch/arm64/boot/dts/apple/t8103-j274.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j274.dts
@@ -144,3 +144,5 @@
 &gpu {
 	apple,perf-base-pstate = <3>;
 };
+
+#include "hwmon-mini.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 74f205e6662697..e0c1fdf9fd4abb 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -239,3 +239,6 @@
 &isp {
 	apple,platform-id = <1>;
 };
+
+#include "hwmon-fan.dtsi"
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-j313.dts b/arch/arm64/boot/dts/apple/t8103-j313.dts
index 97facb88428fe6..6919e0af4ed1d7 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -172,3 +172,5 @@
 &isp {
 	apple,platform-id = <1>;
 };
+
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-j456.dts b/arch/arm64/boot/dts/apple/t8103-j456.dts
index b77f998009fae3..576abb9eb20f9d 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -141,3 +141,5 @@
 &isp {
 	apple,platform-id = <2>;
 };
+
+#include "hwmon-fan-dual.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-j457.dts b/arch/arm64/boot/dts/apple/t8103-j457.dts
index 282dc912a49444..46359682ee6cdd 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -114,3 +114,5 @@
 &isp {
 	apple,platform-id = <2>;
 };
+
+#include "hwmon-fan.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
index 09ae9aa4f1e550..a1fa0d6eecf7f9 100644
--- a/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-jxxx.dtsi
@@ -186,4 +186,6 @@
 	clock-frequency = <900000000>;
 };
 
+#include "hwmon-common.dtsi"
+
 #include "spi1-nvram.dtsi"

From 55576e6bdfb102bbd5caef8c8f157c5d1d3dcee6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Jul 2024 21:31:30 +0200
Subject: [PATCH 0184/1027] arm64: dts: apple: t8112: Add SMC hwmon sensors

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t8112-j413.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8112-j415.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8112-j473.dts  | 2 ++
 arch/arm64/boot/dts/apple/t8112-j493.dts  | 3 +++
 arch/arm64/boot/dts/apple/t8112-jxxx.dtsi | 2 ++
 5 files changed, 11 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 1f75df87a12525..0bbd8666fdff1e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -248,3 +248,5 @@
 	apple,platform-id = <14>;
 	apple,temporal-filter = <1>;
 };
+
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8112-j415.dts b/arch/arm64/boot/dts/apple/t8112-j415.dts
index 8aa576477837d8..1f082f6ce79a0e 100644
--- a/arch/arm64/boot/dts/apple/t8112-j415.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j415.dts
@@ -270,3 +270,5 @@
 	apple,platform-id = <15>;
 	apple,temporal-filter = <1>;
 };
+
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8112-j473.dts b/arch/arm64/boot/dts/apple/t8112-j473.dts
index aa6be5e55e4ce9..0640843b378cfb 100644
--- a/arch/arm64/boot/dts/apple/t8112-j473.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j473.dts
@@ -193,3 +193,5 @@
 &gpu {
 	apple,perf-base-pstate = <3>;
 };
+
+#include "hwmon-mini.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8112-j493.dts b/arch/arm64/boot/dts/apple/t8112-j493.dts
index 059edbdca3ec40..ada6371a1a22d2 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -270,3 +270,6 @@
 &isp {
 	apple,platform-id = <6>;
 };
+
+#include "hwmon-fan.dtsi"
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
index fb93cedeb24a44..5e0742c1fb4450 100644
--- a/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-jxxx.dtsi
@@ -174,4 +174,6 @@
 	clock-frequency = <900000000>;
 };
 
+#include "hwmon-common.dtsi"
+
 #include "spi1-nvram.dtsi"

From 90e645f64f8ae68cdaf80ca76c5582f466513110 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Jul 2024 21:34:02 +0200
Subject: [PATCH 0185/1027] arm64: dts: apple: t600x-j3xx: Add SMC hwmon
 sensors

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6001-j375c.dts      | 2 ++
 arch/arm64/boot/dts/apple/t6002-j375d.dts      | 2 ++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi | 4 ++++
 arch/arm64/boot/dts/apple/t600x-j375.dtsi      | 2 ++
 4 files changed, 10 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6001-j375c.dts b/arch/arm64/boot/dts/apple/t6001-j375c.dts
index f0c31f301dfef0..5abd557aa9d73e 100644
--- a/arch/arm64/boot/dts/apple/t6001-j375c.dts
+++ b/arch/arm64/boot/dts/apple/t6001-j375c.dts
@@ -41,3 +41,5 @@
 		line-name = "usb-hub-rst";
 	};
 };
+
+#include "hwmon-fan-dual.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t6002-j375d.dts b/arch/arm64/boot/dts/apple/t6002-j375d.dts
index e16254b1d6819b..68a982b755d993 100644
--- a/arch/arm64/boot/dts/apple/t6002-j375d.dts
+++ b/arch/arm64/boot/dts/apple/t6002-j375d.dts
@@ -158,3 +158,5 @@
 
 /delete-node/ &ps_disp0_cpu0_die1;
 /delete-node/ &ps_disp0_fe_die1;
+
+#include "hwmon-fan-dual.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
index 9723c6c1fb0138..fc237343727b47 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -520,3 +520,7 @@
 &isp {
 	apple,platform-id = <3>;
 };
+
+#include "hwmon-common.dtsi"
+#include "hwmon-fan-dual.dtsi"
+#include "hwmon-laptop.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t600x-j375.dtsi b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
index e8c355ca0f21c5..84c307a3215dd7 100644
--- a/arch/arm64/boot/dts/apple/t600x-j375.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j375.dtsi
@@ -403,3 +403,5 @@
 #endif
 
 #include "spi1-nvram.dtsi"
+
+#include "hwmon-common.dtsi"

From 09e74251736565c779e11263a0c0b645f2519a95 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 11 Jul 2024 21:36:29 +0200
Subject: [PATCH 0186/1027] arm64: dts: apple: t602x-j4xx: Add SMC hwmon
 sensors

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts | 2 ++
 arch/arm64/boot/dts/apple/t6021-j475c.dts | 2 ++
 arch/arm64/boot/dts/apple/t6022-j475d.dts | 2 ++
 3 files changed, 6 insertions(+)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index 180ea8aa7646bb..026ac039d714a3 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -106,3 +106,5 @@
 	/* Apple does not do this, but they probably should */
 	apple,perf-base-pstate = <3>;
 };
+
+#include "hwmon-mini.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t6021-j475c.dts b/arch/arm64/boot/dts/apple/t6021-j475c.dts
index aeca677cccaa7c..de56ae4f4ac526 100644
--- a/arch/arm64/boot/dts/apple/t6021-j475c.dts
+++ b/arch/arm64/boot/dts/apple/t6021-j475c.dts
@@ -105,3 +105,5 @@
 	phy-names = "dp-phy";
 	apple,dptx-phy = <4>;
 };
+
+#include "hwmon-fan-dual.dtsi"
diff --git a/arch/arm64/boot/dts/apple/t6022-j475d.dts b/arch/arm64/boot/dts/apple/t6022-j475d.dts
index b084ee8be12390..5a60e84fab101c 100644
--- a/arch/arm64/boot/dts/apple/t6022-j475d.dts
+++ b/arch/arm64/boot/dts/apple/t6022-j475d.dts
@@ -84,3 +84,5 @@
 	compatible = "apple,j475-macaudio", "apple,j375-macaudio", "apple,macaudio";
 	model = "Mac Studio J475";
 };
+
+#include "hwmon-fan-dual.dtsi"

From 83de5b49afb43b9780985b08506a53ea3c2f4a65 Mon Sep 17 00:00:00 2001
From: Jonathan Gray <jsg@jsg.id.au>
Date: Sun, 18 Aug 2024 18:32:58 +1000
Subject: [PATCH 0187/1027] arm64: dts: apple: j474s: correct case of Mac mini

Change "Mac Mini" to "Mac mini" to match other device trees and
Apple's usage.

Signed-off-by: Jonathan Gray <jsg@jsg.id.au>
---
 arch/arm64/boot/dts/apple/t6020-j474s.dts      | 4 ++--
 arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/boot/dts/apple/t6020-j474s.dts b/arch/arm64/boot/dts/apple/t6020-j474s.dts
index 026ac039d714a3..bf64a9c47cd807 100644
--- a/arch/arm64/boot/dts/apple/t6020-j474s.dts
+++ b/arch/arm64/boot/dts/apple/t6020-j474s.dts
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0+ OR MIT
 /*
- * Mac Mini (M2 Pro, 2023)
+ * Mac mini (M2 Pro, 2023)
  *
  * target-type: J474s
  *
@@ -16,7 +16,7 @@
 
 / {
 	compatible = "apple,j474s", "apple,t6020", "apple,arm-platform";
-	model = "Apple Mac Mini (M2 Pro, 2023)";
+	model = "Apple Mac mini (M2 Pro, 2023)";
 };
 
 &wifi0 {
diff --git a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
index b1390aefdbd7c1..c0c6eff3159839 100644
--- a/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
+++ b/arch/arm64/boot/dts/apple/t602x-j474-j475.dtsi
@@ -1,6 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0+ OR MIT
 /*
- * Mac Mini (M2 Pro, 2023) and Mac Studio (2023)
+ * Mac mini (M2 Pro, 2023) and Mac Studio (2023)
  *
  * This file contains the parts common to J474 and J475 devices with t6020,
  * t6021 and t6022.

From d2ac28d9d3d591513dbff3c0746ab9f016989a5d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 12 Dec 2021 12:28:41 +0900
Subject: [PATCH 0188/1027] MAINTAINERS: Add Apple dwc3 bindings to ARM/APPLE

This Apple dwc3 controller variance is present on Apple ARM SoCs (t8103/t600x).

Splitting this change from the binding/driver commits to avoid merge
conflicts with other things touching this section, as usual.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd10..4271af20265d3b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2068,6 +2068,7 @@ F:	Documentation/devicetree/bindings/pci/apple,pcie.yaml
 F:	Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
 F:	Documentation/devicetree/bindings/power/apple*
 F:	Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml
+F:	Documentation/devicetree/bindings/usb/apple,dwc3.yaml
 F:	Documentation/devicetree/bindings/watchdog/apple,wdt.yaml
 F:	arch/arm64/boot/dts/apple/
 F:	drivers/bluetooth/hci_bcm4377.c

From 52ddaee5e8b84a5469f2e1b4f91d1b5bbff433b2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 12 Dec 2021 12:28:41 +0900
Subject: [PATCH 0189/1027] MAINTAINERS: Add apple-spi driver & binding files

This Apple SPI controller is present on Apple ARM SoCs (t8103/t6000).

Splitting this change from the binding/driver commits to avoid merge
conflicts with other things touching this section, as usual.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 4271af20265d3b..6b3563713e7e00 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2068,6 +2068,7 @@ F:	Documentation/devicetree/bindings/pci/apple,pcie.yaml
 F:	Documentation/devicetree/bindings/pinctrl/apple,pinctrl.yaml
 F:	Documentation/devicetree/bindings/power/apple*
 F:	Documentation/devicetree/bindings/pwm/apple,s5l-fpwm.yaml
+F:	Documentation/devicetree/bindings/spi/apple,spi.yaml
 F:	Documentation/devicetree/bindings/usb/apple,dwc3.yaml
 F:	Documentation/devicetree/bindings/watchdog/apple,wdt.yaml
 F:	arch/arm64/boot/dts/apple/
@@ -2086,6 +2087,7 @@ F:	drivers/nvmem/apple-efuses.c
 F:	drivers/pinctrl/pinctrl-apple-gpio.c
 F:	drivers/pwm/pwm-apple.c
 F:	drivers/soc/apple/*
+F:	drivers/spi/spi-apple.c
 F:	drivers/watchdog/apple_wdt.c
 F:	include/dt-bindings/interrupt-controller/apple-aic.h
 F:	include/dt-bindings/pinctrl/apple.h

From 9464d8a05a1b5851bbcfded7a89f308674527bd3 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Oct 2022 21:24:57 +0900
Subject: [PATCH 0190/1027] soc: apple: rtkit: Check for failure to send mgmt
 messages & log

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/soc/apple/rtkit.c | 31 +++++++++++++++++++++++--------
 1 file changed, 23 insertions(+), 8 deletions(-)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index e6d940292c9fbd..54f0905789f9a1 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -97,12 +97,20 @@ bool apple_rtkit_is_crashed(struct apple_rtkit *rtk)
 }
 EXPORT_SYMBOL_GPL(apple_rtkit_is_crashed);
 
-static void apple_rtkit_management_send(struct apple_rtkit *rtk, u8 type,
+static int apple_rtkit_management_send(struct apple_rtkit *rtk, u8 type,
 					u64 msg)
 {
+	int ret;
+
 	msg &= ~APPLE_RTKIT_MGMT_TYPE;
 	msg |= FIELD_PREP(APPLE_RTKIT_MGMT_TYPE, type);
-	apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_MGMT, msg, NULL, false);
+	ret = apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_MGMT, msg, NULL, false);
+
+	if (ret) {
+		dev_err(rtk->dev, "RTKit: Failed to send management message: %d\n", ret);
+	}
+
+	return ret;
 }
 
 static void apple_rtkit_management_rx_hello(struct apple_rtkit *rtk, u64 msg)
@@ -742,8 +750,10 @@ static int apple_rtkit_set_ap_power_state(struct apple_rtkit *rtk,
 	reinit_completion(&rtk->ap_pwr_ack_completion);
 
 	msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state);
-	apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_AP_PWR_STATE,
-				    msg);
+	ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_AP_PWR_STATE,
+					  msg);
+	if (ret)
+		return ret;
 
 	ret = apple_rtkit_wait_for_completion(&rtk->ap_pwr_ack_completion);
 	if (ret)
@@ -763,8 +773,10 @@ static int apple_rtkit_set_iop_power_state(struct apple_rtkit *rtk,
 	reinit_completion(&rtk->iop_pwr_ack_completion);
 
 	msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, state);
-	apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
-				    msg);
+	ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
+					  msg);
+	if (ret)
+		return ret;
 
 	ret = apple_rtkit_wait_for_completion(&rtk->iop_pwr_ack_completion);
 	if (ret)
@@ -865,6 +877,7 @@ EXPORT_SYMBOL_GPL(apple_rtkit_quiesce);
 int apple_rtkit_wake(struct apple_rtkit *rtk)
 {
 	u64 msg;
+	int ret;
 
 	if (apple_rtkit_is_running(rtk))
 		return -EINVAL;
@@ -876,8 +889,10 @@ int apple_rtkit_wake(struct apple_rtkit *rtk)
 	 * will wait for the completion anyway.
 	 */
 	msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_ON);
-	apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
-				    msg);
+	ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
+					  msg);
+	if (ret)
+		return ret;
 
 	return apple_rtkit_boot(rtk);
 }

From 89d7c305e00d2185badd2b8fe5358c13b22ff5af Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 27 Sep 2022 05:10:17 +0900
Subject: [PATCH 0191/1027] soc: apple: rtkit: Log failure to send messages

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/soc/apple/rtkit.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index 54f0905789f9a1..6ee256d112602c 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -596,11 +596,18 @@ int apple_rtkit_send_message(struct apple_rtkit *rtk, u8 ep, u64 message,
 		.msg1 = ep,
 	};
 
-	if (rtk->crashed)
+	if (rtk->crashed) {
+		dev_warn(rtk->dev,
+			 "RTKit: Device is crashed, cannot send message\n");
 		return -EINVAL;
+	}
+
 	if (ep >= APPLE_RTKIT_APP_ENDPOINT_START &&
-	    !apple_rtkit_is_running(rtk))
+	    !apple_rtkit_is_running(rtk)) {
+		dev_warn(rtk->dev,
+			 "RTKit: Endpoint 0x%02x is not running, cannot send message\n", ep);
 		return -EINVAL;
+	}
 
 	/*
 	 * The message will be sent with a MMIO write. We need the barrier

From 0a7a3441c954aeab73b610fa88fe423c3dc3a7b6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 18 Aug 2022 02:13:03 +0900
Subject: [PATCH 0192/1027] soc: apple: rtkit: Log failed buffer requests

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/soc/apple/rtkit.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index 6ee256d112602c..87d124b8739d92 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -303,6 +303,9 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk,
 	return 0;
 
 error:
+	dev_err(rtk->dev, "RTKit: failed buffer request for 0x%zx bytes (%d)\n",
+		buffer->size, err);
+
 	buffer->buffer = NULL;
 	buffer->iomem = NULL;
 	buffer->iova = 0;

From dd0b4bfd2913d4f18a5969f16a55e7302588bf7e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 25 Jul 2022 21:38:56 +0200
Subject: [PATCH 0193/1027] soc: apple: rtkit: Add APPLE_RTKIT_PWR_STATE_INIT

This state is needed to wake the dcp IOP after m1n1 shut it down.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/soc/apple/rtkit.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index 87d124b8739d92..7c28a9344b01b5 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -12,6 +12,7 @@ enum {
 	APPLE_RTKIT_PWR_STATE_IDLE = 0x201, /* sleeping, retain state */
 	APPLE_RTKIT_PWR_STATE_QUIESCED = 0x10, /* running but no communication */
 	APPLE_RTKIT_PWR_STATE_ON = 0x20, /* normal operating state */
+	APPLE_RTKIT_PWR_STATE_INIT = 0x220, /* init after starting the coproc */
 };
 
 enum {
@@ -898,7 +899,7 @@ int apple_rtkit_wake(struct apple_rtkit *rtk)
 	 * Use open-coded apple_rtkit_set_iop_power_state since apple_rtkit_boot
 	 * will wait for the completion anyway.
 	 */
-	msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_ON);
+	msg = FIELD_PREP(APPLE_RTKIT_MGMT_PWR_STATE, APPLE_RTKIT_PWR_STATE_INIT);
 	ret = apple_rtkit_management_send(rtk, APPLE_RTKIT_MGMT_SET_IOP_PWR_STATE,
 					  msg);
 	if (ret)

From 3f778b82a920f2877c9273699eabb079e1367c83 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 16 Apr 2023 21:21:17 +0900
Subject: [PATCH 0194/1027] soc: apple: rtkit: Implement OSLog buffers properly

Apparently nobody can figure out where the old logic came from, but it
seems like it has never been actually used on any supported firmware to
this day. OSLog buffers were apparently never requested.

But starting with 13.3, we actually need this implemented properly for
MTP (and later AOP) to work, so let's actually do that.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/soc/apple/rtkit-internal.h |  1 +
 drivers/soc/apple/rtkit.c          | 55 ++++++++++++++++++------------
 2 files changed, 34 insertions(+), 22 deletions(-)

diff --git a/drivers/soc/apple/rtkit-internal.h b/drivers/soc/apple/rtkit-internal.h
index 27c9fa745fd528..b8d5244678f010 100644
--- a/drivers/soc/apple/rtkit-internal.h
+++ b/drivers/soc/apple/rtkit-internal.h
@@ -44,6 +44,7 @@ struct apple_rtkit {
 
 	struct apple_rtkit_shmem ioreport_buffer;
 	struct apple_rtkit_shmem crashlog_buffer;
+	struct apple_rtkit_shmem oslog_buffer;
 
 	struct apple_rtkit_shmem syslog_buffer;
 	char *syslog_msg_buffer;
diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index 7c28a9344b01b5..ae1fd86a617ead 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -67,8 +67,9 @@ enum {
 #define APPLE_RTKIT_SYSLOG_MSG_SIZE  GENMASK_ULL(31, 24)
 
 #define APPLE_RTKIT_OSLOG_TYPE GENMASK_ULL(63, 56)
-#define APPLE_RTKIT_OSLOG_INIT	1
-#define APPLE_RTKIT_OSLOG_ACK	3
+#define APPLE_RTKIT_OSLOG_BUFFER_REQUEST 1
+#define APPLE_RTKIT_OSLOG_SIZE GENMASK_ULL(55, 36)
+#define APPLE_RTKIT_OSLOG_IOVA GENMASK_ULL(35, 0)
 
 #define APPLE_RTKIT_MIN_SUPPORTED_VERSION 11
 #define APPLE_RTKIT_MAX_SUPPORTED_VERSION 12
@@ -260,15 +261,20 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk,
 					    struct apple_rtkit_shmem *buffer,
 					    u8 ep, u64 msg)
 {
-	size_t n_4kpages = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_SIZE, msg);
 	u64 reply;
 	int err;
 
+	if (ep == APPLE_RTKIT_EP_OSLOG) {
+		buffer->size = FIELD_GET(APPLE_RTKIT_OSLOG_SIZE, msg);
+		buffer->iova = FIELD_GET(APPLE_RTKIT_OSLOG_IOVA, msg) << 12;
+	} else {
+		buffer->size = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_SIZE, msg) << 12;
+		buffer->iova = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_IOVA, msg);
+	}
+
 	buffer->buffer = NULL;
 	buffer->iomem = NULL;
 	buffer->is_mapped = false;
-	buffer->iova = FIELD_GET(APPLE_RTKIT_BUFFER_REQUEST_IOVA, msg);
-	buffer->size = n_4kpages << 12;
 
 	dev_dbg(rtk->dev, "RTKit: buffer request for 0x%zx bytes at %pad\n",
 		buffer->size, &buffer->iova);
@@ -293,11 +299,21 @@ static int apple_rtkit_common_rx_get_buffer(struct apple_rtkit *rtk,
 	}
 
 	if (!buffer->is_mapped) {
-		reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE,
-				   APPLE_RTKIT_BUFFER_REQUEST);
-		reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE, n_4kpages);
-		reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA,
-				    buffer->iova);
+		/* oslog uses different fields */
+		if (ep == APPLE_RTKIT_EP_OSLOG) {
+			reply = FIELD_PREP(APPLE_RTKIT_OSLOG_TYPE,
+					   APPLE_RTKIT_OSLOG_BUFFER_REQUEST);
+			reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_SIZE, buffer->size);
+			reply |= FIELD_PREP(APPLE_RTKIT_OSLOG_IOVA,
+					    buffer->iova >> 12);
+		} else {
+			reply = FIELD_PREP(APPLE_RTKIT_SYSLOG_TYPE,
+					   APPLE_RTKIT_BUFFER_REQUEST);
+			reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_SIZE,
+					    buffer->size >> 12);
+			reply |= FIELD_PREP(APPLE_RTKIT_BUFFER_REQUEST_IOVA,
+					    buffer->iova);
+		}
 		apple_rtkit_send_message(rtk, ep, reply, NULL, false);
 	}
 
@@ -494,25 +510,18 @@ static void apple_rtkit_syslog_rx(struct apple_rtkit *rtk, u64 msg)
 	}
 }
 
-static void apple_rtkit_oslog_rx_init(struct apple_rtkit *rtk, u64 msg)
-{
-	u64 ack;
-
-	dev_dbg(rtk->dev, "RTKit: oslog init: msg: 0x%llx\n", msg);
-	ack = FIELD_PREP(APPLE_RTKIT_OSLOG_TYPE, APPLE_RTKIT_OSLOG_ACK);
-	apple_rtkit_send_message(rtk, APPLE_RTKIT_EP_OSLOG, ack, NULL, false);
-}
-
 static void apple_rtkit_oslog_rx(struct apple_rtkit *rtk, u64 msg)
 {
 	u8 type = FIELD_GET(APPLE_RTKIT_OSLOG_TYPE, msg);
 
 	switch (type) {
-	case APPLE_RTKIT_OSLOG_INIT:
-		apple_rtkit_oslog_rx_init(rtk, msg);
+	case APPLE_RTKIT_OSLOG_BUFFER_REQUEST:
+		apple_rtkit_common_rx_get_buffer(rtk, &rtk->oslog_buffer,
+						 APPLE_RTKIT_EP_OSLOG, msg);
 		break;
 	default:
-		dev_warn(rtk->dev, "RTKit: Unknown oslog message: %llx\n", msg);
+		dev_warn(rtk->dev, "RTKit: Unknown oslog message: %llx\n",
+			 msg);
 	}
 }
 
@@ -729,6 +738,7 @@ int apple_rtkit_reinit(struct apple_rtkit *rtk)
 
 	apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer);
 	apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer);
+	apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer);
 	apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer);
 
 	kfree(rtk->syslog_msg_buffer);
@@ -916,6 +926,7 @@ void apple_rtkit_free(struct apple_rtkit *rtk)
 
 	apple_rtkit_free_buffer(rtk, &rtk->ioreport_buffer);
 	apple_rtkit_free_buffer(rtk, &rtk->crashlog_buffer);
+	apple_rtkit_free_buffer(rtk, &rtk->oslog_buffer);
 	apple_rtkit_free_buffer(rtk, &rtk->syslog_buffer);
 
 	kfree(rtk->syslog_msg_buffer);

From 3e0859bf68aad585a365d0a0595062c1aac79c4e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Apr 2023 04:19:44 +0900
Subject: [PATCH 0195/1027] soc: apple: Add driver for Apple PMGR misc controls

Apple SoCs have PMGR blocks that control a bunch of power-related
features. Besides the existing device power state controls (which are
very uniform and handled by apple-pmgr-pwrstate), we also need to manage
more random registers such as SoC-wide fabric and memory controller
power states, which have a different interface.

Add a driver for these kitchen sink controls. Right now it implements
fabric and memory controller power state switching on system
standby/s2idle, which saves about 1W of power or so on t60xx platforms.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/soc/apple/Kconfig           |   9 ++
 drivers/soc/apple/Makefile          |   2 +
 drivers/soc/apple/apple-pmgr-misc.c | 158 ++++++++++++++++++++++++++++
 3 files changed, 169 insertions(+)
 create mode 100644 drivers/soc/apple/apple-pmgr-misc.c

diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index 6388cbe1e56b5a..651319e31bbad2 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -4,6 +4,15 @@ if ARCH_APPLE || COMPILE_TEST
 
 menu "Apple SoC drivers"
 
+config APPLE_PMGR_MISC
+	bool "Apple SoC PMGR miscellaneous support"
+	depends on PM
+	default ARCH_APPLE
+	help
+	  The PMGR block in Apple SoCs provides high-level power state
+	  controls for SoC devices. This driver manages miscellaneous
+	  power controls.
+
 config APPLE_MAILBOX
 	tristate "Apple SoC mailboxes"
 	depends on PM
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 4d9ab8f3037b71..31fb20888523ee 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -1,5 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
+obj-$(CONFIG_APPLE_PMGR_MISC)	+= apple-pmgr-misc.o
+
 obj-$(CONFIG_APPLE_MAILBOX) += apple-mailbox.o
 apple-mailbox-y = mailbox.o
 
diff --git a/drivers/soc/apple/apple-pmgr-misc.c b/drivers/soc/apple/apple-pmgr-misc.c
new file mode 100644
index 00000000000000..e768f34aacc586
--- /dev/null
+++ b/drivers/soc/apple/apple-pmgr-misc.c
@@ -0,0 +1,158 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SoC PMGR device power state driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitops.h>
+#include <linux/bitfield.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+
+#define APPLE_CLKGEN_PSTATE 0
+#define APPLE_CLKGEN_PSTATE_DESIRED GENMASK(3, 0)
+
+#define SYS_DEV_PSTATE_SUSPEND 1
+
+enum sys_device {
+	DEV_FABRIC,
+	DEV_DCS,
+	DEV_MAX,
+};
+
+struct apple_pmgr_sys_device {
+	void __iomem *base;
+	u32 active_state;
+	u32 suspend_state;
+};
+
+struct apple_pmgr_misc {
+	struct device *dev;
+	struct apple_pmgr_sys_device devices[DEV_MAX];
+};
+
+static void apple_pmgr_sys_dev_set_pstate(struct apple_pmgr_misc *misc,
+					  enum sys_device dev, bool active)
+{
+	u32 pstate;
+	u32 val;
+
+	if (!misc->devices[dev].base)
+		return;
+
+	if (active)
+		pstate = misc->devices[dev].active_state;
+	else
+		pstate = misc->devices[dev].suspend_state;
+
+	printk("set %d ps to pstate %d\n", dev, pstate);
+
+	val = readl_relaxed(misc->devices[dev].base + APPLE_CLKGEN_PSTATE);
+	val &= ~APPLE_CLKGEN_PSTATE_DESIRED;
+	val |= FIELD_PREP(APPLE_CLKGEN_PSTATE_DESIRED, pstate);
+	writel_relaxed(val, misc->devices[dev].base);
+}
+
+static int __maybe_unused apple_pmgr_misc_suspend_noirq(struct device *dev)
+{
+	struct apple_pmgr_misc *misc = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < DEV_MAX; i++)
+		apple_pmgr_sys_dev_set_pstate(misc, i, false);
+
+	return 0;
+}
+
+static int __maybe_unused apple_pmgr_misc_resume_noirq(struct device *dev)
+{
+	struct apple_pmgr_misc *misc = dev_get_drvdata(dev);
+	int i;
+
+	for (i = 0; i < DEV_MAX; i++)
+		apple_pmgr_sys_dev_set_pstate(misc, i, true);
+
+	return 0;
+}
+
+static bool apple_pmgr_init_device(struct apple_pmgr_misc *misc,
+				   enum sys_device dev, const char *device_name)
+{
+	void __iomem *base;
+	char name[32];
+	u32 val;
+
+	snprintf(name, sizeof(name), "%s-ps", device_name);
+
+	base = devm_platform_ioremap_resource_byname(
+		to_platform_device(misc->dev), name);
+	if (!base)
+		return false;
+
+	val = readl_relaxed(base + APPLE_CLKGEN_PSTATE);
+
+	misc->devices[dev].base = base;
+	misc->devices[dev].active_state =
+		FIELD_GET(APPLE_CLKGEN_PSTATE_DESIRED, val);
+	misc->devices[dev].suspend_state = SYS_DEV_PSTATE_SUSPEND;
+
+	snprintf(name, sizeof(name), "apple,%s-min-ps", device_name);
+	of_property_read_u32(misc->dev->of_node, name,
+			     &misc->devices[dev].suspend_state);
+
+	return true;
+}
+
+static int apple_pmgr_misc_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct apple_pmgr_misc *misc;
+	int ret = -ENODEV;
+
+	misc = devm_kzalloc(dev, sizeof(*misc), GFP_KERNEL);
+	if (!misc)
+		return -ENOMEM;
+
+	misc->dev = dev;
+
+	if (apple_pmgr_init_device(misc, DEV_FABRIC, "fabric"))
+		ret = 0;
+
+	if (apple_pmgr_init_device(misc, DEV_DCS, "dcs"))
+		ret = 0;
+
+	platform_set_drvdata(pdev, misc);
+
+	return ret;
+}
+
+static const struct of_device_id apple_pmgr_misc_of_match[] = {
+	{ .compatible = "apple,t6000-pmgr-misc" },
+	{}
+};
+
+MODULE_DEVICE_TABLE(of, apple_pmgr_misc_of_match);
+
+static const struct dev_pm_ops apple_pmgr_misc_pm_ops = {
+	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(apple_pmgr_misc_suspend_noirq,
+				      apple_pmgr_misc_resume_noirq)
+};
+
+static struct platform_driver apple_pmgr_misc_driver = {
+	.probe = apple_pmgr_misc_probe,
+	.driver = {
+		.name = "apple-pmgr-misc",
+		.of_match_table = apple_pmgr_misc_of_match,
+		.pm = pm_ptr(&apple_pmgr_misc_pm_ops),
+	},
+};
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("PMGR misc driver for Apple SoCs");
+MODULE_LICENSE("GPL v2");
+
+module_platform_driver(apple_pmgr_misc_driver);

From 02e0f2eaab0c8a7e264d79d1a4e0000fda0e8763 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 17 Apr 2023 20:41:13 +0900
Subject: [PATCH 0196/1027] cpuidle: apple: Add Apple SoC cpuidle driver

May the PSCI conversation happen some day. Until it does, this will make
the user experience a lot less painful in downstream kernels.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/cpuidle/Kconfig.arm     |   8 ++
 drivers/cpuidle/Makefile        |   1 +
 drivers/cpuidle/cpuidle-apple.c | 157 ++++++++++++++++++++++++++++++++
 3 files changed, 166 insertions(+)
 create mode 100644 drivers/cpuidle/cpuidle-apple.c

diff --git a/drivers/cpuidle/Kconfig.arm b/drivers/cpuidle/Kconfig.arm
index a1ee475d180dac..c6870f08457632 100644
--- a/drivers/cpuidle/Kconfig.arm
+++ b/drivers/cpuidle/Kconfig.arm
@@ -130,3 +130,11 @@ config ARM_QCOM_SPM_CPUIDLE
 	  The Subsystem Power Manager (SPM) controls low power modes for the
 	  CPU and L2 cores. It interface with various system drivers to put
 	  the cores in low power modes.
+
+config ARM_APPLE_CPUIDLE
+	bool "Apple SoC CPU idle driver"
+	depends on ARM64
+	default ARCH_APPLE
+	select CPU_IDLE_MULTIPLE_DRIVERS
+	help
+	  Select this to enable cpuidle on Apple SoCs.
diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile
index d103342b7cfc21..972b49aec88903 100644
--- a/drivers/cpuidle/Makefile
+++ b/drivers/cpuidle/Makefile
@@ -26,6 +26,7 @@ obj-$(CONFIG_ARM_PSCI_CPUIDLE)		+= cpuidle-psci.o
 obj-$(CONFIG_ARM_PSCI_CPUIDLE_DOMAIN)	+= cpuidle-psci-domain.o
 obj-$(CONFIG_ARM_TEGRA_CPUIDLE)		+= cpuidle-tegra.o
 obj-$(CONFIG_ARM_QCOM_SPM_CPUIDLE)	+= cpuidle-qcom-spm.o
+obj-$(CONFIG_ARM_APPLE_CPUIDLE)		+= cpuidle-apple.o
 
 ###############################################################################
 # MIPS drivers
diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c
new file mode 100644
index 00000000000000..1dfb10cdb5e4d6
--- /dev/null
+++ b/drivers/cpuidle/cpuidle-apple.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Copyright The Asahi Linux Contributors
+ *
+ * CPU idle support for Apple SoCs
+ */
+
+#include <linux/init.h>
+#include <linux/cpuidle.h>
+#include <linux/cpu_pm.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <asm/cpuidle.h>
+
+enum idle_state {
+	STATE_WFI,
+	STATE_PWRDOWN,
+	STATE_COUNT
+};
+
+asm(
+	".pushsection .cpuidle.text, \"ax\"\n"
+	".type apple_cpu_deep_wfi, @function\n"
+	"apple_cpu_deep_wfi:\n"
+		"str x30, [sp, #-16]!\n"
+		"stp x28, x29, [sp, #-16]!\n"
+		"stp x26, x27, [sp, #-16]!\n"
+		"stp x24, x25, [sp, #-16]!\n"
+		"stp x22, x23, [sp, #-16]!\n"
+		"stp x20, x21, [sp, #-16]!\n"
+		"stp x18, x19, [sp, #-16]!\n"
+
+		"mrs x0, s3_5_c15_c5_0\n"
+		"orr x0, x0, #(3L << 24)\n"
+		"msr s3_5_c15_c5_0, x0\n"
+
+	"1:\n"
+		"dsb sy\n"
+		"wfi\n"
+
+		"mrs x0, ISR_EL1\n"
+		"cbz x0, 1b\n"
+
+		"mrs x0, s3_5_c15_c5_0\n"
+		"bic x0, x0, #(1L << 24)\n"
+		"msr s3_5_c15_c5_0, x0\n"
+
+		"ldp x18, x19, [sp], #16\n"
+		"ldp x20, x21, [sp], #16\n"
+		"ldp x22, x23, [sp], #16\n"
+		"ldp x24, x25, [sp], #16\n"
+		"ldp x26, x27, [sp], #16\n"
+		"ldp x28, x29, [sp], #16\n"
+		"ldr x30, [sp], #16\n"
+
+		"ret\n"
+	".popsection\n"
+);
+
+void apple_cpu_deep_wfi(void);
+
+static __cpuidle int apple_enter_wfi(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index)
+{
+	cpu_do_idle();
+	return index;
+}
+
+static __cpuidle int apple_enter_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index)
+{
+	/*
+	 * Deep WFI will clobber FP state, among other things.
+	 * The CPU PM notifier will take care of saving that and anything else
+	 * that needs to be notified of the CPU powering down.
+	 */
+	if (cpu_pm_enter())
+		return -1;
+
+	ct_cpuidle_enter();
+
+	switch(index) {
+	case STATE_PWRDOWN:
+		apple_cpu_deep_wfi();
+		break;
+	default:
+		WARN_ON(1);
+		break;
+	}
+
+	ct_cpuidle_exit();
+
+	cpu_pm_exit();
+
+	return index;
+}
+
+static struct cpuidle_driver apple_idle_driver = {
+	.name = "apple_idle",
+	.owner = THIS_MODULE,
+	.states = {
+		[STATE_WFI] = {
+			.enter			= apple_enter_wfi,
+			.enter_s2idle		= apple_enter_wfi,
+			.exit_latency		= 1,
+			.target_residency	= 1,
+			.power_usage            = UINT_MAX,
+			.name			= "WFI",
+			.desc			= "CPU clock-gated",
+			.flags			= 0,
+		},
+		[STATE_PWRDOWN] = {
+			.enter			= apple_enter_idle,
+			.enter_s2idle		= apple_enter_idle,
+			.exit_latency		= 10,
+			.target_residency	= 10000,
+			.power_usage            = 0,
+			.name			= "CPU PD",
+			.desc			= "CPU/cluster powered down",
+			.flags			= CPUIDLE_FLAG_RCU_IDLE,
+		},
+	},
+	.safe_state_index = STATE_WFI,
+	.state_count = STATE_COUNT,
+};
+
+static int apple_cpuidle_probe(struct platform_device *pdev)
+{
+	return cpuidle_register(&apple_idle_driver, NULL);
+}
+
+static struct platform_driver apple_cpuidle_driver = {
+	.driver = {
+		.name = "cpuidle-apple",
+	},
+	.probe = apple_cpuidle_probe,
+};
+
+static int __init apple_cpuidle_init(void)
+{
+	struct platform_device *pdev;
+	int ret;
+
+	ret = platform_driver_register(&apple_cpuidle_driver);
+	if (ret)
+		return ret;
+
+	if (!of_machine_is_compatible("apple,arm-platform"))
+		return 0;
+
+	pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0);
+	if (IS_ERR(pdev)) {
+		platform_driver_unregister(&apple_cpuidle_driver);
+		return PTR_ERR(pdev);
+	}
+
+	return 0;
+}
+device_initcall(apple_cpuidle_init);

From 0625d3f486cdc7a6e09ab4a15e782195f473a15a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 2 Jan 2023 19:20:55 +0100
Subject: [PATCH 0197/1027] soc: apple: rtkit: Add devm_apple_rtkit_free()

To be used to free a RTKit interface while the associated device remains
alive. Probably useless since it's unknown how or if RTKit based
co-processors can be restarted.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/soc/apple/rtkit.c       | 6 ++++++
 include/linux/soc/apple/rtkit.h | 7 +++++++
 2 files changed, 13 insertions(+)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index ae1fd86a617ead..daed4b512ec480 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -958,6 +958,12 @@ struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie,
 }
 EXPORT_SYMBOL_GPL(devm_apple_rtkit_init);
 
+void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk)
+{
+	devm_release_action(dev, apple_rtkit_free_wrapper, rtk);
+}
+EXPORT_SYMBOL_GPL(devm_apple_rtkit_free);
+
 MODULE_LICENSE("Dual MIT/GPL");
 MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
 MODULE_DESCRIPTION("Apple RTKit driver");
diff --git a/include/linux/soc/apple/rtkit.h b/include/linux/soc/apple/rtkit.h
index c06d17599ae7e3..466adaa328178f 100644
--- a/include/linux/soc/apple/rtkit.h
+++ b/include/linux/soc/apple/rtkit.h
@@ -78,6 +78,13 @@ struct apple_rtkit;
 struct apple_rtkit *devm_apple_rtkit_init(struct device *dev, void *cookie,
 					  const char *mbox_name, int mbox_idx,
 					  const struct apple_rtkit_ops *ops);
+/*
+ * Frees internal RTKit state allocated by devm_apple_rtkit_init().
+ *
+ * @dev:	Pointer to the device node this coprocessor is assocated with
+ * @rtk:	Internal RTKit state initialized by devm_apple_rtkit_init()
+ */
+void devm_apple_rtkit_free(struct device *dev, struct apple_rtkit *rtk);
 
 /*
  * Non-devm version of devm_apple_rtkit_init. Must be freed with

From ca359c201be548fb0b7265a37788a3d4bd1f86e8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 22:11:47 +0900
Subject: [PATCH 0198/1027] dt-bindings: power: apple,pmgr-pwrstate: Add
 force-{disable,reset}

These flags are used for some ISP power domains, that apparently require
more aggressive behavior on power down.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 .../bindings/power/apple,pmgr-pwrstate.yaml          | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
index 59a6af735a2167..1531e26549f209 100644
--- a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
+++ b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
@@ -70,6 +70,18 @@ properties:
     minimum: 0
     maximum: 15
 
+  apple,force-disable:
+    description:
+      Forces this device to be disabled (bus access blocked) when the power
+      domain is powered down.
+    type: boolean
+
+  apple,force-reset:
+    description:
+      Forces a reset/error recovery of the power control logic when the power
+      domain is powered down.
+    type: boolean
+
 required:
   - compatible
   - reg

From 02c4e1825b2388705396c8e5e414582eba8e8eed Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 22:07:30 +0900
Subject: [PATCH 0199/1027] soc: apple: pmgr: Add force-disable/force-reset

It seems some ISP power states should have their force disable device
access flag set when powered down (which may avoid this problem, but
we're still figuring that out), and on some bit 12 is also explicitly
set before shutdown. Add two properties to handle this case.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/pmdomain/apple/pmgr-pwrstate.c | 43 ++++++++++++++++++++++----
 1 file changed, 37 insertions(+), 6 deletions(-)

diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c
index d62a776c89a121..367b1b243f9a69 100644
--- a/drivers/pmdomain/apple/pmgr-pwrstate.c
+++ b/drivers/pmdomain/apple/pmgr-pwrstate.c
@@ -21,7 +21,8 @@
 #define APPLE_PMGR_AUTO_ENABLE  BIT(28)
 #define APPLE_PMGR_PS_AUTO      GENMASK(27, 24)
 #define APPLE_PMGR_PS_MIN       GENMASK(19, 16)
-#define APPLE_PMGR_PARENT_OFF   BIT(11)
+#define APPLE_PMGR_PS_RESET     BIT(12)
+#define APPLE_PMGR_BUSY         BIT(11)
 #define APPLE_PMGR_DEV_DISABLE  BIT(10)
 #define APPLE_PMGR_WAS_CLKGATED BIT(9)
 #define APPLE_PMGR_WAS_PWRGATED BIT(8)
@@ -44,6 +45,8 @@ struct apple_pmgr_ps {
 	struct regmap *regmap;
 	u32 offset;
 	u32 min_state;
+	bool force_disable;
+	bool force_reset;
 };
 
 #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd)
@@ -53,7 +56,7 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
 {
 	int ret;
 	struct apple_pmgr_ps *ps = genpd_to_apple_pmgr_ps(genpd);
-	u32 reg;
+	u32 reg, cur;
 
 	ret = regmap_read(ps->regmap, ps->offset, &reg);
 	if (ret < 0)
@@ -64,7 +67,29 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
 		dev_err(ps->dev, "PS %s: powering off with RESET active\n",
 			genpd->name);
 
-	reg &= ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET);
+	if (pstate != APPLE_PMGR_PS_ACTIVE && (ps->force_disable || ps->force_reset)) {
+		u32 reg_pre = reg & ~(APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS);
+
+		if (ps->force_disable)
+			reg_pre |= APPLE_PMGR_DEV_DISABLE;
+		if (ps->force_reset)
+			reg_pre |= APPLE_PMGR_PS_RESET;
+
+		regmap_write(ps->regmap, ps->offset, reg_pre);
+
+		ret = regmap_read_poll_timeout_atomic(
+			ps->regmap, ps->offset, cur,
+			(cur & (APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET)) ==
+			(reg_pre & (APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET)), 1,
+			APPLE_PMGR_PS_SET_TIMEOUT);
+
+		if (ret < 0)
+			dev_err(ps->dev, "PS %s: Failed to set reset/disable bits (now: 0x%x)\n",
+				genpd->name, reg);
+	}
+
+	reg &= ~(APPLE_PMGR_DEV_DISABLE | APPLE_PMGR_PS_RESET |
+		 APPLE_PMGR_AUTO_ENABLE | APPLE_PMGR_FLAGS | APPLE_PMGR_PS_TARGET);
 	reg |= FIELD_PREP(APPLE_PMGR_PS_TARGET, pstate);
 
 	dev_dbg(ps->dev, "PS %s: pwrstate = 0x%x: 0x%x\n", genpd->name, pstate, reg);
@@ -72,16 +97,16 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
 	regmap_write(ps->regmap, ps->offset, reg);
 
 	ret = regmap_read_poll_timeout_atomic(
-		ps->regmap, ps->offset, reg,
-		(FIELD_GET(APPLE_PMGR_PS_ACTUAL, reg) == pstate), 1,
+		ps->regmap, ps->offset, cur,
+		FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1,
 		APPLE_PMGR_PS_SET_TIMEOUT);
+
 	if (ret < 0)
 		dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n",
 			genpd->name, pstate, reg);
 
 	if (auto_enable) {
 		/* Not all devices implement this; this is a no-op where not implemented. */
-		reg &= ~APPLE_PMGR_FLAGS;
 		reg |= APPLE_PMGR_AUTO_ENABLE;
 		regmap_write(ps->regmap, ps->offset, reg);
 	}
@@ -244,6 +269,12 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
 		}
 	}
 
+	if (of_property_read_bool(node, "apple,force-disable"))
+		ps->force_disable = true;
+
+	if (of_property_read_bool(node, "apple,force-reset"))
+		ps->force_reset = true;
+
 	/* Turn on auto-PM if the domain is already on */
 	if (active)
 		regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE,

From d7a919c4fd712d651edfba0c0e7f07d6447b9cfc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:18:14 +0900
Subject: [PATCH 0200/1027] soc: apple: pmgr: Add externally-clocked property

MCA power states require an external clock to be provided. If they are
powered on while this clock is not active, the power state will only go
into the "clock gated" state.

This is effectively working as intended, so add a property that
instructs the pwrstate driver to consider the PS to be successfully
powered on when it reaches the clock gated state.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pmdomain/apple/pmgr-pwrstate.c | 35 ++++++++++++++++++--------
 1 file changed, 25 insertions(+), 10 deletions(-)

diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c
index 367b1b243f9a69..a272cba58e2f1f 100644
--- a/drivers/pmdomain/apple/pmgr-pwrstate.c
+++ b/drivers/pmdomain/apple/pmgr-pwrstate.c
@@ -47,6 +47,7 @@ struct apple_pmgr_ps {
 	u32 min_state;
 	bool force_disable;
 	bool force_reset;
+	bool externally_clocked;
 };
 
 #define genpd_to_apple_pmgr_ps(_genpd) container_of(_genpd, struct apple_pmgr_ps, genpd)
@@ -96,10 +97,21 @@ static int apple_pmgr_ps_set(struct generic_pm_domain *genpd, u32 pstate, bool a
 
 	regmap_write(ps->regmap, ps->offset, reg);
 
-	ret = regmap_read_poll_timeout_atomic(
-		ps->regmap, ps->offset, cur,
-		FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1,
-		APPLE_PMGR_PS_SET_TIMEOUT);
+	if (ps->externally_clocked && pstate == APPLE_PMGR_PS_ACTIVE) {
+		/*
+		 * If this clock domain requires an external clock, then
+		 * consider the "clock gated" state to be good enough.
+		 */
+		ret = regmap_read_poll_timeout_atomic(
+			ps->regmap, ps->offset, cur,
+			FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) >= APPLE_PMGR_PS_CLKGATE, 1,
+			APPLE_PMGR_PS_SET_TIMEOUT);
+	} else {
+		ret = regmap_read_poll_timeout_atomic(
+			ps->regmap, ps->offset, cur,
+			FIELD_GET(APPLE_PMGR_PS_ACTUAL, cur) == pstate, 1,
+			APPLE_PMGR_PS_SET_TIMEOUT);
+	}
 
 	if (ret < 0)
 		dev_err(ps->dev, "PS %s: Failed to reach power state 0x%x (now: 0x%x)\n",
@@ -259,6 +271,15 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
 		regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_PS_MIN,
 				   FIELD_PREP(APPLE_PMGR_PS_MIN, ps->min_state));
 
+	if (of_property_read_bool(node, "apple,force-disable"))
+		ps->force_disable = true;
+
+	if (of_property_read_bool(node, "apple,force-reset"))
+		ps->force_reset = true;
+
+	if (of_property_read_bool(node, "apple,externally-clocked"))
+		ps->externally_clocked = true;
+
 	active = apple_pmgr_ps_is_active(ps);
 	if (of_property_read_bool(node, "apple,always-on")) {
 		ps->genpd.flags |= GENPD_FLAG_ALWAYS_ON;
@@ -269,12 +290,6 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
 		}
 	}
 
-	if (of_property_read_bool(node, "apple,force-disable"))
-		ps->force_disable = true;
-
-	if (of_property_read_bool(node, "apple,force-reset"))
-		ps->force_reset = true;
-
 	/* Turn on auto-PM if the domain is already on */
 	if (active)
 		regmap_update_bits(regmap, ps->offset, APPLE_PMGR_FLAGS | APPLE_PMGR_AUTO_ENABLE,

From 9690d9b88490a1ffb1e02e8757f6edf1c8a4f7ef Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 May 2024 21:42:57 +0200
Subject: [PATCH 0201/1027] soc: apple: rtkit: Use high prio work queue

rtkit messages as communication with the DCP firmware for framebuffer
swaps or input events are time critical so use WQ_HIGHPRI to prevent
user space CPU load to increase latency.
With kwin_wayland 6's explicit sync mode user space load was able to
delay the IOMFB rtkit communication enough to miss vsync for surface
swaps. Minimal test scenario is constantly resizing a glxgears
Xwayland window.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/soc/apple/rtkit.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/soc/apple/rtkit.c b/drivers/soc/apple/rtkit.c
index daed4b512ec480..79b80e8f38c163 100644
--- a/drivers/soc/apple/rtkit.c
+++ b/drivers/soc/apple/rtkit.c
@@ -695,7 +695,7 @@ struct apple_rtkit *apple_rtkit_init(struct device *dev, void *cookie,
 	rtk->mbox->rx = apple_rtkit_rx;
 	rtk->mbox->cookie = rtk;
 
-	rtk->wq = alloc_ordered_workqueue("rtkit-%s", WQ_MEM_RECLAIM,
+	rtk->wq = alloc_ordered_workqueue("rtkit-%s", WQ_HIGHPRI | WQ_MEM_RECLAIM,
 					  dev_name(rtk->dev));
 	if (!rtk->wq) {
 		ret = -ENOMEM;

From acba3237a73a9952e86197de12dec48c985e370a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 31 Aug 2024 12:19:03 +0200
Subject: [PATCH 0202/1027] cpuidle: apple: Do not load on unsupported Apple
 platforms

Pre-t8103 SoCs do not retain state during deep WFI. Reject to load on
such platforms.

Suggested-by: Nick Chan <towinchenmi@gmail.com>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/cpuidle/cpuidle-apple.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c
index 1dfb10cdb5e4d6..b48c9586df8137 100644
--- a/drivers/cpuidle/cpuidle-apple.c
+++ b/drivers/cpuidle/cpuidle-apple.c
@@ -6,12 +6,15 @@
  */
 
 #include <linux/init.h>
+#include <linux/bitfield.h>
 #include <linux/cpuidle.h>
 #include <linux/cpu_pm.h>
 #include <linux/platform_device.h>
 #include <linux/of.h>
 #include <asm/cpuidle.h>
 
+#define DEEP_WFI_STATE_RETENTION BIT(2) // retains base CPU registers in deep WFI
+
 enum idle_state {
 	STATE_WFI,
 	STATE_PWRDOWN,
@@ -146,6 +149,11 @@ static int __init apple_cpuidle_init(void)
 	if (!of_machine_is_compatible("apple,arm-platform"))
 		return 0;
 
+	if (!FIELD_GET(DEEP_WFI_STATE_RETENTION, read_sysreg(aidr_el1))) {
+		dev_info(&pdev->dev, "cpuidle unavailable CPU does not retain state in deep WFI\n");
+		return 0;
+	}
+
 	pdev = platform_device_register_simple("cpuidle-apple", -1, NULL, 0);
 	if (IS_ERR(pdev)) {
 		platform_driver_unregister(&apple_cpuidle_driver);

From 5e55066e55977da7043b57687054b5f8879b7f30 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 13 Sep 2024 10:05:23 +0200
Subject: [PATCH 0203/1027] fixup! cpuidle: apple: Do not load on unsupported
 Apple platforms

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/cpuidle/cpuidle-apple.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/cpuidle/cpuidle-apple.c b/drivers/cpuidle/cpuidle-apple.c
index b48c9586df8137..27b9144b979d3a 100644
--- a/drivers/cpuidle/cpuidle-apple.c
+++ b/drivers/cpuidle/cpuidle-apple.c
@@ -150,7 +150,7 @@ static int __init apple_cpuidle_init(void)
 		return 0;
 
 	if (!FIELD_GET(DEEP_WFI_STATE_RETENTION, read_sysreg(aidr_el1))) {
-		dev_info(&pdev->dev, "cpuidle unavailable CPU does not retain state in deep WFI\n");
+		pr_info("cpuidle-apple: CPU does not retain state in deep WFI\n");
 		return 0;
 	}
 

From c3200b8119c695a150614cbe35a91dda72061f47 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 22 Oct 2022 12:00:21 +0200
Subject: [PATCH 0204/1027] iommu: Add IOMMU_RESV_TRANSLATED for non 1:1 mapped
 reserved regions

The display controller in Apple silicon SoCs uses bootloader mappings
which require IOMMU translation.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/iommu/iommu.c | 24 ++++++++++++++++++++----
 include/linux/iommu.h | 10 ++++++++++
 2 files changed, 30 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index ed6c5cb60c5aee..26f2ec1da6b2a4 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -85,6 +85,7 @@ static const char * const iommu_group_resv_type_string[] = {
 	[IOMMU_RESV_RESERVED]			= "reserved",
 	[IOMMU_RESV_MSI]			= "msi",
 	[IOMMU_RESV_SW_MSI]			= "msi",
+	[IOMMU_RESV_TRANSLATED]			= "translated",
 };
 
 #define IOMMU_CMD_LINE_DMA_API		BIT(0)
@@ -2781,10 +2782,11 @@ void iommu_put_resv_regions(struct device *dev, struct list_head *list)
 }
 EXPORT_SYMBOL(iommu_put_resv_regions);
 
-struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
-						  size_t length, int prot,
-						  enum iommu_resv_type type,
-						  gfp_t gfp)
+struct iommu_resv_region *iommu_alloc_resv_region_tr(phys_addr_t start,
+						     dma_addr_t dva_start,
+						     size_t length, int prot,
+						     enum iommu_resv_type type,
+						     gfp_t gfp)
 {
 	struct iommu_resv_region *region;
 
@@ -2794,11 +2796,25 @@ struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
 
 	INIT_LIST_HEAD(&region->list);
 	region->start = start;
+	if (type == IOMMU_RESV_TRANSLATED)
+		region->dva = dva_start;
 	region->length = length;
 	region->prot = prot;
 	region->type = type;
 	return region;
 }
+EXPORT_SYMBOL_GPL(iommu_alloc_resv_region_tr);
+
+struct iommu_resv_region *iommu_alloc_resv_region(phys_addr_t start,
+						  size_t length, int prot,
+						  enum iommu_resv_type type,
+						  gfp_t gfp)
+{
+	if (type == IOMMU_RESV_TRANSLATED)
+		return NULL;
+
+	return iommu_alloc_resv_region_tr(start, 0, length, prot, type, gfp);
+}
 EXPORT_SYMBOL_GPL(iommu_alloc_resv_region);
 
 void iommu_set_default_passthrough(bool cmd_line)
diff --git a/include/linux/iommu.h b/include/linux/iommu.h
index bd722f47363520..b3ee7eae898b9c 100644
--- a/include/linux/iommu.h
+++ b/include/linux/iommu.h
@@ -271,12 +271,18 @@ enum iommu_resv_type {
 	IOMMU_RESV_MSI,
 	/* Software-managed MSI translation window */
 	IOMMU_RESV_SW_MSI,
+	/*
+	 * Memory regions which must be mapped with the specified mapping
+	 * at all times.
+	 */
+	IOMMU_RESV_TRANSLATED,
 };
 
 /**
  * struct iommu_resv_region - descriptor for a reserved memory region
  * @list: Linked list pointers
  * @start: System physical start address of the region
+ * @start: Device virtual start address of the region for IOMMU_RESV_TRANSLATED
  * @length: Length of the region in bytes
  * @prot: IOMMU Protection flags (READ/WRITE/...)
  * @type: Type of the reserved region
@@ -285,6 +291,7 @@ enum iommu_resv_type {
 struct iommu_resv_region {
 	struct list_head	list;
 	phys_addr_t		start;
+	dma_addr_t		dva;
 	size_t			length;
 	int			prot;
 	enum iommu_resv_type	type;
@@ -819,6 +826,9 @@ extern bool iommu_default_passthrough(void);
 extern struct iommu_resv_region *
 iommu_alloc_resv_region(phys_addr_t start, size_t length, int prot,
 			enum iommu_resv_type type, gfp_t gfp);
+extern struct iommu_resv_region *
+iommu_alloc_resv_region_tr(phys_addr_t start, dma_addr_t dva_start, size_t length,
+			   int prot, enum iommu_resv_type type, gfp_t gfp);
 extern int iommu_get_group_resv_regions(struct iommu_group *group,
 					struct list_head *head);
 

From a30b739816203252a4b3ccaad8b7e9d930125d3a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 22 Oct 2022 12:24:54 +0200
Subject: [PATCH 0205/1027] iommu: Parse translated reserved regions

These regions are setup by the boot loader and require an iommu to
translate arbitray physical to device VA mappings.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/iommu/dma-iommu.c |  9 +++++++--
 drivers/iommu/of_iommu.c  | 11 +++++++----
 2 files changed, 14 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/dma-iommu.c b/drivers/iommu/dma-iommu.c
index 7b1dfa0665df60..8e62e7b2b211a6 100644
--- a/drivers/iommu/dma-iommu.c
+++ b/drivers/iommu/dma-iommu.c
@@ -582,8 +582,13 @@ static int iova_reserve_iommu_regions(struct device *dev,
 		if (region->type == IOMMU_RESV_SW_MSI)
 			continue;
 
-		lo = iova_pfn(iovad, region->start);
-		hi = iova_pfn(iovad, region->start + region->length - 1);
+		if (region->type == IOMMU_RESV_TRANSLATED) {
+			lo = iova_pfn(iovad, region->dva);
+			hi = iova_pfn(iovad, region->dva + region->length - 1);
+		} else {
+			lo = iova_pfn(iovad, region->start);
+			hi = iova_pfn(iovad, region->start + region->length - 1);
+		}
 		reserve_iova(iovad, lo, hi);
 
 		if (region->type == IOMMU_RESV_MSI)
diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 78d61da75257c2..1d70d55f0e1ff0 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -180,9 +180,7 @@ iommu_resv_region_get_type(struct device *dev,
 	if (start == phys->start && end == phys->end)
 		return IOMMU_RESV_DIRECT;
 
-	dev_warn(dev, "treating non-direct mapping [%pr] -> [%pap-%pap] as reservation\n", phys,
-		 &start, &end);
-	return IOMMU_RESV_RESERVED;
+	return IOMMU_RESV_TRANSLATED;
 }
 
 /**
@@ -253,8 +251,13 @@ void of_iommu_get_resv_regions(struct device *dev, struct list_head *list)
 				}
 				type = iommu_resv_region_get_type(dev, &phys, iova, length);
 
-				region = iommu_alloc_resv_region(iova, length, prot, type,
+				if (type == IOMMU_RESV_TRANSLATED)
+					region = iommu_alloc_resv_region_tr(phys.start, iova, length, prot, type,
+								    GFP_KERNEL);
+				else
+					region = iommu_alloc_resv_region(iova, length, prot, type,
 								 GFP_KERNEL);
+
 				if (region)
 					list_add_tail(&region->list, list);
 			}

From 1625c0a1ee9169a423561c61e2935830bedb0162 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Thu, 26 Aug 2021 12:16:52 -0400
Subject: [PATCH 0206/1027] iommu/dart: Track if the DART is locked

Locked DARTs require special handling. This can be detected with the
configuration register. Check this when probing and save the result.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
---
 drivers/iommu/apple-dart.c | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index eb1e62cd499a58..265a3d4328a58f 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -197,6 +197,7 @@ struct apple_dart_hw {
  * @lock: lock for hardware operations involving this dart
  * @pgsize: pagesize supported by this DART
  * @supports_bypass: indicates if this DART supports bypass mode
+ * @locked: indicates if this DART is locked
  * @sid2group: maps stream ids to iommu_groups
  * @iommu: iommu core device
  */
@@ -217,6 +218,7 @@ struct apple_dart {
 	u32 pgsize;
 	u32 num_streams;
 	u32 supports_bypass : 1;
+	u32 locked : 1;
 
 	struct iommu_group *sid2group[DART_MAX_STREAMS];
 	struct iommu_device iommu;
@@ -1076,6 +1078,11 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
 	return IRQ_HANDLED;
 }
 
+static bool apple_dart_is_locked(struct apple_dart *dart)
+{
+	return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit);
+}
+
 static int apple_dart_probe(struct platform_device *pdev)
 {
 	int ret;
@@ -1143,6 +1150,7 @@ static int apple_dart_probe(struct platform_device *pdev)
 		goto err_clk_disable;
 	}
 
+	dart->locked = apple_dart_is_locked(dart);
 	ret = apple_dart_hw_reset(dart);
 	if (ret)
 		goto err_clk_disable;
@@ -1165,9 +1173,9 @@ static int apple_dart_probe(struct platform_device *pdev)
 
 	dev_info(
 		&pdev->dev,
-		"DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d] initialized\n",
+		"DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d] initialized\n",
 		dart->pgsize, dart->num_streams, dart->supports_bypass,
-		dart->pgsize > PAGE_SIZE);
+		dart->pgsize > PAGE_SIZE, dart->locked);
 	return 0;
 
 err_sysfs_remove:

From f1fbab46bde540faa725c2bc46d03190e22cb632 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Thu, 26 Aug 2021 12:18:28 -0400
Subject: [PATCH 0207/1027] iommu/dart: Allow locked DARTs to probe

Instead of bailing from reset() if the DART is locked, simply skip the
reset for locked DARTs.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
---
 drivers/iommu/apple-dart.c | 16 +++++-----------
 1 file changed, 5 insertions(+), 11 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 265a3d4328a58f..40206e54ccd75d 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -452,17 +452,9 @@ apple_dart_t8110_hw_invalidate_tlb(struct apple_dart_stream_map *stream_map)
 
 static int apple_dart_hw_reset(struct apple_dart *dart)
 {
-	u32 config;
 	struct apple_dart_stream_map stream_map;
 	int i;
 
-	config = readl(dart->regs + dart->hw->lock);
-	if (config & dart->hw->lock_bit) {
-		dev_err(dart->dev, "DART is locked down until reboot: %08x\n",
-			config);
-		return -EINVAL;
-	}
-
 	stream_map.dart = dart;
 	bitmap_zero(stream_map.sidmap, DART_MAX_STREAMS);
 	bitmap_set(stream_map.sidmap, 0, dart->num_streams);
@@ -1151,9 +1143,11 @@ static int apple_dart_probe(struct platform_device *pdev)
 	}
 
 	dart->locked = apple_dart_is_locked(dart);
-	ret = apple_dart_hw_reset(dart);
-	if (ret)
-		goto err_clk_disable;
+	if (!dart->locked) {
+		ret = apple_dart_hw_reset(dart);
+		if (ret)
+			goto err_clk_disable;
+	}
 
 	ret = request_irq(dart->irq, dart->hw->irq_handler, IRQF_SHARED,
 			  "apple-dart fault handler", dart);

From 58524fbd432fc4362c7c35ac9240962935e6b3cd Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 17 Nov 2022 21:31:23 +0900
Subject: [PATCH 0208/1027] iommu: apple-dart: Don't attempt to reset/restore
 locked DARTs

This can't work, and should not be needed in these cases.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 40206e54ccd75d..2a11d84ae92316 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -1186,7 +1186,9 @@ static void apple_dart_remove(struct platform_device *pdev)
 {
 	struct apple_dart *dart = platform_get_drvdata(pdev);
 
-	apple_dart_hw_reset(dart);
+	if (!dart->locked)
+		apple_dart_hw_reset(dart);
+
 	free_irq(dart->irq, dart);
 
 	iommu_device_unregister(&dart->iommu);
@@ -1319,6 +1321,10 @@ static __maybe_unused int apple_dart_resume(struct device *dev)
 	unsigned int sid, idx;
 	int ret;
 
+	/* Locked DARTs can't be restored, and they should not need it */
+	if (dart->locked)
+		return 0;
+
 	ret = apple_dart_hw_reset(dart);
 	if (ret) {
 		dev_err(dev, "Failed to reset DART on resume\n");

From 6cf6a40ee335b1c04efb13c95578c98b2519c158 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Thu, 26 Aug 2021 12:48:59 -0400
Subject: [PATCH 0209/1027] iommu/dart: Set DMA domain for locked DARTs

This is required.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
---
 drivers/iommu/apple-dart.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 2a11d84ae92316..fcbfe0ea077afc 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -941,6 +941,8 @@ static int apple_dart_def_domain_type(struct device *dev)
 		return IOMMU_DOMAIN_IDENTITY;
 	if (!cfg->stream_maps[0].dart->supports_bypass)
 		return IOMMU_DOMAIN_DMA;
+	if (cfg->stream_maps[0].dart->locked)
+		return IOMMU_DOMAIN_DMA;
 
 	return 0;
 }

From 838ff809954fd9ff5325139bf90d3577b9980b80 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Thu, 26 Aug 2021 12:50:07 -0400
Subject: [PATCH 0210/1027] iommu/dart: Reject identity domain for locked DARTs

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Signed-off-by: Janbne Grunau <j@jannau.net>
---
 drivers/iommu/apple-dart.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index fcbfe0ea077afc..21e1620ca40b2d 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -681,6 +681,9 @@ static int apple_dart_attach_dev_identity(struct iommu_domain *domain,
 	if (!cfg->stream_maps[0].dart->supports_bypass)
 		return -EINVAL;
 
+	if (cfg->stream_maps[0].dart->locked)
+		return -EINVAL;
+
 	for_each_stream_map(i, cfg, stream_map)
 		apple_dart_hw_enable_bypass(stream_map);
 	return 0;

From 071ca5c065f44e48e38d54918e8eb08cadfd5c79 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Thu, 26 Aug 2021 13:19:51 -0400
Subject: [PATCH 0211/1027] iommu/dart: Assert !locked when reconfiguring

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
---
 drivers/iommu/apple-dart.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 21e1620ca40b2d..a856fb2b1637b6 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -309,6 +309,7 @@ apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map)
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(stream_map->dart->locked);
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
 		writel(dart->hw->tcr_enabled, dart->regs + DART_TCR(dart, sid));
 }
@@ -318,6 +319,7 @@ static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map)
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(stream_map->dart->locked);
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
 		writel(dart->hw->tcr_disabled, dart->regs + DART_TCR(dart, sid));
 }
@@ -328,6 +330,7 @@ apple_dart_hw_enable_bypass(struct apple_dart_stream_map *stream_map)
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(stream_map->dart->locked);
 	WARN_ON(!stream_map->dart->supports_bypass);
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
 		writel(dart->hw->tcr_bypass,
@@ -340,6 +343,7 @@ static void apple_dart_hw_set_ttbr(struct apple_dart_stream_map *stream_map,
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(stream_map->dart->locked);
 	WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1));
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
 		writel(dart->hw->ttbr_valid |
@@ -353,6 +357,7 @@ static void apple_dart_hw_clear_ttbr(struct apple_dart_stream_map *stream_map,
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(stream_map->dart->locked);
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
 		writel(0, dart->regs + DART_TTBR(dart, sid, idx));
 }

From 746bc9b71378b299efa5759d447987e5e3d71ff1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 20 Nov 2022 14:23:57 +0100
Subject: [PATCH 0212/1027] iommu: apple-dart: Install IOMMU_RESV_TRANSLATED
 mappings

The iommus for the display processors on Apple silicon machines have
locked TTBR registers. To support iommu domain switching use a shadow
L1 page table and sync it on flush to the HW L1 table.

TODO: investigate if it's possible / necessary to optimize the syncing

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/iommu/Kconfig      |   1 +
 drivers/iommu/apple-dart.c | 175 +++++++++++++++++++++++++++++++++++--
 2 files changed, 168 insertions(+), 8 deletions(-)

diff --git a/drivers/iommu/Kconfig b/drivers/iommu/Kconfig
index a82f10054aec86..c7f8ee94d1f214 100644
--- a/drivers/iommu/Kconfig
+++ b/drivers/iommu/Kconfig
@@ -305,6 +305,7 @@ config APPLE_DART
 	depends on !GENERIC_ATOMIC64	# for IOMMU_IO_PGTABLE_DART
 	select IOMMU_API
 	select IOMMU_IO_PGTABLE_DART
+	select OF_IOMMU
 	default ARCH_APPLE
 	help
 	  Support for Apple DART (Device Address Resolution Table) IOMMUs
diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index a856fb2b1637b6..010b6fe5257543 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -225,6 +225,9 @@ struct apple_dart {
 
 	u32 save_tcr[DART_MAX_STREAMS];
 	u32 save_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
+
+	u64 *locked_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
+	u64 *shadow_ttbr[DART_MAX_STREAMS][DART_MAX_TTBR];
 };
 
 /*
@@ -371,6 +374,89 @@ apple_dart_hw_clear_all_ttbrs(struct apple_dart_stream_map *stream_map)
 		apple_dart_hw_clear_ttbr(stream_map, i);
 }
 
+static int
+apple_dart_hw_set_locked_ttbr(struct apple_dart_stream_map *stream_map, u8 idx,
+			      phys_addr_t paddr)
+{
+	struct apple_dart *dart = stream_map->dart;
+	int sid;
+
+	WARN_ON(!dart->locked);
+	WARN_ON(paddr & ((1 << dart->hw->ttbr_shift) - 1));
+	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+		u32 ttbr;
+		phys_addr_t phys;
+		u64 *l1_tbl, *l1_shadow;
+
+		ttbr = readl(dart->regs + DART_TTBR(dart, sid, idx));
+
+		WARN_ON(!(ttbr & dart->hw->ttbr_valid));
+		ttbr &= ~dart->hw->ttbr_valid;
+
+		if (dart->hw->ttbr_addr_field_shift)
+			ttbr >>= dart->hw->ttbr_addr_field_shift;
+		phys = ((phys_addr_t) ttbr) << dart->hw->ttbr_shift;
+
+		l1_tbl = devm_memremap(dart->dev, phys, dart->pgsize,
+				       MEMREMAP_WB);
+		if (!l1_tbl)
+			return -ENOMEM;
+		l1_shadow = devm_memremap(dart->dev, paddr, dart->pgsize,
+				       MEMREMAP_WB);
+		if (!l1_shadow)
+			return -ENOMEM;
+
+		dart->locked_ttbr[sid][idx] = l1_tbl;
+		dart->shadow_ttbr[sid][idx] = l1_shadow;
+	}
+
+	return 0;
+}
+
+static int
+apple_dart_hw_clear_locked_ttbr(struct apple_dart_stream_map *stream_map,
+				u8 idx)
+{
+	struct apple_dart *dart = stream_map->dart;
+	int sid;
+
+	WARN_ON(!dart->locked);
+	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+		/* TODO: locked L1 table might need to be restored to boot state */
+		if (dart->locked_ttbr[sid][idx]) {
+			memset(dart->locked_ttbr[sid][idx], 0, dart->pgsize);
+			devm_memunmap(dart->dev, dart->locked_ttbr[sid][idx]);
+		}
+		dart->locked_ttbr[sid][idx] = NULL;
+		if (dart->shadow_ttbr[sid][idx])
+			devm_memunmap(dart->dev, dart->shadow_ttbr[sid][idx]);
+		dart->shadow_ttbr[sid][idx] = NULL;
+	}
+
+	return 0;
+}
+
+static int
+apple_dart_hw_sync_locked(struct apple_dart_stream_map *stream_map)
+{
+	struct apple_dart *dart = stream_map->dart;
+	int sid;
+
+	WARN_ON(!dart->locked);
+	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams) {
+		for (int idx = 0; idx < dart->hw->ttbr_count; idx++) {
+			u64 *ttbrep = dart->locked_ttbr[sid][idx];
+			u64 *ptep = dart->shadow_ttbr[sid][idx];
+			if (!ttbrep || !ptep)
+				continue;
+			for (int entry = 0; entry < dart->pgsize / sizeof(*ptep); entry++)
+				ttbrep[entry] = ptep[entry];
+		}
+	}
+
+	return 0;
+}
+
 static int
 apple_dart_t8020_hw_stream_command(struct apple_dart_stream_map *stream_map,
 			     u32 command)
@@ -491,6 +577,10 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain)
 		for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++)
 			stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]);
 
+
+		if (stream_map.dart->locked)
+			apple_dart_hw_sync_locked(&stream_map);
+
 		stream_map.dart->hw->invalidate_tlb(&stream_map);
 	}
 }
@@ -559,17 +649,62 @@ apple_dart_setup_translation(struct apple_dart_domain *domain,
 	struct io_pgtable_cfg *pgtbl_cfg =
 		&io_pgtable_ops_to_pgtable(domain->pgtbl_ops)->cfg;
 
-	for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
-		apple_dart_hw_set_ttbr(stream_map, i,
-				       pgtbl_cfg->apple_dart_cfg.ttbr[i]);
-	for (; i < stream_map->dart->hw->ttbr_count; ++i)
-		apple_dart_hw_clear_ttbr(stream_map, i);
+	/* Locked DARTs are set up by the bootloader. */
+	if (stream_map->dart->locked) {
+		for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
+			apple_dart_hw_set_locked_ttbr(stream_map, i,
+					pgtbl_cfg->apple_dart_cfg.ttbr[i]);
+		for (; i < stream_map->dart->hw->ttbr_count; ++i)
+			apple_dart_hw_clear_locked_ttbr(stream_map, i);
+		apple_dart_hw_sync_locked(stream_map);
+	} else {
+		for (i = 0; i < pgtbl_cfg->apple_dart_cfg.n_ttbrs; ++i)
+			apple_dart_hw_set_ttbr(stream_map, i,
+					pgtbl_cfg->apple_dart_cfg.ttbr[i]);
+		for (; i < stream_map->dart->hw->ttbr_count; ++i)
+			apple_dart_hw_clear_ttbr(stream_map, i);
 
-	apple_dart_hw_enable_translation(stream_map);
+		apple_dart_hw_enable_translation(stream_map);
+	}
 	stream_map->dart->hw->invalidate_tlb(stream_map);
 }
 
+static int apple_dart_setup_resv_locked(struct iommu_domain *domain,
+					struct device *dev, size_t pgsize)
+{
+	struct iommu_resv_region *region;
+	LIST_HEAD(resv_regions);
+	int ret = 0;
+
+	of_iommu_get_resv_regions(dev, &resv_regions);
+	list_for_each_entry(region, &resv_regions, list) {
+		size_t mapped = 0;
+
+		/* Only map translated reserved regions */
+		if (region->type != IOMMU_RESV_TRANSLATED)
+			continue;
+
+		while (mapped < region->length) {
+			phys_addr_t paddr = region->start + mapped;
+			unsigned long iova = region->dva + mapped;
+			size_t length = region->length - mapped;
+			size_t pgcount = length / pgsize;
+
+			ret = apple_dart_map_pages(domain, iova,
+			      paddr, pgsize, pgcount,
+			      region->prot, GFP_KERNEL, &mapped);
+
+			if (ret)
+				goto end_put;
+		}
+	}
+end_put:
+	iommu_put_resv_regions(dev, &resv_regions);
+	return ret;
+}
+
 static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
+				      struct device *dev,
 				      struct apple_dart_master_cfg *cfg)
 {
 	struct apple_dart *dart = cfg->stream_maps[0].dart;
@@ -600,6 +735,21 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 		.iommu_dev = dart->dev,
 	};
 
+	if (dart->locked) {
+		unsigned long *sidmap;
+		int sid;
+		u32 ttbr;
+
+		/* Locked DARTs can only have a single stream bound */
+		sidmap = cfg->stream_maps[0].sidmap;
+		sid = find_first_bit(sidmap, dart->num_streams);
+
+		WARN_ON((sid < 0) || bitmap_weight(sidmap, dart->num_streams) > 1);
+		ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0));
+
+		WARN_ON(!(ttbr & dart->hw->ttbr_valid));
+	}
+
 	dart_domain->pgtbl_ops = alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg,
 						      &dart_domain->domain);
 	if (!dart_domain->pgtbl_ops) {
@@ -615,6 +765,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 
 	dart_domain->finalized = true;
 
+	ret = apple_dart_setup_resv_locked(&dart_domain->domain, dev, dart->pgsize);
 done:
 	mutex_unlock(&dart_domain->init_lock);
 	return ret;
@@ -663,7 +814,7 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain,
 	struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 
-	ret = apple_dart_finalize_domain(dart_domain, cfg);
+	ret = apple_dart_finalize_domain(dart_domain, dev, cfg);
 	if (ret)
 		return ret;
 
@@ -743,8 +894,16 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev)
 
 static void apple_dart_release_device(struct device *dev)
 {
+	int i, j;
+	struct apple_dart_stream_map *stream_map;
 	struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
 
+	for_each_stream_map(j, cfg, stream_map) {
+		if (stream_map->dart->locked)
+			for (i = 0; i < stream_map->dart->hw->ttbr_count; ++i)
+				apple_dart_hw_clear_locked_ttbr(stream_map, i);
+	}
+
 	kfree(cfg);
 }
 
@@ -762,7 +921,7 @@ static struct iommu_domain *apple_dart_domain_alloc_paging(struct device *dev)
 		struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
 		int ret;
 
-		ret = apple_dart_finalize_domain(dart_domain, cfg);
+		ret = apple_dart_finalize_domain(dart_domain, dev, cfg);
 		if (ret) {
 			kfree(dart_domain);
 			return ERR_PTR(ret);

From f67ef073acd77ebe05ea5cf1d1a5d2cd7c075afc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 28 Apr 2023 19:10:56 +0200
Subject: [PATCH 0213/1027] iommu: apple-dart: Link to consumers with blanket
 RPM_ACTIVE
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Without the RPM_ACTIVE flag, runtime PM core only seems to consider
the link insofar as it prevents the DART from suspending in case of
consumers *considered active by runtime PM*. Other devices, like those
on which runtime PM has yet to be enabled, or which lack any runtime PM
support, are not considered in preventing the DART from suspending.

DART going through suspend/resume cycle with active consumers can break
the consumers' operation by the DART being reset in its resume path,
among other things.

Add RPM_ACTIVE flag to the link to have the consumer in the link prevent
the DART from being suspended, unless the consumer itself is runtime PM
suspended. This supersedes an earlier PCIe-only workaround.

(TODO: Does this mean devices without bound drivers will keep their
DARTs up indefinitely? This depends on the timing of the iommu
probe_device/release_device calls. Investigate.)

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/iommu/apple-dart.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 010b6fe5257543..03c42a49b91826 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -885,9 +885,9 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev)
 		return ERR_PTR(-ENODEV);
 
 	for_each_stream_map(i, cfg, stream_map)
-		device_link_add(
-			dev, stream_map->dart->dev,
-			DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER);
+		device_link_add(dev, stream_map->dart->dev,
+			DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER |
+			DL_FLAG_RPM_ACTIVE);
 
 	return &cfg->stream_maps[0].dart->iommu;
 }

From 54de489e42a2de186caa2c990d8de5f86db07b17 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 12 Dec 2022 23:53:23 +0900
Subject: [PATCH 0214/1027] iommu: apple-dart: Enable runtime PM

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 42 +++++++++++++++++++++++++++++++++-----
 1 file changed, 37 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 03c42a49b91826..62b011874d30e5 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -28,6 +28,7 @@
 #include <linux/of_platform.h>
 #include <linux/pci.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/slab.h>
 #include <linux/swab.h>
 #include <linux/types.h>
@@ -577,11 +578,13 @@ static void apple_dart_domain_flush_tlb(struct apple_dart_domain *domain)
 		for (j = 0; j < BITS_TO_LONGS(stream_map.dart->num_streams); j++)
 			stream_map.sidmap[j] = atomic_long_read(&domain_stream_map->sidmap[j]);
 
+		WARN_ON(pm_runtime_get_sync(stream_map.dart->dev) < 0);
 
 		if (stream_map.dart->locked)
 			apple_dart_hw_sync_locked(&stream_map);
 
 		stream_map.dart->hw->invalidate_tlb(&stream_map);
+		pm_runtime_put(stream_map.dart->dev);
 	}
 }
 
@@ -814,17 +817,23 @@ static int apple_dart_attach_dev_paging(struct iommu_domain *domain,
 	struct apple_dart_master_cfg *cfg = dev_iommu_priv_get(dev);
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 
+	for_each_stream_map(i, cfg, stream_map)
+		WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0);
+
 	ret = apple_dart_finalize_domain(dart_domain, dev, cfg);
 	if (ret)
-		return ret;
+		goto err;
 
 	ret = apple_dart_domain_add_streams(dart_domain, cfg);
 	if (ret)
-		return ret;
+		goto err;
 
 	for_each_stream_map(i, cfg, stream_map)
 		apple_dart_setup_translation(dart_domain, stream_map);
-	return 0;
+err:
+	for_each_stream_map(i, cfg, stream_map)
+		pm_runtime_put(stream_map->dart->dev);
+	return ret;
 }
 
 static int apple_dart_attach_dev_identity(struct iommu_domain *domain,
@@ -840,8 +849,14 @@ static int apple_dart_attach_dev_identity(struct iommu_domain *domain,
 	if (cfg->stream_maps[0].dart->locked)
 		return -EINVAL;
 
+	for_each_stream_map(i, cfg, stream_map)
+		WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0);
+
 	for_each_stream_map(i, cfg, stream_map)
 		apple_dart_hw_enable_bypass(stream_map);
+
+	for_each_stream_map(i, cfg, stream_map)
+		pm_runtime_put(stream_map->dart->dev);
 	return 0;
 }
 
@@ -861,8 +876,14 @@ static int apple_dart_attach_dev_blocked(struct iommu_domain *domain,
 	struct apple_dart_stream_map *stream_map;
 	int i;
 
+	for_each_stream_map(i, cfg, stream_map)
+		WARN_ON(pm_runtime_get_sync(stream_map->dart->dev) < 0);
+
 	for_each_stream_map(i, cfg, stream_map)
 		apple_dart_hw_disable_dma(stream_map);
+
+	for_each_stream_map(i, cfg, stream_map)
+		pm_runtime_put(stream_map->dart->dev);
 	return 0;
 }
 
@@ -1282,6 +1303,14 @@ static int apple_dart_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	pm_runtime_get_noresume(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_irq_safe(dev);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		goto err_clk_disable;
+
 	dart_params[0] = readl(dart->regs + DART_PARAMS1);
 	dart_params[1] = readl(dart->regs + DART_PARAMS2);
 	dart->pgsize = 1 << FIELD_GET(DART_PARAMS1_PAGE_SHIFT, dart_params[0]);
@@ -1334,6 +1363,8 @@ static int apple_dart_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_sysfs_remove;
 
+	pm_runtime_put(dev);
+
 	dev_info(
 		&pdev->dev,
 		"DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d] initialized\n",
@@ -1346,6 +1377,7 @@ static int apple_dart_probe(struct platform_device *pdev)
 err_free_irq:
 	free_irq(dart->irq, dart);
 err_clk_disable:
+	pm_runtime_put(dev);
 	clk_bulk_disable_unprepare(dart->num_clks, dart->clks);
 
 	return ret;
@@ -1510,7 +1542,7 @@ static __maybe_unused int apple_dart_resume(struct device *dev)
 	return 0;
 }
 
-static DEFINE_SIMPLE_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume);
+static DEFINE_RUNTIME_DEV_PM_OPS(apple_dart_pm_ops, apple_dart_suspend, apple_dart_resume, NULL);
 
 static const struct of_device_id apple_dart_of_match[] = {
 	{ .compatible = "apple,t8103-dart", .data = &apple_dart_hw_t8103 },
@@ -1526,7 +1558,7 @@ static struct platform_driver apple_dart_driver = {
 		.name			= "apple-dart",
 		.of_match_table		= apple_dart_of_match,
 		.suppress_bind_attrs    = true,
-		.pm			= pm_sleep_ptr(&apple_dart_pm_ops),
+		.pm			= pm_ptr(&apple_dart_pm_ops),
 	},
 	.probe	= apple_dart_probe,
 	.remove_new = apple_dart_remove,

From 85e1993a7436608f456e19d8eb36821e318f8380 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 20:02:26 +0900
Subject: [PATCH 0215/1027] iommu: io-pgtable: Add 4-level page table support

DARTs on t602x SoCs are of the t8110 variant but have an IAS of 42,
which means optional support for an extra page table level.

Refactor the PTE management to support an arbitrary level count, and
then calculate how many levels we need for any given configuration.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/io-pgtable-dart.c | 140 ++++++++++++++++++++------------
 include/linux/io-pgtable.h      |   1 +
 2 files changed, 88 insertions(+), 53 deletions(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index c004640640ee50..0acae4c9d80a77 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -27,8 +27,9 @@
 
 #define DART1_MAX_ADDR_BITS	36
 
-#define DART_MAX_TABLES		4
-#define DART_LEVELS		2
+#define DART_MAX_TABLE_BITS	2
+#define DART_MAX_TABLES		BIT(DART_MAX_TABLE_BITS)
+#define DART_MAX_LEVELS		4 /* Includes TTBR level */
 
 /* Struct accessors */
 #define io_pgtable_to_data(x)						\
@@ -68,6 +69,7 @@
 struct dart_io_pgtable {
 	struct io_pgtable	iop;
 
+	int			levels;
 	int			tbl_bits;
 	int			bits_per_level;
 
@@ -165,44 +167,45 @@ static dart_iopte dart_install_table(dart_iopte *table,
 	return old;
 }
 
-static int dart_get_table(struct dart_io_pgtable *data, unsigned long iova)
+static int dart_get_index(struct dart_io_pgtable *data, unsigned long iova, int level)
 {
-	return (iova >> (3 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
-		((1 << data->tbl_bits) - 1);
+	return (iova >> (level * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
+		((1 << data->bits_per_level) - 1);
 }
 
-static int dart_get_l1_index(struct dart_io_pgtable *data, unsigned long iova)
-{
-
-	return (iova >> (2 * data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
-		 ((1 << data->bits_per_level) - 1);
-}
-
-static int dart_get_l2_index(struct dart_io_pgtable *data, unsigned long iova)
+static int dart_get_last_index(struct dart_io_pgtable *data, unsigned long iova)
 {
 
 	return (iova >> (data->bits_per_level + ilog2(sizeof(dart_iopte)))) &
 		 ((1 << data->bits_per_level) - 1);
 }
 
-static  dart_iopte *dart_get_l2(struct dart_io_pgtable *data, unsigned long iova)
+static dart_iopte *dart_get_last(struct dart_io_pgtable *data, unsigned long iova)
 {
 	dart_iopte pte, *ptep;
-	int tbl = dart_get_table(data, iova);
+	int level = data->levels;
+	int tbl = dart_get_index(data, iova, level);
+
+	if (tbl > (1 << data->tbl_bits))
+		return NULL;
 
 	ptep = data->pgd[tbl];
 	if (!ptep)
 		return NULL;
 
-	ptep += dart_get_l1_index(data, iova);
-	pte = READ_ONCE(*ptep);
+	while (--level > 1) {
+		ptep += dart_get_index(data, iova, level);
+		pte = READ_ONCE(*ptep);
 
-	/* Valid entry? */
-	if (!pte)
-		return NULL;
+		/* Valid entry? */
+		if (!pte)
+			return NULL;
 
-	/* Deref to get level 2 table */
-	return iopte_deref(pte, data);
+		/* Deref to get next level table */
+		ptep = iopte_deref(pte, data);
+	}
+
+	return ptep;
 }
 
 static dart_iopte dart_prot_to_pte(struct dart_io_pgtable *data,
@@ -238,6 +241,7 @@ static int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	int ret = 0, tbl, num_entries, max_entries, map_idx_start;
 	dart_iopte pte, *cptep, *ptep;
 	dart_iopte prot;
+	int level = data->levels;
 
 	if (WARN_ON(pgsize != cfg->pgsize_bitmap))
 		return -EINVAL;
@@ -248,31 +252,36 @@ static int dart_map_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	if (!(iommu_prot & (IOMMU_READ | IOMMU_WRITE)))
 		return -EINVAL;
 
-	tbl = dart_get_table(data, iova);
+	tbl = dart_get_index(data, iova, level);
+
+	if (tbl > (1 << data->tbl_bits))
+		return -ENOMEM;
 
 	ptep = data->pgd[tbl];
-	ptep += dart_get_l1_index(data, iova);
-	pte = READ_ONCE(*ptep);
+	while (--level > 1) {
+		ptep += dart_get_index(data, iova, level);
+		pte = READ_ONCE(*ptep);
 
-	/* no L2 table present */
-	if (!pte) {
-		cptep = __dart_alloc_pages(tblsz, gfp);
-		if (!cptep)
-			return -ENOMEM;
+		/* no table present */
+		if (!pte) {
+			cptep = __dart_alloc_pages(tblsz, gfp);
+			if (!cptep)
+				return -ENOMEM;
 
-		pte = dart_install_table(cptep, ptep, 0, data);
-		if (pte)
-			iommu_free_pages(cptep, get_order(tblsz));
+			pte = dart_install_table(cptep, ptep, 0, data);
+			if (pte)
+				iommu_free_pages(cptep, get_order(tblsz));
 
-		/* L2 table is present (now) */
-		pte = READ_ONCE(*ptep);
-	}
+			/* L2 table is present (now) */
+			pte = READ_ONCE(*ptep);
+		}
 
-	ptep = iopte_deref(pte, data);
+		ptep = iopte_deref(pte, data);
+	}
 
 	/* install a leaf entries into L2 table */
 	prot = dart_prot_to_pte(data, iommu_prot);
-	map_idx_start = dart_get_l2_index(data, iova);
+	map_idx_start = dart_get_last_index(data, iova);
 	max_entries = DART_PTES_PER_TABLE(data) - map_idx_start;
 	num_entries = min_t(int, pgcount, max_entries);
 	ptep += map_idx_start;
@@ -301,13 +310,13 @@ static size_t dart_unmap_pages(struct io_pgtable_ops *ops, unsigned long iova,
 	if (WARN_ON(pgsize != cfg->pgsize_bitmap || !pgcount))
 		return 0;
 
-	ptep = dart_get_l2(data, iova);
+	ptep = dart_get_last(data, iova);
 
 	/* Valid L2 IOPTE pointer? */
 	if (WARN_ON(!ptep))
 		return 0;
 
-	unmap_idx_start = dart_get_l2_index(data, iova);
+	unmap_idx_start = dart_get_last_index(data, iova);
 	ptep += unmap_idx_start;
 
 	max_entries = DART_PTES_PER_TABLE(data) - unmap_idx_start;
@@ -338,13 +347,13 @@ static phys_addr_t dart_iova_to_phys(struct io_pgtable_ops *ops,
 	struct dart_io_pgtable *data = io_pgtable_ops_to_data(ops);
 	dart_iopte pte, *ptep;
 
-	ptep = dart_get_l2(data, iova);
+	ptep = dart_get_last(data, iova);
 
 	/* Valid L2 IOPTE pointer? */
 	if (!ptep)
 		return 0;
 
-	ptep += dart_get_l2_index(data, iova);
+	ptep += dart_get_last_index(data, iova);
 
 	pte = READ_ONCE(*ptep);
 	/* Found translation */
@@ -361,21 +370,37 @@ static struct dart_io_pgtable *
 dart_alloc_pgtable(struct io_pgtable_cfg *cfg)
 {
 	struct dart_io_pgtable *data;
-	int tbl_bits, bits_per_level, va_bits, pg_shift;
+	int levels, max_tbl_bits, tbl_bits, bits_per_level, va_bits, pg_shift;
+
+	/*
+	 * Old 4K page DARTs can use up to 4 top-level tables.
+	 * Newer ones only ever use a maximum of 1.
+	 */
+	if (cfg->pgsize_bitmap == SZ_4K)
+		max_tbl_bits = DART_MAX_TABLE_BITS;
+	else
+		max_tbl_bits = 0;
 
 	pg_shift = __ffs(cfg->pgsize_bitmap);
 	bits_per_level = pg_shift - ilog2(sizeof(dart_iopte));
 
 	va_bits = cfg->ias - pg_shift;
 
-	tbl_bits = max_t(int, 0, va_bits - (bits_per_level * DART_LEVELS));
-	if ((1 << tbl_bits) > DART_MAX_TABLES)
+	levels = max_t(int, 2, (va_bits - max_tbl_bits + bits_per_level - 1) / bits_per_level);
+
+	if (levels > (DART_MAX_LEVELS - 1))
+		return NULL;
+
+	tbl_bits = max_t(int, 0, va_bits - (bits_per_level * levels));
+
+	if (tbl_bits > max_tbl_bits)
 		return NULL;
 
 	data = kzalloc(sizeof(*data), GFP_KERNEL);
 	if (!data)
 		return NULL;
 
+	data->levels = levels + 1; /* Table level counts as one level */
 	data->tbl_bits = tbl_bits;
 	data->bits_per_level = bits_per_level;
 
@@ -411,6 +436,7 @@ apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
 		return NULL;
 
 	cfg->apple_dart_cfg.n_ttbrs = 1 << data->tbl_bits;
+	cfg->apple_dart_cfg.n_levels = data->levels;
 
 	for (i = 0; i < cfg->apple_dart_cfg.n_ttbrs; ++i) {
 		data->pgd[i] = __dart_alloc_pages(DART_GRANULE(data), GFP_KERNEL);
@@ -430,24 +456,32 @@ apple_dart_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
 	return NULL;
 }
 
-static void apple_dart_free_pgtable(struct io_pgtable *iop)
+static void apple_dart_free_pgtables(struct dart_io_pgtable *data, dart_iopte *ptep, int level)
 {
-	struct dart_io_pgtable *data = io_pgtable_to_data(iop);
+	dart_iopte *end;
+	dart_iopte *start = ptep;
 	int order = get_order(DART_GRANULE(data));
-	dart_iopte *ptep, *end;
-	int i;
 
-	for (i = 0; i < (1 << data->tbl_bits) && data->pgd[i]; ++i) {
-		ptep = data->pgd[i];
+	if (level > 1) {
 		end = (void *)ptep + DART_GRANULE(data);
 
 		while (ptep != end) {
 			dart_iopte pte = *ptep++;
 
 			if (pte)
-				iommu_free_pages(iopte_deref(pte, data), order);
+				apple_dart_free_pgtables(data, iopte_deref(pte, data), level - 1);
 		}
-		iommu_free_pages(data->pgd[i], order);
+	}
+	iommu_free_pages(start, order);
+}
+
+static void apple_dart_free_pgtable(struct io_pgtable *iop)
+{
+	struct dart_io_pgtable *data = io_pgtable_to_data(iop);
+	int i;
+
+	for (i = 0; i < (1 << data->tbl_bits) && data->pgd[i]; ++i) {
+		apple_dart_free_pgtables(data, data->pgd[i], data->levels - 1);
 	}
 
 	kfree(data);
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index f9a81761bfceda..7851fdaa208a20 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -170,6 +170,7 @@ struct io_pgtable_cfg {
 		struct {
 			u64 ttbr[4];
 			u32 n_ttbrs;
+			u32 n_levels;
 		} apple_dart_cfg;
 	};
 };

From 0844db09cb1ab5bb2ec0912b74296ba87d11d80d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 20:04:05 +0900
Subject: [PATCH 0216/1027] iommu: apple-dart: Clear stream error indicator
 bits for T8110 DARTs

These registers exist at least on the t602x variant, and if not cleared
the IRQ will never clear.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 62b011874d30e5..eadc00e69e860c 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -123,6 +123,8 @@
 #define DART_T8110_ERROR_ADDR_LO 0x170
 #define DART_T8110_ERROR_ADDR_HI 0x174
 
+#define DART_T8110_ERROR_STREAMS 0x1c0
+
 #define DART_T8110_PROTECT 0x200
 #define DART_T8110_UNPROTECT 0x204
 #define DART_T8110_PROTECT_LOCK 0x208
@@ -1231,6 +1233,7 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
 	u32 addr_hi = readl(dart->regs + DART_T8110_ERROR_ADDR_HI);
 	u64 addr = addr_lo | (((u64)addr_hi) << 32);
 	u8 stream_idx = FIELD_GET(DART_T8110_ERROR_STREAM, error);
+	int i;
 
 	if (!(error & DART_T8110_ERROR_FLAG))
 		return IRQ_NONE;
@@ -1257,6 +1260,9 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
 		error, stream_idx, error_code, fault_name, addr);
 
 	writel(error, dart->regs + DART_T8110_ERROR);
+	for (i = 0; i < BITS_TO_U32(dart->num_streams); i++)
+		writel(U32_MAX, dart->regs + DART_T8110_ERROR_STREAMS + 4 * i);
+
 	return IRQ_HANDLED;
 }
 

From bdb105de32c79c7a630c067dc8cce09dea29971d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 20:05:20 +0900
Subject: [PATCH 0217/1027] iommu: apple-dart: Make the hw register fields u32s

The registers are 32-bit and the offsets definitely don't need 64 bits
either, these should've been u32s.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index eadc00e69e860c..73a04c5e5708aa 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -169,22 +169,22 @@ struct apple_dart_hw {
 
 	int max_sid_count;
 
-	u64 lock;
-	u64 lock_bit;
+	u32 lock;
+	u32 lock_bit;
 
-	u64 error;
+	u32 error;
 
-	u64 enable_streams;
+	u32 enable_streams;
 
-	u64 tcr;
-	u64 tcr_enabled;
-	u64 tcr_disabled;
-	u64 tcr_bypass;
+	u32 tcr;
+	u32 tcr_enabled;
+	u32 tcr_disabled;
+	u32 tcr_bypass;
 
-	u64 ttbr;
-	u64 ttbr_valid;
-	u64 ttbr_addr_field_shift;
-	u64 ttbr_shift;
+	u32 ttbr;
+	u32 ttbr_valid;
+	u32 ttbr_addr_field_shift;
+	u32 ttbr_shift;
 	int ttbr_count;
 };
 

From 698749e3125eb0c6546732a000d2afaecd76cd84 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 20:06:44 +0900
Subject: [PATCH 0218/1027] iommu: apple-dart: Add 4-level page table support

The T8110 variant DART implementation on T602x SoCs indicates an IAS of
42, which requires an extra page table level. The extra level is
optional, but let's implement it.

Later it might be useful to restrict this based on the actual attached
devices, since most won't need that much address space anyway.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 34 ++++++++++++++++++++++++++++------
 1 file changed, 28 insertions(+), 6 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 73a04c5e5708aa..52b125bbc6daa2 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -136,6 +136,7 @@
 #define DART_T8110_TCR                  0x1000
 #define DART_T8110_TCR_REMAP            GENMASK(11, 8)
 #define DART_T8110_TCR_REMAP_EN         BIT(7)
+#define DART_T8110_TCR_FOUR_LEVEL       BIT(3)
 #define DART_T8110_TCR_BYPASS_DAPF      BIT(2)
 #define DART_T8110_TCR_BYPASS_DART      BIT(1)
 #define DART_T8110_TCR_TRANSLATE_ENABLE BIT(0)
@@ -180,6 +181,7 @@ struct apple_dart_hw {
 	u32 tcr_enabled;
 	u32 tcr_disabled;
 	u32 tcr_bypass;
+	u32 tcr_4level;
 
 	u32 ttbr;
 	u32 ttbr_valid;
@@ -222,6 +224,7 @@ struct apple_dart {
 	u32 num_streams;
 	u32 supports_bypass : 1;
 	u32 locked : 1;
+	u32 four_level : 1;
 
 	struct iommu_group *sid2group[DART_MAX_STREAMS];
 	struct iommu_device iommu;
@@ -310,14 +313,17 @@ static struct apple_dart_domain *to_dart_domain(struct iommu_domain *dom)
 }
 
 static void
-apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map)
+apple_dart_hw_enable_translation(struct apple_dart_stream_map *stream_map, int levels)
 {
 	struct apple_dart *dart = stream_map->dart;
 	int sid;
 
+	WARN_ON(levels != 3 && levels != 4);
+	WARN_ON(levels == 4 && !dart->four_level);
 	WARN_ON(stream_map->dart->locked);
 	for_each_set_bit(sid, stream_map->sidmap, dart->num_streams)
-		writel(dart->hw->tcr_enabled, dart->regs + DART_TCR(dart, sid));
+		writel(dart->hw->tcr_enabled | (levels == 4 ? dart->hw->tcr_4level : 0),
+		       dart->regs + DART_TCR(dart, sid));
 }
 
 static void apple_dart_hw_disable_dma(struct apple_dart_stream_map *stream_map)
@@ -669,7 +675,8 @@ apple_dart_setup_translation(struct apple_dart_domain *domain,
 		for (; i < stream_map->dart->hw->ttbr_count; ++i)
 			apple_dart_hw_clear_ttbr(stream_map, i);
 
-		apple_dart_hw_enable_translation(stream_map);
+		apple_dart_hw_enable_translation(stream_map,
+						 pgtbl_cfg->apple_dart_cfg.n_levels);
 	}
 	stream_map->dart->hw->invalidate_tlb(stream_map);
 }
@@ -753,6 +760,19 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 		ttbr = readl(dart->regs + DART_TTBR(dart, sid, 0));
 
 		WARN_ON(!(ttbr & dart->hw->ttbr_valid));
+
+		/* If the DART is locked, we need to keep the translation level count. */
+		if (dart->hw->tcr_4level && dart->ias > 36) {
+			if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) {
+				if (dart->ias < 37) {
+					dev_info(dart->dev, "Expanded to ias=37 due to lock\n");
+					pgtbl_cfg.ias = 37;
+				}
+			} else if (dart->ias > 36) {
+				dev_info(dart->dev, "Limited to ias=36 due to lock\n");
+				pgtbl_cfg.ias = 36;
+			}
+		}
 	}
 
 	dart_domain->pgtbl_ops = alloc_io_pgtable_ops(dart->hw->fmt, &pgtbl_cfg,
@@ -765,7 +785,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 	dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
 	dart_domain->domain.geometry.aperture_start = 0;
 	dart_domain->domain.geometry.aperture_end =
-		(dma_addr_t)DMA_BIT_MASK(dart->ias);
+		(dma_addr_t)DMA_BIT_MASK(pgtbl_cfg.ias);
 	dart_domain->domain.geometry.force_aperture = true;
 
 	dart_domain->finalized = true;
@@ -1336,6 +1356,7 @@ static int apple_dart_probe(struct platform_device *pdev)
 		dart->ias = FIELD_GET(DART_T8110_PARAMS3_VA_WIDTH, dart_params[2]);
 		dart->oas = FIELD_GET(DART_T8110_PARAMS3_PA_WIDTH, dart_params[2]);
 		dart->num_streams = FIELD_GET(DART_T8110_PARAMS4_NUM_SIDS, dart_params[3]);
+		dart->four_level = dart->ias > 36;
 		break;
 	}
 
@@ -1373,9 +1394,9 @@ static int apple_dart_probe(struct platform_device *pdev)
 
 	dev_info(
 		&pdev->dev,
-		"DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d] initialized\n",
+		"DART [pagesize %x, %d streams, bypass support: %d, bypass forced: %d, locked: %d, AS %d -> %d] initialized\n",
 		dart->pgsize, dart->num_streams, dart->supports_bypass,
-		dart->pgsize > PAGE_SIZE, dart->locked);
+		dart->pgsize > PAGE_SIZE, dart->locked, dart->ias, dart->oas);
 	return 0;
 
 err_sysfs_remove:
@@ -1499,6 +1520,7 @@ static const struct apple_dart_hw apple_dart_hw_t8110 = {
 	.tcr_enabled = DART_T8110_TCR_TRANSLATE_ENABLE,
 	.tcr_disabled = 0,
 	.tcr_bypass = DART_T8110_TCR_BYPASS_DAPF | DART_T8110_TCR_BYPASS_DART,
+	.tcr_4level = DART_T8110_TCR_FOUR_LEVEL,
 
 	.ttbr = DART_T8110_TTBR,
 	.ttbr_valid = DART_T8110_TTBR_VALID,

From a5e4f3819344f25b817537d7997f1038f3358cb0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 01:32:06 +0900
Subject: [PATCH 0219/1027] iommu: apple-dart: Support specifying the DMA
 aperture in the DT

Apple DARTs are often connected directly to devices that expect only a
portion of their address space to be used for DMA (for example, because
other ranges are mapped directly to something else). Add an
apple,dma-range property to allow specifying this range.

This range *can* be outside of the DART's IAS. In that case, it is
assumed that the hardware truncates addresses and the page tables will
only map the lower bits of the address. However, the specified range
cannot straddle an IAS boundary (you cannot cover more than IAS worth
of address space nor wrap).

This corresponds to the vm-base and vm-size properties on the Apple
device tree side of things.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 63 ++++++++++++++++++++++++++++++++------
 1 file changed, 53 insertions(+), 10 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 52b125bbc6daa2..a2eb799cdb70a3 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -21,6 +21,7 @@
 #include <linux/io-pgtable.h>
 #include <linux/iommu.h>
 #include <linux/iopoll.h>
+#include <linux/minmax.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/of_address.h>
@@ -226,6 +227,9 @@ struct apple_dart {
 	u32 locked : 1;
 	u32 four_level : 1;
 
+	dma_addr_t dma_min;
+	dma_addr_t dma_max;
+
 	struct iommu_group *sid2group[DART_MAX_STREAMS];
 	struct iommu_device iommu;
 
@@ -273,6 +277,7 @@ struct apple_dart_domain {
 	struct io_pgtable_ops *pgtbl_ops;
 
 	bool finalized;
+	u64 mask;
 	struct mutex init_lock;
 	struct apple_dart_atomic_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
 
@@ -623,7 +628,7 @@ static phys_addr_t apple_dart_iova_to_phys(struct iommu_domain *domain,
 	if (!ops)
 		return 0;
 
-	return ops->iova_to_phys(ops, iova);
+	return ops->iova_to_phys(ops, iova & dart_domain->mask);
 }
 
 static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
@@ -637,8 +642,8 @@ static int apple_dart_map_pages(struct iommu_domain *domain, unsigned long iova,
 	if (!ops)
 		return -ENODEV;
 
-	return ops->map_pages(ops, iova, paddr, pgsize, pgcount, prot, gfp,
-			      mapped);
+	return ops->map_pages(ops, iova & dart_domain->mask, paddr, pgsize,
+			      pgcount, prot, gfp, mapped);
 }
 
 static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
@@ -649,7 +654,8 @@ static size_t apple_dart_unmap_pages(struct iommu_domain *domain,
 	struct apple_dart_domain *dart_domain = to_dart_domain(domain);
 	struct io_pgtable_ops *ops = dart_domain->pgtbl_ops;
 
-	return ops->unmap_pages(ops, iova, pgsize, pgcount, gather);
+	return ops->unmap_pages(ops, iova & dart_domain->mask, pgsize, pgcount,
+				gather);
 }
 
 static void
@@ -721,6 +727,8 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 {
 	struct apple_dart *dart = cfg->stream_maps[0].dart;
 	struct io_pgtable_cfg pgtbl_cfg;
+	dma_addr_t dma_max = dart->dma_max;
+	u32 ias = min_t(u32, dart->ias, fls64(dma_max));
 	int ret = 0;
 	int i, j;
 
@@ -741,7 +749,7 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 
 	pgtbl_cfg = (struct io_pgtable_cfg){
 		.pgsize_bitmap = dart->pgsize,
-		.ias = dart->ias,
+		.ias = ias,
 		.oas = dart->oas,
 		.coherent_walk = 1,
 		.iommu_dev = dart->dev,
@@ -764,13 +772,21 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 		/* If the DART is locked, we need to keep the translation level count. */
 		if (dart->hw->tcr_4level && dart->ias > 36) {
 			if (readl(dart->regs + DART_TCR(dart, sid)) & dart->hw->tcr_4level) {
-				if (dart->ias < 37) {
+				if (ias < 37) {
 					dev_info(dart->dev, "Expanded to ias=37 due to lock\n");
 					pgtbl_cfg.ias = 37;
 				}
-			} else if (dart->ias > 36) {
+			} else if (ias > 36) {
 				dev_info(dart->dev, "Limited to ias=36 due to lock\n");
 				pgtbl_cfg.ias = 36;
+				if (dart->dma_min == 0 && dma_max == DMA_BIT_MASK(dart->ias)) {
+					dma_max = DMA_BIT_MASK(pgtbl_cfg.ias);
+				} else if ((dart->dma_min ^ dma_max) & ~DMA_BIT_MASK(36)) {
+					dev_err(dart->dev,
+						"Invalid DMA range for locked 3-level PT\n");
+					ret = -ENOMEM;
+					goto done;
+				}
 			}
 		}
 	}
@@ -782,10 +798,16 @@ static int apple_dart_finalize_domain(struct apple_dart_domain *dart_domain,
 		goto done;
 	}
 
+	if (pgtbl_cfg.pgsize_bitmap == SZ_4K)
+		dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 32));
+	else if (pgtbl_cfg.apple_dart_cfg.n_levels == 3)
+		dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 36));
+	else if (pgtbl_cfg.apple_dart_cfg.n_levels == 4)
+		dart_domain->mask = DMA_BIT_MASK(min_t(u32, dart->ias, 47));
+
 	dart_domain->domain.pgsize_bitmap = pgtbl_cfg.pgsize_bitmap;
-	dart_domain->domain.geometry.aperture_start = 0;
-	dart_domain->domain.geometry.aperture_end =
-		(dma_addr_t)DMA_BIT_MASK(pgtbl_cfg.ias);
+	dart_domain->domain.geometry.aperture_start = dart->dma_min;
+	dart_domain->domain.geometry.aperture_end = dma_max;
 	dart_domain->domain.geometry.force_aperture = true;
 
 	dart_domain->finalized = true;
@@ -1298,6 +1320,7 @@ static int apple_dart_probe(struct platform_device *pdev)
 	struct resource *res;
 	struct apple_dart *dart;
 	struct device *dev = &pdev->dev;
+	u64 dma_range[2];
 
 	dart = devm_kzalloc(dev, sizeof(*dart), GFP_KERNEL);
 	if (!dart)
@@ -1360,6 +1383,26 @@ static int apple_dart_probe(struct platform_device *pdev)
 		break;
 	}
 
+	dart->dma_min = 0;
+	dart->dma_max = DMA_BIT_MASK(dart->ias);
+
+	ret = of_property_read_u64_array(dev->of_node, "apple,dma-range", dma_range, 2);
+	if (ret == -EINVAL) {
+		ret = 0;
+	} else if (ret) {
+		goto err_clk_disable;
+	} else {
+		dart->dma_min = dma_range[0];
+		dart->dma_max = dma_range[0] + dma_range[1] - 1;
+		if ((dart->dma_min ^ dart->dma_max) & ~DMA_BIT_MASK(dart->ias)) {
+			dev_err(&pdev->dev, "Invalid DMA range for ias=%d\n",
+				dart->ias);
+			goto err_clk_disable;
+		}
+		dev_info(&pdev->dev, "Limiting DMA range to %pad..%pad\n",
+			 &dart->dma_min, &dart->dma_max);
+	}
+
 	if (dart->num_streams > DART_MAX_STREAMS) {
 		dev_err(&pdev->dev, "Too many streams (%d > %d)\n",
 			dart->num_streams, DART_MAX_STREAMS);

From 22cd73f9b9194270879cd1a641cf9c9826d721b4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 10 Sep 2023 23:36:59 +0900
Subject: [PATCH 0220/1027] iommu: apple-dart: Increase MAX_DARTS_PER_DEVICE to
 3

ISP needs this.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index a2eb799cdb70a3..41c7fd346e1edf 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -38,7 +38,7 @@
 
 #define DART_MAX_STREAMS 256
 #define DART_MAX_TTBR 4
-#define MAX_DARTS_PER_DEVICE 2
+#define MAX_DARTS_PER_DEVICE 3
 
 /* Common registers */
 

From 4000aa243f9407c8619ff495b4c4d9e536a998bc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Sep 2023 00:06:48 +0900
Subject: [PATCH 0221/1027] iommu: apple-dart: Allow mismatched bypass support

This is needed by ISP, which has DART0 with bypass and DART1/2 without.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/apple-dart.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 41c7fd346e1edf..605668c7190976 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -293,6 +293,9 @@ struct apple_dart_domain {
  * @streams: streams for this device
  */
 struct apple_dart_master_cfg {
+	/* Union of DART capabilitles */
+	u32 supports_bypass : 1;
+
 	struct apple_dart_stream_map stream_maps[MAX_DARTS_PER_DEVICE];
 };
 
@@ -887,7 +890,7 @@ static int apple_dart_attach_dev_identity(struct iommu_domain *domain,
 	struct apple_dart_stream_map *stream_map;
 	int i;
 
-	if (!cfg->stream_maps[0].dart->supports_bypass)
+	if (!cfg->supports_bypass)
 		return -EINVAL;
 
 	if (cfg->stream_maps[0].dart->locked)
@@ -1018,20 +1021,25 @@ static int apple_dart_of_xlate(struct device *dev,
 		return -EINVAL;
 	sid = args->args[0];
 
-	if (!cfg)
+	if (!cfg) {
 		cfg = kzalloc(sizeof(*cfg), GFP_KERNEL);
+
+		/* Will be ANDed with DART capabilities */
+		cfg->supports_bypass = true;
+	}
 	if (!cfg)
 		return -ENOMEM;
 	dev_iommu_priv_set(dev, cfg);
 
 	cfg_dart = cfg->stream_maps[0].dart;
 	if (cfg_dart) {
-		if (cfg_dart->supports_bypass != dart->supports_bypass)
-			return -EINVAL;
 		if (cfg_dart->pgsize != dart->pgsize)
 			return -EINVAL;
 	}
 
+	if (!dart->supports_bypass)
+		cfg->supports_bypass = false;
+
 	for (i = 0; i < MAX_DARTS_PER_DEVICE; ++i) {
 		if (cfg->stream_maps[i].dart == dart) {
 			set_bit(sid, cfg->stream_maps[i].sidmap);
@@ -1171,7 +1179,7 @@ static int apple_dart_def_domain_type(struct device *dev)
 
 	if (cfg->stream_maps[0].dart->pgsize > PAGE_SIZE)
 		return IOMMU_DOMAIN_IDENTITY;
-	if (!cfg->stream_maps[0].dart->supports_bypass)
+	if (!cfg->supports_bypass)
 		return IOMMU_DOMAIN_DMA;
 	if (cfg->stream_maps[0].dart->locked)
 		return IOMMU_DOMAIN_DMA;

From b6e59e752d38e1754b007124c6661066379d225f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 19:46:53 +0900
Subject: [PATCH 0222/1027] iommu: apple-dart: Power on device when handling
 IRQs

It's possible for an IRQ to fire and the device to be RPM suspended
before we can handle it, which then causes device register accesses to
fail in the IRQ handler.

Since RPM is IRQ-safe for this device, just make sure we power on the
DART in the IRQ handler too.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/iommu/apple-dart.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 605668c7190976..4f7c104e23eb93 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -1316,6 +1316,17 @@ static irqreturn_t apple_dart_t8110_irq(int irq, void *dev)
 	return IRQ_HANDLED;
 }
 
+static irqreturn_t apple_dart_irq(int irq, void *dev)
+{
+	irqreturn_t ret;
+	struct apple_dart *dart = dev;
+
+	WARN_ON(pm_runtime_get_sync(dart->dev) < 0);
+	ret = dart->hw->irq_handler(irq, dev);
+	pm_runtime_put(dart->dev);
+	return ret;
+}
+
 static bool apple_dart_is_locked(struct apple_dart *dart)
 {
 	return !!(readl(dart->regs + dart->hw->lock) & dart->hw->lock_bit);
@@ -1425,7 +1436,7 @@ static int apple_dart_probe(struct platform_device *pdev)
 			goto err_clk_disable;
 	}
 
-	ret = request_irq(dart->irq, dart->hw->irq_handler, IRQF_SHARED,
+	ret = request_irq(dart->irq, apple_dart_irq, IRQF_SHARED,
 			  "apple-dart fault handler", dart);
 	if (ret)
 		goto err_clk_disable;

From efd319b2b1797b88c440e85b50fe1e8978fda824 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 23 Nov 2023 18:08:50 +0900
Subject: [PATCH 0223/1027] iommu: apple-dart: Check for fwspec in the device
 probe path

We need to check for a fwspec in the probe path, to ensure that the
driver does not probe as a bus iommu driver. This, along with related
fixes to the IOMMU core code, fixes races and issues when multiple
IOMMUs assigned to the same device probe at different times.

Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Hector Martin <marcan@marcan.st>
iommu: apple-dart:
---
 drivers/iommu/apple-dart.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/iommu/apple-dart.c b/drivers/iommu/apple-dart.c
index 4f7c104e23eb93..f9ad281975c59a 100644
--- a/drivers/iommu/apple-dart.c
+++ b/drivers/iommu/apple-dart.c
@@ -949,7 +949,7 @@ static struct iommu_device *apple_dart_probe_device(struct device *dev)
 	struct apple_dart_stream_map *stream_map;
 	int i;
 
-	if (!cfg)
+	if (!dev_iommu_fwspec_get(dev) || !cfg)
 		return ERR_PTR(-ENODEV);
 
 	for_each_stream_map(i, cfg, stream_map)

From 1cd2f413a03ac0f27339722db62d467e9ebe5b60 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 24 Mar 2024 18:06:46 +0100
Subject: [PATCH 0224/1027] iommu/of: Free fwspec on probe deferrel

For devices with multiple iommus of_iommu_configure_device() potentially
inits the fwspec for one of the iommus but another iommu device might
have not yet been probe resulting in -EPROBE_DEFER. Clear the fwspec in
such cases to ensure the next of_iommu_configure() call retries to
configure all iommus.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/iommu/of_iommu.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/iommu/of_iommu.c b/drivers/iommu/of_iommu.c
index 1d70d55f0e1ff0..163b5854515ded 100644
--- a/drivers/iommu/of_iommu.c
+++ b/drivers/iommu/of_iommu.c
@@ -147,6 +147,8 @@ int of_iommu_configure(struct device *dev, struct device_node *master_np,
 		of_pci_check_device_ats(dev, master_np);
 	} else {
 		err = of_iommu_configure_device(master_np, dev, id);
+		if (err == -EPROBE_DEFER)
+			iommu_fwspec_free(dev);
 	}
 
 	if (err)

From 1684511e16235ff81184972892d1259afa468ebf Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 20 Sep 2021 02:23:11 +0900
Subject: [PATCH 0225/1027] tty: serial: samsung_tty: Support runtime PM

This allows idle UART devices to be suspended using the standard
runtime-PM framework. The logic is modeled after stm32-usart.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/tty/serial/samsung_tty.c | 89 ++++++++++++++++++++------------
 1 file changed, 56 insertions(+), 33 deletions(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index dc35eb77d2ef34..0d06c34ba48826 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -34,6 +34,7 @@
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
 #include <linux/serial.h>
 #include <linux/serial_core.h>
 #include <linux/serial_s3c.h>
@@ -1296,30 +1297,49 @@ static int apple_s5l_serial_startup(struct uart_port *port)
 	return ret;
 }
 
+static int __maybe_unused s3c24xx_serial_runtime_suspend(struct device *dev)
+{
+	struct uart_port *port = dev_get_drvdata(dev);
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+	int timeout = 10000;
+
+	while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
+		udelay(100);
+
+	if (!IS_ERR(ourport->baudclk))
+		clk_disable_unprepare(ourport->baudclk);
+
+	clk_disable_unprepare(ourport->clk);
+	return 0;
+};
+
+static int __maybe_unused s3c24xx_serial_runtime_resume(struct device *dev)
+{
+	struct uart_port *port = dev_get_drvdata(dev);
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
+
+	clk_prepare_enable(ourport->clk);
+
+	if (!IS_ERR(ourport->baudclk))
+		clk_prepare_enable(ourport->baudclk);
+	return 0;
+};
+
 static void s3c24xx_serial_pm(struct uart_port *port, unsigned int level,
 			      unsigned int old)
 {
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
-	int timeout = 10000;
 
 	ourport->pm_level = level;
 
 	switch (level) {
-	case 3:
-		while (--timeout && !s3c24xx_serial_txempty_nofifo(port))
-			udelay(100);
-
-		if (!IS_ERR(ourport->baudclk))
-			clk_disable_unprepare(ourport->baudclk);
-
-		clk_disable_unprepare(ourport->clk);
+	case UART_PM_STATE_OFF:
+		pm_runtime_mark_last_busy(port->dev);
+		pm_runtime_put_sync(port->dev);
 		break;
 
-	case 0:
-		clk_prepare_enable(ourport->clk);
-
-		if (!IS_ERR(ourport->baudclk))
-			clk_prepare_enable(ourport->baudclk);
+	case UART_PM_STATE_ON:
+		pm_runtime_get_sync(port->dev);
 		break;
 	default:
 		dev_err(port->dev, "s3c24xx_serial: unknown pm %d\n", level);
@@ -2042,18 +2062,15 @@ static int s3c24xx_serial_probe(struct platform_device *pdev)
 		}
 	}
 
+	pm_runtime_get_noresume(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
 	dev_dbg(&pdev->dev, "%s: adding port\n", __func__);
 	uart_add_one_port(&s3c24xx_uart_drv, &ourport->port);
 	platform_set_drvdata(pdev, &ourport->port);
 
-	/*
-	 * Deactivate the clock enabled in s3c24xx_serial_init_port here,
-	 * so that a potential re-enablement through the pm-callback overlaps
-	 * and keeps the clock enabled in this case.
-	 */
-	clk_disable_unprepare(ourport->clk);
-	if (!IS_ERR(ourport->baudclk))
-		clk_disable_unprepare(ourport->baudclk);
+	pm_runtime_put_sync(&pdev->dev);
 
 	probe_index++;
 
@@ -2063,16 +2080,26 @@ static int s3c24xx_serial_probe(struct platform_device *pdev)
 static void s3c24xx_serial_remove(struct platform_device *dev)
 {
 	struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
+	struct s3c24xx_uart_port *ourport = to_ourport(port);
 
 	if (port)
+		pm_runtime_get_sync(&dev->dev);
 		uart_remove_one_port(&s3c24xx_uart_drv, port);
 
+		clk_disable_unprepare(ourport->clk);
+		if (!IS_ERR(ourport->baudclk))
+			clk_disable_unprepare(ourport->baudclk);
+
+		pm_runtime_disable(&dev->dev);
+		pm_runtime_set_suspended(&dev->dev);
+		pm_runtime_put_noidle(&dev->dev);
+
 	uart_unregister_driver(&s3c24xx_uart_drv);
 }
 
 /* UART power management code */
-#ifdef CONFIG_PM_SLEEP
-static int s3c24xx_serial_suspend(struct device *dev)
+
+static int __maybe_unused s3c24xx_serial_suspend(struct device *dev)
 {
 	struct uart_port *port = s3c24xx_dev_to_port(dev);
 
@@ -2082,7 +2109,7 @@ static int s3c24xx_serial_suspend(struct device *dev)
 	return 0;
 }
 
-static int s3c24xx_serial_resume(struct device *dev)
+static int __maybe_unused s3c24xx_serial_resume(struct device *dev)
 {
 	struct uart_port *port = s3c24xx_dev_to_port(dev);
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -2102,7 +2129,7 @@ static int s3c24xx_serial_resume(struct device *dev)
 	return 0;
 }
 
-static int s3c24xx_serial_resume_noirq(struct device *dev)
+static int __maybe_unused s3c24xx_serial_resume_noirq(struct device *dev)
 {
 	struct uart_port *port = s3c24xx_dev_to_port(dev);
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
@@ -2174,13 +2201,9 @@ static int s3c24xx_serial_resume_noirq(struct device *dev)
 static const struct dev_pm_ops s3c24xx_serial_pm_ops = {
 	SET_SYSTEM_SLEEP_PM_OPS(s3c24xx_serial_suspend, s3c24xx_serial_resume)
 	SET_NOIRQ_SYSTEM_SLEEP_PM_OPS(NULL, s3c24xx_serial_resume_noirq)
+	SET_RUNTIME_PM_OPS(s3c24xx_serial_runtime_suspend,
+			   s3c24xx_serial_runtime_resume, NULL)
 };
-#define SERIAL_SAMSUNG_PM_OPS	(&s3c24xx_serial_pm_ops)
-
-#else /* !CONFIG_PM_SLEEP */
-
-#define SERIAL_SAMSUNG_PM_OPS	NULL
-#endif /* CONFIG_PM_SLEEP */
 
 /* Console code */
 
@@ -2655,7 +2678,7 @@ static struct platform_driver samsung_serial_driver = {
 	.id_table	= s3c24xx_serial_driver_ids,
 	.driver		= {
 		.name	= "samsung-uart",
-		.pm	= SERIAL_SAMSUNG_PM_OPS,
+		.pm	= &s3c24xx_serial_pm_ops,
 		.of_match_table	= of_match_ptr(s3c24xx_uart_dt_match),
 	},
 };

From a618dcd6cd3d04c13a293101bbdd6eb51b38921e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 12 Mar 2022 00:07:09 +0900
Subject: [PATCH 0226/1027] of: Demote "Bad cell count" to debug message

This happens on the SPMI bus... TODO: figure out what the right solution
is here.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/of/address.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/of/address.c b/drivers/of/address.c
index d669ce25b5f9c1..af2a5fb9c9cad8 100644
--- a/drivers/of/address.c
+++ b/drivers/of/address.c
@@ -549,7 +549,7 @@ static u64 __of_translate_address(struct device_node *node,
 		pbus = of_match_bus(parent);
 		pbus->count_cells(dev, &pna, &pns);
 		if (!OF_CHECK_COUNTS(pna, pns)) {
-			pr_err("Bad cell count for %pOF\n", dev);
+			pr_debug("Bad cell count for %pOF\n", dev);
 			return OF_BAD_ADDR;
 		}
 

From d01b70361e447277aee15fa6692371c9f52a1449 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 May 2022 01:40:31 +0900
Subject: [PATCH 0227/1027] mmc: sdhci-pci: Support external CD GPIO on all OF
 systems

Allow OF systems to specify an external CD GPIO on all devices,
even if they have an internal CD feature.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/mmc/host/sdhci-pci-core.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c
index ed45ed0bdafd96..87147449e5b9bc 100644
--- a/drivers/mmc/host/sdhci-pci-core.c
+++ b/drivers/mmc/host/sdhci-pci-core.c
@@ -26,6 +26,7 @@
 #include <linux/debugfs.h>
 #include <linux/acpi.h>
 #include <linux/dmi.h>
+#include <linux/of.h>
 
 #include <linux/mmc/host.h>
 #include <linux/mmc/mmc.h>
@@ -2143,6 +2144,15 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
 			dev_warn(&pdev->dev, "failed to setup card detect gpio\n");
 			slot->cd_idx = -1;
 		}
+	} else if (is_of_node(pdev->dev.fwnode)) {
+		/* Allow all OF systems to use a CD GPIO if provided */
+
+		ret = mmc_gpiod_request_cd(host->mmc, "cd", 0,
+					   slot->cd_override_level, 0);
+		if (ret == -EPROBE_DEFER)
+			goto remove;
+		else if (ret == 0)
+			slot->cd_idx = 0;
 	}
 
 	if (chip->fixes && chip->fixes->add_host)

From 1309b7f3671265ebdc2c3653159e1bec2141f040 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 May 2022 02:27:35 +0900
Subject: [PATCH 0228/1027] mmc: sdhci-pci: Support setting CD debounce delay

Some systems (e.g. 2021 MacBook Pro 14/16") have noncompliant connectors
where CD activates before the card is fully inserted. We need debounce
delay support on these to avoid detection failures when the card isn't
inserted very quickly.

Set the default to 200ms for all systems instead of 0. This is the
default on non-PCI platforms, and will probably help other systems too.
The naughty MacBooks will need closer to 750ms in the device tree to
be reliable...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/mmc/host/sdhci-pci-core.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/drivers/mmc/host/sdhci-pci-core.c b/drivers/mmc/host/sdhci-pci-core.c
index 87147449e5b9bc..1ad1d6763c5b00 100644
--- a/drivers/mmc/host/sdhci-pci-core.c
+++ b/drivers/mmc/host/sdhci-pci-core.c
@@ -2063,6 +2063,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
 	struct sdhci_host *host;
 	int ret, bar = first_bar + slotno;
 	size_t priv_size = chip->fixes ? chip->fixes->priv_size : 0;
+	u32 cd_debounce_delay_ms;
 
 	if (!(pci_resource_flags(pdev, bar) & IORESOURCE_MEM)) {
 		dev_err(&pdev->dev, "BAR %d is not iomem. Aborting.\n", bar);
@@ -2129,6 +2130,10 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
 	if (host->mmc->caps & MMC_CAP_CD_WAKE)
 		device_init_wakeup(&pdev->dev, true);
 
+	if (device_property_read_u32(&pdev->dev, "cd-debounce-delay-ms",
+				     &cd_debounce_delay_ms))
+		cd_debounce_delay_ms = 200;
+
 	if (slot->cd_idx >= 0) {
 		ret = mmc_gpiod_request_cd(host->mmc, "cd", slot->cd_idx,
 					   slot->cd_override_level, 0);
@@ -2136,7 +2141,7 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
 			ret = mmc_gpiod_request_cd(host->mmc, NULL,
 						   slot->cd_idx,
 						   slot->cd_override_level,
-						   0);
+						   cd_debounce_delay_ms * 1000);
 		if (ret == -EPROBE_DEFER)
 			goto remove;
 
@@ -2148,7 +2153,8 @@ static struct sdhci_pci_slot *sdhci_pci_probe_slot(
 		/* Allow all OF systems to use a CD GPIO if provided */
 
 		ret = mmc_gpiod_request_cd(host->mmc, "cd", 0,
-					   slot->cd_override_level, 0);
+					   slot->cd_override_level,
+					   cd_debounce_delay_ms * 1000);
 		if (ret == -EPROBE_DEFER)
 			goto remove;
 		else if (ret == 0)

From 9a4a5f79e561d0eae405ff3da7c9ea5b6595c71f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 9 Sep 2022 19:52:19 +0200
Subject: [PATCH 0229/1027] PCI: apple: Add depends on PAGE_SIZE_16KB

The iommu on Apple's M1 and M2 supports only a page size of 16kB and is
mandatory for PCIe devices. The PCI controller itself is not affeccted
by the CPU page size the page size mismatch devices are renderer useless
due to non-working DMA. While the the iommu prints a warning in this
scenario it seems a common and hard to debug problem.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/pci/controller/Kconfig b/drivers/pci/controller/Kconfig
index 4d2c188f583527..65af4dbd73294b 100644
--- a/drivers/pci/controller/Kconfig
+++ b/drivers/pci/controller/Kconfig
@@ -39,6 +39,7 @@ config PCIE_APPLE
 	depends on ARCH_APPLE || COMPILE_TEST
 	depends on OF
 	depends on PCI_MSI
+	depends on PAGE_SIZE_16KB || COMPILE_TEST
 	select PCI_HOST_COMMON
 	help
 	  Say Y here if you want to enable PCIe controller support on Apple

From af9f3848857824b997344630e131a37c5b6edea9 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 25 Oct 2022 01:12:17 +0900
Subject: [PATCH 0230/1027] firmware_loader: Add /lib/firmware/vendor path

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/base/firmware_loader/main.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/base/firmware_loader/main.c b/drivers/base/firmware_loader/main.c
index a03ee4b11134cf..090c00b261f54a 100644
--- a/drivers/base/firmware_loader/main.c
+++ b/drivers/base/firmware_loader/main.c
@@ -471,6 +471,8 @@ static int fw_decompress_xz(struct device *dev, struct fw_priv *fw_priv,
 static char fw_path_para[256];
 static const char * const fw_path[] = {
 	fw_path_para,
+	"/lib/firmware/vendor/" UTS_RELEASE,
+	"/lib/firmware/vendor",
 	"/lib/firmware/updates/" UTS_RELEASE,
 	"/lib/firmware/updates",
 	"/lib/firmware/" UTS_RELEASE,

From e9e8fb344cc58f921b7606d5b938a0050ca2705e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 2 Nov 2022 01:50:55 +0900
Subject: [PATCH 0231/1027] i2c: pasemi: Improve timeout handling and error
 recovery

The hardware (supposedly) has a 25ms timeout for clock stretching, but
the driver uses a 10ms timeout, which is too low (and actually gets hit
with the tipd controllers on Apple Silicon machines sporadically).

Increase the timeout to 100ms, which should be plenty, and then add
handling for all the missing error condition, and better recovery in
pasemi_smb_clear(). Since this needs a bunch more bit defines, take
the change to switch to bitfield.h macros, which is much more readable.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/i2c/busses/i2c-pasemi-core.c | 105 ++++++++++++++++++++-------
 1 file changed, 78 insertions(+), 27 deletions(-)

diff --git a/drivers/i2c/busses/i2c-pasemi-core.c b/drivers/i2c/busses/i2c-pasemi-core.c
index dac694a9d781f8..75005d4afaff7e 100644
--- a/drivers/i2c/busses/i2c-pasemi-core.c
+++ b/drivers/i2c/busses/i2c-pasemi-core.c
@@ -5,6 +5,7 @@
  * SMBus host driver for PA Semi PWRficient
  */
 
+#include <linux/bitfield.h>
 #include <linux/module.h>
 #include <linux/pci.h>
 #include <linux/kernel.h>
@@ -26,21 +27,31 @@
 #define REG_REV		0x28
 
 /* Register defs */
-#define MTXFIFO_READ	0x00000400
-#define MTXFIFO_STOP	0x00000200
-#define MTXFIFO_START	0x00000100
-#define MTXFIFO_DATA_M	0x000000ff
-
-#define MRXFIFO_EMPTY	0x00000100
-#define MRXFIFO_DATA_M	0x000000ff
-
-#define SMSTA_XEN	0x08000000
-#define SMSTA_MTN	0x00200000
-
-#define CTL_MRR		0x00000400
-#define CTL_MTR		0x00000200
-#define CTL_EN		0x00000800
-#define CTL_CLK_M	0x000000ff
+#define MTXFIFO_READ	BIT(10)
+#define MTXFIFO_STOP	BIT(9)
+#define MTXFIFO_START	BIT(8)
+#define MTXFIFO_DATA_M	GENMASK(7, 0)
+
+#define MRXFIFO_EMPTY	BIT(8)
+#define MRXFIFO_DATA_M	GENMASK(7, 0)
+
+#define SMSTA_XIP	BIT(28)
+#define SMSTA_XEN	BIT(27)
+#define SMSTA_JMD	BIT(25)
+#define SMSTA_JAM	BIT(24)
+#define SMSTA_MTO	BIT(23)
+#define SMSTA_MTA	BIT(22)
+#define SMSTA_MTN	BIT(21)
+#define SMSTA_MRNE	BIT(19)
+#define SMSTA_MTE	BIT(16)
+#define SMSTA_TOM	BIT(6)
+
+#define CTL_EN		BIT(11)
+#define CTL_MRR		BIT(10)
+#define CTL_MTR		BIT(9)
+#define CTL_CLK_M	GENMASK(7, 0)
+
+#define TRANSFER_TIMEOUT_MS	100
 
 static inline void reg_write(struct pasemi_smbus *smbus, int reg, int val)
 {
@@ -70,23 +81,45 @@ static void pasemi_reset(struct pasemi_smbus *smbus)
 	reinit_completion(&smbus->irq_completion);
 }
 
-static void pasemi_smb_clear(struct pasemi_smbus *smbus)
+static int pasemi_smb_clear(struct pasemi_smbus *smbus)
 {
 	unsigned int status;
+	int timeout = TRANSFER_TIMEOUT_MS;
 
 	status = reg_read(smbus, REG_SMSTA);
+
+	/* First wait for the bus to go idle */
+	while ((status & (SMSTA_XIP | SMSTA_JAM)) && timeout--) {
+		msleep(1);
+		status = reg_read(smbus, REG_SMSTA);
+	}
+
+	if (timeout < 0) {
+		dev_warn(smbus->dev, "Bus is still stuck (status 0x%08x)\n", status);
+		return -EIO;
+	}
+
+	/* If any badness happened or there is data in the FIFOs, reset the FIFOs */
+	if ((status & (SMSTA_MRNE | SMSTA_JMD | SMSTA_MTO | SMSTA_TOM | SMSTA_MTN | SMSTA_MTA)) ||
+		!(status & SMSTA_MTE))
+		pasemi_reset(smbus);
+
+	/* Clear the flags */
 	reg_write(smbus, REG_SMSTA, status);
+
+	return 0;
 }
 
 static int pasemi_smb_waitready(struct pasemi_smbus *smbus)
 {
-	int timeout = 100;
+	int timeout = TRANSFER_TIMEOUT_MS;
 	unsigned int status;
 
 	if (smbus->use_irq) {
 		reinit_completion(&smbus->irq_completion);
-		reg_write(smbus, REG_IMASK, SMSTA_XEN | SMSTA_MTN);
-		wait_for_completion_timeout(&smbus->irq_completion, msecs_to_jiffies(100));
+		/* XEN should be set when a transaction terminates, whether due to error or not */
+		reg_write(smbus, REG_IMASK, SMSTA_XEN);
+		wait_for_completion_timeout(&smbus->irq_completion, msecs_to_jiffies(timeout));
 		reg_write(smbus, REG_IMASK, 0);
 		status = reg_read(smbus, REG_SMSTA);
 	} else {
@@ -97,16 +130,32 @@ static int pasemi_smb_waitready(struct pasemi_smbus *smbus)
 		}
 	}
 
-	/* Got NACK? */
-	if (status & SMSTA_MTN)
-		return -ENXIO;
+	/* Controller timeout? */
+	if (status & SMSTA_TOM) {
+		dev_warn(smbus->dev, "Controller timeout, status 0x%08x\n", status);
+		return -EIO;
+	}
 
-	if (timeout < 0) {
-		dev_warn(smbus->dev, "Timeout, status 0x%08x\n", status);
-		reg_write(smbus, REG_SMSTA, status);
+	/* Peripheral timeout? */
+	if (status & SMSTA_MTO) {
+		dev_warn(smbus->dev, "Peripheral timeout, status 0x%08x\n", status);
 		return -ETIME;
 	}
 
+	/* Still stuck in a transaction? */
+	if (status & SMSTA_XIP) {
+		dev_warn(smbus->dev, "Bus stuck, status 0x%08x\n", status);
+		return -EIO;
+	}
+
+	/* Arbitration loss? */
+	if (status & SMSTA_MTA)
+		return -EBUSY;
+
+	/* Got NACK? */
+	if (status & SMSTA_MTN)
+		return -ENXIO;
+
 	/* Clear XEN */
 	reg_write(smbus, REG_SMSTA, SMSTA_XEN);
 
@@ -167,7 +216,8 @@ static int pasemi_i2c_xfer(struct i2c_adapter *adapter,
 	struct pasemi_smbus *smbus = adapter->algo_data;
 	int ret, i;
 
-	pasemi_smb_clear(smbus);
+	if (pasemi_smb_clear(smbus))
+		return -EIO;
 
 	ret = 0;
 
@@ -190,7 +240,8 @@ static int pasemi_smb_xfer(struct i2c_adapter *adapter,
 	addr <<= 1;
 	read_flag = read_write == I2C_SMBUS_READ;
 
-	pasemi_smb_clear(smbus);
+	if (pasemi_smb_clear(smbus))
+		return -EIO;
 
 	switch (size) {
 	case I2C_SMBUS_QUICK:

From 0852af698f88b6a59bbd3794c93e7b27dd07b36a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 2 Nov 2022 02:07:16 +0900
Subject: [PATCH 0232/1027] usb: typec: tipd: Be more verbose about errors

Make sure to print out error codes and log exactly what packet sizes
were received in case of a mismatch. This is very useful for debugging
badness.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/usb/typec/tipd/core.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index dd51a25480bfb9..9549079d44a53b 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -176,11 +176,15 @@ tps6598x_block_read(struct tps6598x *tps, u8 reg, void *val, size_t len)
 		return regmap_raw_read(tps->regmap, reg, val, len);
 
 	ret = regmap_raw_read(tps->regmap, reg, data, len + 1);
-	if (ret)
+	if (ret) {
+		dev_err(tps->dev, "regmap_raw_read returned %d\n", ret);
 		return ret;
+	}
 
-	if (data[0] < len)
+	if (data[0] < len) {
+		dev_err(tps->dev, "expected %zu bytes, got %d\n", len, data[0]);
 		return -EIO;
+	}
 
 	memcpy(val, &data[1], len);
 	return 0;
@@ -475,7 +479,7 @@ static bool tps6598x_read_status(struct tps6598x *tps, u32 *status)
 
 	ret = tps6598x_read32(tps, TPS_REG_STATUS, status);
 	if (ret) {
-		dev_err(tps->dev, "%s: failed to read status\n", __func__);
+		dev_err(tps->dev, "%s: failed to read status: %d\n", __func__, ret);
 		return false;
 	}
 

From 2bee79c35167b6d6833911542da108da73408227 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Nov 2022 19:09:49 +0900
Subject: [PATCH 0233/1027] Add 'asahi' localversion to 05-asahi.localversion

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 localversion.05-asahi | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 localversion.05-asahi

diff --git a/localversion.05-asahi b/localversion.05-asahi
new file mode 100644
index 00000000000000..6742ba757f12ac
--- /dev/null
+++ b/localversion.05-asahi
@@ -0,0 +1 @@
+-asahi

From 020e03c28bee4a5d8c0ecff099b2dd10d09655e6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 14 Jan 2023 13:01:31 +0900
Subject: [PATCH 0234/1027] i2c: pasemi: Enable the unjam machine

The I2C bus can get stuck under some conditions (desync between
controller and device). The pasemi controllers include an unjam feature
that is enabled on reset, but was being disabled by the driver. Keep it
enabled by explicitly setting the UJM bit in the CTL register. This
should help recover the bus from certain conditions, which would
otherwise remain stuck forever.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/i2c/busses/i2c-pasemi-core.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/i2c/busses/i2c-pasemi-core.c b/drivers/i2c/busses/i2c-pasemi-core.c
index 75005d4afaff7e..683c861edc1a4f 100644
--- a/drivers/i2c/busses/i2c-pasemi-core.c
+++ b/drivers/i2c/busses/i2c-pasemi-core.c
@@ -49,6 +49,7 @@
 #define CTL_EN		BIT(11)
 #define CTL_MRR		BIT(10)
 #define CTL_MTR		BIT(9)
+#define CTL_UJM		BIT(8)
 #define CTL_CLK_M	GENMASK(7, 0)
 
 #define TRANSFER_TIMEOUT_MS	100
@@ -72,7 +73,7 @@ static inline int reg_read(struct pasemi_smbus *smbus, int reg)
 
 static void pasemi_reset(struct pasemi_smbus *smbus)
 {
-	u32 val = (CTL_MTR | CTL_MRR | (smbus->clk_div & CTL_CLK_M));
+	u32 val = (CTL_MTR | CTL_MRR | CTL_UJM | (smbus->clk_div & CTL_CLK_M));
 
 	if (smbus->hw_rev >= 6)
 		val |= CTL_EN;

From cb0a5717d226fc458fdc32c824adc6a7e5b98522 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 15 Jan 2023 20:29:40 +0900
Subject: [PATCH 0235/1027] i2c: pasemi: Log bus reset causes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/i2c/busses/i2c-pasemi-core.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/drivers/i2c/busses/i2c-pasemi-core.c b/drivers/i2c/busses/i2c-pasemi-core.c
index 683c861edc1a4f..8f2538c8768771 100644
--- a/drivers/i2c/busses/i2c-pasemi-core.c
+++ b/drivers/i2c/busses/i2c-pasemi-core.c
@@ -21,6 +21,7 @@
 /* Register offsets */
 #define REG_MTXFIFO	0x00
 #define REG_MRXFIFO	0x04
+#define REG_XFSTA	0x0c
 #define REG_SMSTA	0x14
 #define REG_IMASK	0x18
 #define REG_CTL		0x1c
@@ -84,7 +85,7 @@ static void pasemi_reset(struct pasemi_smbus *smbus)
 
 static int pasemi_smb_clear(struct pasemi_smbus *smbus)
 {
-	unsigned int status;
+	unsigned int status, xfstatus;
 	int timeout = TRANSFER_TIMEOUT_MS;
 
 	status = reg_read(smbus, REG_SMSTA);
@@ -95,15 +96,21 @@ static int pasemi_smb_clear(struct pasemi_smbus *smbus)
 		status = reg_read(smbus, REG_SMSTA);
 	}
 
+	xfstatus = reg_read(smbus, REG_XFSTA);
+
 	if (timeout < 0) {
-		dev_warn(smbus->dev, "Bus is still stuck (status 0x%08x)\n", status);
+		dev_warn(smbus->dev, "Bus is still stuck (status 0x%08x xfstatus 0x%08x)\n",
+			 status, xfstatus);
 		return -EIO;
 	}
 
 	/* If any badness happened or there is data in the FIFOs, reset the FIFOs */
 	if ((status & (SMSTA_MRNE | SMSTA_JMD | SMSTA_MTO | SMSTA_TOM | SMSTA_MTN | SMSTA_MTA)) ||
-		!(status & SMSTA_MTE))
+		!(status & SMSTA_MTE)) {
+		dev_warn(smbus->dev, "Issuing reset due to status 0x%08x (xfstatus 0x%08x)\n",
+			 status, xfstatus);
 		pasemi_reset(smbus);
+	}
 
 	/* Clear the flags */
 	reg_write(smbus, REG_SMSTA, status);

From 637e518cf0ad2a41fb91cbb374feef946a5bb07b Mon Sep 17 00:00:00 2001
From: Neal Gompa <neal@gompa.dev>
Date: Mon, 12 Dec 2022 02:53:26 -0500
Subject: [PATCH 0236/1027] init/Kconfig: Only block on RANDSTRUCT for RUST

When enabling Rust in the kernel, we only need to block on the
RANDSTRUCT feature and GCC plugin. The rest of the GCC plugins
are reasonably safe to enable.

Signed-off-by: Neal Gompa <neal@gompa.dev>
---
 init/Kconfig | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/init/Kconfig b/init/Kconfig
index 5783a0b8751726..65c408e6b2cc62 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1900,8 +1900,8 @@ config RUST
 	depends on RUST_IS_AVAILABLE
 	depends on !CFI_CLANG
 	depends on !MODVERSIONS
-	depends on !GCC_PLUGINS
-	depends on !RANDSTRUCT
+	depends on !GCC_PLUGIN_RANDSTRUCT
+	depends on RANDSTRUCT_NONE
 	depends on !SHADOW_CALL_STACK
 	depends on !DEBUG_INFO_BTF || PAHOLE_HAS_LANG_EXCLUDE
 	help

From 34cf5e8da22570cfef00e8563707600611062161 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 24 Apr 2023 23:05:40 +0900
Subject: [PATCH 0237/1027] driver core: fw_devlink: Add
 fw_devlink_count_absent_consumers()

Some platforms have power domains that are active on boot and must
remain powered up until all of their consumers probe. The genpd core
needs a way to count how many consumers haven't probed yet to avoid
powering off such domains.

Add a fw_devlink_count_absent_consumers() function, which returns the
total count of consumer devices which either have not been created at
all yet (only fwlinks exist) or have been created but have no driver
bound and fully probed yet.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/base/core.c    | 26 ++++++++++++++++++++++++++
 include/linux/fwnode.h |  1 +
 2 files changed, 27 insertions(+)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 8c0733d3aad8e9..757f719c090096 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -2345,6 +2345,32 @@ static void fw_devlink_link_device(struct device *dev)
 	mutex_unlock(&fwnode_link_lock);
 }
 
+/**
+ * fw_devlink_count_absent_consumers - Return how many consumers have
+ * either not been created yet, or do not yet have a driver attached.
+ * @fwnode: fwnode of the supplier
+ */
+int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode)
+{
+	struct fwnode_link *link, *tmp;
+	struct device_link *dlink, *dtmp;
+	struct device *sup_dev = get_dev_from_fwnode(fwnode);
+	int count = 0;
+
+	list_for_each_entry_safe(link, tmp, &fwnode->consumers, s_hook)
+		count++;
+
+	if (!sup_dev)
+		return count;
+
+	list_for_each_entry_safe(dlink, dtmp, &sup_dev->links.consumers, s_node)
+		if (dlink->consumer->links.status != DL_DEV_DRIVER_BOUND)
+			count++;
+
+	return count;
+}
+EXPORT_SYMBOL_GPL(fw_devlink_count_absent_consumers);
+
 /* Device links support end. */
 
 static struct kobject *dev_kobj;
diff --git a/include/linux/fwnode.h b/include/linux/fwnode.h
index 0d79070c5a70f2..ff1b9bdf6f972f 100644
--- a/include/linux/fwnode.h
+++ b/include/linux/fwnode.h
@@ -221,5 +221,6 @@ int fwnode_link_add(struct fwnode_handle *con, struct fwnode_handle *sup,
 void fwnode_links_purge(struct fwnode_handle *fwnode);
 void fw_devlink_purge_absent_suppliers(struct fwnode_handle *fwnode);
 bool fw_devlink_is_strict(void);
+int fw_devlink_count_absent_consumers(struct fwnode_handle *fwnode);
 
 #endif

From 6703d8d1fbe2ada2ecba1959c957b873e0e520ac Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 24 Apr 2023 23:08:22 +0900
Subject: [PATCH 0238/1027] PM: domains: Add a flag to defer power-off until
 all consumers probe

In some cases, power domains are active on boot and must remain turned
on until all their dependent drivers probe. Examples are:

- Boot-time framebuffers
- Devices that run coprocessors which are handed off already running
- Parent power domains with children that are also on at boot

The genpd core currently powers off the genpd as soon as a single
consumer device probes and goes into runtime suspend or when general
probing is complete, whichever comes first. That breaks any devices
which haven't probed yet.

To fix this, add a GENPD_FLAG_DEFER_OFF which requests that the genpd
core refuse to power down a domain if there are any consumer devices
that either haven't probed yet, or whose device nodes do not exist yet
(but fwlinks do). Genpd providers can set this if they expect to be
critical for devices (e.g. if they are powered on at boot).

It is possible for a device to be runtime suspended from its probe
callback. If this is the last device to probe, this is allowable. To
account for this, check whether the device whose callbacks are being
invoked in the probing state, and in that case, allow 1 instead of 0
pending devices.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pmdomain/core.c   | 54 ++++++++++++++++++++++++++++++++++-----
 include/linux/pm_domain.h |  8 ++++++
 2 files changed, 56 insertions(+), 6 deletions(-)

diff --git a/drivers/pmdomain/core.c b/drivers/pmdomain/core.c
index 7a61aa88c0614a..d4a52104bead61 100644
--- a/drivers/pmdomain/core.c
+++ b/drivers/pmdomain/core.c
@@ -7,6 +7,7 @@
 #define pr_fmt(fmt) "PM: " fmt
 
 #include <linux/delay.h>
+#include <linux/fwnode.h>
 #include <linux/kernel.h>
 #include <linux/io.h>
 #include <linux/platform_device.h>
@@ -129,6 +130,7 @@ static const struct genpd_lock_ops genpd_spin_ops = {
 #define genpd_is_cpu_domain(genpd)	(genpd->flags & GENPD_FLAG_CPU_DOMAIN)
 #define genpd_is_rpm_always_on(genpd)	(genpd->flags & GENPD_FLAG_RPM_ALWAYS_ON)
 #define genpd_is_opp_table_fw(genpd)	(genpd->flags & GENPD_FLAG_OPP_TABLE_FW)
+#define genpd_is_defer_off(genpd)	(genpd->flags & GENPD_FLAG_DEFER_OFF)
 
 static inline bool irq_safe_dev_in_sleep_domain(struct device *dev,
 		const struct generic_pm_domain *genpd)
@@ -763,6 +765,27 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd)
 	queue_work(pm_wq, &genpd->power_off_work);
 }
 
+/**
+ * genpd_must_defer - Check whether the genpd cannot be safely powered off.
+ * @genpd: PM domain about to be powered down.
+ * @one_dev_probing: True if we are being called from RPM callbacks on a device that
+ * is probing, to allow poweroff if that device is the sole remaining consumer probing.
+ *
+ * Returns true if the @genpd has the GENPD_FLAG_DEFER_OFF flag and there
+ * are any consumer devices which either do not exist yet (only represented
+ * by fwlinks) or whose drivers have not probed yet.
+ */
+static bool genpd_must_defer(struct generic_pm_domain *genpd, bool one_dev_probing)
+{
+	if (genpd_is_defer_off(genpd) && genpd->has_provider) {
+		int absent = fw_devlink_count_absent_consumers(genpd->provider);
+
+		if (absent > (one_dev_probing ? 1 : 0))
+			return true;
+	}
+	return false;
+}
+
 /**
  * genpd_power_off - Remove power from a given PM domain.
  * @genpd: PM domain to power down.
@@ -776,7 +799,7 @@ static void genpd_queue_power_off_work(struct generic_pm_domain *genpd)
  * have been powered down, remove power from @genpd.
  */
 static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
-			   unsigned int depth)
+			   bool one_dev_probing, unsigned int depth)
 {
 	struct pm_domain_data *pdd;
 	struct gpd_link *link;
@@ -826,6 +849,14 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
 	if (not_suspended > 1 || (not_suspended == 1 && !one_dev_on))
 		return -EBUSY;
 
+	/*
+	 * Do not allow PM domain to be powered off if it is marked
+	 * as GENPD_FLAG_DEFER_OFF and there are consumer devices
+	 * which have not probed yet.
+	 */
+	if (genpd_must_defer(genpd, one_dev_probing))
+		return -EBUSY;
+
 	if (genpd->gov && genpd->gov->power_down_ok) {
 		if (!genpd->gov->power_down_ok(&genpd->domain))
 			return -EAGAIN;
@@ -852,7 +883,7 @@ static int genpd_power_off(struct generic_pm_domain *genpd, bool one_dev_on,
 	list_for_each_entry(link, &genpd->child_links, child_node) {
 		genpd_sd_counter_dec(link->parent);
 		genpd_lock_nested(link->parent, depth + 1);
-		genpd_power_off(link->parent, false, depth + 1);
+		genpd_power_off(link->parent, false, false, depth + 1);
 		genpd_unlock(link->parent);
 	}
 
@@ -910,7 +941,7 @@ static int genpd_power_on(struct generic_pm_domain *genpd, unsigned int depth)
 					child_node) {
 		genpd_sd_counter_dec(link->parent);
 		genpd_lock_nested(link->parent, depth + 1);
-		genpd_power_off(link->parent, false, depth + 1);
+		genpd_power_off(link->parent, false, false, depth + 1);
 		genpd_unlock(link->parent);
 	}
 
@@ -977,7 +1008,7 @@ static void genpd_power_off_work_fn(struct work_struct *work)
 	genpd = container_of(work, struct generic_pm_domain, power_off_work);
 
 	genpd_lock(genpd);
-	genpd_power_off(genpd, false, 0);
+	genpd_power_off(genpd, false, false, 0);
 	genpd_unlock(genpd);
 }
 
@@ -1042,6 +1073,7 @@ static int genpd_runtime_suspend(struct device *dev)
 	struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev);
 	struct gpd_timing_data *td = gpd_data->td;
 	bool runtime_pm = pm_runtime_enabled(dev);
+	bool probing = dev->links.status != DL_DEV_DRIVER_BOUND;
 	ktime_t time_start = 0;
 	s64 elapsed_ns;
 	int ret;
@@ -1096,7 +1128,7 @@ static int genpd_runtime_suspend(struct device *dev)
 		return 0;
 
 	genpd_lock(genpd);
-	genpd_power_off(genpd, true, 0);
+	genpd_power_off(genpd, true, probing, 0);
 	gpd_data->rpm_pstate = genpd_drop_performance_state(dev);
 	genpd_unlock(genpd);
 
@@ -1117,6 +1149,7 @@ static int genpd_runtime_resume(struct device *dev)
 	struct generic_pm_domain_data *gpd_data = dev_gpd_data(dev);
 	struct gpd_timing_data *td = gpd_data->td;
 	bool timed = td && pm_runtime_enabled(dev);
+	bool probing = dev->links.status != DL_DEV_DRIVER_BOUND;
 	ktime_t time_start = 0;
 	s64 elapsed_ns;
 	int ret;
@@ -1174,7 +1207,7 @@ static int genpd_runtime_resume(struct device *dev)
 err_poweroff:
 	if (!pm_runtime_is_irq_safe(dev) || genpd_is_irq_safe(genpd)) {
 		genpd_lock(genpd);
-		genpd_power_off(genpd, true, 0);
+		genpd_power_off(genpd, true, probing, 0);
 		gpd_data->rpm_pstate = genpd_drop_performance_state(dev);
 		genpd_unlock(genpd);
 	}
@@ -1241,6 +1274,9 @@ static void genpd_sync_power_off(struct generic_pm_domain *genpd, bool use_lock,
 	    || atomic_read(&genpd->sd_count) > 0)
 		return;
 
+	if (genpd_must_defer(genpd, false))
+		return;
+
 	/* Check that the children are in their deepest (powered-off) state. */
 	list_for_each_entry(link, &genpd->parent_links, parent_node) {
 		struct generic_pm_domain *child = link->child;
@@ -2210,6 +2246,12 @@ int pm_genpd_init(struct generic_pm_domain *genpd,
 		return -EINVAL;
 	}
 
+	/* Deferred-off power domains should be powered on at initialization. */
+	if (genpd_is_defer_off(genpd) && !genpd_status_on(genpd)) {
+		pr_warn("deferred-off PM domain %s is not on at init\n", genpd->name);
+		genpd->flags &= ~GENPD_FLAG_DEFER_OFF;
+	}
+
 	/* Multiple states but no governor doesn't make sense. */
 	if (!gov && genpd->state_count > 1)
 		pr_warn("%s: no governor for states\n", genpd->name);
diff --git a/include/linux/pm_domain.h b/include/linux/pm_domain.h
index 858c8e7851fb5d..c573a925e74fd0 100644
--- a/include/linux/pm_domain.h
+++ b/include/linux/pm_domain.h
@@ -92,6 +92,13 @@ struct dev_pm_domain_list {
  * GENPD_FLAG_OPP_TABLE_FW:	The genpd provider supports performance states,
  *				but its corresponding OPP tables are not
  *				described in DT, but are given directly by FW.
+ *
+ * GENPD_FLAG_DEFER_OFF:	Defer powerdown if there are any consumer
+ *				device fwlinks indicating that some consumer
+ *				devices have not yet probed. This is useful
+ *				for power domains which are active at boot and
+ *				must not be shut down until all consumers
+ *				complete their probe sequence.
  */
 #define GENPD_FLAG_PM_CLK	 (1U << 0)
 #define GENPD_FLAG_IRQ_SAFE	 (1U << 1)
@@ -101,6 +108,7 @@ struct dev_pm_domain_list {
 #define GENPD_FLAG_RPM_ALWAYS_ON (1U << 5)
 #define GENPD_FLAG_MIN_RESIDENCY (1U << 6)
 #define GENPD_FLAG_OPP_TABLE_FW	 (1U << 7)
+#define GENPD_FLAG_DEFER_OFF	 (1U << 8)
 
 enum gpd_status {
 	GENPD_STATE_ON = 0,	/* PM domain is on */

From ae635d429e41072d30277e2927222e9dcf6f630c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 24 Apr 2023 23:13:14 +0900
Subject: [PATCH 0239/1027] soc: apple: apple-pmgr-pwrstate: Mark on-at-boot
 PDs as DEFER_OFF

We consider any domains that are found to be powered on at boot as
potentially critical for probing consumer devices. This prevents
badness like the boot-time display controller being powered down as soon
as its IOMMU probes.

Fixes a pile of PD probe order dependencies and races that have required
ALWAYS_ON workaround hacks until now, including:

- ANS2 (NVMe) breaking if left on at handoff.
- DISP0/DCP (boot display) completely breaking.
- PM domains failing to probe when their parent was inadvertently shut
  down before the child probed.
- PCIe losing state/fuse info/etc when it powers down before the driver
  is ready.
- Touch Bar (DFR) display controller losing bootloader-configured state
  before its driver can probe and save it.

The downside is that any spuriously on domains will remain on if their
drivers are missing. We consider missing drivers that never get loaded a
downstream bug. For older kernels running on newer DTs with extra
devices, this shouldn't cause any major problems other than perhaps
slightly increased power consumption (and we can always fix it in the
bootloader by powering down those PDs if they don't need to be left on,
since the bootloader is updated together with the DTs).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pmdomain/apple/pmgr-pwrstate.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c
index d62a776c89a121..7a8a4f5894cbae 100644
--- a/drivers/pmdomain/apple/pmgr-pwrstate.c
+++ b/drivers/pmdomain/apple/pmgr-pwrstate.c
@@ -242,6 +242,8 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
 			/* Turn it on so pm_genpd_init does not fail */
 			active = apple_pmgr_ps_power_on(&ps->genpd) == 0;
 		}
+	} else if (active) {
+		ps->genpd.flags |= GENPD_FLAG_DEFER_OFF;
 	}
 
 	/* Turn on auto-PM if the domain is already on */

From 9afdc589546f8fdcedb3b279add6580b7b03dfa8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 25 Apr 2023 01:46:23 +0900
Subject: [PATCH 0240/1027] tty: serial: samsung_tty: Mark as wakeup_path on
 no_console_suspend

Devices not in the wakeup path always have their power domains shut down
on suspend, which breaks no_console_suspend. Use the wakeup path feature
to stop this from happening.

This is somewhat an abuse of the concept as named, but the end result is
exactly what we desire.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/tty/serial/samsung_tty.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 0d06c34ba48826..ee0ee7421d36fc 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -2103,6 +2103,9 @@ static int __maybe_unused s3c24xx_serial_suspend(struct device *dev)
 {
 	struct uart_port *port = s3c24xx_dev_to_port(dev);
 
+	if (!console_suspend_enabled && uart_console(port))
+		device_set_wakeup_path(dev);
+
 	if (port)
 		uart_suspend_port(&s3c24xx_uart_drv, port);
 

From ee912b118971fa238dcdac66675fe1f2f0790887 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 25 Apr 2023 01:40:11 +0900
Subject: [PATCH 0241/1027] soc: apple: apple-pmgr-pwrstate: Mark on-at-boot
 PDs as wakeup

The genpd core does not have a generic mechanism for skipping genpd
shutdown on system sleep, but it does have the wakeup path mechanism
that is essentially the same thing.

Mark all PDs that are on at boot as potentially wakeup-relevant, which
means they can *optionally* stay on. Drivers have to opt into this with
device_set_wakeup_path() to actually force them to remain on.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pmdomain/apple/pmgr-pwrstate.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pmdomain/apple/pmgr-pwrstate.c b/drivers/pmdomain/apple/pmgr-pwrstate.c
index 7a8a4f5894cbae..fdab263e7ae7e8 100644
--- a/drivers/pmdomain/apple/pmgr-pwrstate.c
+++ b/drivers/pmdomain/apple/pmgr-pwrstate.c
@@ -243,7 +243,7 @@ static int apple_pmgr_ps_probe(struct platform_device *pdev)
 			active = apple_pmgr_ps_power_on(&ps->genpd) == 0;
 		}
 	} else if (active) {
-		ps->genpd.flags |= GENPD_FLAG_DEFER_OFF;
+		ps->genpd.flags |= GENPD_FLAG_DEFER_OFF | GENPD_FLAG_ACTIVE_WAKEUP;
 	}
 
 	/* Turn on auto-PM if the domain is already on */

From 230e0264dca72016374702325f5a189711c84132 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 12 May 2023 18:55:48 +0900
Subject: [PATCH 0242/1027] HACK: Lol libwebrtc

Remove this in a year or two, hopefully the Chromium/CEF ecosystem of
fail will have caught up with their libwebrtc version by then.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 fs/fcntl.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/fcntl.c b/fs/fcntl.c
index 300e5d9ad913b5..01d48e0a1133af 100644
--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -356,6 +356,8 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
 		err = f_dupfd(argi, filp, 0);
 		break;
 	case F_DUPFD_CLOEXEC:
+		if (arg >= 1024)
+			argi = 0; /* Lol libwebrtc */
 		err = f_dupfd(argi, filp, O_CLOEXEC);
 		break;
 	case F_DUPFD_QUERY:

From dc441701e55aee1da9f164bb2b04149f8cf238d6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 22 Sep 2023 22:21:54 +0900
Subject: [PATCH 0243/1027] iommu: Only allocate FQ domains for IOMMUs that
 support them

Commit a4fdd9762272 ("iommu: Use flush queue capability") hid the
IOMMU_DOMAIN_DMA_FQ domain type from domain allocation. A check was
introduced in iommu_dma_init_domain() to fall back if not supported, but
this check runs too late: by that point, devices have been attached to
the IOMMU, and the IOMMU driver might not expect FQ domains at
ops->attach_dev() time.

Ensure that we immediately clamp FQ domains to plain DMA if not
supported by the driver at device attach time, not later.

This regressed apple-dart in v6.5.

Cc: regressions@lists.linux.dev
Cc: stable@vger.kernel.org
Fixes: a4fdd9762272 ("iommu: Use flush queue capability")
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/iommu/iommu.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/iommu/iommu.c b/drivers/iommu/iommu.c
index ed6c5cb60c5aee..ee0b6c14e17929 100644
--- a/drivers/iommu/iommu.c
+++ b/drivers/iommu/iommu.c
@@ -2080,6 +2080,15 @@ static int __iommu_attach_device(struct iommu_domain *domain,
 	if (unlikely(domain->ops->attach_dev == NULL))
 		return -ENODEV;
 
+	/*
+	 * Ensure we do not try to attach devices to FQ domains if the
+	 * IOMMU does not support them. We can safely fall back to
+	 * non-FQ.
+	 */
+	if (domain->type == IOMMU_DOMAIN_DMA_FQ &&
+	    !device_iommu_capable(dev, IOMMU_CAP_DEFERRED_FLUSH))
+		domain->type = IOMMU_DOMAIN_DMA;
+
 	ret = domain->ops->attach_dev(domain, dev);
 	if (ret)
 		return ret;

From 192b19a93748062da88c83d73679c114fde86cfc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 15 Oct 2023 17:41:32 +0200
Subject: [PATCH 0244/1027] drm/simpledrm: Set DMA and coherency mask

Simpledrm is "DMA" access is not limited. All CPU addressible memory
can be used via direct DMA mappings.

Fixes following warning on Apple silicon systems. Physical memory on
those systems starts at (1 << 35) or (1 << 40) so 32-bit direct DMA
mappings are not possible.
------------[ cut here ]------------
simple-framebuffer 9e5064000.framebuffer: swiotlb addr 0x00000009de654000+16384 overflow (mask ffffffff, bus limit 0).
WARNING: CPU: 3 PID: 961 at kernel/dma/swiotlb.c:928 swiotlb_map+0x1f4/0x2a0
Modules linked in: ...
CPU: 3 PID: 961 Comm: kwin_wayland Not tainted 6.5.0-asahi+ #1
Hardware name: Apple Mac mini (M2, 2023) (DT)
...
Call trace:
 swiotlb_map+0x1f4/0x2a0
 dma_direct_map_sg+0x8c/0x2a8
 dma_map_sgtable+0x5c/0xd0
 drm_gem_map_dma_buf+0x64/0xb8
 dma_buf_map_attachment+0xac/0x158
 dma_buf_map_attachment_unlocked+0x48/0x80
 drm_gem_prime_import_dev+0xa0/0x1a0
 drm_gem_prime_fd_to_handle+0xc8/0x218
 drm_prime_fd_to_handle_ioctl+0x34/0x50
 drm_ioctl_kernel+0xe4/0x160
 drm_ioctl+0x23c/0x3e0
...
---[ end trace 0000000000000000 ]---

Avoids using swiotbl bounce buffers on other platforms when the mapped
memory is above 4GB.

Fixes: 11e8f5fd223b ("drm: Add simpledrm driver")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/tiny/simpledrm.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/tiny/simpledrm.c b/drivers/gpu/drm/tiny/simpledrm.c
index d19e102894282f..fad21ec6071f03 100644
--- a/drivers/gpu/drm/tiny/simpledrm.c
+++ b/drivers/gpu/drm/tiny/simpledrm.c
@@ -1029,6 +1029,12 @@ static int simpledrm_probe(struct platform_device *pdev)
 	unsigned int color_mode;
 	int ret;
 
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret)
+		ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to set dma mask\n");
+
 	sdev = simpledrm_device_create(&simpledrm_driver, pdev);
 	if (IS_ERR(sdev))
 		return PTR_ERR(sdev);

From eee3198431aa3bbcc9e65aa0f4350a538be7f43a Mon Sep 17 00:00:00 2001
From: David Rheinsberg <david@readahead.eu>
Date: Tue, 24 Jan 2023 12:04:59 +0100
Subject: [PATCH 0245/1027] x86/insn_decoder_test: allow longer symbol-names

Increase the allowed line-length of the insn-decoder-test to 4k to allow
for symbol-names longer than 256 characters.

The insn-decoder-test takes objdump output as input, which may contain
symbol-names as instruction arguments. With rust-code entering the
kernel, those symbol-names will include mangled-symbols which might
exceed the current line-length-limit of the tool.

By bumping the line-length-limit of the tool to 4k, we get a reasonable
buffer for all objdump outputs I have seen so far. Unfortunately, ELF
symbol-names are not restricted in length, so technically this might
still end up failing if we encounter longer names in the future.

My compile-failure looks like this:

    arch/x86/tools/insn_decoder_test: error: malformed line 1152000:
    tBb_+0xf2>

..which overflowed by 10 characters reading this line:

    ffffffff81458193:   74 3d                   je     ffffffff814581d2 <_RNvXse_NtNtNtCshGpAVYOtgW1_4core4iter8adapters7flattenINtB5_13FlattenCompatINtNtB7_3map3MapNtNtNtBb_3str4iter5CharsNtB1v_17CharEscapeDefaultENtNtBb_4char13EscapeDefaultENtNtBb_3fmt5Debug3fmtBb_+0xf2>

Signed-off-by: David Rheinsberg <david@readahead.eu>
---
 arch/x86/tools/insn_decoder_test.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/x86/tools/insn_decoder_test.c b/arch/x86/tools/insn_decoder_test.c
index 472540aeabc235..366e07546344b1 100644
--- a/arch/x86/tools/insn_decoder_test.c
+++ b/arch/x86/tools/insn_decoder_test.c
@@ -106,7 +106,7 @@ static void parse_args(int argc, char **argv)
 	}
 }
 
-#define BUFSIZE 256
+#define BUFSIZE 4096
 
 int main(int argc, char **argv)
 {

From 544b6ec057a35adf6bfdedb7264abd6cd4ee7fbe Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 26 May 2024 17:30:04 +0900
Subject: [PATCH 0246/1027] arm64: Increase kernel stack size to 32K

To work around stack overflow with the drm/asahi driver plus zram
swap-out, TBD if we can refactor things enough to bring it under 16K
again...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/include/asm/memory.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memory.h
index 54fb014eba0582..afbd7e9eedb383 100644
--- a/arch/arm64/include/asm/memory.h
+++ b/arch/arm64/include/asm/memory.h
@@ -110,7 +110,7 @@
 #define PAGE_END		(_PAGE_END(VA_BITS_MIN))
 #endif /* CONFIG_KASAN */
 
-#define MIN_THREAD_SHIFT	(14 + KASAN_THREAD_SHIFT)
+#define MIN_THREAD_SHIFT	(15 + KASAN_THREAD_SHIFT)
 
 /*
  * VMAP'd stacks are allocated at page granularity, so we must ensure that such

From acd01bf66eaa7de007b280e9f4ad59918a7d92af Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Jun 2024 14:58:59 +0900
Subject: [PATCH 0247/1027] fixup! tty: serial: samsung_tty: Support runtime PM

---
 drivers/tty/serial/samsung_tty.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index ee0ee7421d36fc..b70f5eac6907f9 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -2082,7 +2082,7 @@ static void s3c24xx_serial_remove(struct platform_device *dev)
 	struct uart_port *port = s3c24xx_dev_to_port(&dev->dev);
 	struct s3c24xx_uart_port *ourport = to_ourport(port);
 
-	if (port)
+	if (port) {
 		pm_runtime_get_sync(&dev->dev);
 		uart_remove_one_port(&s3c24xx_uart_drv, port);
 
@@ -2093,6 +2093,7 @@ static void s3c24xx_serial_remove(struct platform_device *dev)
 		pm_runtime_disable(&dev->dev);
 		pm_runtime_set_suspended(&dev->dev);
 		pm_runtime_put_noidle(&dev->dev);
+	}
 
 	uart_unregister_driver(&s3c24xx_uart_drv);
 }

From 9910442caa0d6ded64a0664834d2d02560454a27 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 17 Jul 2024 18:23:44 +0900
Subject: [PATCH 0248/1027] Increase MAX_LOCKDEP_CHAIN_HLOCKS

Got a warning somewhere in the USB subsystem while unplugging a
device...
---
 kernel/locking/lockdep_internals.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/kernel/locking/lockdep_internals.h b/kernel/locking/lockdep_internals.h
index bbe9000260d02a..edafad07b588bc 100644
--- a/kernel/locking/lockdep_internals.h
+++ b/kernel/locking/lockdep_internals.h
@@ -119,7 +119,7 @@ static const unsigned long LOCKF_USED_IN_IRQ_READ =
 
 #define MAX_LOCKDEP_CHAINS	(1UL << MAX_LOCKDEP_CHAINS_BITS)
 
-#define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS*5)
+#define MAX_LOCKDEP_CHAIN_HLOCKS (MAX_LOCKDEP_CHAINS*10)
 
 extern struct lock_chain lock_chains[];
 

From 22c167576cbec5370be31d90e49d01acb29aaa48 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 18 Jul 2024 21:42:27 +0900
Subject: [PATCH 0249/1027] Revert "arm64: defconfig: Enable LPA2 support"

This reverts commit 5d101654226d64ac0a6928019fbf476b46e9d14b.

This defconfig is very much broken.
---
 arch/arm64/Kconfig           | 4 +++-
 arch/arm64/configs/defconfig | 1 +
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index a2f8ff354ca670..1d9efd050d292c 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1320,7 +1320,9 @@ endchoice
 
 choice
 	prompt "Virtual address space size"
-	default ARM64_VA_BITS_52
+	default ARM64_VA_BITS_39 if ARM64_4K_PAGES
+	default ARM64_VA_BITS_47 if ARM64_16K_PAGES
+	default ARM64_VA_BITS_42 if ARM64_64K_PAGES
 	help
 	  Allows choosing one of multiple possible virtual address
 	  space sizes. The level of translation table is determined by
diff --git a/arch/arm64/configs/defconfig b/arch/arm64/configs/defconfig
index 362df939026383..4135cf5e1025a0 100644
--- a/arch/arm64/configs/defconfig
+++ b/arch/arm64/configs/defconfig
@@ -77,6 +77,7 @@ CONFIG_ARCH_VEXPRESS=y
 CONFIG_ARCH_VISCONTI=y
 CONFIG_ARCH_XGENE=y
 CONFIG_ARCH_ZYNQMP=y
+CONFIG_ARM64_VA_BITS_48=y
 CONFIG_SCHED_MC=y
 CONFIG_SCHED_SMT=y
 CONFIG_NUMA=y

From 2db7f4e39c3d3cdc13146a958b6bef441b804e65 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 18 Jul 2024 21:46:12 +0900
Subject: [PATCH 0250/1027] Partial revert "arm64: Enable 52-bit virtual
 addressing for 4k and 16k granule configs"

This reverts commit 352b0395b5053fca01b9dc60294235511f5f3d65.

FEAT_LPA2 support is broken.
---
 arch/arm64/Kconfig | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 1d9efd050d292c..36659dfd83863b 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -1349,7 +1349,7 @@ config ARM64_VA_BITS_48
 
 config ARM64_VA_BITS_52
 	bool "52-bit"
-	depends on ARM64_PAN || !ARM64_SW_TTBR0_PAN
+	depends on ARM64_64K_PAGES && (ARM64_PAN || !ARM64_SW_TTBR0_PAN)
 	help
 	  Enable 52-bit virtual addressing for userspace when explicitly
 	  requested via a hint to mmap(). The kernel will also use 52-bit
@@ -1396,11 +1396,10 @@ choice
 
 config ARM64_PA_BITS_48
 	bool "48-bit"
-	depends on ARM64_64K_PAGES || !ARM64_VA_BITS_52
 
 config ARM64_PA_BITS_52
-	bool "52-bit"
-	depends on ARM64_64K_PAGES || ARM64_VA_BITS_52
+	bool "52-bit (ARMv8.2)"
+	depends on ARM64_64K_PAGES
 	depends on ARM64_PAN || !ARM64_SW_TTBR0_PAN
 	help
 	  Enable support for a 52-bit physical address space, introduced as

From ac5bb3788b4edeac2f30cd6fd10f276e2ae98bb9 Mon Sep 17 00:00:00 2001
From: Yangyu Chen <cyy@cyyself.name>
Date: Wed, 7 Aug 2024 11:35:18 +0900
Subject: [PATCH 0251/1027] drivers/perf: apple_m1: add known PMU events

This patch adds known PMU events that can be found on /usr/share/kpep in
macOS. The m1_pmu_events and m1_pmu_event_affinity are generated from
the script [1], which consumes the plist file from Apple. And then added
these events to m1_pmu_perf_map and m1_pmu_event_attrs with Apple's
documentation [2].

Link: https://github.com/cyyself/m1-pmu-gen [1]
Link: https://developer.apple.com/download/apple-silicon-cpu-optimization-guide/ [2]
Signed-off-by: Yangyu Chen <cyy@cyyself.name>
Acked-by: Hector Martin <marcan@marcan.st>
---
 drivers/perf/apple_m1_cpu_pmu.c | 178 +++++++++++++++++++-------------
 1 file changed, 105 insertions(+), 73 deletions(-)

diff --git a/drivers/perf/apple_m1_cpu_pmu.c b/drivers/perf/apple_m1_cpu_pmu.c
index f322e5ca1114b9..3961e2b6cd9b13 100644
--- a/drivers/perf/apple_m1_cpu_pmu.c
+++ b/drivers/perf/apple_m1_cpu_pmu.c
@@ -47,46 +47,79 @@
  * implementations, we'll have to introduce per cpu-type tables.
  */
 enum m1_pmu_events {
-	M1_PMU_PERFCTR_UNKNOWN_01	= 0x01,
-	M1_PMU_PERFCTR_CPU_CYCLES	= 0x02,
-	M1_PMU_PERFCTR_INSTRUCTIONS	= 0x8c,
-	M1_PMU_PERFCTR_UNKNOWN_8d	= 0x8d,
-	M1_PMU_PERFCTR_UNKNOWN_8e	= 0x8e,
-	M1_PMU_PERFCTR_UNKNOWN_8f	= 0x8f,
-	M1_PMU_PERFCTR_UNKNOWN_90	= 0x90,
-	M1_PMU_PERFCTR_UNKNOWN_93	= 0x93,
-	M1_PMU_PERFCTR_UNKNOWN_94	= 0x94,
-	M1_PMU_PERFCTR_UNKNOWN_95	= 0x95,
-	M1_PMU_PERFCTR_UNKNOWN_96	= 0x96,
-	M1_PMU_PERFCTR_UNKNOWN_97	= 0x97,
-	M1_PMU_PERFCTR_UNKNOWN_98	= 0x98,
-	M1_PMU_PERFCTR_UNKNOWN_99	= 0x99,
-	M1_PMU_PERFCTR_UNKNOWN_9a	= 0x9a,
-	M1_PMU_PERFCTR_UNKNOWN_9b	= 0x9b,
-	M1_PMU_PERFCTR_UNKNOWN_9c	= 0x9c,
-	M1_PMU_PERFCTR_UNKNOWN_9f	= 0x9f,
-	M1_PMU_PERFCTR_UNKNOWN_bf	= 0xbf,
-	M1_PMU_PERFCTR_UNKNOWN_c0	= 0xc0,
-	M1_PMU_PERFCTR_UNKNOWN_c1	= 0xc1,
-	M1_PMU_PERFCTR_UNKNOWN_c4	= 0xc4,
-	M1_PMU_PERFCTR_UNKNOWN_c5	= 0xc5,
-	M1_PMU_PERFCTR_UNKNOWN_c6	= 0xc6,
-	M1_PMU_PERFCTR_UNKNOWN_c8	= 0xc8,
-	M1_PMU_PERFCTR_UNKNOWN_ca	= 0xca,
-	M1_PMU_PERFCTR_UNKNOWN_cb	= 0xcb,
-	M1_PMU_PERFCTR_UNKNOWN_f5	= 0xf5,
-	M1_PMU_PERFCTR_UNKNOWN_f6	= 0xf6,
-	M1_PMU_PERFCTR_UNKNOWN_f7	= 0xf7,
-	M1_PMU_PERFCTR_UNKNOWN_f8	= 0xf8,
-	M1_PMU_PERFCTR_UNKNOWN_fd	= 0xfd,
-	M1_PMU_PERFCTR_LAST		= M1_PMU_CFG_EVENT,
+	M1_PMU_PERFCTR_RETIRE_UOP				= 0x1,
+	M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE			= 0x2,
+	M1_PMU_PERFCTR_L1I_TLB_FILL				= 0x4,
+	M1_PMU_PERFCTR_L1D_TLB_FILL				= 0x5,
+	M1_PMU_PERFCTR_MMU_TABLE_WALK_INSTRUCTION		= 0x7,
+	M1_PMU_PERFCTR_MMU_TABLE_WALK_DATA			= 0x8,
+	M1_PMU_PERFCTR_L2_TLB_MISS_INSTRUCTION			= 0xa,
+	M1_PMU_PERFCTR_L2_TLB_MISS_DATA				= 0xb,
+	M1_PMU_PERFCTR_MMU_VIRTUAL_MEMORY_FAULT_NONSPEC		= 0xd,
+	M1_PMU_PERFCTR_SCHEDULE_UOP				= 0x52,
+	M1_PMU_PERFCTR_INTERRUPT_PENDING			= 0x6c,
+	M1_PMU_PERFCTR_MAP_STALL_DISPATCH			= 0x70,
+	M1_PMU_PERFCTR_MAP_REWIND				= 0x75,
+	M1_PMU_PERFCTR_MAP_STALL				= 0x76,
+	M1_PMU_PERFCTR_MAP_INT_UOP				= 0x7c,
+	M1_PMU_PERFCTR_MAP_LDST_UOP				= 0x7d,
+	M1_PMU_PERFCTR_MAP_SIMD_UOP				= 0x7e,
+	M1_PMU_PERFCTR_FLUSH_RESTART_OTHER_NONSPEC		= 0x84,
+	M1_PMU_PERFCTR_INST_ALL					= 0x8c,
+	M1_PMU_PERFCTR_INST_BRANCH				= 0x8d,
+	M1_PMU_PERFCTR_INST_BRANCH_CALL				= 0x8e,
+	M1_PMU_PERFCTR_INST_BRANCH_RET				= 0x8f,
+	M1_PMU_PERFCTR_INST_BRANCH_TAKEN			= 0x90,
+	M1_PMU_PERFCTR_INST_BRANCH_INDIR			= 0x93,
+	M1_PMU_PERFCTR_INST_BRANCH_COND				= 0x94,
+	M1_PMU_PERFCTR_INST_INT_LD				= 0x95,
+	M1_PMU_PERFCTR_INST_INT_ST				= 0x96,
+	M1_PMU_PERFCTR_INST_INT_ALU				= 0x97,
+	M1_PMU_PERFCTR_INST_SIMD_LD				= 0x98,
+	M1_PMU_PERFCTR_INST_SIMD_ST				= 0x99,
+	M1_PMU_PERFCTR_INST_SIMD_ALU				= 0x9a,
+	M1_PMU_PERFCTR_INST_LDST				= 0x9b,
+	M1_PMU_PERFCTR_INST_BARRIER				= 0x9c,
+	M1_PMU_PERFCTR_UNKNOWN_9f				= 0x9f,
+	M1_PMU_PERFCTR_L1D_TLB_ACCESS				= 0xa0,
+	M1_PMU_PERFCTR_L1D_TLB_MISS				= 0xa1,
+	M1_PMU_PERFCTR_L1D_CACHE_MISS_ST			= 0xa2,
+	M1_PMU_PERFCTR_L1D_CACHE_MISS_LD			= 0xa3,
+	M1_PMU_PERFCTR_LD_UNIT_UOP				= 0xa6,
+	M1_PMU_PERFCTR_ST_UNIT_UOP				= 0xa7,
+	M1_PMU_PERFCTR_L1D_CACHE_WRITEBACK			= 0xa8,
+	M1_PMU_PERFCTR_LDST_X64_UOP				= 0xb1,
+	M1_PMU_PERFCTR_LDST_XPG_UOP				= 0xb2,
+	M1_PMU_PERFCTR_ATOMIC_OR_EXCLUSIVE_SUCC			= 0xb3,
+	M1_PMU_PERFCTR_ATOMIC_OR_EXCLUSIVE_FAIL			= 0xb4,
+	M1_PMU_PERFCTR_L1D_CACHE_MISS_LD_NONSPEC		= 0xbf,
+	M1_PMU_PERFCTR_L1D_CACHE_MISS_ST_NONSPEC		= 0xc0,
+	M1_PMU_PERFCTR_L1D_TLB_MISS_NONSPEC			= 0xc1,
+	M1_PMU_PERFCTR_ST_MEMORY_ORDER_VIOLATION_NONSPEC	= 0xc4,
+	M1_PMU_PERFCTR_BRANCH_COND_MISPRED_NONSPEC		= 0xc5,
+	M1_PMU_PERFCTR_BRANCH_INDIR_MISPRED_NONSPEC		= 0xc6,
+	M1_PMU_PERFCTR_BRANCH_RET_INDIR_MISPRED_NONSPEC		= 0xc8,
+	M1_PMU_PERFCTR_BRANCH_CALL_INDIR_MISPRED_NONSPEC	= 0xca,
+	M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC			= 0xcb,
+	M1_PMU_PERFCTR_L1I_TLB_MISS_DEMAND			= 0xd4,
+	M1_PMU_PERFCTR_MAP_DISPATCH_BUBBLE			= 0xd6,
+	M1_PMU_PERFCTR_L1I_CACHE_MISS_DEMAND			= 0xdb,
+	M1_PMU_PERFCTR_FETCH_RESTART				= 0xde,
+	M1_PMU_PERFCTR_ST_NT_UOP				= 0xe5,
+	M1_PMU_PERFCTR_LD_NT_UOP				= 0xe6,
+	M1_PMU_PERFCTR_UNKNOWN_f5				= 0xf5,
+	M1_PMU_PERFCTR_UNKNOWN_f6				= 0xf6,
+	M1_PMU_PERFCTR_UNKNOWN_f7				= 0xf7,
+	M1_PMU_PERFCTR_UNKNOWN_f8				= 0xf8,
+	M1_PMU_PERFCTR_UNKNOWN_fd				= 0xfd,
+	M1_PMU_PERFCTR_LAST					= M1_PMU_CFG_EVENT,
 
 	/*
 	 * From this point onwards, these are not actual HW events,
 	 * but attributes that get stored in hw->config_base.
 	 */
-	M1_PMU_CFG_COUNT_USER		= BIT(8),
-	M1_PMU_CFG_COUNT_KERNEL		= BIT(9),
+	M1_PMU_CFG_COUNT_USER					= BIT(8),
+	M1_PMU_CFG_COUNT_KERNEL					= BIT(9),
 };
 
 /*
@@ -96,46 +129,45 @@ enum m1_pmu_events {
  * counters had strange affinities.
  */
 static const u16 m1_pmu_event_affinity[M1_PMU_PERFCTR_LAST + 1] = {
-	[0 ... M1_PMU_PERFCTR_LAST]	= ANY_BUT_0_1,
-	[M1_PMU_PERFCTR_UNKNOWN_01]	= BIT(7),
-	[M1_PMU_PERFCTR_CPU_CYCLES]	= ANY_BUT_0_1 | BIT(0),
-	[M1_PMU_PERFCTR_INSTRUCTIONS]	= BIT(7) | BIT(1),
-	[M1_PMU_PERFCTR_UNKNOWN_8d]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_8e]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_8f]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_90]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_93]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_94]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_95]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_96]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_97]	= BIT(7),
-	[M1_PMU_PERFCTR_UNKNOWN_98]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_99]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_9a]	= BIT(7),
-	[M1_PMU_PERFCTR_UNKNOWN_9b]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_9c]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_9f]	= BIT(7),
-	[M1_PMU_PERFCTR_UNKNOWN_bf]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c0]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c1]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c4]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c5]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c6]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_c8]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_ca]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_cb]	= ONLY_5_6_7,
-	[M1_PMU_PERFCTR_UNKNOWN_f5]	= ONLY_2_4_6,
-	[M1_PMU_PERFCTR_UNKNOWN_f6]	= ONLY_2_4_6,
-	[M1_PMU_PERFCTR_UNKNOWN_f7]	= ONLY_2_4_6,
-	[M1_PMU_PERFCTR_UNKNOWN_f8]	= ONLY_2_TO_7,
-	[M1_PMU_PERFCTR_UNKNOWN_fd]	= ONLY_2_4_6,
+	[0 ... M1_PMU_PERFCTR_LAST]				= ANY_BUT_0_1,
+	[M1_PMU_PERFCTR_RETIRE_UOP]				= BIT(7),
+	[M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE]			= ANY_BUT_0_1 | BIT(0),
+	[M1_PMU_PERFCTR_INST_ALL]				= BIT(7) | BIT(1),
+	[M1_PMU_PERFCTR_INST_BRANCH]				= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_BRANCH_CALL]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_BRANCH_RET]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_BRANCH_TAKEN]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_BRANCH_INDIR]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_BRANCH_COND]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_INT_LD]				= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_INT_ST]				= BIT(7),
+	[M1_PMU_PERFCTR_INST_INT_ALU]				= BIT(7),
+	[M1_PMU_PERFCTR_INST_SIMD_LD]				= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_SIMD_ST]				= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_INST_SIMD_ALU]				= BIT(7),
+	[M1_PMU_PERFCTR_INST_LDST]				= BIT(7),
+	[M1_PMU_PERFCTR_INST_BARRIER]				= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_UNKNOWN_9f]				= BIT(7),
+	[M1_PMU_PERFCTR_L1D_CACHE_MISS_LD_NONSPEC]		= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_L1D_CACHE_MISS_ST_NONSPEC]		= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_L1D_TLB_MISS_NONSPEC]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_ST_MEMORY_ORDER_VIOLATION_NONSPEC]	= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_BRANCH_COND_MISPRED_NONSPEC]		= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_BRANCH_INDIR_MISPRED_NONSPEC]		= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_BRANCH_RET_INDIR_MISPRED_NONSPEC]	= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_BRANCH_CALL_INDIR_MISPRED_NONSPEC]	= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC]			= ONLY_5_6_7,
+	[M1_PMU_PERFCTR_UNKNOWN_f5]				= ONLY_2_4_6,
+	[M1_PMU_PERFCTR_UNKNOWN_f6]				= ONLY_2_4_6,
+	[M1_PMU_PERFCTR_UNKNOWN_f7]				= ONLY_2_4_6,
+	[M1_PMU_PERFCTR_UNKNOWN_f8]				= ONLY_2_TO_7,
+	[M1_PMU_PERFCTR_UNKNOWN_fd]				= ONLY_2_4_6,
 };
 
 static const unsigned m1_pmu_perf_map[PERF_COUNT_HW_MAX] = {
 	PERF_MAP_ALL_UNSUPPORTED,
-	[PERF_COUNT_HW_CPU_CYCLES]	= M1_PMU_PERFCTR_CPU_CYCLES,
-	[PERF_COUNT_HW_INSTRUCTIONS]	= M1_PMU_PERFCTR_INSTRUCTIONS,
-	/* No idea about the rest yet */
+	[PERF_COUNT_HW_CPU_CYCLES]		= M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE,
+	[PERF_COUNT_HW_INSTRUCTIONS]		= M1_PMU_PERFCTR_INST_ALL,
 };
 
 /* sysfs definitions */
@@ -154,8 +186,8 @@ static ssize_t m1_pmu_events_sysfs_show(struct device *dev,
 	PMU_EVENT_ATTR_ID(name, m1_pmu_events_sysfs_show, config)
 
 static struct attribute *m1_pmu_event_attrs[] = {
-	M1_PMU_EVENT_ATTR(cycles, M1_PMU_PERFCTR_CPU_CYCLES),
-	M1_PMU_EVENT_ATTR(instructions, M1_PMU_PERFCTR_INSTRUCTIONS),
+	M1_PMU_EVENT_ATTR(cycles, M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE),
+	M1_PMU_EVENT_ATTR(instructions, M1_PMU_PERFCTR_INST_ALL),
 	NULL,
 };
 

From 87561af7171eacd834123d55776e7a5434987345 Mon Sep 17 00:00:00 2001
From: Jiajie Chen <c@jia.je>
Date: Thu, 11 Jul 2024 11:32:20 +0800
Subject: [PATCH 0252/1027] drivers/perf: apple_m1: Add mapping for branch
 counters

Apple released their performance counters definition in
/usr/share/kpep/a14.plist file on macOS. The file lists the branch
counters (number of branch instructions and branch mispredictions):

"INST_BRANCH" => {
  "counters_mask" => 224
  "description" => "Retired branch instructions including calls and
returns"
  "number" => 141
}

and

"BRANCH_MISPRED_NONSPEC" => {
  "counters_mask" => 224
  "description" => "Retired branch instructions including calls and
returns that mispredicted"
  "number" => 203
}

In this commit, the performance counters numbered 0x8d(141) and
0xcb(203) are renamed to their actual names and the mappings from
generalized performence event types in `enum perf_hw_id` to hardware
performance counters are added respectively.

Signed-off-by: Jiajie Chen <c@jia.je>
---
 drivers/perf/apple_m1_cpu_pmu.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/perf/apple_m1_cpu_pmu.c b/drivers/perf/apple_m1_cpu_pmu.c
index 3961e2b6cd9b13..969ed39f7208b3 100644
--- a/drivers/perf/apple_m1_cpu_pmu.c
+++ b/drivers/perf/apple_m1_cpu_pmu.c
@@ -168,6 +168,8 @@ static const unsigned m1_pmu_perf_map[PERF_COUNT_HW_MAX] = {
 	PERF_MAP_ALL_UNSUPPORTED,
 	[PERF_COUNT_HW_CPU_CYCLES]		= M1_PMU_PERFCTR_CORE_ACTIVE_CYCLE,
 	[PERF_COUNT_HW_INSTRUCTIONS]		= M1_PMU_PERFCTR_INST_ALL,
+	[PERF_COUNT_HW_BRANCH_INSTRUCTIONS]	= M1_PMU_PERFCTR_INST_BRANCH,
+	[PERF_COUNT_HW_BRANCH_MISSES]		= M1_PMU_PERFCTR_BRANCH_MISPRED_NONSPEC,
 };
 
 /* sysfs definitions */

From 83063150a84ea95ccdee3c37da1d609698fe608d Mon Sep 17 00:00:00 2001
From: Jens Axboe <axboe@kernel.dk>
Date: Wed, 16 Feb 2022 12:17:58 -0700
Subject: [PATCH 0253/1027] apple-nvme: defer cache flushes by a specified
 amount

Cache flushes on the M1 nvme are really slow, taking 17-18 msec to
complete. This can slow down workloads considerably, pure random writes
end up being bound by the flush latency and hence run at 55-60 IOPS.

Add a deferred flush work around to provide better performance, at a
minimal risk. By default, flushes are delayed at most 1 second, but this
is configurable.

With this work-around, a pure random write workload runs at ~12K IOPS
rather than 56 IOPS.

Signed-off-by: Jens Axboe <axboe@kernel.dk>
---
 drivers/nvme/host/apple.c | 69 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c
index b1387dc459a323..4beb1bea7c4137 100644
--- a/drivers/nvme/host/apple.c
+++ b/drivers/nvme/host/apple.c
@@ -195,8 +195,20 @@ struct apple_nvme {
 
 	int irq;
 	spinlock_t lock;
+
+	/*
+	 * Delayed cache flush handling state
+	 */
+	struct nvme_ns *flush_ns;
+	unsigned long flush_interval;
+	unsigned long last_flush;
+	struct delayed_work flush_dwork;
 };
 
+unsigned int flush_interval = 1000;
+module_param(flush_interval, uint, 0644);
+MODULE_PARM_DESC(flush_interval, "Grace period in msecs between flushes");
+
 static_assert(sizeof(struct nvme_command) == 64);
 static_assert(sizeof(struct apple_nvmmu_tcb) == 128);
 
@@ -729,6 +741,26 @@ static int apple_nvme_remove_sq(struct apple_nvme *anv)
 	return nvme_submit_sync_cmd(anv->ctrl.admin_q, &c, NULL, 0);
 }
 
+static bool apple_nvme_delayed_flush(struct apple_nvme *anv, struct nvme_ns *ns,
+				     struct request *req)
+{
+	if (!anv->flush_interval || req_op(req) != REQ_OP_FLUSH)
+		return false;
+	if (delayed_work_pending(&anv->flush_dwork))
+		return true;
+	if (time_before(jiffies, anv->last_flush + anv->flush_interval)) {
+		kblockd_mod_delayed_work_on(WORK_CPU_UNBOUND, &anv->flush_dwork,
+						anv->flush_interval);
+		if (WARN_ON_ONCE(anv->flush_ns && anv->flush_ns != ns))
+			goto out;
+		anv->flush_ns = ns;
+		return true;
+	}
+out:
+	anv->last_flush = jiffies;
+	return false;
+}
+
 static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
 					const struct blk_mq_queue_data *bd)
 {
@@ -764,6 +796,12 @@ static blk_status_t apple_nvme_queue_rq(struct blk_mq_hw_ctx *hctx,
 	}
 
 	nvme_start_request(req);
+
+	if (apple_nvme_delayed_flush(anv, ns, req)) {
+		blk_mq_complete_request(req);
+		return BLK_STS_OK;
+	}
+
 	apple_nvme_submit_cmd(q, cmnd);
 	return BLK_STS_OK;
 
@@ -1388,6 +1426,28 @@ static void devm_apple_nvme_mempool_destroy(void *data)
 	mempool_destroy(data);
 }
 
+static void apple_nvme_flush_work(struct work_struct *work)
+{
+	struct nvme_command c = { };
+	struct apple_nvme *anv;
+	struct nvme_ns *ns;
+	int err;
+
+	anv = container_of(work, struct apple_nvme, flush_dwork.work);
+	ns = anv->flush_ns;
+	if (WARN_ON_ONCE(!ns))
+		return;
+
+	c.common.opcode = nvme_cmd_flush;
+	c.common.nsid = cpu_to_le32(anv->flush_ns->head->ns_id);
+	err = nvme_submit_sync_cmd(ns->queue, &c, NULL, 0);
+	if (err) {
+		dev_err(anv->dev, "Deferred flush failed: %d\n", err);
+	} else {
+		anv->last_flush = jiffies;
+	}
+}
+
 static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -1542,6 +1602,14 @@ static int apple_nvme_probe(struct platform_device *pdev)
 		goto out_uninit_ctrl;
 	}
 
+	if (flush_interval) {
+		anv->flush_interval = msecs_to_jiffies(flush_interval);
+		anv->flush_ns = NULL;
+		anv->last_flush = jiffies - anv->flush_interval;
+	}
+
+	INIT_DELAYED_WORK(&anv->flush_dwork, apple_nvme_flush_work);
+
 	nvme_reset_ctrl(&anv->ctrl);
 	async_schedule(apple_nvme_async_probe, anv);
 
@@ -1575,6 +1643,7 @@ static void apple_nvme_shutdown(struct platform_device *pdev)
 {
 	struct apple_nvme *anv = platform_get_drvdata(pdev);
 
+	flush_delayed_work(&anv->flush_dwork);
 	apple_nvme_disable(anv, true);
 	if (apple_rtkit_is_running(anv->rtk))
 		apple_rtkit_shutdown(anv->rtk);

From d9bf51f2a1be61266b7f5edc9c476f6c3c7345b9 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 27 Jun 2022 21:47:43 +0900
Subject: [PATCH 0254/1027] apple-nvme: Release power domains when probe fails

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/nvme/host/apple.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c
index 4beb1bea7c4137..9ccf6cd5ff7d29 100644
--- a/drivers/nvme/host/apple.c
+++ b/drivers/nvme/host/apple.c
@@ -1578,6 +1578,7 @@ static struct apple_nvme *apple_nvme_alloc(struct platform_device *pdev)
 
 	return anv;
 put_dev:
+	apple_nvme_detach_genpd(anv);
 	put_device(anv->dev);
 	return ERR_PTR(ret);
 }
@@ -1619,6 +1620,7 @@ static int apple_nvme_probe(struct platform_device *pdev)
 	nvme_uninit_ctrl(&anv->ctrl);
 out_put_ctrl:
 	nvme_put_ctrl(&anv->ctrl);
+	apple_nvme_detach_genpd(anv);
 	return ret;
 }
 

From 56cd78035e0feb5170488fea296c6b9de87ad878 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 02:56:59 +0900
Subject: [PATCH 0255/1027] apple-nvme: Support coprocessors left idle

iBoot on at least some firmwares/machines leaves ANS2 running, requiring
a wake command instead of a CPU boot (and if we reset ANS2 in that
state, everything breaks).

Only stop the CPU if RTKit was running, and only do the reset dance if
the CPU is stopped.

Normal shutdown handoff:
- RTKit not yet running
- CPU detected not running
- Reset
- CPU powerup
- RTKit boot wait

ANS2 left running/idle:
- RTKit not yet running
- CPU detected running
- RTKit wake message

Sleep/resume cycle:
- RTKit shutdown
- CPU stopped
- (sleep here)
- CPU detected not running
- Reset
- CPU powerup
- RTKit boot wait

Shutdown or device removal:
- RTKit shutdown
- CPU stopped

Therefore, the CPU running bit serves as a consistent flag of whether
the coprocessor is fully stopped or just idle.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/nvme/host/apple.c | 53 ++++++++++++++++++++++++++-------------
 1 file changed, 36 insertions(+), 17 deletions(-)

diff --git a/drivers/nvme/host/apple.c b/drivers/nvme/host/apple.c
index 9ccf6cd5ff7d29..c7114444e9bc78 100644
--- a/drivers/nvme/host/apple.c
+++ b/drivers/nvme/host/apple.c
@@ -1049,25 +1049,37 @@ static void apple_nvme_reset_work(struct work_struct *work)
 		ret = apple_rtkit_shutdown(anv->rtk);
 		if (ret)
 			goto out;
+
+		writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
 	}
 
-	writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+	/*
+	 * Only do the soft-reset if the CPU is not running, which means either we
+	 * or the previous stage shut it down cleanly.
+	 */
+	if (!(readl(anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL) &
+		APPLE_ANS_COPROC_CPU_CONTROL_RUN)) {
 
-	ret = reset_control_assert(anv->reset);
-	if (ret)
-		goto out;
+		ret = reset_control_assert(anv->reset);
+		if (ret)
+			goto out;
 
-	ret = apple_rtkit_reinit(anv->rtk);
-	if (ret)
-		goto out;
+		ret = apple_rtkit_reinit(anv->rtk);
+		if (ret)
+			goto out;
 
-	ret = reset_control_deassert(anv->reset);
-	if (ret)
-		goto out;
+		ret = reset_control_deassert(anv->reset);
+		if (ret)
+			goto out;
+
+		writel(APPLE_ANS_COPROC_CPU_CONTROL_RUN,
+		       anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+
+		ret = apple_rtkit_boot(anv->rtk);
+	} else {
+		ret = apple_rtkit_wake(anv->rtk);
+	}
 
-	writel(APPLE_ANS_COPROC_CPU_CONTROL_RUN,
-	       anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
-	ret = apple_rtkit_boot(anv->rtk);
 	if (ret) {
 		dev_err(anv->dev, "ANS did not boot");
 		goto out;
@@ -1635,9 +1647,12 @@ static void apple_nvme_remove(struct platform_device *pdev)
 	apple_nvme_disable(anv, true);
 	nvme_uninit_ctrl(&anv->ctrl);
 
-	if (apple_rtkit_is_running(anv->rtk))
+	if (apple_rtkit_is_running(anv->rtk)) {
 		apple_rtkit_shutdown(anv->rtk);
 
+		writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+	}
+
 	apple_nvme_detach_genpd(anv);
 }
 
@@ -1647,8 +1662,11 @@ static void apple_nvme_shutdown(struct platform_device *pdev)
 
 	flush_delayed_work(&anv->flush_dwork);
 	apple_nvme_disable(anv, true);
-	if (apple_rtkit_is_running(anv->rtk))
+	if (apple_rtkit_is_running(anv->rtk)) {
 		apple_rtkit_shutdown(anv->rtk);
+
+		writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+	}
 }
 
 static int apple_nvme_resume(struct device *dev)
@@ -1665,10 +1683,11 @@ static int apple_nvme_suspend(struct device *dev)
 
 	apple_nvme_disable(anv, true);
 
-	if (apple_rtkit_is_running(anv->rtk))
+	if (apple_rtkit_is_running(anv->rtk)) {
 		ret = apple_rtkit_shutdown(anv->rtk);
 
-	writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+		writel(0, anv->mmio_coproc + APPLE_ANS_COPROC_CPU_CONTROL);
+	}
 
 	return ret;
 }

From 6c80f92a713d24ab93bbc8c545bd128bdf6822c7 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 12 Dec 2021 11:46:33 +0900
Subject: [PATCH 0256/1027] dt-bindings: spi: apple,spi: Add binding for Apple
 SPI controllers

The Apple SPI controller is present in SoCs such as the M1 (t8103) and
M1 Pro/Max (t600x). This controller uses one IRQ and one clock, and
doesn't need any special properties, so the binding is trivial.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../devicetree/bindings/spi/apple,spi.yaml    | 63 +++++++++++++++++++
 1 file changed, 63 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/spi/apple,spi.yaml

diff --git a/Documentation/devicetree/bindings/spi/apple,spi.yaml b/Documentation/devicetree/bindings/spi/apple,spi.yaml
new file mode 100644
index 00000000000000..bcbdc8943e92a3
--- /dev/null
+++ b/Documentation/devicetree/bindings/spi/apple,spi.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/spi/apple,spi.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple ARM SoC SPI controller
+
+allOf:
+  - $ref: "spi-controller.yaml#"
+
+maintainers:
+  - Hector Martin <marcan@marcan.st>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,t8103-spi
+          - apple,t6000-spi
+      - const: apple,spi
+
+  reg:
+    maxItems: 1
+
+  clocks:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  power-domains:
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+  - clocks
+  - interrupts
+  - '#address-cells'
+  - '#size-cells'
+
+unevaluatedProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/apple-aic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    soc {
+      #address-cells = <2>;
+      #size-cells = <2>;
+
+      spi: spi@39b104000 {
+        compatible = "apple,t6000-spi", "apple,spi";
+        reg = <0x3 0x9b104000 0x0 0x4000>;
+        interrupt-parent = <&aic>;
+        interrupts = <AIC_IRQ 0 1107 IRQ_TYPE_LEVEL_HIGH>;
+        #address-cells = <1>;
+        #size-cells = <0>;
+        clocks = <&clk>;
+      };
+    };

From 07ba59e7ab2db1c92de3a1513250881d00b91890 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 18:09:39 +0200
Subject: [PATCH 0257/1027] Revert "ASoC: ops: Don't modify the driver's
 plaform_max when reading state"

This reverts commit 30ac49841386f933339817771ec315a34a4c0edd.
---
 sound/soc/soc-ops.c | 24 ++++++++----------------
 1 file changed, 8 insertions(+), 16 deletions(-)

diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 19928f098d8dcb..51336d2ec92d53 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -177,28 +177,20 @@ int snd_soc_info_volsw(struct snd_kcontrol *kcontrol,
 {
 	struct soc_mixer_control *mc =
 		(struct soc_mixer_control *)kcontrol->private_value;
-	const char *vol_string = NULL;
-	int max;
+	int platform_max;
 
-	max = uinfo->value.integer.max = mc->max - mc->min;
-	if (mc->platform_max && mc->platform_max < max)
-		max = mc->platform_max;
+	if (!mc->platform_max)
+		mc->platform_max = mc->max;
+	platform_max = mc->platform_max;
 
-	if (max == 1) {
-		/* Even two value controls ending in Volume should always be integer */
-		vol_string = strstr(kcontrol->id.name, " Volume");
-		if (vol_string && !strcmp(vol_string, " Volume"))
-			uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-		else
-			uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
-	} else {
+	if (platform_max == 1 && !strstr(kcontrol->id.name, " Volume"))
+		uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	else
 		uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
-	}
 
 	uinfo->count = snd_soc_volsw_is_stereo(mc) ? 2 : 1;
 	uinfo->value.integer.min = 0;
-	uinfo->value.integer.max = max;
-
+	uinfo->value.integer.max = platform_max - mc->min;
 	return 0;
 }
 EXPORT_SYMBOL_GPL(snd_soc_info_volsw);

From e5350683bb0b404321f8c48db116e08ee9053ce1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 11 Mar 2022 11:55:44 +0100
Subject: [PATCH 0258/1027] ASoC: tas2764: Extend driver to SN012776
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

SN012776 is a speaker amp chip found in Apple's 2021 laptops. It appears
similar and more-or-less compatible to TAS2764. Extend the TAS2764
driver with some SN012776 specifics and configure the chip assuming
it's in one of the Apple machines.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 50 ++++++++++++++++++++++++++++++++++----
 sound/soc/codecs/tas2764.h |  3 +++
 2 files changed, 48 insertions(+), 5 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 5eaddf07aadcc7..9e4c3a8b182fa2 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -15,6 +15,7 @@
 #include <linux/regulator/consumer.h>
 #include <linux/regmap.h>
 #include <linux/of.h>
+#include <linux/of_device.h>
 #include <linux/slab.h>
 #include <sound/soc.h>
 #include <sound/pcm.h>
@@ -24,6 +25,11 @@
 
 #include "tas2764.h"
 
+enum tas2764_devid {
+	DEVID_TAS2764  = 0,
+	DEVID_SN012776 = 1
+};
+
 struct tas2764_priv {
 	struct snd_soc_component *component;
 	struct gpio_desc *reset_gpio;
@@ -31,7 +37,8 @@ struct tas2764_priv {
 	struct regmap *regmap;
 	struct device *dev;
 	int irq;
-	
+	enum tas2764_devid devid;
+
 	int v_sense_slot;
 	int i_sense_slot;
 
@@ -527,10 +534,16 @@ static struct snd_soc_dai_driver tas2764_dai_driver[] = {
 	},
 };
 
+static uint8_t sn012776_bop_presets[] = {
+	0x01, 0x32, 0x02, 0x22, 0x83, 0x2d, 0x80, 0x02, 0x06,
+	0x32, 0x46, 0x30, 0x02, 0x06, 0x38, 0x40, 0x30, 0x02,
+	0x06, 0x3e, 0x37, 0x30, 0xff, 0xe6
+};
+
 static int tas2764_codec_probe(struct snd_soc_component *component)
 {
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
-	int ret;
+	int ret, i;
 
 	tas2764->component = component;
 
@@ -579,6 +592,23 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 	if (ret < 0)
 		return ret;
 
+	if (tas2764->devid == DEVID_SN012776) {
+		ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
+					TAS2764_PWR_CTRL_BOP_SRC,
+					TAS2764_PWR_CTRL_BOP_SRC);
+		if (ret < 0)
+			return ret;
+
+		for (i = 0; i < ARRAY_SIZE(sn012776_bop_presets); i++) {
+			ret = snd_soc_component_write(component,
+						TAS2764_BOP_CFG0 + i,
+						sn012776_bop_presets[i]);
+
+			if (ret < 0)
+				return ret;
+		}
+	}
+
 	return 0;
 }
 
@@ -698,9 +728,12 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)
 	return 0;
 }
 
+static const struct of_device_id tas2764_of_match[];
+
 static int tas2764_i2c_probe(struct i2c_client *client)
 {
 	struct tas2764_priv *tas2764;
+	const struct of_device_id *of_id = NULL;
 	int result;
 
 	tas2764 = devm_kzalloc(&client->dev, sizeof(struct tas2764_priv),
@@ -708,6 +741,14 @@ static int tas2764_i2c_probe(struct i2c_client *client)
 	if (!tas2764)
 		return -ENOMEM;
 
+	if (client->dev.of_node)
+		of_id = of_match_device(tas2764_of_match, &client->dev);
+
+	if (of_id)
+		tas2764->devid = (enum tas2764_devid) of_id->data;
+	else
+		tas2764->devid = DEVID_TAS2764;
+
 	tas2764->dev = &client->dev;
 	tas2764->irq = client->irq;
 	i2c_set_clientdata(client, tas2764);
@@ -742,13 +783,12 @@ static const struct i2c_device_id tas2764_i2c_id[] = {
 };
 MODULE_DEVICE_TABLE(i2c, tas2764_i2c_id);
 
-#if defined(CONFIG_OF)
 static const struct of_device_id tas2764_of_match[] = {
-	{ .compatible = "ti,tas2764" },
+	{ .compatible = "ti,tas2764",  .data = (void*) DEVID_TAS2764 },
+	{ .compatible = "ti,sn012776", .data = (void*) DEVID_SN012776 },
 	{},
 };
 MODULE_DEVICE_TABLE(of, tas2764_of_match);
-#endif
 
 static struct i2c_driver tas2764_i2c_driver = {
 	.driver = {
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 168af772a898ff..0a40166040e7e8 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -29,6 +29,7 @@
 #define TAS2764_PWR_CTRL_ACTIVE		0x0
 #define TAS2764_PWR_CTRL_MUTE		BIT(0)
 #define TAS2764_PWR_CTRL_SHUTDOWN	BIT(1)
+#define TAS2764_PWR_CTRL_BOP_SRC	BIT(7)
 
 #define TAS2764_VSENSE_POWER_EN		3
 #define TAS2764_ISENSE_POWER_EN		4
@@ -110,4 +111,6 @@
 #define TAS2764_INT_CLK_CFG             TAS2764_REG(0x0, 0x5c)
 #define TAS2764_INT_CLK_CFG_IRQZ_CLR    BIT(2)
 
+#define TAS2764_BOP_CFG0                TAS2764_REG(0X0, 0x1d)
+
 #endif /* __TAS2764__ */

From 89c41dc41b7a33a966ebaa351cc0e7c3e3f41b3b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Sat, 20 Aug 2022 20:13:05 +0200
Subject: [PATCH 0259/1027] ASoC: tas2764: Add control concerning overcurrent
 events
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add control to expose the option of autoretry behavior on overcurrent
events in the codec.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 9 +++++++++
 sound/soc/codecs/tas2764.h | 4 ++++
 2 files changed, 13 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 9e4c3a8b182fa2..a81a66890f3c6c 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -624,12 +624,21 @@ static SOC_ENUM_SINGLE_DECL(
 	tas2764_hpf_enum, TAS2764_DC_BLK0,
 	TAS2764_DC_BLK0_HPF_FREQ_PB_SHIFT, tas2764_hpf_texts);
 
+static const char * const tas2764_oce_texts[] = {
+	"Disable", "Retry",
+};
+
+static SOC_ENUM_SINGLE_DECL(
+	tas2764_oce_enum, TAS2764_MISC_CFG1,
+	TAS2764_MISC_CFG1_OCE_RETRY_SHIFT, tas2764_oce_texts);
+
 static const struct snd_kcontrol_new tas2764_snd_controls[] = {
 	SOC_SINGLE_TLV("Speaker Volume", TAS2764_DVC, 0,
 		       TAS2764_DVC_MAX, 1, tas2764_playback_volume),
 	SOC_SINGLE_TLV("Amp Gain Volume", TAS2764_CHNL_0, 1, 0x14, 0,
 		       tas2764_digital_tlv),
 	SOC_ENUM("HPF Corner Frequency", tas2764_hpf_enum),
+	SOC_ENUM("OCE Handling", tas2764_oce_enum),
 };
 
 static const struct snd_soc_component_driver soc_component_driver_tas2764 = {
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 0a40166040e7e8..20628e51bf94f0 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -44,6 +44,10 @@
 
 #define TAS2764_CHNL_0  TAS2764_REG(0X0, 0x03)
 
+/* Miscellaneous */
+#define TAS2764_MISC_CFG1		TAS2764_REG(0x0, 0x06)
+#define TAS2764_MISC_CFG1_OCE_RETRY_SHIFT  5
+
 /* TDM Configuration Reg0 */
 #define TAS2764_TDM_CFG0		TAS2764_REG(0X0, 0x08)
 #define TAS2764_TDM_CFG0_SMP_MASK	BIT(5)

From f9c57cd43c374385fcb949d186352ab93762a728 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 18:38:04 +0200
Subject: [PATCH 0260/1027] ASoC: ops: Move guts out of snd_soc_limit_volume
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In advance of other changes, move the modification of the control itself
into function of its own.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/soc-ops.c | 27 ++++++++++++++++-----------
 1 file changed, 16 insertions(+), 11 deletions(-)

diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 51336d2ec92d53..5558fa24047e3e 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -632,6 +632,16 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 }
 EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
 
+static int soc_limit_volume(struct snd_kcontrol *kctl, int max)
+{
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
+
+	if (max <= 0 || max > mc->max - mc->min)
+		return -EINVAL;
+	mc->platform_max = max;
+	return 0;
+}
+
 /**
  * snd_soc_limit_volume - Set new limit to an existing volume control.
  *
@@ -645,21 +655,16 @@ int snd_soc_limit_volume(struct snd_soc_card *card,
 	const char *name, int max)
 {
 	struct snd_kcontrol *kctl;
-	int ret = -EINVAL;
 
-	/* Sanity check for name and max */
-	if (unlikely(!name || max <= 0))
+	/* Sanity check for name */
+	if (unlikely(!name))
 		return -EINVAL;
 
 	kctl = snd_soc_card_get_kcontrol(card, name);
-	if (kctl) {
-		struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
-		if (max <= mc->max - mc->min) {
-			mc->platform_max = max;
-			ret = 0;
-		}
-	}
-	return ret;
+	if (!kctl)
+		return -EINVAL;
+
+	return soc_limit_volume(kctl, max);
 }
 EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
 

From 1df053caa7163b86330ea1d287f8ccb7e23d9909 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 18:58:29 +0200
Subject: [PATCH 0261/1027] ASoC: ops: Enforce platform maximum on initial
 value
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Lower the volume if it is violating the platform maximum at its initial
value (i.e. at the time of the the 'snd_soc_limit_volume' call).

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/soc-ops.c | 30 +++++++++++++++++++++++++++++-
 1 file changed, 29 insertions(+), 1 deletion(-)

diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 5558fa24047e3e..bace7d33c39f6d 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -632,6 +632,33 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 }
 EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
 
+static int soc_clip_to_platform_max(struct snd_kcontrol *kctl)
+{
+	struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
+	struct snd_ctl_elem_value uctl;
+	int ret;
+
+	if (!mc->platform_max)
+		return 0;
+
+	ret = kctl->get(kctl, &uctl);
+	if (ret < 0)
+		return ret;
+
+	if (uctl.value.integer.value[0] > mc->platform_max)
+		uctl.value.integer.value[0] = mc->platform_max;
+
+	if (snd_soc_volsw_is_stereo(mc) && 
+	    uctl.value.integer.value[1] > mc->platform_max)
+		uctl.value.integer.value[1] = mc->platform_max;
+
+	ret = kctl->put(kctl, &uctl);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 static int soc_limit_volume(struct snd_kcontrol *kctl, int max)
 {
 	struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
@@ -639,7 +666,8 @@ static int soc_limit_volume(struct snd_kcontrol *kctl, int max)
 	if (max <= 0 || max > mc->max - mc->min)
 		return -EINVAL;
 	mc->platform_max = max;
-	return 0;
+
+	return soc_clip_to_platform_max(kctl);
 }
 
 /**

From ec20f6e29e5713e90ec0b2cf2fb96120cc079ddb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 19:15:54 +0200
Subject: [PATCH 0262/1027] ASoC: ops: Accept patterns in snd_soc_limit_volume
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

In snd_soc_limit_volume, instead of looking up a single control by name,
also understand wildcard-starting patterns like '* Amp Gain Volume' to
touch many controls at one.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/soc-ops.c | 51 +++++++++++++++++++++++++++++++++++++++------
 1 file changed, 45 insertions(+), 6 deletions(-)

diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index bace7d33c39f6d..6a9aaccfd707a7 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -632,6 +632,29 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 }
 EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
 
+static bool soc_control_matches(struct snd_kcontrol *kctl,
+	const char *pattern)
+{
+	const char *name = kctl->id.name;
+
+	if (pattern[0] == '*') {
+		int namelen;
+		int patternlen;
+
+		pattern++;
+		if (pattern[0] == ' ')
+			pattern++;
+
+		namelen = strlen(name);
+		patternlen = strlen(pattern);
+
+		if (namelen > patternlen)
+			name += namelen - patternlen;
+	}
+
+	return !strcmp(name, pattern);
+}
+
 static int soc_clip_to_platform_max(struct snd_kcontrol *kctl)
 {
 	struct soc_mixer_control *mc = (struct soc_mixer_control *)kctl->private_value;
@@ -671,28 +694,44 @@ static int soc_limit_volume(struct snd_kcontrol *kctl, int max)
 }
 
 /**
- * snd_soc_limit_volume - Set new limit to an existing volume control.
+ * snd_soc_limit_volume - Set new limit to existing volume controls
  *
  * @card: where to look for the control
- * @name: Name of the control
+ * @name: name pattern
  * @max: new maximum limit
+ * 
+ * Finds controls matching the given name (which can be either a name
+ * verbatim, or a pattern starting with the wildcard '*') and sets
+ * a platform volume limit on them.
  *
- * Return 0 for success, else error.
+ * Return number of matching controls on success, else error. At least
+ * one control needs to match the pattern.
  */
 int snd_soc_limit_volume(struct snd_soc_card *card,
 	const char *name, int max)
 {
 	struct snd_kcontrol *kctl;
+	int hits = 0;
+	int ret;
 
 	/* Sanity check for name */
 	if (unlikely(!name))
 		return -EINVAL;
 
-	kctl = snd_soc_card_get_kcontrol(card, name);
-	if (!kctl)
+	list_for_each_entry(kctl, &card->snd_card->controls, list) {
+		if (!soc_control_matches(kctl, name))
+			continue;
+
+		ret = soc_limit_volume(kctl, max);
+		if (ret < 0)
+			return ret;
+		hits++;
+	}
+
+	if (!hits)
 		return -EINVAL;
 
-	return soc_limit_volume(kctl, max);
+	return hits;
 }
 EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
 

From 10e39d7db3453fe7977d46b98e5f1f6b92ee841a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 19:24:35 +0200
Subject: [PATCH 0263/1027] ASoC: ops: Introduce 'snd_soc_deactivate_kctl'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The new function can be used to deactivate controls -- either a single
one or in bulk by pattern. It is something a machine driver may call in
fixup_controls.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 include/sound/soc.h |  2 ++
 sound/soc/soc-ops.c | 38 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index a8e66bbf932b33..e74a51e8791d87 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -609,6 +609,8 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 	struct snd_ctl_elem_value *ucontrol);
 int snd_soc_limit_volume(struct snd_soc_card *card,
 	const char *name, int max);
+int snd_soc_deactivate_kctl(struct snd_soc_card *card,
+	const char *name, int active);
 int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
 		       struct snd_ctl_elem_info *uinfo);
 int snd_soc_bytes_get(struct snd_kcontrol *kcontrol,
diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 6a9aaccfd707a7..97ab4c5380b8e7 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -735,6 +735,44 @@ int snd_soc_limit_volume(struct snd_soc_card *card,
 }
 EXPORT_SYMBOL_GPL(snd_soc_limit_volume);
 
+/**
+ * snd_soc_deactivate_kctl - Activate/deactive controls matching a pattern
+ *
+ * @card: where to look for the controls
+ * @name: name pattern
+ * @active: non-zero to activate, zero to deactivate
+ *
+ * Return number of matching controls on success, else error.
+ * No controls need to match.
+ */
+int snd_soc_deactivate_kctl(struct snd_soc_card *card,
+	const char *name, int active)
+{
+	struct snd_kcontrol *kctl;
+	int hits = 0;
+	int ret;
+
+	/* Sanity check for name */
+	if (unlikely(!name))
+		return -EINVAL;
+
+	list_for_each_entry(kctl, &card->snd_card->controls, list) {
+		if (!soc_control_matches(kctl, name))
+			continue;
+
+		ret = snd_ctl_activate_id(card->snd_card, &kctl->id, active);
+		if (ret < 0)
+			return ret;
+		hits++;
+	}
+
+	if (!hits)
+		return -EINVAL;
+
+	return hits;
+}
+EXPORT_SYMBOL_GPL(snd_soc_deactivate_kctl);
+
 int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
 		       struct snd_ctl_elem_info *uinfo)
 {

From 47fe9e411abdafff70ad2e8f45014ad664dbb3b9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 19:25:36 +0200
Subject: [PATCH 0264/1027] ASoC: ops: Introduce 'soc_set_enum_kctl'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The new function is to be used to set enumerated controls to desired
values -- either a single control or many controls in bulk by pattern.
It is something a machine driver may call in fixup_controls.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 include/sound/soc.h |  2 ++
 sound/soc/soc-ops.c | 70 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 72 insertions(+)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index e74a51e8791d87..e51a86b7f8c065 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -611,6 +611,8 @@ int snd_soc_limit_volume(struct snd_soc_card *card,
 	const char *name, int max);
 int snd_soc_deactivate_kctl(struct snd_soc_card *card,
 	const char *name, int active);
+int snd_soc_set_enum_kctl(struct snd_soc_card *card,
+	const char *name, const char *strval);
 int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
 		       struct snd_ctl_elem_info *uinfo);
 int snd_soc_bytes_get(struct snd_kcontrol *kcontrol,
diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 97ab4c5380b8e7..096918a73a3633 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -773,6 +773,76 @@ int snd_soc_deactivate_kctl(struct snd_soc_card *card,
 }
 EXPORT_SYMBOL_GPL(snd_soc_deactivate_kctl);
 
+static int soc_set_enum_kctl(struct snd_kcontrol *kctl, const char *strval)
+{
+	struct snd_ctl_elem_value value;
+	struct snd_ctl_elem_info info;
+	int sel, i, ret;
+
+	ret = kctl->info(kctl, &info);
+	if (ret < 0)
+		return ret;
+
+	if (info.type != SNDRV_CTL_ELEM_TYPE_ENUMERATED)
+		return -EINVAL;
+
+	for (sel = 0; sel < info.value.enumerated.items; sel++) {
+		info.value.enumerated.item = sel;
+		ret = kctl->info(kctl, &info);
+		if (ret < 0)
+			return ret;
+
+		if (!strcmp(strval, info.value.enumerated.name))
+			break;
+	}
+
+	if (sel == info.value.enumerated.items)
+		return -EINVAL;
+
+	for (i = 0; i < info.count; i++)
+		value.value.enumerated.item[i] = sel;
+
+	return kctl->put(kctl, &value);
+}
+
+/**
+ * snd_soc_set_enum_kctl - Set enumerated controls matching a pattern
+ *
+ * @card: where to look for the controls
+ * @name: name pattern
+ * @value: string value to set the controls to
+ *
+ * Return number of matching and set controls on success, else error.
+ * No controls need to match.
+ */
+int snd_soc_set_enum_kctl(struct snd_soc_card *card,
+	const char *name, const char *value)
+{
+	struct snd_kcontrol *kctl;
+	int hits = 0;
+	int ret;
+
+	/* Sanity check for name */
+	if (unlikely(!name))
+		return -EINVAL;
+
+	list_for_each_entry(kctl, &card->snd_card->controls, list) {
+		if (!soc_control_matches(kctl, name))
+			continue;
+
+		ret = soc_set_enum_kctl(kctl, value);
+		if (ret < 0)
+			return ret;
+		hits++;
+	}
+
+	if (!hits)
+		return -EINVAL;
+
+	return hits;
+}
+EXPORT_SYMBOL_GPL(snd_soc_set_enum_kctl);
+
 int snd_soc_bytes_info(struct snd_kcontrol *kcontrol,
 		       struct snd_ctl_elem_info *uinfo)
 {

From ac43733b4c0dda505d7841a947c21562c6e66b15 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 19 Aug 2022 21:09:35 +0200
Subject: [PATCH 0265/1027] ASoC: card: Let 'fixup_controls' return errors
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Let the 'fixup_controls' card method return error values which will roll
back the half-done binding of the card.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 include/sound/soc-card.h                  |  2 +-
 include/sound/soc.h                       |  2 +-
 sound/soc/mediatek/mt8188/mt8188-mt6359.c |  4 +++-
 sound/soc/soc-card.c                      | 12 +++++++++---
 sound/soc/soc-core.c                      |  5 ++++-
 5 files changed, 18 insertions(+), 7 deletions(-)

diff --git a/include/sound/soc-card.h b/include/sound/soc-card.h
index 1f4c39922d8250..22682332ec5e55 100644
--- a/include/sound/soc-card.h
+++ b/include/sound/soc-card.h
@@ -46,7 +46,7 @@ int snd_soc_card_resume_post(struct snd_soc_card *card);
 
 int snd_soc_card_probe(struct snd_soc_card *card);
 int snd_soc_card_late_probe(struct snd_soc_card *card);
-void snd_soc_card_fixup_controls(struct snd_soc_card *card);
+int snd_soc_card_fixup_controls(struct snd_soc_card *card);
 int snd_soc_card_remove(struct snd_soc_card *card);
 
 int snd_soc_card_set_bias_level(struct snd_soc_card *card,
diff --git a/include/sound/soc.h b/include/sound/soc.h
index e51a86b7f8c065..455b81a268f982 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -1032,7 +1032,7 @@ struct snd_soc_card {
 
 	int (*probe)(struct snd_soc_card *card);
 	int (*late_probe)(struct snd_soc_card *card);
-	void (*fixup_controls)(struct snd_soc_card *card);
+	int (*fixup_controls)(struct snd_soc_card *card);
 	int (*remove)(struct snd_soc_card *card);
 
 	/* the pre and post PM functions are used to do any PM work before and
diff --git a/sound/soc/mediatek/mt8188/mt8188-mt6359.c b/sound/soc/mediatek/mt8188/mt8188-mt6359.c
index 08ae962afeb929..f62e8f27d201dd 100644
--- a/sound/soc/mediatek/mt8188/mt8188-mt6359.c
+++ b/sound/soc/mediatek/mt8188/mt8188-mt6359.c
@@ -1224,7 +1224,7 @@ static struct snd_soc_dai_link mt8188_mt6359_dai_links[] = {
 	},
 };
 
-static void mt8188_fixup_controls(struct snd_soc_card *card)
+static int mt8188_fixup_controls(struct snd_soc_card *card)
 {
 	struct mtk_soc_card_data *soc_card_data = snd_soc_card_get_drvdata(card);
 	struct mtk_platform_card_data *card_data = soc_card_data->card_data;
@@ -1246,6 +1246,8 @@ static void mt8188_fixup_controls(struct snd_soc_card *card)
 		else
 			dev_warn(card->dev, "Cannot find ctl : Headphone Switch\n");
 	}
+
+	return 0;
 }
 
 static struct snd_soc_card mt8188_mt6359_soc_card = {
diff --git a/sound/soc/soc-card.c b/sound/soc/soc-card.c
index 0a3104d4ad2356..0e41a8bea32842 100644
--- a/sound/soc/soc-card.c
+++ b/sound/soc/soc-card.c
@@ -204,10 +204,16 @@ int snd_soc_card_late_probe(struct snd_soc_card *card)
 	return 0;
 }
 
-void snd_soc_card_fixup_controls(struct snd_soc_card *card)
+int snd_soc_card_fixup_controls(struct snd_soc_card *card)
 {
-	if (card->fixup_controls)
-		card->fixup_controls(card);
+	if (card->fixup_controls) {
+		int ret = card->fixup_controls(card);
+
+		if (ret < 0)
+			return soc_card_ret(card, ret);
+	}
+
+	return 0;
 }
 
 int snd_soc_card_remove(struct snd_soc_card *card)
diff --git a/sound/soc/soc-core.c b/sound/soc/soc-core.c
index 724fe1f033b550..dce71372fcb4a0 100644
--- a/sound/soc/soc-core.c
+++ b/sound/soc/soc-core.c
@@ -2295,7 +2295,10 @@ static int snd_soc_bind_card(struct snd_soc_card *card)
 		goto probe_end;
 
 	snd_soc_dapm_new_widgets(card);
-	snd_soc_card_fixup_controls(card);
+
+	ret = snd_soc_card_fixup_controls(card);
+	if (ret < 0)
+		goto probe_end;
 
 	ret = snd_card_register(card->snd_card);
 	if (ret < 0) {

From ff49d9851f56015444aac1d3ba7b379b73747b64 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 31 Mar 2022 01:16:48 +0200
Subject: [PATCH 0266/1027] dt-bindings: sound: Add Apple Macs sound
 peripherals
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add binding for Apple Silicon Macs' machine-level integration of sound
peripherals.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 .../bindings/sound/apple,macaudio.yaml        | 162 ++++++++++++++++++
 1 file changed, 162 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/sound/apple,macaudio.yaml

diff --git a/Documentation/devicetree/bindings/sound/apple,macaudio.yaml b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml
new file mode 100644
index 00000000000000..8fe22dec3015d6
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/apple,macaudio.yaml
@@ -0,0 +1,162 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/apple,macaudio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple Silicon Macs integrated sound peripherals
+
+description:
+  This binding represents the overall machine-level integration of sound
+  peripherals on 'Apple Silicon' machines by Apple.
+
+maintainers:
+  - Martin Povišer <povik+lin@cutebit.org>
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,j274-macaudio
+          - apple,j293-macaudio
+          - apple,j314-macaudio
+      - const: apple,macaudio
+
+  "#address-cells":
+    const: 1
+
+  "#size-cells":
+    const: 0
+
+  model:
+    description:
+      Model name for presentation to users
+    $ref: /schemas/types.yaml#/definitions/string
+
+patternProperties:
+  "^dai-link(@[0-9a-f]+)?$":
+    description: |
+      Node for each sound peripheral such as the speaker array, headphones jack,
+      or microphone.
+    type: object
+
+    additionalProperties: false
+
+    properties:
+      reg:
+        maxItems: 1
+
+      link-name:
+        description: |
+          Name for the peripheral, expecting 'Speaker' or 'Speakers' if this is
+          the speaker array.
+        $ref: /schemas/types.yaml#/definitions/string
+
+      cpu:
+        type: object
+
+        properties:
+          sound-dai:
+            description: |
+              DAI list with CPU-side I2S ports involved in this peripheral.
+            minItems: 1
+            maxItems: 2
+
+        required:
+          - sound-dai
+
+      codec:
+        type: object
+
+        properties:
+          sound-dai:
+            minItems: 1
+            maxItems: 8
+            description: |
+              DAI list with the CODEC-side DAIs connected to the above CPU-side
+              DAIs and involved in this sound peripheral.
+
+              The list is in left/right order if applicable. If there are more
+              than one CPU-side DAIs (there can be two), the CODECs must be
+              listed first those connected to the first CPU, then those
+              connected to the second.
+
+              In addition, on some machines with many speaker codecs, the CODECs
+              are listed in this fixed order:
+
+              J293: Left Front, Left Rear, Right Front, Right Rear
+              J314: Left Woofer 1, Left Tweeter, Left Woofer 2,
+                    Right Woofer 1, Right Tweeter, Right Woofer 2
+
+        required:
+          - sound-dai
+
+    required:
+      - reg
+      - cpu
+      - codec
+
+required:
+  - compatible
+  - model
+
+additionalProperties: false
+
+examples:
+  - |
+    mca: mca@9b600000 {
+      compatible = "apple,t6000-mca", "apple,mca";
+      reg = <0x9b600000 0x10000>,
+            <0x9b500000 0x20000>;
+
+      clocks = <&nco 0>, <&nco 1>, <&nco 2>, <&nco 3>;
+      power-domains = <&ps_audio_p>, <&ps_mca0>, <&ps_mca1>,
+                      <&ps_mca2>, <&ps_mca3>;
+      dmas = <&admac 0>, <&admac 1>, <&admac 2>, <&admac 3>,
+             <&admac 4>, <&admac 5>, <&admac 6>, <&admac 7>,
+             <&admac 8>, <&admac 9>, <&admac 10>, <&admac 11>,
+             <&admac 12>, <&admac 13>, <&admac 14>, <&admac 15>;
+      dma-names = "tx0a", "rx0a", "tx0b", "rx0b",
+                  "tx1a", "rx1a", "tx1b", "rx1b",
+                  "tx2a", "rx2a", "tx2b", "rx2b",
+                  "tx3a", "rx3a", "tx3b", "rx3b";
+
+      #sound-dai-cells = <1>;
+    };
+
+    sound {
+      compatible = "apple,j314-macaudio", "apple,macaudio";
+      model = "MacBook Pro J314 integrated audio";
+
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      dai-link@0 {
+        reg = <0>;
+        link-name = "Speakers";
+
+        cpu {
+          sound-dai = <&mca 0>, <&mca 1>;
+        };
+        codec {
+          sound-dai = <&speaker_left_woof1>,
+                      <&speaker_left_tweet>,
+                      <&speaker_left_woof2>,
+                      <&speaker_right_woof1>,
+                      <&speaker_right_tweet>,
+                      <&speaker_right_woof2>;
+        };
+      };
+
+      dai-link@1 {
+        reg = <1>;
+        link-name = "Headphones Jack";
+
+        cpu {
+          sound-dai = <&mca 2>;
+        };
+        codec {
+          sound-dai = <&jack_codec>;
+        };
+      };
+    };

From 5c35f3969b33f8ef71a7a03aa86ea3c285a7552f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sat, 19 Feb 2022 09:49:56 +0100
Subject: [PATCH 0267/1027] ASoC: apple: Add macaudio machine driver
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/Kconfig    |  17 +
 sound/soc/apple/Makefile   |   4 +
 sound/soc/apple/macaudio.c | 923 +++++++++++++++++++++++++++++++++++++
 3 files changed, 944 insertions(+)
 create mode 100644 sound/soc/apple/macaudio.c

diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index 793f7782e0d721..992e416108be5f 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -6,3 +6,20 @@ config SND_SOC_APPLE_MCA
 	help
 	  This option enables an ASoC platform driver for MCA peripherals found
 	  on Apple Silicon SoCs.
+
+config SND_SOC_APPLE_MACAUDIO
+	tristate "Sound support for Apple Silicon Macs"
+	depends on ARCH_APPLE || COMPILE_TEST
+	select SND_SOC_APPLE_MCA
+	select SND_SIMPLE_CARD_UTILS
+	select APPLE_ADMAC
+	select COMMON_CLK_APPLE_NCO
+	select SND_SOC_TAS2764
+	select SND_SOC_TAS2770
+	select SND_SOC_CS42L83
+	select SND_SOC_CS42L84
+	default ARCH_APPLE
+	help
+	  This option enables an ASoC machine-level driver for Apple Silicon Macs
+	  and it also enables the required SoC and codec drivers for overall
+	  sound support on these machines.
diff --git a/sound/soc/apple/Makefile b/sound/soc/apple/Makefile
index 1eb8fbef60c617..c78178f365ea65 100644
--- a/sound/soc/apple/Makefile
+++ b/sound/soc/apple/Makefile
@@ -1,3 +1,7 @@
 snd-soc-apple-mca-y	:= mca.o
 
 obj-$(CONFIG_SND_SOC_APPLE_MCA)	+= snd-soc-apple-mca.o
+
+snd-soc-macaudio-objs	:= macaudio.o
+
+obj-$(CONFIG_SND_SOC_APPLE_MACAUDIO)	+= snd-soc-macaudio.o
diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
new file mode 100644
index 00000000000000..1e6007bd5336bf
--- /dev/null
+++ b/sound/soc/apple/macaudio.c
@@ -0,0 +1,923 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * ASoC machine driver for Apple Silicon Macs
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/qcom/{sc7180.c|common.c}
+ * Copyright (c) 2018, Linaro Limited.
+ * Copyright (c) 2020, The Linux Foundation. All rights reserved.
+ *
+ *
+ * The platform driver has independent frontend and backend DAIs with the
+ * option of routing backends to any of the frontends. The platform
+ * driver configures the routing based on DPCM couplings in ASoC runtime
+ * structures, which in turn are determined from DAPM paths by ASoC. But the
+ * platform driver doesn't supply relevant DAPM paths and leaves that up for
+ * the machine driver to fill in. The filled-in virtual topology can be
+ * anything as long as any backend isn't connected to more than one frontend
+ * at any given time. (The limitation is due to the unsupported case of
+ * reparenting of live BEs.)
+ */
+
+#define DEBUG
+
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/simple_card_utils.h>
+#include <sound/soc.h>
+#include <sound/soc-jack.h>
+#include <uapi/linux/input-event-codes.h>
+
+#define DRIVER_NAME "snd-soc-macaudio"
+
+/*
+ * CPU side is bit and frame clock provider
+ * I2S has both clocks inverted
+ */
+#define MACAUDIO_DAI_FMT	(SND_SOC_DAIFMT_I2S | \
+				 SND_SOC_DAIFMT_CBC_CFC | \
+				 SND_SOC_DAIFMT_GATED | \
+				 SND_SOC_DAIFMT_IB_IF)
+#define MACAUDIO_JACK_MASK	(SND_JACK_HEADSET | SND_JACK_HEADPHONE)
+#define MACAUDIO_SLOTWIDTH	32
+
+struct macaudio_snd_data {
+	struct snd_soc_card card;
+	struct snd_soc_jack jack;
+	int jack_plugin_state;
+
+	bool has_speakers;
+
+	struct macaudio_link_props {
+		/* frontend props */
+		unsigned int bclk_ratio;
+
+		/* backend props */
+		bool is_speakers;
+		bool is_headphones;
+		unsigned int tdm_mask;
+	} *link_props;
+
+	unsigned int speaker_nchans_array[2];
+	struct snd_pcm_hw_constraint_list speaker_nchans_list;
+};
+
+static bool void_warranty;
+module_param(void_warranty, bool, 0644);
+MODULE_PARM_DESC(void_warranty, "Do not bail if safety is not assured");
+
+SND_SOC_DAILINK_DEFS(primary,
+	DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU
+	DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+	DAILINK_COMP_ARRAY(COMP_EMPTY())); // platform (filled at runtime)
+
+SND_SOC_DAILINK_DEFS(secondary,
+	DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-1")), // CPU
+	DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
+static struct snd_soc_dai_link macaudio_fe_links[] = {
+	{
+		.name = "Primary",
+		.stream_name = "Primary",
+		.dynamic = 1,
+		.dpcm_playback = 1,
+		.dpcm_capture = 1,
+		.dpcm_merged_rate = 1,
+		.dpcm_merged_chan = 1,
+		.dpcm_merged_format = 1,
+		.dai_fmt = MACAUDIO_DAI_FMT,
+		SND_SOC_DAILINK_REG(primary),
+	},
+	{
+		.name = "Secondary",
+		.stream_name = "Secondary",
+		.dynamic = 1,
+		.dpcm_playback = 1,
+		.dpcm_merged_rate = 1,
+		.dpcm_merged_chan = 1,
+		.dpcm_merged_format = 1,
+		.dai_fmt = MACAUDIO_DAI_FMT,
+		SND_SOC_DAILINK_REG(secondary),
+	},
+};
+
+static struct macaudio_link_props macaudio_fe_link_props[] = {
+	{
+		/*
+		 * Primary FE
+		 *
+		 * The bclk ratio at 64 for the primary frontend is important
+		 * to ensure that the headphones codec's idea of left and right
+		 * in a stereo stream over I2S fits in nicely with everyone else's.
+		 * (This is until the headphones codec's driver supports
+		 * set_tdm_slot.)
+		 *
+		 * The low bclk ratio precludes transmitting more than two
+		 * channels over I2S, but that's okay since there is the secondary
+		 * FE for speaker arrays anyway.
+		 */
+		.bclk_ratio = 64,
+	},
+	{
+		/*
+		 * Secondary FE
+		 *
+		 * Here we want frames plenty long to be able to drive all
+		 * those fancy speaker arrays.
+		 */
+		.bclk_ratio = 256,
+	}
+};
+
+static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target,
+			       struct snd_soc_dai_link *source)
+{
+	memcpy(target, source, sizeof(struct snd_soc_dai_link));
+
+	target->cpus = devm_kmemdup(dev, target->cpus,
+				sizeof(*target->cpus) * target->num_cpus,
+				GFP_KERNEL);
+	target->codecs = devm_kmemdup(dev, target->codecs,
+				sizeof(*target->codecs) * target->num_codecs,
+				GFP_KERNEL);
+	target->platforms = devm_kmemdup(dev, target->platforms,
+				sizeof(*target->platforms) * target->num_platforms,
+				GFP_KERNEL);
+
+	if (!target->cpus || !target->codecs || !target->platforms)
+		return -ENOMEM;
+
+	return 0;
+}
+
+static int macaudio_parse_of_component(struct device_node *node, int index,
+				struct snd_soc_dai_link_component *comp)
+{
+	struct of_phandle_args args;
+	int ret;
+
+	ret = of_parse_phandle_with_args(node, "sound-dai", "#sound-dai-cells",
+						index, &args);
+	if (ret)
+		return ret;
+	comp->of_node = args.np;
+	return snd_soc_get_dai_name(&args, &comp->dai_name);
+}
+
+/*
+ * Parse one DPCM backend from the devicetree. This means taking one
+ * of the CPU DAIs and combining it with one or more CODEC DAIs.
+ */
+static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma,
+				struct snd_soc_dai_link *link,
+				int be_index, int ncodecs_per_be,
+				struct device_node *cpu,
+				struct device_node *codec)
+{
+	struct snd_soc_dai_link_component *comp;
+	struct device *dev = ma->card.dev;
+	int codec_base = be_index * ncodecs_per_be;
+	int ret, i;
+
+	link->no_pcm = 1;
+	link->dpcm_playback = 1;
+	link->dpcm_capture = 1;
+
+	link->dai_fmt = MACAUDIO_DAI_FMT;
+
+	link->num_codecs = ncodecs_per_be;
+	link->codecs = devm_kcalloc(dev, ncodecs_per_be,
+				    sizeof(*comp), GFP_KERNEL);
+	link->num_cpus = 1;
+	link->cpus = devm_kzalloc(dev, sizeof(*comp), GFP_KERNEL);
+
+	if (!link->codecs || !link->cpus)
+		return -ENOMEM;
+
+	link->num_platforms = 0;
+
+	for_each_link_codecs(link, i, comp) {
+		ret = macaudio_parse_of_component(codec, codec_base + i, comp);
+		if (ret)
+			return ret;
+	}
+
+	ret = macaudio_parse_of_component(cpu, be_index, link->cpus);
+	if (ret)
+		return ret;
+
+	link->name = link->cpus[0].dai_name;
+
+	return 0;
+}
+
+static int macaudio_parse_of(struct macaudio_snd_data *ma)
+{
+	struct device_node *codec = NULL;
+	struct device_node *cpu = NULL;
+	struct device_node *np = NULL;
+	struct device_node *platform = NULL;
+	struct snd_soc_dai_link *link = NULL;
+	struct snd_soc_card *card = &ma->card;
+	struct device *dev = card->dev;
+	struct macaudio_link_props *link_props;
+	int ret, num_links, i;
+
+	ret = snd_soc_of_parse_card_name(card, "model");
+	if (ret) {
+		dev_err(dev, "Error parsing card name: %d\n", ret);
+		return ret;
+	}
+
+	/* Populate links, start with the fixed number of FE links */
+	num_links = ARRAY_SIZE(macaudio_fe_links);
+
+	/* Now add together the (dynamic) number of BE links */
+	for_each_available_child_of_node(dev->of_node, np) {
+		int num_cpus;
+
+		cpu = of_get_child_by_name(np, "cpu");
+		if (!cpu) {
+			dev_err(dev, "missing CPU DAI node at %pOF\n", np);
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		num_cpus = of_count_phandle_with_args(cpu, "sound-dai",
+						"#sound-dai-cells");
+
+		if (num_cpus <= 0) {
+			dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
+			ret = -EINVAL;
+			goto err_free;
+		}
+		of_node_put(cpu);
+		cpu = NULL;
+
+		/* Each CPU specified counts as one BE link */
+		num_links += num_cpus;
+	}
+
+	/* Allocate the DAI link array */
+	card->dai_link = devm_kcalloc(dev, num_links, sizeof(*link), GFP_KERNEL);
+	ma->link_props = devm_kcalloc(dev, num_links, sizeof(*ma->link_props), GFP_KERNEL);
+	if (!card->dai_link || !ma->link_props)
+		return -ENOMEM;
+
+	card->num_links = num_links;
+	link = card->dai_link;
+	link_props = ma->link_props;
+
+	for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) {
+		ret = macaudio_copy_link(dev, link, &macaudio_fe_links[i]);
+		if (ret)
+			goto err_free;
+
+		memcpy(link_props, &macaudio_fe_link_props[i], sizeof(struct macaudio_link_props));
+		link++; link_props++;
+	}
+
+	for (i = 0; i < num_links; i++)
+		card->dai_link[i].id = i;
+
+	/* Fill in the BEs */
+	for_each_available_child_of_node(dev->of_node, np) {
+		const char *link_name;
+		bool speakers;
+		int be_index, num_codecs, num_bes, ncodecs_per_cpu, nchannels;
+		unsigned int left_mask, right_mask;
+
+		ret = of_property_read_string(np, "link-name", &link_name);
+		if (ret) {
+			dev_err(card->dev, "missing link name\n");
+			goto err_free;
+		}
+
+		speakers = !strcmp(link_name, "Speaker")
+			   || !strcmp(link_name, "Speakers");
+		if (speakers)
+			ma->has_speakers = 1;
+
+		cpu = of_get_child_by_name(np, "cpu");
+		codec = of_get_child_by_name(np, "codec");
+
+		if (!codec || !cpu) {
+			dev_err(dev, "missing DAI specifications for '%s'\n", link_name);
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		num_bes = of_count_phandle_with_args(cpu, "sound-dai",
+						     "#sound-dai-cells");
+		if (num_bes <= 0) {
+			dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		num_codecs = of_count_phandle_with_args(codec, "sound-dai",
+							"#sound-dai-cells");
+		if (num_codecs <= 0) {
+			dev_err(card->dev, "missing sound-dai property at %pOF\n", codec);
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		if (num_codecs % num_bes != 0) {
+			dev_err(card->dev, "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n",
+				num_codecs, num_bes, np);
+			ret = -EINVAL;
+			goto err_free;
+		}
+
+		/*
+		 * Now parse the cpu/codec lists into a number of DPCM backend links.
+		 * In each link there will be one DAI from the cpu list paired with
+		 * an evenly distributed number of DAIs from the codec list. (As is
+		 * the binding semantics.)
+		 */
+		ncodecs_per_cpu = num_codecs / num_bes;
+		nchannels = num_codecs * (speakers ? 1 : 2);
+
+		/*
+		 * If there is a single speaker, assign two channels to it, because
+		 * it can do downmix.
+		 */
+		if (nchannels < 2)
+			nchannels = 2;
+
+		left_mask = 0;
+		for (i = 0; i < nchannels; i += 2)
+			left_mask = left_mask << 2 | 1;
+		right_mask = left_mask << 1;
+
+		for (be_index = 0; be_index < num_bes; be_index++) {
+			ret = macaudio_parse_of_be_dai_link(ma, link, be_index,
+							    ncodecs_per_cpu, cpu, codec);
+			if (ret)
+				goto err_free;
+
+			link_props->is_speakers = speakers;
+			link_props->is_headphones = !speakers;
+
+			if (num_bes == 2)
+				/* This sound peripheral is split between left and right BE */
+				link_props->tdm_mask = be_index ? right_mask : left_mask;
+			else
+				/* One BE covers all of the peripheral */
+				link_props->tdm_mask = left_mask | right_mask;
+
+			/* Steal platform OF reference for use in FE links later */
+			platform = link->cpus->of_node;
+
+			link++; link_props++;
+		}
+
+		of_node_put(codec);
+		of_node_put(cpu);
+		cpu = codec = NULL;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++)
+		card->dai_link[i].platforms->of_node = platform;
+
+	return 0;
+
+err_free:
+	of_node_put(codec);
+	of_node_put(cpu);
+	of_node_put(np);
+
+	if (!card->dai_link)
+		return ret;
+
+	for (i = 0; i < num_links; i++) {
+		/*
+		 * TODO: If we don't go through this path are the references
+		 * freed inside ASoC?
+		 */
+		snd_soc_of_put_dai_link_codecs(&card->dai_link[i]);
+		snd_soc_of_put_dai_link_cpus(&card->dai_link[i]);
+	}
+
+	return ret;
+}
+
+static int macaudio_get_runtime_bclk_ratio(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct snd_soc_dpcm *dpcm;
+
+	/*
+	 * If this is a FE, look it up in link_props directly.
+	 * If this is a BE, look it up in the respective FE.
+	 */
+	if (!rtd->dai_link->no_pcm)
+		return ma->link_props[rtd->dai_link->id].bclk_ratio;
+
+	for_each_dpcm_fe(rtd, substream->stream, dpcm) {
+		int fe_id = dpcm->fe->dai_link->id;
+
+		return ma->link_props[fe_id].bclk_ratio;
+	}
+
+	return 0;
+}
+
+static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+	int i;
+
+	if (bclk_ratio) {
+		struct snd_soc_dai *dai;
+		int mclk = params_rate(params) * bclk_ratio;
+
+		for_each_rtd_codec_dais(rtd, i, dai) {
+			snd_soc_dai_set_sysclk(dai, 0, mclk, SND_SOC_CLOCK_IN);
+			snd_soc_dai_set_bclk_ratio(dai, bclk_ratio);
+		}
+
+		snd_soc_dai_set_sysclk(cpu_dai, 0, mclk, SND_SOC_CLOCK_OUT);
+		snd_soc_dai_set_bclk_ratio(cpu_dai, bclk_ratio);
+	}
+
+	return 0;
+}
+
+static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_soc_dai *dai;
+	int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
+	int i;
+
+	if (bclk_ratio) {
+		for_each_rtd_codec_dais(rtd, i, dai)
+			snd_soc_dai_set_sysclk(dai, 0, 0, SND_SOC_CLOCK_IN);
+
+		snd_soc_dai_set_sysclk(cpu_dai, 0, 0, SND_SOC_CLOCK_OUT);
+	}
+}
+
+static const struct snd_soc_ops macaudio_fe_ops = {
+	.shutdown	= macaudio_dpcm_shutdown,
+	.hw_params	= macaudio_dpcm_hw_params,
+};
+
+static const struct snd_soc_ops macaudio_be_ops = {
+	.shutdown	= macaudio_dpcm_shutdown,
+	.hw_params	= macaudio_dpcm_hw_params,
+};
+
+static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+	struct snd_soc_dai *dai;
+	unsigned int mask;
+	int nslots, ret, i;
+
+	if (!props->tdm_mask)
+		return 0;
+
+	mask = props->tdm_mask;
+	nslots = __fls(mask) + 1;
+
+	if (rtd->dai_link->num_codecs == 1) {
+		ret = snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_codec(rtd, 0), mask,
+					       0, nslots, MACAUDIO_SLOTWIDTH);
+
+		/*
+		 * Headphones get a pass on -ENOTSUPP (see the comment
+		 * around bclk_ratio value for primary FE).
+		 */
+		if (ret == -ENOTSUPP && props->is_headphones)
+			return 0;
+
+		return ret;
+	}
+
+	for_each_rtd_codec_dais(rtd, i, dai) {
+		int slot = __ffs(mask);
+
+		mask &= ~(1 << slot);
+		ret = snd_soc_dai_set_tdm_slot(dai, 1 << slot, 0, nslots,
+					       MACAUDIO_SLOTWIDTH);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int macaudio_be_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+	struct snd_soc_dai *dai;
+	int i, ret;
+
+	ret = macaudio_be_assign_tdm(rtd);
+	if (ret < 0)
+		return ret;
+
+	if (props->is_headphones) {
+		for_each_rtd_codec_dais(rtd, i, dai)
+			snd_soc_component_set_jack(dai->component, &ma->jack, NULL);
+	}
+
+	return 0;
+}
+
+static void macaudio_be_exit(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+	struct snd_soc_dai *dai;
+	int i;
+
+	if (props->is_headphones) {
+		for_each_rtd_codec_dais(rtd, i, dai)
+			snd_soc_component_set_jack(dai->component, NULL, NULL);
+	}
+}
+
+static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd)
+{
+	struct snd_soc_card *card = rtd->card;
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+	int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH;
+
+	return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1,
+					(1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH);
+}
+
+static struct snd_soc_jack_pin macaudio_jack_pins[] = {
+	{
+		.pin = "Headphone",
+		.mask = SND_JACK_HEADPHONE,
+	},
+	{
+		.pin = "Headset Mic",
+		.mask = SND_JACK_MICROPHONE,
+	},
+	{
+		.pin = "Speaker",
+		.mask = SND_JACK_HEADPHONE,
+		.invert = 1,
+	},
+};
+
+static int macaudio_probe(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	int ret;
+
+	dev_dbg(card->dev, "%s!\n", __func__);
+
+	ret = snd_soc_card_jack_new_pins(card, "Headphone Jack",
+			SND_JACK_HEADSET | SND_JACK_HEADPHONE,
+			&ma->jack, macaudio_jack_pins,
+			ARRAY_SIZE(macaudio_jack_pins));
+	if (ret < 0) {
+		dev_err(card->dev, "jack creation failed: %d\n", ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_soc_dai *dai,
+					  bool is_speakers)
+{
+	struct snd_soc_dapm_route routes[2];
+	struct snd_soc_dapm_route *r;
+	int nroutes = 0;
+	int ret;
+
+	memset(routes, 0, sizeof(routes));
+
+	dev_dbg(card->dev, "adding routes for '%s'\n", dai->name);
+
+	r = &routes[nroutes++];
+	if (is_speakers)
+		r->source = "Speaker Playback";
+	else
+		r->source = "Headphone Playback";
+	r->sink = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget->name;
+
+	/* If headphone jack, add capture path */
+	if (!is_speakers) {
+		r = &routes[nroutes++];
+		r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
+		r->sink = "Headphone Capture";
+	}
+
+	ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
+	if (ret)
+		dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+			dai->name);
+	return ret;
+}
+
+static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component,
+				   bool is_speakers)
+{
+	struct snd_soc_dapm_route routes[1];
+	struct snd_soc_dapm_route *r;
+	int nroutes = 0;
+	char buf[32];
+	int ret;
+
+	memset(routes, 0, sizeof(routes));
+
+	/* Connect the far ends of CODECs to pins */
+	if (is_speakers) {
+		r = &routes[nroutes++];
+		r->source = "OUT";
+		if (component->name_prefix) {
+			snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix);
+			r->source = buf;
+		}
+		r->sink = "Speaker Pin Demux";
+	} else {
+		r = &routes[nroutes++];
+		r->source = "Jack HP";
+		r->sink = "Headphone";
+	}
+
+
+	ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
+	if (ret)
+		dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
+			component->name);
+	return ret;
+}
+
+static int macaudio_late_probe(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dai *dai;
+	int ret, i;
+
+	/* Add the dynamic DAPM routes */
+	for_each_card_rtds(card, rtd) {
+		struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+		if (!rtd->dai_link->no_pcm)
+			continue;
+
+		for_each_rtd_cpu_dais(rtd, i, dai) {
+			ret = macaudio_add_backend_dai_route(card, dai, props->is_speakers);
+
+			if (ret)
+				return ret;
+		}
+
+		for_each_rtd_codec_dais(rtd, i, dai) {
+			ret = macaudio_add_pin_routes(card, dai->component,
+						      props->is_speakers);
+
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+#define CHECK(call, pattern, value) \
+	{ \
+		int ret = call(card, pattern, value); \
+		if (ret < 1 && !void_warranty) { \
+			dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \
+			return ret; \
+		} \
+		dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \
+	}
+
+
+static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers) {
+		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+	}
+
+	return 0;	
+}
+
+static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers) {
+		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
+		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
+		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
+		CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Freq", "800 Hz");
+		CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Freq", 0);
+
+		/*
+		 * The speaker amps suffer from spurious overcurrent
+		 * events on their unmute, so enable autoretry.
+		 */
+		CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry");
+		CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0);
+
+		/*
+		 * Since we don't set the right slots yet to avoid
+		 * driver conflict on the I2S bus sending ISENSE/VSENSE
+		 * samples from the codecs back to us, disable the
+		 * controls.
+		 */
+		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
+		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+	}
+
+	return 0;
+}
+
+static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers && !void_warranty) {
+		dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+#undef CHECK
+
+static const char * const macaudio_spk_mux_texts[] = {
+	"Primary",
+	"Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_spk_mux_enum, macaudio_spk_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_spk_mux =
+	SOC_DAPM_ENUM("Speaker Playback Mux", macaudio_spk_mux_enum);
+
+static const char * const macaudio_hp_mux_texts[] = {
+	"Primary",
+	"Secondary"
+};
+
+SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts);
+
+static const struct snd_kcontrol_new macaudio_hp_mux =
+	SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum);
+
+static const char *macaudio_spk_demux_texts[] = {
+	"Inverse Jack", "Static",
+};
+
+static SOC_ENUM_SINGLE_DECL(macaudio_spk_demux_enum,
+			    SND_SOC_NOPM, 0, macaudio_spk_demux_texts);
+
+static const struct snd_kcontrol_new macaudio_spk_demux =
+	SOC_DAPM_ENUM("Speaker Pin Demux", macaudio_spk_demux_enum);
+
+static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
+	SND_SOC_DAPM_SPK("Speaker", NULL),
+	SND_SOC_DAPM_SPK("Speaker (Static)", NULL),
+	SND_SOC_DAPM_HP("Headphone", NULL),
+	SND_SOC_DAPM_MIC("Headset Mic", NULL),
+
+	SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux),
+	SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux),
+	SND_SOC_DAPM_DEMUX("Speaker Pin Demux", SND_SOC_NOPM, 0, 0, &macaudio_spk_demux),
+
+	SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
+
+	SND_SOC_DAPM_AIF_IN("Headphone Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+};
+
+static const struct snd_kcontrol_new macaudio_controls[] = {
+	SOC_DAPM_PIN_SWITCH("Speaker"),
+	SOC_DAPM_PIN_SWITCH("Speaker (Static)"),
+	SOC_DAPM_PIN_SWITCH("Headphone"),
+	SOC_DAPM_PIN_SWITCH("Headset Mic"),
+};
+
+static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
+	/* Playback paths */
+	{ "Speaker Playback Mux", "Primary", "PCM0 TX" },
+	{ "Speaker Playback Mux", "Secondary", "PCM1 TX" },
+	{ "Speaker Playback", NULL, "Speaker Playback Mux"},
+
+	{ "Headphone Playback Mux", "Primary", "PCM0 TX" },
+	{ "Headphone Playback Mux", "Secondary", "PCM1 TX" },
+	{ "Headphone Playback", NULL, "Headphone Playback Mux"},
+	/*
+	 * Additional paths (to specific I2S ports) are added dynamically.
+	 */
+
+	{ "Speaker", "Inverse Jack", "Speaker Pin Demux" },
+	{ "Speaker (Static)", "Static", "Speaker Pin Demux" },
+
+	/* Capture paths */
+	{ "PCM0 RX", NULL, "Headphone Capture" },
+};
+
+static const struct of_device_id macaudio_snd_device_id[]  = {
+	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
+	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
+	{ .compatible = "apple,macaudio"},
+	{ }
+};
+MODULE_DEVICE_TABLE(of, macaudio_snd_device_id);
+
+static int macaudio_snd_platform_probe(struct platform_device *pdev)
+{
+	struct snd_soc_card *card;
+	struct macaudio_snd_data *data;
+	struct device *dev = &pdev->dev;
+	struct snd_soc_dai_link *link;
+	const struct of_device_id *of_id;
+	int ret;
+	int i;
+
+	of_id = of_match_device(macaudio_snd_device_id, dev);
+	if (!of_id)
+		return -EINVAL;
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+	card = &data->card;
+	snd_soc_card_set_drvdata(card, data);
+
+	card->owner = THIS_MODULE;
+	card->driver_name = DRIVER_NAME;
+	card->dev = dev;
+	card->dapm_widgets = macaudio_snd_widgets;
+	card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets);
+	card->dapm_routes = macaudio_dapm_routes;
+	card->num_dapm_routes = ARRAY_SIZE(macaudio_dapm_routes);
+	card->controls = macaudio_controls;
+	card->num_controls = ARRAY_SIZE(macaudio_controls);
+	card->probe = macaudio_probe;
+	card->late_probe = macaudio_late_probe;
+	card->component_chaining = true;
+	card->fully_routed = true;
+
+	if (of_id->data)
+		card->fixup_controls = of_id->data;
+	else
+		card->fixup_controls = macaudio_fallback_fixup_controls;
+
+	ret = macaudio_parse_of(data);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "failed OF parsing\n");
+
+	for_each_card_prelinks(card, i, link) {
+		if (link->no_pcm) {
+			link->ops = &macaudio_be_ops;
+			link->init = macaudio_be_init;
+			link->exit = macaudio_be_exit;
+		} else {
+			link->ops = &macaudio_fe_ops;
+			link->init = macaudio_fe_init;
+		}
+	}
+
+	return devm_snd_soc_register_card(dev, card);
+}
+
+static struct platform_driver macaudio_snd_driver = {
+	.probe = macaudio_snd_platform_probe,
+	.driver = {
+		.name = DRIVER_NAME,
+		.of_match_table = macaudio_snd_device_id,
+		.pm = &snd_soc_pm_ops,
+	},
+};
+module_platform_driver(macaudio_snd_driver);
+
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_DESCRIPTION("Apple Silicon Macs machine-level sound driver");
+MODULE_LICENSE("GPL");

From 411ad5abebee810ea1b63dcecf36d710904e0e3c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Wed, 3 Aug 2022 17:25:43 +0200
Subject: [PATCH 0268/1027] ASoC: cs42l42: Fix typo
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l42.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c
index 6400ac875e6f6c..398a69e5148505 100644
--- a/sound/soc/codecs/cs42l42.c
+++ b/sound/soc/codecs/cs42l42.c
@@ -1676,7 +1676,7 @@ irqreturn_t cs42l42_irq_thread(int irq, void *data)
 		return IRQ_NONE;
 	}
 
-	/* Read sticky registers to clear interurpt */
+	/* Read sticky registers to clear interrupt */
 	for (i = 0; i < ARRAY_SIZE(stickies); i++) {
 		regmap_read(cs42l42->regmap, irq_params_table[i].status_addr,
 				&(stickies[i]));

From b82a314ba30a651c214907be685004397ef15274 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 6 Sep 2022 14:51:29 +0200
Subject: [PATCH 0269/1027] ASoC: cs42l42: Do not advertise sample bit symmetry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l42.c | 1 -
 1 file changed, 1 deletion(-)

diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c
index 398a69e5148505..6ae740a21106d6 100644
--- a/sound/soc/codecs/cs42l42.c
+++ b/sound/soc/codecs/cs42l42.c
@@ -1148,7 +1148,6 @@ struct snd_soc_dai_driver cs42l42_dai = {
 			.formats = CS42L42_FORMATS,
 		},
 		.symmetric_rate = 1,
-		.symmetric_sample_bits = 1,
 		.ops = &cs42l42_ops,
 };
 EXPORT_SYMBOL_NS_GPL(cs42l42_dai, SND_SOC_CS42L42_CORE);

From e06da1020a6afdaf894116f501305a017f266ce3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sat, 20 Aug 2022 20:50:24 +0200
Subject: [PATCH 0270/1027] dt-bindings: sound: Add CS42L84 codec
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

CS42L84 is a headphone jack codec made by Cirrus Logic and seen in Apple
computer models starting with 2021 Macbook Pros. It is not a publicly
documented part. To a degree the part is similar to the public CS42L42.
(The L84 superseded L83 seen in earlier Apple models, and the L83 was
pretty much the same as L42.)

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cirrus,cs42l84.yaml | 60 ++++++++++++++++++++++++++++
 1 file changed, 60 insertions(+)
 create mode 100644 sound/soc/codecs/cirrus,cs42l84.yaml

diff --git a/sound/soc/codecs/cirrus,cs42l84.yaml b/sound/soc/codecs/cirrus,cs42l84.yaml
new file mode 100644
index 00000000000000..12bc6dbeeddfac
--- /dev/null
+++ b/sound/soc/codecs/cirrus,cs42l84.yaml
@@ -0,0 +1,60 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/cirrus,cs42l84.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Cirrus Logic CS42L84 audio CODEC
+
+maintainers:
+  - povik+lin@cutebit.org
+
+description:
+  The CS42L84 is a headphone jack codec made by Cirrus Logic and embedded
+  in personal computers sold by Apple. It was first seen in 2021 Macbook Pro
+  models.
+
+  It has stereo DAC for playback, mono ADC for capture, and is somewhat
+  similar to CS42L42 but with a different regmap.
+
+properties:
+  compatible:
+    enum:
+      - cirrus,cs42l84
+
+  reg:
+    description:
+      I2C address of the device
+    maxItems: 1
+
+  reset-gpios:
+    description:
+      Reset pin, asserted to reset the device, deasserted to bring
+      the device online
+    maxItems: 1
+
+  interrupts:
+    description:
+      Interrupt for the IRQ output line of the device
+    maxItems: 1
+
+required:
+  - compatible
+  - reg
+
+additionalProperties: false
+
+examples:
+  - |
+    i2c {
+      #address-cells = <1>;
+      #size-cells = <0>;
+
+      jack_codec: codec@4b {
+          compatible = "cirrus,cs42l84";
+          reg = <0x4b>;
+          reset-gpios = <&pinctrl_nub 4 GPIO_ACTIVE_LOW>;
+          interrupts-extended = <&pinctrl_ap 180 IRQ_TYPE_LEVEL_LOW>;
+          #sound-dai-cells = <0>;
+      };
+    };

From 9ee029babce6b9f263a0cf514395750176e4ff48 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 29 Jun 2022 20:32:14 +0200
Subject: [PATCH 0271/1027] wip: ASoC: cs42l84: Start new codec driver
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/Kconfig   |    5 +
 sound/soc/codecs/Makefile  |    2 +
 sound/soc/codecs/cs42l84.c | 1045 ++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/cs42l84.h |  214 ++++++++
 4 files changed, 1266 insertions(+)
 create mode 100644 sound/soc/codecs/cs42l84.c
 create mode 100644 sound/soc/codecs/cs42l84.h

diff --git a/sound/soc/codecs/Kconfig b/sound/soc/codecs/Kconfig
index b5e6d0a986c8e9..a09275e905b8b1 100644
--- a/sound/soc/codecs/Kconfig
+++ b/sound/soc/codecs/Kconfig
@@ -85,6 +85,7 @@ config SND_SOC_ALL_CODECS
 	imply SND_SOC_CS42L52
 	imply SND_SOC_CS42L56
 	imply SND_SOC_CS42L73
+	imply SND_SOC_CS42L84
 	imply SND_SOC_CS4234
 	imply SND_SOC_CS4265
 	imply SND_SOC_CS4270
@@ -925,6 +926,10 @@ config SND_SOC_CS42L83
 	select REGMAP_I2C
 	select SND_SOC_CS42L42_CORE
 
+config SND_SOC_CS42L84
+	tristate "Cirrus Logic CS42L84 CODEC"
+	depends on I2C
+
 config SND_SOC_CS4234
 	tristate "Cirrus Logic CS4234 CODEC"
 	depends on I2C
diff --git a/sound/soc/codecs/Makefile b/sound/soc/codecs/Makefile
index 622e360f00866b..2aaeae0f902296 100644
--- a/sound/soc/codecs/Makefile
+++ b/sound/soc/codecs/Makefile
@@ -91,6 +91,7 @@ snd-soc-cs42l52-y := cs42l52.o
 snd-soc-cs42l56-y := cs42l56.o
 snd-soc-cs42l73-y := cs42l73.o
 snd-soc-cs42l83-i2c-y := cs42l83-i2c.o
+snd-soc-cs42l84-objs := cs42l84.o
 snd-soc-cs4234-y := cs4234.o
 snd-soc-cs4265-y := cs4265.o
 snd-soc-cs4270-y := cs4270.o
@@ -497,6 +498,7 @@ obj-$(CONFIG_SND_SOC_CS42L52)	+= snd-soc-cs42l52.o
 obj-$(CONFIG_SND_SOC_CS42L56)	+= snd-soc-cs42l56.o
 obj-$(CONFIG_SND_SOC_CS42L73)	+= snd-soc-cs42l73.o
 obj-$(CONFIG_SND_SOC_CS42L83)	+= snd-soc-cs42l83-i2c.o
+obj-$(CONFIG_SND_SOC_CS42L84)	+= snd-soc-cs42l84.o
 obj-$(CONFIG_SND_SOC_CS4234)	+= snd-soc-cs4234.o
 obj-$(CONFIG_SND_SOC_CS4265)	+= snd-soc-cs4265.o
 obj-$(CONFIG_SND_SOC_CS4270)	+= snd-soc-cs4270.o
diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
new file mode 100644
index 00000000000000..38dfee80ed9f73
--- /dev/null
+++ b/sound/soc/codecs/cs42l84.c
@@ -0,0 +1,1045 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * cs42l84.c -- CS42L84 ALSA SoC audio driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/codecs/cs42l42{.c,.h}
+ *   Copyright 2016 Cirrus Logic, Inc.
+ */
+
+#define DEBUG
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/version.h>
+#include <linux/kernel.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/i2c.h>
+#include <linux/gpio.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+#include <linux/platform_device.h>
+#include <linux/property.h>
+#include <linux/regulator/consumer.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of_device.h>
+#include <sound/core.h>
+#include <sound/jack.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+#include <sound/initval.h>
+#include <sound/tlv.h>
+
+#include "cs42l84.h"
+#include "cirrus_legacy.h"
+
+struct cs42l84_private {
+	struct regmap *regmap;
+	struct device *dev;
+	struct gpio_desc *reset_gpio;
+	struct snd_soc_jack *jack;
+	struct mutex irq_lock;
+	u8 plug_state;
+	int pll_config;
+	int bclk;
+	u8 pll_mclk_f;
+	u32 srate;
+	u8 stream_use;
+	int hs_type;
+};
+
+static const struct regmap_config cs42l84_regmap = {
+	.reg_bits = 16,
+	.val_bits = 8,
+
+	.max_register = 0xffff,
+	.cache_type = REGCACHE_NONE,
+
+	.use_single_read = true,
+	.use_single_write = true,
+};
+
+static int cs42l84_put_dac_vol(struct snd_kcontrol *kctl,
+			struct snd_ctl_elem_value *val)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
+	unsigned int vola, volb;
+	int ret, ret2;
+
+	vola = val->value.integer.value[0];
+	volb = val->value.integer.value[1];
+
+	ret = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,
+					    CS42L84_FRZ_CTL_ENGAGE,
+					    CS42L84_FRZ_CTL_ENGAGE);
+	if (ret < 0)
+		goto bail;
+
+	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_LSB,
+					    0xff, vola & 0xff);
+	if (ret < 0)
+		goto bail;
+	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_MSB,
+					    0xff, (vola >> 8) & 0x01);
+	if (ret < 0)
+		goto bail;
+	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_LSB,
+					    0xff, volb & 0xff);
+	if (ret < 0)
+		goto bail;
+	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_MSB,
+					    0xff, (volb >> 8) & 0x01);
+	if (ret < 0)
+		goto bail;
+
+bail:
+	ret2 = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,
+					     CS42L84_FRZ_CTL_ENGAGE, 0);
+	if (ret2 < 0 && ret >= 0)
+		ret = ret2;
+
+	return ret;
+}
+
+static int cs42l84_get_dac_vol(struct snd_kcontrol *kctl,
+			struct snd_ctl_elem_value *val)
+{
+	struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
+	unsigned int vola, volb;
+	int ret;
+
+	ret = snd_soc_component_read(component, CS42L84_DAC_CHA_VOL_LSB);
+	if (ret < 0)
+		return ret;
+	vola = ret;
+
+	ret = snd_soc_component_read(component, CS42L84_DAC_CHA_VOL_MSB);
+	if (ret < 0)
+		return ret;
+	vola |= (ret & 1) << 8;
+
+	ret = snd_soc_component_read(component, CS42L84_DAC_CHB_VOL_LSB);
+	if (ret < 0)
+		return ret;
+	volb = ret;
+
+	ret = snd_soc_component_read(component, CS42L84_DAC_CHB_VOL_MSB);
+	if (ret < 0)
+		return ret;
+	volb |= (ret & 1) << 8;
+
+	val->value.integer.value[0] = vola;
+	val->value.integer.value[1] = volb;
+
+	return 0;
+}
+
+/* TODO */
+static const DECLARE_TLV_DB_SCALE(cs42l84_dac_tlv, -25600, 50, 1);
+
+static const struct snd_kcontrol_new cs42l84_snd_controls[] = {
+	SOC_DOUBLE_R_EXT_TLV("DAC Playback Volume", CS42L84_DAC_CHA_VOL_LSB,
+			CS42L84_DAC_CHB_VOL_LSB, 0, 511, 0,
+			cs42l84_get_dac_vol, cs42l84_put_dac_vol, cs42l84_dac_tlv),
+	SOC_SINGLE("ADC Preamp Gain", CS42L84_ADC_CTL1,
+			CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT, 2, 0),
+	SOC_SINGLE("ADC PGA Gain", CS42L84_ADC_CTL1,
+			CS42L84_ADC_CTL1_PGA_GAIN_SHIFT, 31, 0),
+	SOC_SINGLE("ADC WNF Switch", CS42L84_ADC_CTL4,
+			CS42L84_ADC_CTL4_WNF_EN_SHIFT, 1, 0),
+	SOC_SINGLE("WNF Corner Frequency", CS42L84_ADC_CTL4,
+			CS42L84_ADC_CTL4_WNF_CF_SHIFT, 3, 0),
+	SOC_SINGLE("ADC HPF Switch", CS42L84_ADC_CTL4,
+			CS42L84_ADC_CTL4_HPF_EN_SHIFT, 1, 0),
+	SOC_SINGLE("HPF Corner Frequency", CS42L84_ADC_CTL4,
+			CS42L84_ADC_CTL4_HPF_CF_SHIFT, 3, 0),
+};
+
+static const char* const cs42l84_mux_text[] = {
+	"Blank", "ADC", "ASP RX CH1", "ASP RX CH2",
+};
+
+static const unsigned int cs42l84_mux_values[] = {
+	0b0000, 0b0111, 0b1101, 0b1110,
+};
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_daca_mux_enum,
+		CS42L84_BUS_DAC_SRC, CS42L84_BUS_DAC_SRC_DACA_SHIFT,
+		0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_dacb_mux_enum,
+		CS42L84_BUS_DAC_SRC, CS42L84_BUS_DAC_SRC_DACB_SHIFT,
+		0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static SOC_VALUE_ENUM_SINGLE_DECL(cs42l84_sdout1_mux_enum,
+		CS42L84_BUS_ASP_TX_SRC, CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT,
+		0b1111, cs42l84_mux_text, cs42l84_mux_values);
+
+static const struct snd_kcontrol_new cs42l84_daca_mux_ctrl =
+	SOC_DAPM_ENUM("DACA Select", cs42l84_daca_mux_enum);
+
+static const struct snd_kcontrol_new cs42l84_dacb_mux_ctrl =
+	SOC_DAPM_ENUM("DACB Select", cs42l84_dacb_mux_enum);
+
+static const struct snd_kcontrol_new cs42l84_sdout1_mux_ctrl =
+	SOC_DAPM_ENUM("SDOUT1 Select", cs42l84_sdout1_mux_enum);
+
+static const struct snd_soc_dapm_widget cs42l84_dapm_widgets[] = {
+	/* Playback Path */
+	SND_SOC_DAPM_OUTPUT("HP"),
+	SND_SOC_DAPM_DAC("DAC", NULL, CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_DAC_SHIFT, 0),
+	SND_SOC_DAPM_MUX("DACA Select", SND_SOC_NOPM, 0, 0, &cs42l84_daca_mux_ctrl),
+	SND_SOC_DAPM_MUX("DACB Select", SND_SOC_NOPM, 0, 0, &cs42l84_dacb_mux_ctrl),
+	SND_SOC_DAPM_AIF_IN("SDIN1", NULL, 0, CS42L84_ASP_RX_EN, CS42L84_ASP_RX_EN_CH1_SHIFT, 0),
+	SND_SOC_DAPM_AIF_IN("SDIN2", NULL, 1, CS42L84_ASP_RX_EN, CS42L84_ASP_RX_EN_CH2_SHIFT, 0),
+
+	/* Capture Path */
+	SND_SOC_DAPM_INPUT("HS"),
+	SND_SOC_DAPM_ADC("ADC", NULL, CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_ADC_SHIFT, 0),
+	SND_SOC_DAPM_MUX("SDOUT1 Select", SND_SOC_NOPM, 0, 0, &cs42l84_sdout1_mux_ctrl),
+	SND_SOC_DAPM_AIF_OUT("SDOUT1", NULL, 0, CS42L84_ASP_TX_EN, CS42L84_ASP_TX_EN_CH1_SHIFT, 0),
+
+	/* Playback/Capture Requirements */
+	SND_SOC_DAPM_SUPPLY("BUS", CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_BUS_SHIFT, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("ASP", CS42L84_MSM_BLOCK_EN2, CS42L84_MSM_BLOCK_EN2_ASP_SHIFT, 0, NULL, 0),
+	SND_SOC_DAPM_SUPPLY("BCLK", CS42L84_ASP_CTL, CS42L84_ASP_CTL_BCLK_EN_SHIFT, 0, NULL, 0),
+};
+
+static const struct snd_soc_dapm_route cs42l84_audio_map[] = {
+	/* Playback Path */
+	{"HP", NULL, "DAC"},
+	{"DAC", NULL, "DACA Select"},
+	{"DAC", NULL, "DACB Select"},
+	{"DACA Select", "ASP RX CH1", "SDIN1"},
+	{"DACA Select", "ASP RX CH2", "SDIN2"},
+	{"DACB Select", "ASP RX CH1", "SDIN1"},
+	{"DACB Select", "ASP RX CH2", "SDIN2"},
+	{"SDIN1", NULL, "Playback"},
+	{"SDIN2", NULL, "Playback"},
+
+	{"ADC", NULL, "HS"},
+	{"SDOUT1 Select", "ADC", "ADC"},
+	{"SDOUT1", NULL, "SDOUT1 Select"},
+	{"Capture", NULL, "SDOUT1"},
+
+	/* Playback Requirements */
+	{"DAC", NULL, "BUS"},
+	{"SDIN1", NULL, "ASP"},
+	{"SDIN2", NULL, "ASP"},
+	{"SDIN1", NULL, "BCLK"},
+	{"SDIN2", NULL, "BCLK"},
+
+	/* Capture Requirements */
+	{"SDOUT1", NULL, "BUS"},
+	{"SDOUT1", NULL, "ASP"},
+	{"SDOUT1", NULL, "BCLK"},
+};
+
+static int cs42l84_set_jack(struct snd_soc_component *component, struct snd_soc_jack *jk, void *d)
+{
+	struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+
+	/* Prevent race with interrupt handler */
+	mutex_lock(&cs42l84->irq_lock);
+	cs42l84->jack = jk;
+	snd_soc_jack_report(jk, cs42l84->hs_type, SND_JACK_HEADSET);
+	mutex_unlock(&cs42l84->irq_lock);
+
+	return 0;
+}
+
+static int cs42l84_component_probe(struct snd_soc_component *component)
+{
+	snd_soc_component_update_bits(component, CS42L84_ASP_CTL,
+			CS42L84_ASP_CTL_TDM_MODE, 0);
+	snd_soc_component_update_bits(component, CS42L84_HP_VOL_CTL,
+			CS42L84_HP_VOL_CTL_SOFT | CS42L84_HP_VOL_CTL_ZERO_CROSS,
+			CS42L84_HP_VOL_CTL_ZERO_CROSS);
+
+	/* TDM settings */
+	snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH1_CTL1,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE |
+			CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB, 0);
+	snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH1_CTL2,
+			CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+	snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH2_CTL1,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE |
+			CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE);
+	snd_soc_component_update_bits(component, CS42L84_ASP_RX_CH2_CTL2,
+			CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+	snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH1_CTL1,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE | \
+			CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB, 0);
+	snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH1_CTL2,
+			CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+	snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH2_CTL1,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE | \
+			CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB,
+			CS42L84_ASP_RX_CHx_CTL1_EDGE);
+	snd_soc_component_update_bits(component, CS42L84_ASP_TX_CH2_CTL2,
+			CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB, 0);
+	/* Routing defaults */
+	snd_soc_component_write(component, CS42L84_BUS_DAC_SRC,
+			0b1101 << CS42L84_BUS_DAC_SRC_DACA_SHIFT |
+			0b1110 << CS42L84_BUS_DAC_SRC_DACB_SHIFT);
+	snd_soc_component_write(component, CS42L84_BUS_ASP_TX_SRC,
+			0b0111 << CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT);
+
+	return 0;
+}
+
+static const struct snd_soc_component_driver soc_component_dev_cs42l84 = {
+	.set_jack		= cs42l84_set_jack,
+	.probe			= cs42l84_component_probe,
+	.controls		= cs42l84_snd_controls,
+	.num_controls		= ARRAY_SIZE(cs42l84_snd_controls),
+	.dapm_widgets		= cs42l84_dapm_widgets,
+	.num_dapm_widgets	= ARRAY_SIZE(cs42l84_dapm_widgets),
+	.dapm_routes		= cs42l84_audio_map,
+	.num_dapm_routes	= ARRAY_SIZE(cs42l84_audio_map),
+	.endianness		= 1,
+};
+
+struct cs42l84_pll_params {
+	u32 bclk;
+	u8 mclk_src_sel;
+	u8 bclk_prediv;
+	u8 pll_div_int;
+	u32 pll_div_frac;
+	u8 pll_mode;
+	u8 pll_divout;
+	u32 mclk_int;
+};
+
+/*
+ * Common PLL Settings for given BCLK
+ */
+static const struct cs42l84_pll_params pll_ratio_table[] = {
+	{  3072000, 1, 0, 0x40, 0x000000, 0x03, 0x10, 12288000},
+	{  6144000, 1, 1, 0x40, 0x000000, 0x03, 0x10, 12288000},
+	{ 12288000, 0, 0, 0, 0, 0, 0,                 12288000},
+	{ 24576000, 1, 3, 0x40, 0x000000, 0x03, 0x10, 12288000},
+};
+
+static int cs42l84_pll_config(struct snd_soc_component *component)
+{
+	struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+	int i;
+	u32 clk;
+	u32 fsync;
+
+	clk = cs42l84->bclk;
+
+	/* Don't reconfigure if there is an audio stream running */
+	if (cs42l84->stream_use) {
+		if (pll_ratio_table[cs42l84->pll_config].bclk == clk)
+			return 0;
+		else
+			return -EBUSY;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {
+		if (pll_ratio_table[i].bclk == clk) {
+			cs42l84->pll_config = i;
+			break;
+		}
+	}
+
+	if (i == ARRAY_SIZE(pll_ratio_table))
+		return -EINVAL;
+
+	/* Set up the LRCLK */
+	fsync = clk / cs42l84->srate;
+	if (((fsync * cs42l84->srate) != clk)
+			|| ((fsync % 2) != 0)) {
+		dev_err(component->dev,
+			"Unsupported bclk %d/sample rate %d\n",
+			clk, cs42l84->srate);
+		return -EINVAL;
+	}
+
+	/* Set the LRCLK period */
+	snd_soc_component_update_bits(component, CS42L84_ASP_FSYNC_CTL2,
+		CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO,
+		FIELD_PREP(CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO, fsync & 0x7f));
+	snd_soc_component_update_bits(component, CS42L84_ASP_FSYNC_CTL3,
+		CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI,
+		FIELD_PREP(CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI, fsync >> 7));
+
+	/* Save what the MCLK will be */
+	switch (pll_ratio_table[i].mclk_int) {
+	case 12000000:
+		cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12MHZ;
+		break;
+	case 12288000:
+		cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_12_288KHZ;
+		break;
+	case 24000000:
+		cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_24MHZ;
+		break;
+	case 24576000:
+		cs42l84->pll_mclk_f = CS42L84_CCM_CTL1_MCLK_F_24_576KHZ;
+		break;
+	}
+
+	if (pll_ratio_table[i].mclk_src_sel) {
+		/* Configure PLL */
+		snd_soc_component_update_bits(component,
+			CS42L84_CCM_CTL3, CS42L84_CCM_CTL3_REFCLK_DIV,
+			FIELD_PREP(CS42L84_CCM_CTL3_REFCLK_DIV, pll_ratio_table[i].bclk_prediv));
+		snd_soc_component_write(component,
+			CS42L84_PLL_DIV_INT,
+			pll_ratio_table[i].pll_div_int);
+		snd_soc_component_write(component,
+			CS42L84_PLL_DIV_FRAC0,
+			pll_ratio_table[i].pll_div_frac);
+		snd_soc_component_write(component,
+			CS42L84_PLL_DIV_FRAC1,
+			pll_ratio_table[i].pll_div_frac >> 8);
+		snd_soc_component_write(component,
+			CS42L84_PLL_DIV_FRAC2,
+			pll_ratio_table[i].pll_div_frac >> 16);
+		snd_soc_component_update_bits(component,
+			CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_MODE,
+			FIELD_PREP(CS42L84_PLL_CTL1_MODE, pll_ratio_table[i].pll_mode));
+		snd_soc_component_write(component,
+			CS42L84_PLL_DIVOUT,
+			pll_ratio_table[i].pll_divout);
+
+		snd_soc_component_update_bits(component,
+			CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_EN,
+			CS42L84_PLL_CTL1_EN);
+	}
+
+	return 0;
+}
+
+static int cs42l84_set_dai_fmt(struct snd_soc_dai *codec_dai, unsigned int fmt)
+{
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BC_FC:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
+	case SND_SOC_DAIFMT_I2S:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/* Bitclock/frame inversion */
+	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
+	case SND_SOC_DAIFMT_IB_IF:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int cs42l84_pcm_hw_params(struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params,
+				struct snd_soc_dai *dai)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+	int ret;
+	u32 ccm_samp_rate;
+
+	cs42l84->srate = params_rate(params);
+
+	ret = cs42l84_pll_config(component);
+	if (ret)
+		return ret;
+
+	switch (params_rate(params)) {
+	case 44100:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_44K1HZ;
+		break;
+	case 48000:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_48KHZ;
+		break;
+	case 88200:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_88K2HZ;
+		break;
+	case 96000:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_96KHZ;
+		break;
+	case 176400:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_176K4HZ;
+		break;
+	case 192000:
+		ccm_samp_rate = CS42L84_CCM_SAMP_RATE_RATE_192KHZ;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	snd_soc_component_write(component, CS42L84_CCM_SAMP_RATE, ccm_samp_rate);
+
+	switch (substream->stream) {
+	case SNDRV_PCM_STREAM_PLAYBACK:
+		snd_soc_component_write(component, CS42L84_ASP_RX_CH1_WIDTH,
+					params_width(params) - 1);
+		snd_soc_component_write(component, CS42L84_ASP_RX_CH2_WIDTH,
+					params_width(params) - 1);
+		break;
+
+	case SNDRV_PCM_STREAM_CAPTURE:
+		snd_soc_component_write(component, CS42L84_ASP_TX_CH1_WIDTH,
+					params_width(params) - 1);
+		snd_soc_component_write(component, CS42L84_ASP_TX_CH2_WIDTH,
+					params_width(params) - 1);
+		break;
+	}
+
+	return 0;
+}
+
+static int cs42l84_set_sysclk(struct snd_soc_dai *dai,
+				int clk_id, unsigned int freq, int dir)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+	int i;
+
+	if (freq == 0) {
+		cs42l84->bclk = 0;
+		return 0;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(pll_ratio_table); i++) {
+		if (pll_ratio_table[i].bclk == freq) {
+			cs42l84->bclk = freq;
+			return 0;
+		}
+	}
+
+	dev_err(component->dev, "BCLK %u not supported\n", freq);
+
+	return -EINVAL;
+}
+
+static int cs42l84_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
+{
+	struct snd_soc_component *component = dai->component;
+	struct cs42l84_private *cs42l84 = snd_soc_component_get_drvdata(component);
+	unsigned int regval;
+	int ret;
+
+	if (mute) {
+		/* Mute the headphone */
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			snd_soc_component_update_bits(component, CS42L84_DAC_CTL1,
+						      CS42L84_DAC_CTL1_UNMUTE, 0);
+		cs42l84->stream_use &= ~(1 << stream);
+		if (!cs42l84->stream_use) {
+			/* Must disconnect PLL before stopping it */
+			snd_soc_component_write(component, CS42L84_CCM_CTL1,
+						CS42L84_CCM_CTL1_RCO);
+
+			usleep_range(150, 300);
+
+			snd_soc_component_update_bits(component, CS42L84_PLL_CTL1,
+			      			      CS42L84_PLL_CTL1_EN, 0);
+
+			snd_soc_component_update_bits(component, CS42L84_CCM_CTL4,
+						      CS42L84_CCM_CTL4_REFCLK_EN, 0);
+		}
+	} else {
+		if (!cs42l84->stream_use) {
+			/* SCLK must be running before codec unmute.
+			 *
+			 * Note carried over from CS42L42:
+			 *
+			 * PLL must not be started with ADC and HP both off
+			 * otherwise the FILT+ supply will not charge properly.
+			 * DAPM widgets power-up before stream unmute so at least
+			 * one of the "DAC" or "ADC" widgets will already have
+			 * powered-up.
+			 */
+
+			snd_soc_component_update_bits(component, CS42L84_CCM_CTL4,
+						      CS42L84_CCM_CTL4_REFCLK_EN,
+						      CS42L84_CCM_CTL4_REFCLK_EN);
+
+			if (pll_ratio_table[cs42l84->pll_config].mclk_src_sel) {
+				snd_soc_component_update_bits(component, CS42L84_PLL_CTL1,
+							      CS42L84_PLL_CTL1_EN,
+							      CS42L84_PLL_CTL1_EN);
+				/* TODO: should we be doing something with divout here? */
+
+				ret = regmap_read_poll_timeout(cs42l84->regmap,
+							       CS42L84_PLL_LOCK_STATUS,
+							       regval,
+							       (regval & CS42L84_PLL_LOCK_STATUS_LOCKED),
+							       CS42L84_PLL_LOCK_POLL_US,
+							       CS42L84_PLL_LOCK_TIMEOUT_US);
+				if (ret < 0)
+					dev_warn(component->dev, "PLL failed to lock: %d\n", ret);
+
+				/* PLL must be running to drive glitchless switch logic */
+				snd_soc_component_update_bits(component,
+					CS42L84_CCM_CTL1,
+					CS42L84_CCM_CTL1_MCLK_SRC | CS42L84_CCM_CTL1_MCLK_FREQ,
+					FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_PLL)
+					| FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, cs42l84->pll_mclk_f));
+				usleep_range(CS42L84_CLOCK_SWITCH_DELAY_US, CS42L84_CLOCK_SWITCH_DELAY_US*2);
+			} else {
+				snd_soc_component_update_bits(component,
+					CS42L84_CCM_CTL1,
+					CS42L84_CCM_CTL1_MCLK_SRC | CS42L84_CCM_CTL1_MCLK_FREQ,
+					FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_BCLK)
+					| FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, cs42l84->pll_mclk_f));
+				usleep_range(CS42L84_CLOCK_SWITCH_DELAY_US, CS42L84_CLOCK_SWITCH_DELAY_US*2);
+			}
+		}
+		cs42l84->stream_use |= 1 << stream;
+
+		if (stream == SNDRV_PCM_STREAM_PLAYBACK)
+			/* Un-mute the headphone */
+			snd_soc_component_update_bits(component, CS42L84_DAC_CTL1,
+						      CS42L84_DAC_CTL1_UNMUTE,
+						      CS42L84_DAC_CTL1_UNMUTE);
+	}
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops cs42l84_ops = {
+	.hw_params	= cs42l84_pcm_hw_params,
+	.set_fmt	= cs42l84_set_dai_fmt,
+	.set_sysclk	= cs42l84_set_sysclk,
+	.mute_stream	= cs42l84_mute_stream,
+};
+
+#define CS42L84_FORMATS (SNDRV_PCM_FMTBIT_S16_LE |\
+			 SNDRV_PCM_FMTBIT_S24_LE |\
+			 SNDRV_PCM_FMTBIT_S32_LE)
+
+static struct snd_soc_dai_driver cs42l84_dai = {
+		.name = "cs42l84",
+		.playback = {
+			.stream_name = "Playback",
+			.channels_min = 1,
+			.channels_max = 2,
+			.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+			.formats = CS42L84_FORMATS,
+		},
+		.capture = {
+			.stream_name = "Capture",
+			.channels_min = 1,
+			.channels_max = 1,
+			.rates = SNDRV_PCM_RATE_48000 | SNDRV_PCM_RATE_96000,
+			.formats = CS42L84_FORMATS,
+		},
+		.symmetric_rate = 1,
+		.symmetric_sample_bits = 1,
+		.ops = &cs42l84_ops,
+};
+
+struct cs42l84_irq_params {
+	u16 status_addr;
+	u16 mask_addr;
+	u8 mask;
+};
+
+static const struct cs42l84_irq_params irq_params_table[] = {
+	{CS42L84_TSRS_PLUG_INT_STATUS, CS42L84_TSRS_PLUG_INT_MASK,
+		CS42L84_TSRS_PLUG_VAL_MASK}
+};
+
+static void cs42l84_detect_hs(struct cs42l84_private *cs42l84)
+{
+	unsigned int reg;
+
+	/* Power up HSBIAS */
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MISC_DET_CTL,
+		CS42L84_MISC_DET_CTL_HSBIAS_CTL | CS42L84_MISC_DET_CTL_DETECT_MODE,
+		FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 3) | /* 2.7 V */
+		FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 0));
+
+	/* Power up level detection circuitry */
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MISC_DET_CTL,
+		CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET, 0);
+
+	/* TODO: Optimize */
+	msleep(100);
+
+	/* Connect HSBIAS in CTIA wiring */
+	/* TODO: Should likely be subject of detection */
+	regmap_write(cs42l84->regmap,
+		CS42L84_HS_SWITCH_CTL,
+		CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+		CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+		CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+		CS42L84_HS_SWITCH_CTL_HSB_HS4);
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_HS_DET_CTL2,
+		CS42L84_HS_DET_CTL2_SET,
+		FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 0));
+
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MISC_DET_CTL,
+		CS42L84_MISC_DET_CTL_DETECT_MODE,
+		FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 3));
+
+	/* TODO: Optimize */
+	msleep(100);
+
+	regmap_read(cs42l84->regmap, CS42L84_HS_DET_STATUS2, &reg);
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MISC_DET_CTL,
+		CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET,
+		CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET);
+
+	switch (reg & 0b11) {
+	case 0b11: /* shorted */
+	case 0b00: /* open */
+		/* Power down HSBIAS */
+		regmap_update_bits(cs42l84->regmap,
+			CS42L84_MISC_DET_CTL,
+			CS42L84_MISC_DET_CTL_HSBIAS_CTL,
+			FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 1)); /* 0.0 V */
+		break;
+	}
+
+	switch (reg & 0b11) {
+	case 0b10: /* load */
+		dev_dbg(cs42l84->dev, "Detected mic\n");
+		cs42l84->hs_type = SND_JACK_HEADSET;
+		snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADSET,
+				SND_JACK_HEADSET);
+		break;
+
+	case 0b00: /* open */
+		dev_dbg(cs42l84->dev, "Detected line-in\n");
+		cs42l84->hs_type = SND_JACK_HEADSET;
+		snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADSET,
+				SND_JACK_HEADSET);
+		break;
+
+	case 0b11: /* shorted */
+	default:
+		snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADPHONE,
+				SND_JACK_HEADSET);
+		cs42l84->hs_type = SND_JACK_HEADPHONE;
+		dev_dbg(cs42l84->dev, "Detected bare headphone (no mic)\n");
+	}
+}
+
+static void cs42l84_revert_hs(struct cs42l84_private *cs42l84)
+{
+	/* Power down HSBIAS */
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MISC_DET_CTL,
+		CS42L84_MISC_DET_CTL_HSBIAS_CTL | CS42L84_MISC_DET_CTL_DETECT_MODE,
+		FIELD_PREP(CS42L84_MISC_DET_CTL_HSBIAS_CTL, 1) | /* 0.0 V */
+		FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 0));
+
+	/* Disconnect HSBIAS */
+	regmap_write(cs42l84->regmap,
+		CS42L84_HS_SWITCH_CTL,
+		CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+		CS42L84_HS_SWITCH_CTL_REF_HS4 | \
+		CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+		CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4 | \
+		CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+		CS42L84_HS_SWITCH_CTL_GNDHS_HS4);
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_HS_DET_CTL2,
+		CS42L84_HS_DET_CTL2_SET,
+		FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 2));
+}
+
+static irqreturn_t cs42l84_irq_thread(int irq, void *data)
+{
+	struct cs42l84_private *cs42l84 = (struct cs42l84_private *)data;
+	unsigned int stickies[1];
+	unsigned int masks[1];
+	unsigned int reg;
+	u8 current_plug_status;
+	int i;
+
+	mutex_lock(&cs42l84->irq_lock);
+	/* Read sticky registers to clear interrupt */
+	for (i = 0; i < ARRAY_SIZE(stickies); i++) {
+		regmap_read(cs42l84->regmap, irq_params_table[i].status_addr,
+				&(stickies[i]));
+		regmap_read(cs42l84->regmap, irq_params_table[i].mask_addr,
+				&(masks[i]));
+		stickies[i] = stickies[i] & (~masks[i]) &
+				irq_params_table[i].mask;
+	}
+
+	if ((~masks[0]) & irq_params_table[0].mask) {
+		regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
+		current_plug_status = (((char) reg) &
+		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+		      CS42L84_TS_PLUG_SHIFT;
+
+		switch (current_plug_status) {
+		case CS42L84_PLUG:
+			if (cs42l84->plug_state != CS42L84_PLUG) {
+				cs42l84->plug_state = CS42L84_PLUG;
+				dev_dbg(cs42l84->dev, "Plug event\n");
+
+				cs42l84_detect_hs(cs42l84);
+
+				/*
+				 * Check the tip sense status again, and possibly invalidate
+				 * the detection result
+				 *
+				 * Thanks to debounce, this should reliably indicate if the tip
+				 * was disconnected at any point during the detection procedure.
+				 */
+				regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
+				current_plug_status = (((char) reg) &
+				      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+				      CS42L84_TS_PLUG_SHIFT;
+				if (current_plug_status != CS42L84_PLUG) {
+					dev_dbg(cs42l84->dev, "Wobbly connection, detection invalidated\n");
+					cs42l84->plug_state = CS42L84_UNPLUG;
+					cs42l84_revert_hs(cs42l84);
+				}
+			}
+			break;
+
+		case CS42L84_UNPLUG:
+			if (cs42l84->plug_state != CS42L84_UNPLUG) {
+				cs42l84->plug_state = CS42L84_UNPLUG;
+				dev_dbg(cs42l84->dev, "Unplug event\n");
+
+				cs42l84_revert_hs(cs42l84);
+				cs42l84->hs_type = 0;
+				snd_soc_jack_report(cs42l84->jack, 0,
+						    SND_JACK_HEADSET);
+			}
+			break;
+
+		default:
+			if (cs42l84->plug_state != CS42L84_TRANS)
+				cs42l84->plug_state = CS42L84_TRANS;
+		}
+	}
+	mutex_unlock(&cs42l84->irq_lock);
+
+	return IRQ_HANDLED;
+}
+
+static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84)
+{
+	regmap_update_bits(cs42l84->regmap, CS42L84_TSRS_PLUG_INT_MASK,
+			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG |
+			CS42L84_TS_PLUG | CS42L84_TS_UNPLUG,
+			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG);
+}
+
+static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
+{
+	unsigned int reg;
+
+	/* Set up plug detection */
+	regmap_update_bits(cs42l84->regmap, CS42L84_MIC_DET_CTL4,
+			CS42L84_MIC_DET_CTL4_LATCH_TO_VP,
+			CS42L84_MIC_DET_CTL4_LATCH_TO_VP);
+	regmap_update_bits(cs42l84->regmap, CS42L84_TIP_SENSE_CTL2,
+			CS42L84_TIP_SENSE_CTL2_MODE,
+			FIELD_PREP(CS42L84_TIP_SENSE_CTL2_MODE, CS42L84_TIP_SENSE_CTL2_MODE_SHORT_DET));
+	regmap_update_bits(cs42l84->regmap, CS42L84_RING_SENSE_CTL,
+			CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
+			CS42L84_RING_SENSE_CTL_RISETIME | CS42L84_RING_SENSE_CTL_FALLTIME,
+			CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
+			FIELD_PREP(CS42L84_RING_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_125MS) |
+			FIELD_PREP(CS42L84_RING_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+	regmap_update_bits(cs42l84->regmap, CS42L84_TIP_SENSE_CTL,
+			CS42L84_TIP_SENSE_CTL_INV |
+			CS42L84_TIP_SENSE_CTL_RISETIME | CS42L84_TIP_SENSE_CTL_FALLTIME,
+			CS42L84_TIP_SENSE_CTL_INV |
+			FIELD_PREP(CS42L84_TIP_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_500MS) |
+			FIELD_PREP(CS42L84_TIP_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+	regmap_update_bits(cs42l84->regmap, CS42L84_MSM_BLOCK_EN3,
+			CS42L84_MSM_BLOCK_EN3_TR_SENSE,
+			CS42L84_MSM_BLOCK_EN3_TR_SENSE);
+
+	/* Save the initial status of the tip sense */
+	regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
+	cs42l84->plug_state = (((char) reg) &
+		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
+		      CS42L84_TS_PLUG_SHIFT;
+
+	/* Set up mic detection */
+
+	/* Disconnect HSBIAS (initially) */
+	regmap_write(cs42l84->regmap,
+		CS42L84_HS_SWITCH_CTL,
+		CS42L84_HS_SWITCH_CTL_REF_HS3 | \
+		CS42L84_HS_SWITCH_CTL_REF_HS4 | \
+		CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3 | \
+		CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4 | \
+		CS42L84_HS_SWITCH_CTL_GNDHS_HS3 | \
+		CS42L84_HS_SWITCH_CTL_GNDHS_HS4);
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_HS_DET_CTL2,
+		CS42L84_HS_DET_CTL2_SET | CS42L84_HS_DET_CTL2_CTL,
+		FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 2) |
+		FIELD_PREP(CS42L84_HS_DET_CTL2_CTL, 0));
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_HS_CLAMP_DISABLE, 1, 1);
+
+}
+
+static int cs42l84_i2c_probe(struct i2c_client *i2c_client)
+{
+	struct cs42l84_private *cs42l84;
+	int ret, devid;
+	unsigned int reg;
+
+	cs42l84 = devm_kzalloc(&i2c_client->dev, sizeof(struct cs42l84_private),
+			       GFP_KERNEL);
+	if (!cs42l84)
+		return -ENOMEM;
+
+	cs42l84->dev = &i2c_client->dev;
+	i2c_set_clientdata(i2c_client, cs42l84);
+	mutex_init(&cs42l84->irq_lock);
+
+	cs42l84->regmap = devm_regmap_init_i2c(i2c_client, &cs42l84_regmap);
+	if (IS_ERR(cs42l84->regmap)) {
+		ret = PTR_ERR(cs42l84->regmap);
+		dev_err(&i2c_client->dev, "regmap_init() failed: %d\n", ret);
+		return ret;
+	}
+
+	/* Reset the Device */
+	cs42l84->reset_gpio = devm_gpiod_get_optional(&i2c_client->dev,
+		"reset", GPIOD_OUT_LOW);
+	if (IS_ERR(cs42l84->reset_gpio)) {
+		ret = PTR_ERR(cs42l84->reset_gpio);
+		goto err_disable_noreset;
+	}
+
+	if (cs42l84->reset_gpio) {
+		dev_dbg(&i2c_client->dev, "Found reset GPIO\n");
+		gpiod_set_value_cansleep(cs42l84->reset_gpio, 1);
+	}
+	usleep_range(CS42L84_BOOT_TIME_US, CS42L84_BOOT_TIME_US * 2);
+
+	/* Request IRQ if one was specified */
+	if (i2c_client->irq) {
+		ret = request_threaded_irq(i2c_client->irq,
+					   NULL, cs42l84_irq_thread,
+					   IRQF_ONESHOT,
+					   "cs42l84", cs42l84);
+		if (ret == -EPROBE_DEFER) {
+			goto err_disable_noirq;
+		} else if (ret != 0) {
+			dev_err(&i2c_client->dev,
+				"Failed to request IRQ: %d\n", ret);
+			goto err_disable_noirq;
+		}
+	}
+
+	/* initialize codec */
+	devid = cirrus_read_device_id(cs42l84->regmap, CS42L84_DEVID);
+	if (devid < 0) {
+		ret = devid;
+		dev_err(&i2c_client->dev, "Failed to read device ID: %d\n", ret);
+		goto err_disable;
+	}
+
+	if (devid != CS42L84_CHIP_ID) {
+		dev_err(&i2c_client->dev,
+			"CS42L84 Device ID (%X). Expected %X\n",
+			devid, CS42L84_CHIP_ID);
+		ret = -EINVAL;
+		goto err_disable;
+	}
+
+	ret = regmap_read(cs42l84->regmap, CS42L84_REVID, &reg);
+	if (ret < 0) {
+		dev_err(&i2c_client->dev, "Get Revision ID failed\n");
+		goto err_shutdown;
+	}
+
+	dev_info(&i2c_client->dev,
+		 "Cirrus Logic CS42L84, Revision: %02X\n", reg & 0xFF);
+
+	/* Setup plug detection */
+	cs42l84_setup_plug_detect(cs42l84);
+
+	/* Mask/Unmask Interrupts */
+	cs42l84_set_interrupt_masks(cs42l84);
+
+	/* Register codec for machine driver */
+	ret = devm_snd_soc_register_component(&i2c_client->dev,
+			&soc_component_dev_cs42l84, &cs42l84_dai, 1);
+	if (ret < 0)
+		goto err_shutdown;
+
+	return 0;
+
+err_shutdown:
+	/* Nothing to do */
+
+err_disable:
+	if (i2c_client->irq)
+		free_irq(i2c_client->irq, cs42l84);
+
+err_disable_noirq:
+	gpiod_set_value_cansleep(cs42l84->reset_gpio, 0);
+err_disable_noreset:
+	return ret;
+}
+
+static void cs42l84_i2c_remove(struct i2c_client *i2c_client)
+{
+	struct cs42l84_private *cs42l84 = i2c_get_clientdata(i2c_client);
+
+	if (i2c_client->irq)
+		free_irq(i2c_client->irq, cs42l84);
+
+	gpiod_set_value_cansleep(cs42l84->reset_gpio, 0);
+}
+
+static const struct of_device_id cs42l84_of_match[] = {
+	{ .compatible = "cirrus,cs42l84", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, cs42l84_of_match);
+
+static const struct i2c_device_id cs42l84_id[] = {
+	{"cs42l84", 0},
+	{}
+};
+MODULE_DEVICE_TABLE(i2c, cs42l84_id);
+
+static struct i2c_driver cs42l84_i2c_driver = {
+	.driver = {
+		.name = "cs42l84",
+		.of_match_table = of_match_ptr(cs42l84_of_match),
+	},
+	.id_table = cs42l84_id,
+	.probe = cs42l84_i2c_probe,
+	.remove = cs42l84_i2c_remove,
+};
+
+module_i2c_driver(cs42l84_i2c_driver);
+
+MODULE_DESCRIPTION("ASoC CS42L84 driver");
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_LICENSE("GPL");
diff --git a/sound/soc/codecs/cs42l84.h b/sound/soc/codecs/cs42l84.h
new file mode 100644
index 00000000000000..e7cbf5f0e2d0bb
--- /dev/null
+++ b/sound/soc/codecs/cs42l84.h
@@ -0,0 +1,214 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on sound/soc/codecs/cs42l42.h
+ *
+ * Copyright 2016 Cirrus Logic, Inc.
+ */
+
+
+#ifndef __CS42L84_H__
+#define __CS42L84_H__
+
+#include <linux/bits.h>
+
+#define CS42L84_CHIP_ID				0x42a84
+
+#define CS42L84_DEVID				0x0000
+#define CS42L84_REVID				0x73fe
+#define CS42L84_FRZ_CTL				0x0006
+#define CS42L84_FRZ_CTL_ENGAGE			BIT(0)
+
+#define CS42L84_TSRS_PLUG_INT_STATUS		0x0400
+#define CS42L84_TSRS_PLUG_INT_MASK		0x0418
+#define CS42L84_RS_PLUG_SHIFT			0
+#define CS42L84_RS_PLUG				BIT(0)
+#define CS42L84_RS_UNPLUG			BIT(1)
+#define CS42L84_TS_PLUG_SHIFT			2
+#define CS42L84_TS_PLUG				BIT(2)
+#define CS42L84_TS_UNPLUG			BIT(3)
+#define CS42L84_TSRS_PLUG_VAL_MASK		GENMASK(3, 0)
+#define CS42L84_PLL_LOCK_STATUS			0x040e // probably bit 0x10
+#define CS42L84_PLL_LOCK_STATUS_LOCKED		BIT(4)
+
+#define CS42L84_PLUG				3
+#define CS42L84_UNPLUG				0
+#define CS42L84_TRANS				1
+
+#if 0
+    l84.regs.RING_SENSE_CTRL.set(INV=1, UNK1=1,
+	RISETIME=E_DEBOUNCE_TIME.T_125MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+    l84.regs.TIP_SENSE_CTRL.set(INV=1,
+	RISETIME=E_DEBOUNCE_TIME.T_500MS, FALLTIME=E_DEBOUNCE_TIME.T_125MS)
+    l84.regs.MSM_BLOCK_EN3.set(TR_SENSE_EN=1)
+#endif
+
+#define CS42L84_CCM_CTL1			0x0600
+#define CS42L84_CCM_CTL1_MCLK_SRC		GENMASK(1, 0)
+#define CS42L84_CCM_CTL1_MCLK_SRC_RCO		0
+#define CS42L84_CCM_CTL1_MCLK_SRC_MCLK		1
+#define CS42L84_CCM_CTL1_MCLK_SRC_BCLK		2
+#define CS42L84_CCM_CTL1_MCLK_SRC_PLL		3
+#define CS42L84_CCM_CTL1_MCLK_FREQ		GENMASK(3, 2)
+#define CS42L84_CCM_CTL1_MCLK_F_12MHZ		0b00
+#define CS42L84_CCM_CTL1_MCLK_F_24MHZ		0b01
+#define CS42L84_CCM_CTL1_MCLK_F_12_288KHZ	0b10
+#define CS42L84_CCM_CTL1_MCLK_F_24_576KHZ	0b11
+#define CS42L84_CCM_CTL1_RCO \
+	(FIELD_PREP(CS42L84_CCM_CTL1_MCLK_SRC, CS42L84_CCM_CTL1_MCLK_SRC_RCO) \
+	| FIELD_PREP(CS42L84_CCM_CTL1_MCLK_FREQ, CS42L84_CCM_CTL1_MCLK_F_12MHZ))
+
+#define CS42L84_CCM_SAMP_RATE			0x0601
+#define CS42L84_CCM_SAMP_RATE_RATE_48KHZ	4
+#define CS42L84_CCM_SAMP_RATE_RATE_96KHZ	5
+#define CS42L84_CCM_SAMP_RATE_RATE_192KHZ	6
+#define CS42L84_CCM_SAMP_RATE_RATE_44K1HZ	12
+#define CS42L84_CCM_SAMP_RATE_RATE_88K2HZ	13
+#define CS42L84_CCM_SAMP_RATE_RATE_176K4HZ	14
+#define CS42L84_CCM_CTL3			0x0602
+#define CS42L84_CCM_CTL3_REFCLK_DIV		GENMASK(2, 1)
+#define CS42L84_CCM_CTL4			0x0603
+#define CS42L84_CCM_CTL4_REFCLK_EN		BIT(0)
+
+#define CS42L84_CCM_ASP_CLK_CTRL		0x0608
+
+#define CS42L84_PLL_CTL1			0x0800
+#define CS42L84_PLL_CTL1_EN			BIT(0)
+#define CS42L84_PLL_CTL1_MODE			GENMASK(2, 1)
+#define CS42L84_PLL_DIV_FRAC0			0x0804
+#define CS42L84_PLL_DIV_FRAC1			0x0805
+#define CS42L84_PLL_DIV_FRAC2			0x0806
+#define CS42L84_PLL_DIV_INT			0x0807
+#define CS42L84_PLL_DIVOUT			0x0808
+
+#define CS42L84_RING_SENSE_CTL			0x1282
+#define CS42L84_RING_SENSE_CTL_INV		BIT(7)
+#define CS42L84_RING_SENSE_CTL_UNK1		BIT(6)
+#define CS42L84_RING_SENSE_CTL_FALLTIME		GENMASK(5, 3)
+#define CS42L84_RING_SENSE_CTL_RISETIME		GENMASK(2, 0)
+#define CS42L84_TIP_SENSE_CTL			0x1283
+#define CS42L84_TIP_SENSE_CTL_INV		BIT(7)
+#define CS42L84_TIP_SENSE_CTL_FALLTIME		GENMASK(5, 3)
+#define CS42L84_TIP_SENSE_CTL_RISETIME		GENMASK(2, 0)
+
+#define CS42L84_TSRS_PLUG_STATUS		0x1288
+
+#define CS42L84_TIP_SENSE_CTL2			0x1473
+#define CS42L84_TIP_SENSE_CTL2_MODE		GENMASK(7, 6)
+#define CS42L84_TIP_SENSE_CTL2_MODE_DISABLED	0b00
+#define CS42L84_TIP_SENSE_CTL2_MODE_DIG_INPUT	0b01
+#define CS42L84_TIP_SENSE_CTL2_MODE_SHORT_DET	0b11
+#define CS42L84_TIP_SENSE_CTL2_INV		BIT(5)
+
+#define CS42L84_MISC_DET_CTL                    0x1474
+#define CS42L84_MISC_DET_CTL_DETECT_MODE        GENMASK(4, 3)
+#define CS42L84_MISC_DET_CTL_HSBIAS_CTL         GENMASK(2, 1)
+#define CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET    BIT(0)
+
+#define CS42L84_MIC_DET_CTL4			0x1477
+#define CS42L84_MIC_DET_CTL4_LATCH_TO_VP	BIT(1)
+
+#define CS42L84_HS_DET_STATUS2                  0x147d
+
+#define CS42L84_MSM_BLOCK_EN1			0x1800
+#define CS42L84_MSM_BLOCK_EN2			0x1801
+#define CS42L84_MSM_BLOCK_EN2_ASP_SHIFT 	6
+#define CS42L84_MSM_BLOCK_EN2_BUS_SHIFT 	5
+#define CS42L84_MSM_BLOCK_EN2_DAC_SHIFT 	4
+#define CS42L84_MSM_BLOCK_EN2_ADC_SHIFT     3
+#define CS42L84_MSM_BLOCK_EN3			0x1802
+#define CS42L84_MSM_BLOCK_EN3_TR_SENSE		BIT(3)
+
+#define CS42L84_HS_DET_CTL2                     0x1811
+#define CS42L84_HS_DET_CTL2_CTL                 GENMASK(7, 6)
+#define CS42L84_HS_DET_CTL2_SET                 GENMASK(5, 4)
+#define CS42L84_HS_DET_CTL2_REF                 BIT(3)
+#define CS42L84_HS_DET_CTL2_AUTO_TIME           GENMASK(1, 0)
+
+#define CS42L84_HS_SWITCH_CTL			0x1812
+#define CS42L84_HS_SWITCH_CTL_REF_HS3           BIT(7)
+#define CS42L84_HS_SWITCH_CTL_REF_HS4           BIT(6)
+#define CS42L84_HS_SWITCH_CTL_HSB_FILT_HS3      BIT(5)
+#define CS42L84_HS_SWITCH_CTL_HSB_FILT_HS4      BIT(4)
+#define CS42L84_HS_SWITCH_CTL_HSB_HS3           BIT(3)
+#define CS42L84_HS_SWITCH_CTL_HSB_HS4           BIT(2)
+#define CS42L84_HS_SWITCH_CTL_GNDHS_HS3         BIT(1)
+#define CS42L84_HS_SWITCH_CTL_GNDHS_HS4         BIT(0)
+
+#define CS42L84_HS_CLAMP_DISABLE                0x1813
+
+#define CS42L84_ADC_CTL1			0x2000
+#define CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT	6
+#define CS42L84_ADC_CTL1_PGA_GAIN_SHIFT		0
+#define CS42L84_ADC_CTL4			0x2003
+#define CS42L84_ADC_CTL4_WNF_CF_SHIFT		4
+#define CS42L84_ADC_CTL4_WNF_EN_SHIFT		3
+#define CS42L84_ADC_CTL4_HPF_CF_SHIFT		1
+#define CS42L84_ADC_CTL4_HPF_EN_SHIFT		0
+
+#define CS42L84_DAC_CTL1			0x3000
+#define CS42L84_DAC_CTL1_UNMUTE			BIT(0)
+//#define CS42L84_DAC_CTL1_DACB_INV_SHIFT 1
+//#define CS42L84_DAC_CTL1_DACA_INV_SHIFT 0
+#define CS42L84_DAC_CTL2			0x3001
+
+#define CS42L84_DAC_CHA_VOL_LSB			0x3004
+#define CS42L84_DAC_CHA_VOL_MSB			0x3005
+#define CS42L84_DAC_CHB_VOL_LSB			0x3006
+#define CS42L84_DAC_CHB_VOL_MSB			0x3007
+#define CS42L84_HP_VOL_CTL			0x3020
+#define CS42L84_HP_VOL_CTL_ZERO_CROSS		BIT(1)
+#define CS42L84_HP_VOL_CTL_SOFT			BIT(0)
+
+#define CS42L84_SRC_ASP_RX_CH1			0b1101
+#define CS42L84_SRC_ASP_RX_CH2			0b1110
+
+#define CS42L84_BUS_ASP_TX_SRC                  0x4000
+#define CS42L84_BUS_ASP_TX_SRC_CH1_SHIFT        0
+#define CS42L84_BUS_DAC_SRC			0x4001
+#define CS42L84_BUS_DAC_SRC_DACA_SHIFT		0
+#define CS42L84_BUS_DAC_SRC_DACB_SHIFT		4
+
+#define CS42L84_ASP_CTL				0x5000
+#define CS42L84_ASP_CTL_BCLK_EN_SHIFT		1
+#define CS42L84_ASP_CTL_TDM_MODE		BIT(2)
+#define CS42L84_ASP_FSYNC_CTL2			0x5010
+#define CS42L84_ASP_FSYNC_CTL2_BCLK_PERIOD_LO	GENMASK(7, 1)
+#define CS42L84_ASP_FSYNC_CTL3			0x5011
+#define CS42L84_ASP_FSYNC_CTL3_BCLK_PERIOD_HI	GENMASK(4, 0)
+#define CS42L84_ASP_DATA_CTL			0x5018
+
+#define CS42L84_ASP_RX_EN			0x5020
+#define CS42L84_ASP_RX_EN_CH1_SHIFT		0
+#define CS42L84_ASP_RX_EN_CH2_SHIFT		1
+#define CS42L84_ASP_TX_EN			0x5024
+#define CS42L84_ASP_TX_EN_CH1_SHIFT             0
+
+#define CS42L84_ASP_RX_CH1_CTL1			0x5028
+#define CS42L84_ASP_RX_CH1_CTL2			0x5029
+#define CS42L84_ASP_RX_CH1_WIDTH		0x502a
+#define CS42L84_ASP_RX_CH2_CTL1			0x502c
+#define CS42L84_ASP_RX_CH2_CTL2			0x502d
+#define CS42L84_ASP_RX_CH2_WIDTH		0x502e
+
+#define CS42L84_ASP_RX_CHx_CTL1_EDGE		BIT(0)
+#define CS42L84_ASP_RX_CHx_CTL1_SLOT_START_LSB	GENMASK(7, 1)
+#define CS42L84_ASP_RX_CHx_CTL2_SLOT_START_MSB	GENMASK(2, 0)
+
+#define CS42L84_ASP_TX_CH1_CTL1			0x5068
+#define CS42L84_ASP_TX_CH1_CTL2			0x5069
+#define CS42L84_ASP_TX_CH1_WIDTH		0x506a
+#define CS42L84_ASP_TX_CH2_CTL1			0x506c
+#define CS42L84_ASP_TX_CH2_CTL2			0x506d
+#define CS42L84_ASP_TX_CH2_WIDTH		0x506e
+
+#define CS42L84_DEBOUNCE_TIME_125MS	0b001
+#define CS42L84_DEBOUNCE_TIME_500MS	0b011
+
+#define CS42L84_BOOT_TIME_US		3000
+#define CS42L84_CLOCK_SWITCH_DELAY_US	150
+#define CS42L84_PLL_LOCK_POLL_US	250
+#define CS42L84_PLL_LOCK_TIMEOUT_US	1250
+
+#endif /* __CS42L84_H__ */

From 0de84e2237543ebe1f66d3fbdd84238b6cb13bf2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 21 Aug 2022 02:40:29 +0200
Subject: [PATCH 0272/1027] ASoC: macaudio: Fix headset routes
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 12 +++++++-----
 1 file changed, 7 insertions(+), 5 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 1e6007bd5336bf..d150676cacd0a1 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -626,7 +626,7 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_
 	if (!is_speakers) {
 		r = &routes[nroutes++];
 		r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
-		r->sink = "Headphone Capture";
+		r->sink = "Headset Capture";
 	}
 
 	ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
@@ -639,7 +639,7 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_
 static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_component *component,
 				   bool is_speakers)
 {
-	struct snd_soc_dapm_route routes[1];
+	struct snd_soc_dapm_route routes[2];
 	struct snd_soc_dapm_route *r;
 	int nroutes = 0;
 	char buf[32];
@@ -660,9 +660,11 @@ static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_com
 		r = &routes[nroutes++];
 		r->source = "Jack HP";
 		r->sink = "Headphone";
+		r = &routes[nroutes++];
+		r->source = "Headset Mic";
+		r->sink = "Jack HS";
 	}
 
-
 	ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
 	if (ret)
 		dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
@@ -813,7 +815,7 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 	SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
 	SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
 
-	SND_SOC_DAPM_AIF_IN("Headphone Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
 };
 
 static const struct snd_kcontrol_new macaudio_controls[] = {
@@ -840,7 +842,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 	{ "Speaker (Static)", "Static", "Speaker Pin Demux" },
 
 	/* Capture paths */
-	{ "PCM0 RX", NULL, "Headphone Capture" },
+	{ "PCM0 RX", NULL, "Headset Capture" },
 };
 
 static const struct of_device_id macaudio_snd_device_id[]  = {

From eb8c65695824810d950397a09623d5fcc0e53602 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 21 Aug 2022 02:40:54 +0200
Subject: [PATCH 0273/1027] ASoC: dapm: Export new 'graph.dot' file in debugfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/soc-dapm.c | 137 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 137 insertions(+)

diff --git a/sound/soc/soc-dapm.c b/sound/soc/soc-dapm.c
index 32c556c625577d..4ae53661101594 100644
--- a/sound/soc/soc-dapm.c
+++ b/sound/soc/soc-dapm.c
@@ -2251,6 +2251,139 @@ static const struct file_operations dapm_bias_fops = {
 	.llseek = default_llseek,
 };
 
+static ssize_t dapm_graph_read_file(struct file *file, char __user *user_buf,
+				    size_t count, loff_t *ppos)
+{
+	struct snd_soc_card *card = file->private_data;
+	struct snd_soc_dapm_context *dapm;
+	struct snd_soc_dapm_path *p;
+	struct snd_soc_dapm_widget *w;
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_soc_dapm_widget *wdone[16];
+	struct snd_soc_dai *dai;
+	int i, num_wdone = 0, cluster = 0;
+	char *buf;
+	ssize_t bufsize;
+	ssize_t ret = 0;
+
+	bufsize = 1024 * card->num_dapm_widgets;
+	buf = kmalloc(bufsize, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	mutex_lock(&card->dapm_mutex);
+
+#define bufprintf(...) \
+		ret += scnprintf(buf + ret, bufsize - ret, __VA_ARGS__)
+
+	bufprintf("digraph dapm {\n");
+
+	/*
+	 * Print the user-visible devices of the card.
+	 */
+	bufprintf("subgraph cluster_%d {\n", cluster++);
+	bufprintf("label=\"Devices\";style=filled;fillcolor=gray;\n");
+	for_each_card_rtds(card, rtd) {
+		if (rtd->dai_link->no_pcm)
+			continue;
+
+		bufprintf("w%pK [label=\"%d: %s\"];\n", rtd,
+			  rtd->pcm->device, rtd->dai_link->name);
+	}
+	bufprintf("};\n");
+
+	/*
+	 * Print the playback/capture widgets of DAIs just next to
+	 * the user-visible devices. Keep the list of already printed
+	 * widgets in 'wdone', so they will be skipped later.
+	 */
+	for_each_card_rtds(card, rtd) {
+		for_each_rtd_cpu_dais(rtd, i, dai) {
+			if (dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget) {
+				w = dai->stream[SNDRV_PCM_STREAM_PLAYBACK].widget;
+				bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+				if (!rtd->dai_link->no_pcm)
+					bufprintf("w%pK -> w%pK;\n", rtd, w);
+				wdone[num_wdone] = w;
+				if (num_wdone < ARRAY_SIZE(wdone))
+					num_wdone++;
+			}
+
+			if (dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget) {
+				w = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget;
+				bufprintf("w%pK [label=\"%s\"];\n", w, w->name);
+				if (!rtd->dai_link->no_pcm)
+					bufprintf("w%pK -> w%pK;\n", w, rtd);
+				wdone[num_wdone] = w;
+				if (num_wdone < ARRAY_SIZE(wdone))
+					num_wdone++;
+			}
+		}
+	}
+
+	for_each_card_dapms(card, dapm) {
+		const char *prefix = soc_dapm_prefix(dapm);
+
+		if (dapm != &card->dapm) {
+			bufprintf("subgraph cluster_%d {\n", cluster++);
+			if (prefix)
+				bufprintf("label=\"%s\";\n", prefix);
+			else if (dapm->component)
+				bufprintf("label=\"%s\";\n",
+					  dapm->component->name);
+		}
+
+		for_each_card_widgets(dapm->card, w) {
+			const char *name = w->name;
+			bool skip = false;
+
+			if (w->dapm != dapm)
+				continue;
+
+			if (list_empty(&w->edges[0]) && list_empty(&w->edges[1]))
+				continue;
+
+			for (i = 0; i < num_wdone; i++)
+				if (wdone[i] == w)
+					skip = true;
+			if (skip)
+				continue;
+
+			if (prefix && strlen(name) > strlen(prefix) + 1)
+				name += strlen(prefix) + 1;
+
+			bufprintf("w%pK [label=\"%s\"];\n", w, name);
+		}
+
+		if (dapm != &card->dapm)
+			bufprintf("}\n");
+	}
+
+	list_for_each_entry(p, &card->paths, list) {
+		if (p->name)
+			bufprintf("w%pK -> w%pK [label=\"%s\"];\n",
+				  p->source, p->sink, p->name);
+		else
+			bufprintf("w%pK -> w%pK;\n", p->source, p->sink);
+	}
+
+	bufprintf("}\n");
+#undef bufprintf
+
+	mutex_unlock(&card->dapm_mutex);
+
+	ret = simple_read_from_buffer(user_buf, count, ppos, buf, ret);
+
+	kfree(buf);
+	return ret;
+}
+
+static const struct file_operations dapm_graph_fops = {
+	.open = simple_open,
+	.read = dapm_graph_read_file,
+	.llseek = default_llseek,
+};
+
 void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
 	struct dentry *parent)
 {
@@ -2261,6 +2394,10 @@ void snd_soc_dapm_debugfs_init(struct snd_soc_dapm_context *dapm,
 
 	debugfs_create_file("bias_level", 0444, dapm->debugfs_dapm, dapm,
 			    &dapm_bias_fops);
+
+	if (dapm == &dapm->card->dapm)
+		debugfs_create_file("graph.dot", 0444, dapm->debugfs_dapm,
+				    dapm->card, &dapm_graph_fops);
 }
 
 static void dapm_debugfs_add_widget(struct snd_soc_dapm_widget *w)

From 6233021a86e28e9ab7be2f93a900047ee5e9879e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 23 Aug 2022 11:36:24 +0200
Subject: [PATCH 0274/1027] ASoC: macaudio: Add j375 fixup_controls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index d150676cacd0a1..82808a3fb6df84 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -758,6 +758,17 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 	return 0;
 }
 
+static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers) {
+		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+	}
+
+	return 0;
+}
+
 static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -848,6 +859,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 static const struct of_device_id macaudio_snd_device_id[]  = {
 	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
 	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
+	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
 	{ .compatible = "apple,macaudio"},
 	{ }
 };

From 3ce9b3974735afb3e3edc71f9882a5ca502a2ec8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 30 Aug 2022 10:20:09 +0200
Subject: [PATCH 0275/1027] ASoC: macaudio: Add j493 fixup_controls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 82808a3fb6df84..543dc0c1134816 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -769,6 +769,17 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 	return 0;
 }
 
+static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
+{
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers) {
+		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
+	}
+
+	return 0;	
+}
+
 static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -860,6 +871,7 @@ static const struct of_device_id macaudio_snd_device_id[]  = {
 	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
 	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
+	{ .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls },
 	{ .compatible = "apple,macaudio"},
 	{ }
 };

From 67ffb464accd6f50c7b80877378f1c65531c57dd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 4 Sep 2022 10:29:34 +0200
Subject: [PATCH 0276/1027] ASoC: macaudio: Rename ALSA driver to simple
 'macaudio'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 543dc0c1134816..f5f43002c4a2b8 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -898,7 +898,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 	snd_soc_card_set_drvdata(card, data);
 
 	card->owner = THIS_MODULE;
-	card->driver_name = DRIVER_NAME;
+	card->driver_name = "macaudio";
 	card->dev = dev;
 	card->dapm_widgets = macaudio_snd_widgets;
 	card->num_dapm_widgets = ARRAY_SIZE(macaudio_snd_widgets);

From ada32da52e913fcb7bd37ab597ef9d3c7141734f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 2 Sep 2022 19:40:16 +0200
Subject: [PATCH 0277/1027] ASoC: macaudio: Drop the 'inverse jack' speaker
 stuff
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 22 +---------------------
 1 file changed, 1 insertion(+), 21 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index f5f43002c4a2b8..fdc7293f376599 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -577,11 +577,6 @@ static struct snd_soc_jack_pin macaudio_jack_pins[] = {
 		.pin = "Headset Mic",
 		.mask = SND_JACK_MICROPHONE,
 	},
-	{
-		.pin = "Speaker",
-		.mask = SND_JACK_HEADPHONE,
-		.invert = 1,
-	},
 };
 
 static int macaudio_probe(struct snd_soc_card *card)
@@ -655,7 +650,7 @@ static int macaudio_add_pin_routes(struct snd_soc_card *card, struct snd_soc_com
 			snprintf(buf, sizeof(buf) - 1, "%s OUT", component->name_prefix);
 			r->source = buf;
 		}
-		r->sink = "Speaker Pin Demux";
+		r->sink = "Speaker";
 	} else {
 		r = &routes[nroutes++];
 		r->source = "Jack HP";
@@ -814,16 +809,6 @@ SOC_ENUM_SINGLE_VIRT_DECL(macaudio_hp_mux_enum, macaudio_hp_mux_texts);
 static const struct snd_kcontrol_new macaudio_hp_mux =
 	SOC_DAPM_ENUM("Headphones Playback Mux", macaudio_hp_mux_enum);
 
-static const char *macaudio_spk_demux_texts[] = {
-	"Inverse Jack", "Static",
-};
-
-static SOC_ENUM_SINGLE_DECL(macaudio_spk_demux_enum,
-			    SND_SOC_NOPM, 0, macaudio_spk_demux_texts);
-
-static const struct snd_kcontrol_new macaudio_spk_demux =
-	SOC_DAPM_ENUM("Speaker Pin Demux", macaudio_spk_demux_enum);
-
 static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 	SND_SOC_DAPM_SPK("Speaker", NULL),
 	SND_SOC_DAPM_SPK("Speaker (Static)", NULL),
@@ -832,7 +817,6 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 
 	SND_SOC_DAPM_MUX("Speaker Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_spk_mux),
 	SND_SOC_DAPM_MUX("Headphone Playback Mux", SND_SOC_NOPM, 0, 0, &macaudio_hp_mux),
-	SND_SOC_DAPM_DEMUX("Speaker Pin Demux", SND_SOC_NOPM, 0, 0, &macaudio_spk_demux),
 
 	SND_SOC_DAPM_AIF_OUT("Speaker Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
 	SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
@@ -842,7 +826,6 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 
 static const struct snd_kcontrol_new macaudio_controls[] = {
 	SOC_DAPM_PIN_SWITCH("Speaker"),
-	SOC_DAPM_PIN_SWITCH("Speaker (Static)"),
 	SOC_DAPM_PIN_SWITCH("Headphone"),
 	SOC_DAPM_PIN_SWITCH("Headset Mic"),
 };
@@ -860,9 +843,6 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 	 * Additional paths (to specific I2S ports) are added dynamically.
 	 */
 
-	{ "Speaker", "Inverse Jack", "Speaker Pin Demux" },
-	{ "Speaker (Static)", "Static", "Speaker Pin Demux" },
-
 	/* Capture paths */
 	{ "PCM0 RX", NULL, "Headset Capture" },
 };

From 9ce7ad0bb658c5b12ecc26fd106c591b4bac9456 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 6 Sep 2022 15:16:44 +0200
Subject: [PATCH 0278/1027] ASoC: macaudio: s/Freq/Frequency/ in TAS2764
 control
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index fdc7293f376599..09310014d5e636 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -730,8 +730,8 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
 		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
-		CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Freq", "800 Hz");
-		CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Freq", 0);
+		CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Frequency", "800 Hz");
+		CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Frequency", 0);
 
 		/*
 		 * The speaker amps suffer from spurious overcurrent

From 8f74cf82c095bfdab3d039f9cdb81537849c9b7a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Sep 2022 19:56:12 +0900
Subject: [PATCH 0279/1027] ASoC: macaudio:
 s/void_warranty/please_blow_up_my_speakers/

We have no idea whether any of this voids warranties, but what it does
do is blow up your speakers, so let's be explicit about what users are
signing up for.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 09310014d5e636..4806854ee0656f 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -67,9 +67,9 @@ struct macaudio_snd_data {
 	struct snd_pcm_hw_constraint_list speaker_nchans_list;
 };
 
-static bool void_warranty;
-module_param(void_warranty, bool, 0644);
-MODULE_PARM_DESC(void_warranty, "Do not bail if safety is not assured");
+static bool please_blow_up_my_speakers;
+module_param(please_blow_up_my_speakers, bool, 0644);
+MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations");
 
 SND_SOC_DAILINK_DEFS(primary,
 	DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-0")), // CPU
@@ -703,7 +703,7 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 #define CHECK(call, pattern, value) \
 	{ \
 		int ret = call(card, pattern, value); \
-		if (ret < 1 && !void_warranty) { \
+		if (ret < 1 && !please_blow_up_my_speakers) { \
 			dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \
 			return ret; \
 		} \
@@ -779,7 +779,7 @@ static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
-	if (ma->has_speakers && !void_warranty) {
+	if (ma->has_speakers && !please_blow_up_my_speakers) {
 		dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
 		return -EINVAL;
 	}

From 0c4781964469d060c2839b6bd7b508569ee74e64 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Sep 2022 19:56:47 +0900
Subject: [PATCH 0280/1027] ASoC: macaudio: Gate off experimental platforms

We know at least some machines can have their speakers blown, even with
these limits, so let's play it safe for now and require that users both
enable stuff in the DT *and* pass this flag.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 4806854ee0656f..2d4b21b95309e6 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -727,6 +727,11 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
+		if (!please_blow_up_my_speakers) {
+			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+			return -EINVAL;
+		}
+
 		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
 		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
@@ -758,6 +763,11 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
+		if (!please_blow_up_my_speakers) {
+			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+			return -EINVAL;
+		}
+
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
 	}
 
@@ -769,6 +779,11 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
+		if (!please_blow_up_my_speakers) {
+			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+			return -EINVAL;
+		}
+
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
 	}
 

From 1a545e06dbf044ee2d34681cc90b161be2527beb Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 13 Sep 2022 19:58:17 +0900
Subject: [PATCH 0281/1027] ASoC: macaudio: Alias f413 fixups to j314

This works as far as following the same intent as j314, but we *know*
these limits are not sufficient, so this one really needs the module
parameter gate.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 2d4b21b95309e6..0dd1253ac4f68b 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -866,6 +866,7 @@ static const struct of_device_id macaudio_snd_device_id[]  = {
 	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
 	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
+	{ .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls },
 	{ .compatible = "apple,macaudio"},
 	{ }

From 3754682795cb9144a72a5c428b947ea0743af04a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Fri, 14 Oct 2022 16:23:21 +0200
Subject: [PATCH 0282/1027] ASoC: cs42l84: There's no line-in on the jack
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l84.c | 8 ++------
 1 file changed, 2 insertions(+), 6 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index 38dfee80ed9f73..791d178bdbcf03 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -727,12 +727,8 @@ static void cs42l84_detect_hs(struct cs42l84_private *cs42l84)
 		break;
 
 	case 0b00: /* open */
-		dev_dbg(cs42l84->dev, "Detected line-in\n");
-		cs42l84->hs_type = SND_JACK_HEADSET;
-		snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADSET,
-				SND_JACK_HEADSET);
-		break;
-
+		dev_dbg(cs42l84->dev, "Detected open circuit on HS4\n");
+		fallthrough;
 	case 0b11: /* shorted */
 	default:
 		snd_soc_jack_report(cs42l84->jack, SND_JACK_HEADPHONE,

From 3ba23a9f811cff2afecc4327ff70e045604817c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Fri, 14 Oct 2022 17:46:44 +0200
Subject: [PATCH 0283/1027] ASoC: cs42l84: Adjust mic-detection voltage
 threshold
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Raise the mic-detection voltage threshold to address some mics not
getting detected.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l84.c | 5 ++++-
 sound/soc/codecs/cs42l84.h | 3 +++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index 791d178bdbcf03..9f3508abae2a20 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -878,7 +878,10 @@ static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
 		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
 		      CS42L84_TS_PLUG_SHIFT;
 
-	/* Set up mic detection */
+	/* Set mic-detection threshold */
+	regmap_update_bits(cs42l84->regmap,
+		CS42L84_MIC_DET_CTL1, CS42L84_MIC_DET_CTL1_HS_DET_LEVEL,
+		FIELD_PREP(CS42L84_MIC_DET_CTL1_HS_DET_LEVEL, 0x2c)); /* ~1.9 V */
 
 	/* Disconnect HSBIAS (initially) */
 	regmap_write(cs42l84->regmap,
diff --git a/sound/soc/codecs/cs42l84.h b/sound/soc/codecs/cs42l84.h
index e7cbf5f0e2d0bb..9aaf19051d395f 100644
--- a/sound/soc/codecs/cs42l84.h
+++ b/sound/soc/codecs/cs42l84.h
@@ -106,6 +106,9 @@
 #define CS42L84_MISC_DET_CTL_HSBIAS_CTL         GENMASK(2, 1)
 #define CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET    BIT(0)
 
+#define CS42L84_MIC_DET_CTL1			0x1475
+#define CS42L84_MIC_DET_CTL1_HS_DET_LEVEL	GENMASK(5, 0)
+
 #define CS42L84_MIC_DET_CTL4			0x1477
 #define CS42L84_MIC_DET_CTL4_LATCH_TO_VP	BIT(1)
 

From 5363b2ca2375387a23012f6091657e259aab0ff2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Sun, 16 Oct 2022 13:33:21 +0200
Subject: [PATCH 0284/1027] ASoC: cs42l84: Put the volume control in shape
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The previous limits of the volume control were placeholders, this
reflects the current understanding of the register semantics.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l84.c | 28 +++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index 9f3508abae2a20..f2c5957ceee608 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -70,11 +70,15 @@ static int cs42l84_put_dac_vol(struct snd_kcontrol *kctl,
 			struct snd_ctl_elem_value *val)
 {
 	struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
-	unsigned int vola, volb;
+	struct soc_mixer_control *mc = (struct soc_mixer_control *) kctl->private_value;
+	int vola, volb;
 	int ret, ret2;
 
-	vola = val->value.integer.value[0];
-	volb = val->value.integer.value[1];
+	vola = val->value.integer.value[0] + mc->min;
+	volb = val->value.integer.value[1] + mc->min;
+
+	if (vola < mc->min || vola > mc->max || volb < mc->min || volb > mc->max)
+		return -EINVAL;
 
 	ret = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,
 					    CS42L84_FRZ_CTL_ENGAGE,
@@ -112,7 +116,8 @@ static int cs42l84_get_dac_vol(struct snd_kcontrol *kctl,
 			struct snd_ctl_elem_value *val)
 {
 	struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
-	unsigned int vola, volb;
+	struct soc_mixer_control *mc = (struct soc_mixer_control *) kctl->private_value;
+	int vola, volb;
 	int ret;
 
 	ret = snd_soc_component_read(component, CS42L84_DAC_CHA_VOL_LSB);
@@ -135,18 +140,23 @@ static int cs42l84_get_dac_vol(struct snd_kcontrol *kctl,
 		return ret;
 	volb |= (ret & 1) << 8;
 
-	val->value.integer.value[0] = vola;
-	val->value.integer.value[1] = volb;
+	if (vola & BIT(8))
+		vola |= ~((int)(BIT(8) - 1));
+	if (volb & BIT(8))
+		volb |= ~((int)(BIT(8) - 1));
+
+	val->value.integer.value[0] = vola - mc->min;
+	val->value.integer.value[1] = volb - mc->min;
 
 	return 0;
 }
 
 /* TODO */
-static const DECLARE_TLV_DB_SCALE(cs42l84_dac_tlv, -25600, 50, 1);
+static const DECLARE_TLV_DB_SCALE(cs42l84_dac_tlv, -12800, 50, true);
 
 static const struct snd_kcontrol_new cs42l84_snd_controls[] = {
-	SOC_DOUBLE_R_EXT_TLV("DAC Playback Volume", CS42L84_DAC_CHA_VOL_LSB,
-			CS42L84_DAC_CHB_VOL_LSB, 0, 511, 0,
+	SOC_DOUBLE_R_S_EXT_TLV("DAC Playback Volume", CS42L84_DAC_CHA_VOL_LSB,
+			CS42L84_DAC_CHB_VOL_LSB, 0, -256, 24, 8, 0,
 			cs42l84_get_dac_vol, cs42l84_put_dac_vol, cs42l84_dac_tlv),
 	SOC_SINGLE("ADC Preamp Gain", CS42L84_ADC_CTL1,
 			CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT, 2, 0),

From 02bb14ffda2bc190de87e6cc46f24457f17c9220 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 17 Oct 2022 11:23:45 +0200
Subject: [PATCH 0285/1027] ASoC: apple: mca: Constrain channels according to
 TDM mask
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

We don't (and can't) configure the hardware correctly if the number of
channels exceeds the weight of the TDM mask. Report that constraint in
startup of FE.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 3780aca710769e..fb23cb27b00ad7 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -464,6 +464,28 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit,
 	return -EINVAL;
 }
 
+static int mca_fe_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+	unsigned int mask, nchannels;
+
+	if (cl->tdm_slots) {
+		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
+			mask = cl->tdm_tx_mask;
+		else
+			mask = cl->tdm_rx_mask;
+
+		nchannels = hweight32(mask);
+	} else {
+		nchannels = 2;
+	}
+
+	return snd_pcm_hw_constraint_minmax(substream->runtime,
+					    SNDRV_PCM_HW_PARAM_CHANNELS,
+					    1, nchannels);
+}
+
 static int mca_fe_set_tdm_slot(struct snd_soc_dai *dai, unsigned int tx_mask,
 			       unsigned int rx_mask, int slots, int slot_width)
 {
@@ -680,6 +702,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream,
 }
 
 static const struct snd_soc_dai_ops mca_fe_ops = {
+	.startup = mca_fe_startup,
 	.set_fmt = mca_fe_set_fmt,
 	.set_bclk_ratio = mca_set_bclk_ratio,
 	.set_tdm_slot = mca_fe_set_tdm_slot,

From 3d04a027250a210ce1c5afbc825d61a105090839 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 17 Oct 2022 12:16:20 +0200
Subject: [PATCH 0286/1027] ASoC: macaudio: Improve message on opening of
 unrouted PCM devices
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 25 ++++++++++++++++++++++++-
 1 file changed, 24 insertions(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 0dd1253ac4f68b..3ccfacefbf7e85 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -455,6 +455,29 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
 	return 0;
 }
 
+static int macaudio_fe_hw_params(struct snd_pcm_substream *substream,
+				   struct snd_pcm_hw_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_pcm_runtime *be;
+	struct snd_soc_dpcm *dpcm;
+
+	be = NULL;
+	for_each_dpcm_be(rtd, substream->stream, dpcm) {
+		be = dpcm->be;
+		break;
+	}
+
+	if (!be) {
+		dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured (bad settings applied to the sound card)\n",
+				rtd->dai_link->name);
+		return -EINVAL;
+	}
+
+	return macaudio_dpcm_hw_params(substream, params);
+}
+
+
 static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
@@ -473,7 +496,7 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
 
 static const struct snd_soc_ops macaudio_fe_ops = {
 	.shutdown	= macaudio_dpcm_shutdown,
-	.hw_params	= macaudio_dpcm_hw_params,
+	.hw_params	= macaudio_fe_hw_params,
 };
 
 static const struct snd_soc_ops macaudio_be_ops = {

From 9537b623e67130c36ebb9b6848e3a024bf2eff59 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik@protonmail.com>
Date: Mon, 17 Oct 2022 12:26:27 +0200
Subject: [PATCH 0287/1027] ASoC: cs42l84: Enable regcache (initially)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/cs42l84.c | 27 ++++++++++++++++++++++++++-
 1 file changed, 26 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index f2c5957ceee608..ae1d65a3ee46c8 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -55,12 +55,37 @@ struct cs42l84_private {
 	int hs_type;
 };
 
+/*
+static const struct reg_default cs42l84_reg_defaults[] = {
+};
+*/
+
+static bool cs42l84_volatile_register(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case CS42L84_DEVID ... CS42L84_DEVID+5:
+	case CS42L84_TSRS_PLUG_INT_STATUS:
+	case CS42L84_PLL_LOCK_STATUS:
+	case CS42L84_TSRS_PLUG_STATUS:
+	case CS42L84_HS_DET_STATUS2:
+		return true;
+	default:
+		return false;
+	}
+}
+
 static const struct regmap_config cs42l84_regmap = {
 	.reg_bits = 16,
 	.val_bits = 8,
 
+	.volatile_reg = cs42l84_volatile_register,
+
 	.max_register = 0xffff,
-	.cache_type = REGCACHE_NONE,
+	/*
+	.reg_defaults = cs42l84_reg_defaults,
+	.num_reg_defaults = ARRAY_SIZE(cs42l84_reg_defaults),
+	*/
+	.cache_type = REGCACHE_RBTREE,
 
 	.use_single_read = true,
 	.use_single_write = true,

From 811a30f285032eb6e6371e3c3f2d42be6c9ead43 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 13:29:57 +0900
Subject: [PATCH 0288/1027] ASoC: cs42l84: Report volume updates correctly

cs42l84_put_dac_vol needs to return 1 if the volume was updated. Before
this patch, it was only doing that when the MSB changed. Correctly track
changes in all the registers.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/cs42l84.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index ae1d65a3ee46c8..f2784946b6900d 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -97,7 +97,7 @@ static int cs42l84_put_dac_vol(struct snd_kcontrol *kctl,
 	struct snd_soc_component *component = snd_soc_kcontrol_component(kctl);
 	struct soc_mixer_control *mc = (struct soc_mixer_control *) kctl->private_value;
 	int vola, volb;
-	int ret, ret2;
+	int ret, ret2, updated = 0;
 
 	vola = val->value.integer.value[0] + mc->min;
 	volb = val->value.integer.value[1] + mc->min;
@@ -110,23 +110,31 @@ static int cs42l84_put_dac_vol(struct snd_kcontrol *kctl,
 					    CS42L84_FRZ_CTL_ENGAGE);
 	if (ret < 0)
 		goto bail;
+	updated |= ret;
 
 	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_LSB,
 					    0xff, vola & 0xff);
 	if (ret < 0)
 		goto bail;
+	updated |= ret;
+
 	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHA_VOL_MSB,
 					    0xff, (vola >> 8) & 0x01);
 	if (ret < 0)
 		goto bail;
+	updated |= ret;
+
 	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_LSB,
 					    0xff, volb & 0xff);
 	if (ret < 0)
 		goto bail;
+	updated |= ret;
+
 	ret = snd_soc_component_update_bits(component, CS42L84_DAC_CHB_VOL_MSB,
 					    0xff, (volb >> 8) & 0x01);
 	if (ret < 0)
 		goto bail;
+	ret |= updated;
 
 bail:
 	ret2 = snd_soc_component_update_bits(component, CS42L84_FRZ_CTL,

From a0b5cf481f5ad1023bc4f3e5164a63e9f9885b39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 27 Oct 2022 11:09:19 +0200
Subject: [PATCH 0289/1027] ASoC: macaudio: Add initial j313 fixup_controls
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 3ccfacefbf7e85..a3da4ec0dae6a0 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -745,6 +745,36 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
 	return 0;	
 }
 
+static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (ma->has_speakers) {
+		if (!please_blow_up_my_speakers) {
+			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
+			return -EINVAL;
+		}
+
+		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
+		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
+
+		/* !!! This is copied from j274, not obtained by looking at
+		 *     what macOS sets.
+		 */
+		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14);
+
+		/*
+		 * Since we don't set the right slots yet to avoid
+		 * driver conflict on the I2S bus sending ISENSE/VSENSE
+		 * samples from the codecs back to us, disable the
+		 * controls.
+		 */
+		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
+		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+	}
+
+	return 0;
+}
+
 static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -887,6 +917,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 
 static const struct of_device_id macaudio_snd_device_id[]  = {
 	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
+	{ .compatible = "apple,j313-macaudio", .data = macaudio_j313_fixup_controls },
 	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
 	{ .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls },

From 1b7979b58146ceb26895aade5b7517e31490dd91 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Mon, 24 Oct 2022 21:17:31 +1000
Subject: [PATCH 0290/1027] ASoC: macaudio: constrain frontend channel counts

In order to support the wide range of audio arrangements possible
on this platform in a generic way, it is necessary for the frontend
PCMs to be populated with enough TDM slots to cover all intended use
cases. Userspace therefore attempts to open "phantom" channels
when a frontend has more channels than its associated backend, which
results in garbled audio samples and dropped frames.

We must therefore dynamically constrain the frontends when they
are started to ensure that userspace can never open more channels
than are present on the hardware being represented by the
frontend in question.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/apple/macaudio.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index a3da4ec0dae6a0..e24006ca79c8fb 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -52,6 +52,7 @@ struct macaudio_snd_data {
 	int jack_plugin_state;
 
 	bool has_speakers;
+	unsigned int max_channels;
 
 	struct macaudio_link_props {
 		/* frontend props */
@@ -345,6 +346,10 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 		ncodecs_per_cpu = num_codecs / num_bes;
 		nchannels = num_codecs * (speakers ? 1 : 2);
 
+		/* Save the max number of channels on the platform */
+		if (nchannels > ma->max_channels)
+			ma->max_channels = nchannels;
+
 		/*
 		 * If there is a single speaker, assign two channels to it, because
 		 * it can do downmix.
@@ -455,6 +460,25 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
 	return 0;
 }
 
+static int macaudio_fe_startup(struct snd_pcm_substream *substream)
+{
+
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	int ret;
+
+	/* The FEs must never have more channels than the hardware */
+	ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+					SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels);
+
+	if (ret < 0) {
+		dev_err(rtd->dev, "Failed to constrain FE %d! %d", rtd->dai_link->id, ret);
+		return ret;
+		}
+
+	return 0;
+}
+
 static int macaudio_fe_hw_params(struct snd_pcm_substream *substream,
 				   struct snd_pcm_hw_params *params)
 {
@@ -495,6 +519,7 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
 }
 
 static const struct snd_soc_ops macaudio_fe_ops = {
+	.startup	= macaudio_fe_startup,
 	.shutdown	= macaudio_dpcm_shutdown,
 	.hw_params	= macaudio_fe_hw_params,
 };

From 08e9f9d365a298d2e8d39910a72ffd6c51c88092 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 15 Jan 2023 22:20:38 +0900
Subject: [PATCH 0291/1027] ASoC: cs42l84: Do not enable the PLL before the
 clocks are ready

Enabling the PLL with no valid input clock leads to PLL errors. Don't do
that, always disable the PLL before reconfiguring it and let unmute take
care of enabling the PLL.

Also monitor the PLL error bit and complain if it's set.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/cs42l84.c | 9 +++++----
 sound/soc/codecs/cs42l84.h | 1 +
 2 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index f2784946b6900d..cc4dff97e7a42d 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -433,6 +433,8 @@ static int cs42l84_pll_config(struct snd_soc_component *component)
 		break;
 	}
 
+	snd_soc_component_update_bits(component, CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_EN, 0);
+
 	if (pll_ratio_table[i].mclk_src_sel) {
 		/* Configure PLL */
 		snd_soc_component_update_bits(component,
@@ -456,10 +458,6 @@ static int cs42l84_pll_config(struct snd_soc_component *component)
 		snd_soc_component_write(component,
 			CS42L84_PLL_DIVOUT,
 			pll_ratio_table[i].pll_divout);
-
-		snd_soc_component_update_bits(component,
-			CS42L84_PLL_CTL1, CS42L84_PLL_CTL1_EN,
-			CS42L84_PLL_CTL1_EN);
 	}
 
 	return 0;
@@ -633,6 +631,9 @@ static int cs42l84_mute_stream(struct snd_soc_dai *dai, int mute, int stream)
 				if (ret < 0)
 					dev_warn(component->dev, "PLL failed to lock: %d\n", ret);
 
+				if (regval & CS42L84_PLL_LOCK_STATUS_ERROR)
+					dev_warn(component->dev, "PLL lock error\n");
+
 				/* PLL must be running to drive glitchless switch logic */
 				snd_soc_component_update_bits(component,
 					CS42L84_CCM_CTL1,
diff --git a/sound/soc/codecs/cs42l84.h b/sound/soc/codecs/cs42l84.h
index 9aaf19051d395f..35bd15e2ef17c9 100644
--- a/sound/soc/codecs/cs42l84.h
+++ b/sound/soc/codecs/cs42l84.h
@@ -31,6 +31,7 @@
 #define CS42L84_TSRS_PLUG_VAL_MASK		GENMASK(3, 0)
 #define CS42L84_PLL_LOCK_STATUS			0x040e // probably bit 0x10
 #define CS42L84_PLL_LOCK_STATUS_LOCKED		BIT(4)
+#define CS42L84_PLL_LOCK_STATUS_ERROR		BIT(5)
 
 #define CS42L84_PLUG				3
 #define CS42L84_UNPLUG				0

From 176de8b396775656b90fa5fb9d92e49a6aa4207c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 16 Apr 2023 19:27:40 +0900
Subject: [PATCH 0292/1027] ASoC: cs42l42: Set a faster digital ramp-up rate

With the default ramp-up rate, there is a noticeable fade-in when
streams start. This can be undesirable with aggressive muting for power
saving, since the beginning of the stream is lost.

Lower the digital output ramp-up time from 8 samples per period to 2
samples per period. This still leaves some fade-in to avoid pops, but it
is a lot less noticeable and no longer feels like the stream is fading
in.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 include/sound/cs42l42.h    |  4 ++++
 sound/soc/codecs/cs42l42.c | 10 ++++++++++
 2 files changed, 14 insertions(+)

diff --git a/include/sound/cs42l42.h b/include/sound/cs42l42.h
index 1bd8eee54f6665..b3657965d49109 100644
--- a/include/sound/cs42l42.h
+++ b/include/sound/cs42l42.h
@@ -62,6 +62,10 @@
 #define CS42L42_INTERNAL_FS_MASK	(1 << CS42L42_INTERNAL_FS_SHIFT)
 
 #define CS42L42_SFTRAMP_RATE		(CS42L42_PAGE_10 + 0x0A)
+#define CS42L42_SFTRAMP_ASR_RATE_MASK	GENMASK(7, 4)
+#define CS42L42_SFTRAMP_ASR_RATE_SHIFT	4
+#define CS42L42_SFTRAMP_DSR_RATE_MASK	GENMASK(3, 0)
+#define CS42L42_SFTRAMP_DSR_RATE_SHIFT	0
 #define CS42L42_SLOW_START_ENABLE	(CS42L42_PAGE_10 + 0x0B)
 #define CS42L42_SLOW_START_EN_MASK	GENMASK(6, 4)
 #define CS42L42_SLOW_START_EN_SHIFT	4
diff --git a/sound/soc/codecs/cs42l42.c b/sound/soc/codecs/cs42l42.c
index 6ae740a21106d6..27ce23bff76f2f 100644
--- a/sound/soc/codecs/cs42l42.c
+++ b/sound/soc/codecs/cs42l42.c
@@ -2419,6 +2419,16 @@ int cs42l42_init(struct cs42l42_private *cs42l42)
 			(1 << CS42L42_ADC_PDN_SHIFT) |
 			(0 << CS42L42_PDN_ALL_SHIFT));
 
+	/*
+	 * Configure a faster digital ramp time, to avoid an audible
+	 * fade-in when streams start.
+	 */
+	regmap_update_bits(cs42l42->regmap, CS42L42_SFTRAMP_RATE,
+			   CS42L42_SFTRAMP_ASR_RATE_MASK |
+			   CS42L42_SFTRAMP_DSR_RATE_MASK,
+			   (10 << CS42L42_SFTRAMP_ASR_RATE_SHIFT) |
+			   (1 << CS42L42_SFTRAMP_DSR_RATE_SHIFT));
+
 	ret = cs42l42_handle_device_data(cs42l42->dev, cs42l42);
 	if (ret != 0)
 		goto err_shutdown;

From 0a62999a7de065a04058fc19fab1b17b3c6d3aef Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 16 Apr 2023 18:53:40 +0900
Subject: [PATCH 0293/1027] ASoC: apple: mca: Move clock shutdown to be
 shutdown

Codecs are set to mute after hw_free, so yanking the clock out from
under them in hw_free leads to breakage. Move the clock shutdown to the
shutdown op, which is late enough.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 48 ++++++++++++++++++-------------------------
 1 file changed, 20 insertions(+), 28 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index fb23cb27b00ad7..477819e818811f 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -355,33 +355,6 @@ static int mca_be_prepare(struct snd_pcm_substream *substream,
 	return 0;
 }
 
-static int mca_be_hw_free(struct snd_pcm_substream *substream,
-			  struct snd_soc_dai *dai)
-{
-	struct mca_cluster *cl = mca_dai_to_cluster(dai);
-	struct mca_data *mca = cl->host;
-	struct mca_cluster *fe_cl;
-
-	if (cl->port_driver < 0)
-		return -EINVAL;
-
-	/*
-	 * We are operating on a foreign cluster here, but since we
-	 * belong to the same PCM, accesses should have been
-	 * synchronized at ASoC level.
-	 */
-	fe_cl = &mca->clusters[cl->port_driver];
-	if (!mca_fe_clocks_in_use(fe_cl))
-		return 0; /* Nothing to do */
-
-	cl->clocks_in_use[substream->stream] = false;
-
-	if (!mca_fe_clocks_in_use(fe_cl))
-		mca_fe_disable_clocks(fe_cl);
-
-	return 0;
-}
-
 static unsigned int mca_crop_mask(unsigned int mask, int nchans)
 {
 	while (hweight32(mask) > nchans)
@@ -779,6 +752,26 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
 	struct mca_cluster *cl = mca_dai_to_cluster(dai);
 	struct mca_data *mca = cl->host;
 
+	if (cl->clocks_in_use[substream->stream] &&
+		!WARN_ON(cl->port_driver < 0)) {
+		struct mca_cluster *fe_cl = &mca->clusters[cl->port_driver];
+
+		/*
+		 * Typically the CODECs we are paired with will require clocks
+		 * to be present at time of mute with the 'mute_stream' op.
+		 * We need to disable the clocks here at the earliest (hw_free
+		 * would be too early).
+		 *
+		 * We are operating on a foreign cluster here, but since we
+		 * belong to the same PCM, accesses should have been
+		 * synchronized at ASoC level.
+		 */
+		cl->clocks_in_use[substream->stream] = false;
+
+		if (!mca_fe_clocks_in_use(fe_cl))
+			mca_fe_disable_clocks(fe_cl);
+	}
+
 	cl->port_started[substream->stream] = false;
 
 	if (!mca_be_started(cl)) {
@@ -796,7 +789,6 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
 
 static const struct snd_soc_dai_ops mca_be_ops = {
 	.prepare = mca_be_prepare,
-	.hw_free = mca_be_hw_free,
 	.startup = mca_be_startup,
 	.shutdown = mca_be_shutdown,
 };

From 352b7ca98e96c99077981bc6d8f77a7ca180e14b Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 3 Sep 2023 17:09:59 +1000
Subject: [PATCH 0294/1027] ASoC: macaudio: alias j415 kcontrols to j314

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/apple/macaudio.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index e24006ca79c8fb..b5543f9caf44c1 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -946,6 +946,7 @@ static const struct of_device_id macaudio_snd_device_id[]  = {
 	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
 	{ .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls },
+	{ .compatible = "apple,j415-macaudio", .data = macaudio_j314_fixup_controls },
 	{ .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls },
 	{ .compatible = "apple,macaudio"},
 	{ }

From f3a8053ab5ea964497b57fbec934b93cd58aa468 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 20 Oct 2023 03:35:24 +0900
Subject: [PATCH 0295/1027] ASoC: cs42l84: Fix capture gain range & add TLVs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/cs42l84.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index cc4dff97e7a42d..d8fc7bdb89bb00 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -186,15 +186,17 @@ static int cs42l84_get_dac_vol(struct snd_kcontrol *kctl,
 
 /* TODO */
 static const DECLARE_TLV_DB_SCALE(cs42l84_dac_tlv, -12800, 50, true);
+static const DECLARE_TLV_DB_SCALE(cs42l84_adc_tlv, -1200, 50, false);
+static const DECLARE_TLV_DB_SCALE(cs42l84_pre_tlv, 0, 1000, false);
 
 static const struct snd_kcontrol_new cs42l84_snd_controls[] = {
 	SOC_DOUBLE_R_S_EXT_TLV("DAC Playback Volume", CS42L84_DAC_CHA_VOL_LSB,
 			CS42L84_DAC_CHB_VOL_LSB, 0, -256, 24, 8, 0,
 			cs42l84_get_dac_vol, cs42l84_put_dac_vol, cs42l84_dac_tlv),
-	SOC_SINGLE("ADC Preamp Gain", CS42L84_ADC_CTL1,
-			CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT, 2, 0),
-	SOC_SINGLE("ADC PGA Gain", CS42L84_ADC_CTL1,
-			CS42L84_ADC_CTL1_PGA_GAIN_SHIFT, 31, 0),
+	SOC_SINGLE_TLV("ADC Preamp Capture Volume", CS42L84_ADC_CTL1,
+			CS42L84_ADC_CTL1_PREAMP_GAIN_SHIFT, 2, 0, cs42l84_pre_tlv),
+	SOC_SINGLE_TLV("ADC PGA Capture Volume", CS42L84_ADC_CTL1,
+			CS42L84_ADC_CTL1_PGA_GAIN_SHIFT, 24, 0, cs42l84_adc_tlv),
 	SOC_SINGLE("ADC WNF Switch", CS42L84_ADC_CTL4,
 			CS42L84_ADC_CTL4_WNF_EN_SHIFT, 1, 0),
 	SOC_SINGLE("WNF Corner Frequency", CS42L84_ADC_CTL4,

From 88ea18cecfc9e23580bfeb52d4eb07021154fce0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 9 Oct 2023 20:45:36 +0900
Subject: [PATCH 0296/1027] ALSA: control: Add kcontrol callbacks for
 lock/unlock

This allows drivers to implement policy around locking/unlocking
controls, such as enforcing that a group of controls may only be locked
by the same process/file, and taking actions when the controls
lock/unlock (such as granting special access on lock and resetting
values on unlock).

This is, in particular, useful to implement volume safety controls, such
that only a particular process (that locks controls and completes a
handshake) may increase volumes above a given safe limit. It also allows
the volume to be automatically lowered if that process dies (which will
trigger an implicit unlock).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 include/sound/control.h |  7 +++++++
 sound/core/control.c    | 16 ++++++++++++++--
 2 files changed, 21 insertions(+), 2 deletions(-)

diff --git a/include/sound/control.h b/include/sound/control.h
index c1659036c4a778..8a9f51e34a03a7 100644
--- a/include/sound/control.h
+++ b/include/sound/control.h
@@ -14,9 +14,12 @@
 #define snd_kcontrol_chip(kcontrol) ((kcontrol)->private_data)
 
 struct snd_kcontrol;
+struct snd_ctl_file;
 typedef int (snd_kcontrol_info_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_info * uinfo);
 typedef int (snd_kcontrol_get_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
 typedef int (snd_kcontrol_put_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_elem_value * ucontrol);
+typedef int (snd_kcontrol_lock_t) (struct snd_kcontrol * kcontrol, struct snd_ctl_file *owner);
+typedef void (snd_kcontrol_unlock_t) (struct snd_kcontrol * kcontrol);
 typedef int (snd_kcontrol_tlv_rw_t)(struct snd_kcontrol *kcontrol,
 				    int op_flag, /* SNDRV_CTL_TLV_OP_XXX */
 				    unsigned int size,
@@ -55,6 +58,8 @@ struct snd_kcontrol_new {
 	snd_kcontrol_info_t *info;
 	snd_kcontrol_get_t *get;
 	snd_kcontrol_put_t *put;
+	snd_kcontrol_lock_t *lock;
+	snd_kcontrol_unlock_t *unlock;
 	union {
 		snd_kcontrol_tlv_rw_t *c;
 		const unsigned int *p;
@@ -74,6 +79,8 @@ struct snd_kcontrol {
 	snd_kcontrol_info_t *info;
 	snd_kcontrol_get_t *get;
 	snd_kcontrol_put_t *put;
+	snd_kcontrol_lock_t *lock;
+	snd_kcontrol_unlock_t *unlock;
 	union {
 		snd_kcontrol_tlv_rw_t *c;
 		const unsigned int *p;
diff --git a/sound/core/control.c b/sound/core/control.c
index f64a555f404f0a..a8dce2ce311a7d 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -123,10 +123,12 @@ static int snd_ctl_release(struct inode *inode, struct file *file)
 	scoped_guard(rwsem_write, &card->controls_rwsem) {
 		list_for_each_entry(control, &card->controls, list)
 			for (idx = 0; idx < control->count; idx++)
-				if (control->vd[idx].owner == ctl)
+				if (control->vd[idx].owner == ctl) {
 					control->vd[idx].owner = NULL;
+					if (control->unlock)
+						control->unlock(control);
+				}
 	}
-
 	snd_fasync_free(ctl->fasync);
 	snd_ctl_empty_read_queue(ctl);
 	put_pid(ctl->pid);
@@ -303,6 +305,8 @@ struct snd_kcontrol *snd_ctl_new1(const struct snd_kcontrol_new *ncontrol,
 	kctl->info = ncontrol->info;
 	kctl->get = ncontrol->get;
 	kctl->put = ncontrol->put;
+	kctl->lock = ncontrol->lock;
+	kctl->unlock = ncontrol->unlock;
 	kctl->tlv.p = ncontrol->tlv.p;
 
 	kctl->private_value = ncontrol->private_value;
@@ -1387,6 +1391,12 @@ static int snd_ctl_elem_lock(struct snd_ctl_file *file,
 	vd = &kctl->vd[snd_ctl_get_ioff(kctl, &id)];
 	if (vd->owner)
 		return -EBUSY;
+
+	if (kctl->lock) {
+		int err = kctl->lock(kctl, file);
+		if (err < 0)
+			return err;
+	}
 	vd->owner = file;
 	return 0;
 }
@@ -1411,6 +1421,8 @@ static int snd_ctl_elem_unlock(struct snd_ctl_file *file,
 	if (vd->owner != file)
 		return -EPERM;
 	vd->owner = NULL;
+	if (kctl->unlock)
+		kctl->unlock(kctl);
 	return 0;
 }
 

From c6858ee1efc0a1b5f2653328f9479df0154238f9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 19 Jan 2023 07:45:47 +0100
Subject: [PATCH 0297/1027] ASoC: macaudio: Condition selecting NCO driver on
 COMMON_CLK
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Only select the NCO driver's symbol if COMMON_CLK is selected, otherwise
we risk misconfiguration.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index 992e416108be5f..9fb902340fff6e 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -13,7 +13,7 @@ config SND_SOC_APPLE_MACAUDIO
 	select SND_SOC_APPLE_MCA
 	select SND_SIMPLE_CARD_UTILS
 	select APPLE_ADMAC
-	select COMMON_CLK_APPLE_NCO
+	select COMMON_CLK_APPLE_NCO if COMMON_CLK
 	select SND_SOC_TAS2764
 	select SND_SOC_TAS2770
 	select SND_SOC_CS42L83

From a605e75a5ae831e1b2e1ba397bb9c962075c976f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 20 Jan 2023 20:59:52 +0100
Subject: [PATCH 0298/1027] ASoC: macaudio: Tune DT parsing error messages
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 48 ++++++++++++++++++++++++--------------
 1 file changed, 31 insertions(+), 17 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index b5543f9caf44c1..83cef7eda7f1bd 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -206,12 +206,14 @@ static int macaudio_parse_of_be_dai_link(struct macaudio_snd_data *ma,
 	for_each_link_codecs(link, i, comp) {
 		ret = macaudio_parse_of_component(codec, codec_base + i, comp);
 		if (ret)
-			return ret;
+			return dev_err_probe(ma->card.dev, ret, "parsing CODEC DAI of link '%s' at %pOF\n",
+					     link->name, codec);
 	}
 
 	ret = macaudio_parse_of_component(cpu, be_index, link->cpus);
 	if (ret)
-		return ret;
+		return dev_err_probe(ma->card.dev, ret, "parsing CPU DAI of link '%s' at %pOF\n",
+				     link->name, codec);
 
 	link->name = link->cpus[0].dai_name;
 
@@ -232,7 +234,7 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 
 	ret = snd_soc_of_parse_card_name(card, "model");
 	if (ret) {
-		dev_err(dev, "Error parsing card name: %d\n", ret);
+		dev_err_probe(dev, ret, "parsing card name\n");
 		return ret;
 	}
 
@@ -245,8 +247,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 
 		cpu = of_get_child_by_name(np, "cpu");
 		if (!cpu) {
-			dev_err(dev, "missing CPU DAI node at %pOF\n", np);
-			ret = -EINVAL;
+			ret = dev_err_probe(dev, -EINVAL,
+				"missing CPU DAI node at %pOF\n", np);;
 			goto err_free;
 		}
 
@@ -254,8 +256,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 						"#sound-dai-cells");
 
 		if (num_cpus <= 0) {
-			dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
-			ret = -EINVAL;
+			ret = dev_err_probe(card->dev, -EINVAL,
+				"missing sound-dai property at %pOF\n", cpu);
 			goto err_free;
 		}
 		of_node_put(cpu);
@@ -296,10 +298,12 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 
 		ret = of_property_read_string(np, "link-name", &link_name);
 		if (ret) {
-			dev_err(card->dev, "missing link name\n");
+			dev_err_probe(card->dev, ret, "missing link name\n");
 			goto err_free;
 		}
 
+		dev_dbg(ma->card.dev, "parsing link '%s'\n", link_name);
+
 		speakers = !strcmp(link_name, "Speaker")
 			   || !strcmp(link_name, "Speakers");
 		if (speakers)
@@ -309,31 +313,34 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 		codec = of_get_child_by_name(np, "codec");
 
 		if (!codec || !cpu) {
-			dev_err(dev, "missing DAI specifications for '%s'\n", link_name);
-			ret = -EINVAL;
+			ret = dev_err_probe(dev, -EINVAL,
+				"missing DAI specifications for '%s'\n", link_name);
 			goto err_free;
 		}
 
 		num_bes = of_count_phandle_with_args(cpu, "sound-dai",
 						     "#sound-dai-cells");
 		if (num_bes <= 0) {
-			dev_err(card->dev, "missing sound-dai property at %pOF\n", cpu);
-			ret = -EINVAL;
+			ret = dev_err_probe(card->dev, -EINVAL,
+				"missing sound-dai property at %pOF\n", cpu);
 			goto err_free;
 		}
 
 		num_codecs = of_count_phandle_with_args(codec, "sound-dai",
 							"#sound-dai-cells");
 		if (num_codecs <= 0) {
-			dev_err(card->dev, "missing sound-dai property at %pOF\n", codec);
-			ret = -EINVAL;
+			ret = dev_err_probe(card->dev, -EINVAL,
+				"missing sound-dai property at %pOF\n", codec);
 			goto err_free;
 		}
 
+		dev_dbg(ma->card.dev, "link '%s': %d CPUs %d CODECs\n",
+			link_name, num_bes, num_codecs);
+
 		if (num_codecs % num_bes != 0) {
-			dev_err(card->dev, "bad combination of CODEC (%d) and CPU (%d) number at %pOF\n",
+			ret = dev_err_probe(card->dev, -EINVAL,
+				"bad combination of CODEC (%d) and CPU (%d) number at %pOF\n",
 				num_codecs, num_bes, np);
-			ret = -EINVAL;
 			goto err_free;
 		}
 
@@ -363,6 +370,13 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 		right_mask = left_mask << 1;
 
 		for (be_index = 0; be_index < num_bes; be_index++) {
+			/*
+			 * Set initial link name to be overwritten by a BE-specific
+			 * name later so that we can use at least use the provisional
+			 * name in error messages.
+			 */
+			link->name = link_name;
+
 			ret = macaudio_parse_of_be_dai_link(ma, link, be_index,
 							    ncodecs_per_cpu, cpu, codec);
 			if (ret)
@@ -994,7 +1008,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 
 	ret = macaudio_parse_of(data);
 	if (ret)
-		return dev_err_probe(&pdev->dev, ret, "failed OF parsing\n");
+		return ret;
 
 	for_each_card_prelinks(card, i, link) {
 		if (link->no_pcm) {

From 5504358ad54ea5c2e6878a87166ae0036a2c708c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 16 Sep 2022 13:43:25 +0200
Subject: [PATCH 0299/1027] ASoC: apple: mca: Separate data & clock port setup
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Up until now FEs were always the clock providers -- feeding the clocks
on any ports (BEs) they are attached to. This will soon change and FEs
will be allowed to be clock consumers. Once that happens, the routing
of clocks and data will to some degree decouple.

In advance of the change, make preparations:

 * Narrow down semantics of what was formerly the 'port_driver' field
   to refer to clocks only.

 * On 'startup' of BEs, separate the clock and data aspects of the port
   setup.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 67 ++++++++++++++++++++++++++-----------------
 1 file changed, 40 insertions(+), 27 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 477819e818811f..72235a9f1e7c6a 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -133,8 +133,8 @@ struct mca_cluster {
 	struct clk *clk_parent;
 	struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1];
 
-	bool port_started[SNDRV_PCM_STREAM_LAST + 1];
-	int port_driver; /* The cluster driving this cluster's port */
+	bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1];
+	int port_clk_driver; /* The cluster driving this cluster's port */
 
 	bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1];
 	struct device_link *pd_link;
@@ -157,7 +157,7 @@ struct mca_data {
 	struct reset_control *rstc;
 	struct device_link *pd_link;
 
-	/* Mutex for accessing port_driver of foreign clusters */
+	/* Mutex for accessing port_clk_driver of foreign clusters */
 	struct mutex port_mutex;
 
 	int nclusters;
@@ -311,7 +311,7 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl)
 	for (i = 0; i < mca->nclusters; i++) {
 		be_cl = &mca->clusters[i];
 
-		if (be_cl->port_driver != cl->no)
+		if (be_cl->port_clk_driver != cl->no)
 			continue;
 
 		for_each_pcm_streams(stream) {
@@ -333,10 +333,10 @@ static int mca_be_prepare(struct snd_pcm_substream *substream,
 	struct mca_cluster *fe_cl;
 	int ret;
 
-	if (cl->port_driver < 0)
+	if (cl->port_clk_driver < 0)
 		return -EINVAL;
 
-	fe_cl = &mca->clusters[cl->port_driver];
+	fe_cl = &mca->clusters[cl->port_clk_driver];
 
 	/*
 	 * Typically the CODECs we are paired with will require clocks
@@ -683,12 +683,15 @@ static const struct snd_soc_dai_ops mca_fe_ops = {
 	.trigger = mca_fe_trigger,
 };
 
-static bool mca_be_started(struct mca_cluster *cl)
+/*
+ * Is there a FE attached which will be feeding this port's clocks?
+ */
+static bool mca_be_clk_started(struct mca_cluster *cl)
 {
 	int stream;
 
 	for_each_pcm_streams(stream)
-		if (cl->port_started[stream])
+		if (cl->port_clk_started[stream])
 			return true;
 	return false;
 }
@@ -719,29 +722,35 @@ static int mca_be_startup(struct snd_pcm_substream *substream,
 
 	fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0));
 
-	if (mca_be_started(cl)) {
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no),
+			       cl->base + REG_PORT_DATA_SEL);
+		mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA,
+			   PORT_ENABLES_TX_DATA);
+	}
+
+	if (mca_be_clk_started(cl)) {
 		/*
 		 * Port is already started in the other direction.
 		 * Make sure there isn't a conflict with another cluster
-		 * driving the port.
+		 * driving the port clocks.
 		 */
-		if (cl->port_driver != fe_cl->no)
+		if (cl->port_clk_driver != fe_cl->no)
 			return -EINVAL;
 
-		cl->port_started[substream->stream] = true;
+		cl->port_clk_started[substream->stream] = true;
 		return 0;
 	}
 
-	writel_relaxed(PORT_ENABLES_CLOCKS | PORT_ENABLES_TX_DATA,
-		       cl->base + REG_PORT_ENABLES);
 	writel_relaxed(FIELD_PREP(PORT_CLOCK_SEL, fe_cl->no + 1),
 		       cl->base + REG_PORT_CLOCK_SEL);
-	writel_relaxed(PORT_DATA_SEL_TXA(fe_cl->no),
-		       cl->base + REG_PORT_DATA_SEL);
+	mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS,
+		   PORT_ENABLES_CLOCKS);
+
 	mutex_lock(&mca->port_mutex);
-	cl->port_driver = fe_cl->no;
+	cl->port_clk_driver = fe_cl->no;
 	mutex_unlock(&mca->port_mutex);
-	cl->port_started[substream->stream] = true;
+	cl->port_clk_started[substream->stream] = true;
 
 	return 0;
 }
@@ -753,8 +762,8 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
 	struct mca_data *mca = cl->host;
 
 	if (cl->clocks_in_use[substream->stream] &&
-		!WARN_ON(cl->port_driver < 0)) {
-		struct mca_cluster *fe_cl = &mca->clusters[cl->port_driver];
+		!WARN_ON(cl->port_clk_driver < 0)) {
+		struct mca_cluster *fe_cl = &mca->clusters[cl->port_clk_driver];
 
 		/*
 		 * Typically the CODECs we are paired with will require clocks
@@ -772,17 +781,21 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
 			mca_fe_disable_clocks(fe_cl);
 	}
 
-	cl->port_started[substream->stream] = false;
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0);
+		writel_relaxed(0, cl->base + REG_PORT_DATA_SEL);
+	}
 
-	if (!mca_be_started(cl)) {
+	cl->port_clk_started[substream->stream] = false;
+	if (!mca_be_clk_started(cl)) {
 		/*
 		 * Were we the last direction to shutdown?
-		 * Turn off the lights.
+		 * Turn off the lights (clocks).
 		 */
-		writel_relaxed(0, cl->base + REG_PORT_ENABLES);
-		writel_relaxed(0, cl->base + REG_PORT_DATA_SEL);
+		mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_CLOCKS, 0);
+		writel_relaxed(0, cl->base + REG_PORT_CLOCK_SEL);
 		mutex_lock(&mca->port_mutex);
-		cl->port_driver = -1;
+		cl->port_clk_driver = -1;
 		mutex_unlock(&mca->port_mutex);
 	}
 }
@@ -1088,7 +1101,7 @@ static int apple_mca_probe(struct platform_device *pdev)
 		cl->host = mca;
 		cl->no = i;
 		cl->base = base + CLUSTER_STRIDE * i;
-		cl->port_driver = -1;
+		cl->port_clk_driver = -1;
 		cl->clk_parent = of_clk_get(pdev->dev.of_node, i);
 		if (IS_ERR(cl->clk_parent)) {
 			dev_err(&pdev->dev, "unable to obtain clock %d: %ld\n",

From 9c12c7877b22caf603c7bcb5ffb0a782731532df Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 16 Sep 2022 14:18:16 +0200
Subject: [PATCH 0300/1027] ASoC: apple: mca: Factor out mca_be_get_fe
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is a function that we also want to use from within mca_be_shutdown,
so factor it out.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 31 ++++++++++++++++++-------------
 1 file changed, 18 insertions(+), 13 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 72235a9f1e7c6a..25c2e9cd3deaec 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -696,30 +696,35 @@ static bool mca_be_clk_started(struct mca_cluster *cl)
 	return false;
 }
 
-static int mca_be_startup(struct snd_pcm_substream *substream,
-			  struct snd_soc_dai *dai)
+static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be,
+						 int stream)
 {
-	struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream);
-	struct snd_soc_pcm_runtime *fe;
-	struct mca_cluster *cl = mca_dai_to_cluster(dai);
-	struct mca_cluster *fe_cl;
-	struct mca_data *mca = cl->host;
+	struct snd_soc_pcm_runtime *fe = NULL;
 	struct snd_soc_dpcm *dpcm;
 
-	fe = NULL;
-
-	for_each_dpcm_fe(be, substream->stream, dpcm) {
+	for_each_dpcm_fe(be, stream, dpcm) {
 		if (fe && dpcm->fe != fe) {
-			dev_err(mca->dev, "many FE per one BE unsupported\n");
-			return -EINVAL;
+			dev_err(be->dev, "many FE per one BE unsupported\n");
+			return NULL;
 		}
 
 		fe = dpcm->fe;
 	}
 
+	return fe;
+}
+
+static int mca_be_startup(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream);
+	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+	struct mca_cluster *fe_cl;
+	struct mca_data *mca = cl->host;
+
 	if (!fe)
 		return -EINVAL;
-
 	fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0));
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {

From b083fb4ee3c7cb8a0bba32124c25d555f3647701 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 16 Sep 2022 14:25:04 +0200
Subject: [PATCH 0301/1027] ASoC: apple: mca: Support FEs being clock consumers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Support FEs being I2S clock consumers. This does not mean we support
accepting clocks from outside the SoC (although it paves the way for
that support in the future), but it means multiple FEs can attach to one
BE, one being clock producer and the rest clock consumers.

This is useful for grabbing I/V sense data on some machines, since in
such a scenario the format of the sense data on the I2S bus differs
from that of the audio data (the two formats differing in slot width).
With two FEs attached to the bus, we can split the responsibilities and
command different slot widths to the two.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 109 ++++++++++++++++++++++++++++++++++--------
 1 file changed, 88 insertions(+), 21 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 25c2e9cd3deaec..89320430afc424 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -133,6 +133,8 @@ struct mca_cluster {
 	struct clk *clk_parent;
 	struct dma_chan *dma_chans[SNDRV_PCM_STREAM_LAST + 1];
 
+	bool clk_provider;
+
 	bool port_clk_started[SNDRV_PCM_STREAM_LAST + 1];
 	int port_clk_driver; /* The cluster driving this cluster's port */
 
@@ -256,11 +258,32 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd,
 	return 0;
 }
 
+static int mca_fe_get_port(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_pcm_runtime *be;
+	struct snd_soc_dpcm *dpcm;
+
+	be = NULL;
+	for_each_dpcm_be(fe, substream->stream, dpcm) {
+		be = dpcm->be;
+		break;
+	}
+
+	if (!be)
+		return -EINVAL;
+
+	return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no;
+}
+
 static int mca_fe_enable_clocks(struct mca_cluster *cl)
 {
 	struct mca_data *mca = cl->host;
 	int ret;
 
+	if (!cl->clk_provider)
+		return -EINVAL;
+
 	ret = clk_prepare_enable(cl->clk_parent);
 	if (ret) {
 		dev_err(mca->dev,
@@ -334,7 +357,7 @@ static int mca_be_prepare(struct snd_pcm_substream *substream,
 	int ret;
 
 	if (cl->port_clk_driver < 0)
-		return -EINVAL;
+		return 0;
 
 	fe_cl = &mca->clusters[cl->port_clk_driver];
 
@@ -355,6 +378,44 @@ static int mca_be_prepare(struct snd_pcm_substream *substream,
 	return 0;
 }
 
+static int mca_fe_prepare(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+	struct mca_data *mca = cl->host;
+
+	if (cl->clk_provider)
+		return 0;
+
+	if (!mca_fe_clocks_in_use(cl)) {
+		int port = mca_fe_get_port(substream);
+		writel_relaxed(port + 6 + 1,
+			       cl->base + REG_SYNCGEN_MCLK_SEL);
+		mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN,
+			   SYNCGEN_STATUS_EN);
+	}
+	cl->clocks_in_use[substream->stream] = true;
+
+	return 0;
+}
+
+static int mca_fe_hw_free(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+
+	if (cl->clk_provider)
+		return 0;
+
+	cl->clocks_in_use[substream->stream] = false;
+	if (mca_fe_clocks_in_use(cl))
+		return 0;
+
+	mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0);
+
+	return 0;
+}
+
 static unsigned int mca_crop_mask(unsigned int mask, int nchans)
 {
 	while (hweight32(mask) > nchans)
@@ -480,9 +541,18 @@ static int mca_fe_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	u32 serdes_conf = 0;
 	u32 bitstart;
 
-	if ((fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) !=
-	    SND_SOC_DAIFMT_BP_FP)
+	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
+	case SND_SOC_DAIFMT_BP_FP:
+		cl->clk_provider = true;
+		break;
+
+	case SND_SOC_DAIFMT_BC_FC:
+		cl->clk_provider = false;
+		break;
+
+	default:
 		goto err;
+	}
 
 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 	case SND_SOC_DAIFMT_I2S:
@@ -539,24 +609,6 @@ static int mca_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
 	return 0;
 }
 
-static int mca_fe_get_port(struct snd_pcm_substream *substream)
-{
-	struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream);
-	struct snd_soc_pcm_runtime *be;
-	struct snd_soc_dpcm *dpcm;
-
-	be = NULL;
-	for_each_dpcm_be(fe, substream->stream, dpcm) {
-		be = dpcm->be;
-		break;
-	}
-
-	if (!be)
-		return -EINVAL;
-
-	return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no;
-}
-
 static int mca_fe_hw_params(struct snd_pcm_substream *substream,
 			    struct snd_pcm_hw_params *params,
 			    struct snd_soc_dai *dai)
@@ -681,6 +733,8 @@ static const struct snd_soc_dai_ops mca_fe_ops = {
 	.set_tdm_slot = mca_fe_set_tdm_slot,
 	.hw_params = mca_fe_hw_params,
 	.trigger = mca_fe_trigger,
+	.prepare = mca_fe_prepare,
+	.hw_free = mca_fe_hw_free,
 };
 
 /*
@@ -734,6 +788,9 @@ static int mca_be_startup(struct snd_pcm_substream *substream,
 			   PORT_ENABLES_TX_DATA);
 	}
 
+	if (!fe_cl->clk_provider)
+		return 0;
+
 	if (mca_be_clk_started(cl)) {
 		/*
 		 * Port is already started in the other direction.
@@ -763,7 +820,10 @@ static int mca_be_startup(struct snd_pcm_substream *substream,
 static void mca_be_shutdown(struct snd_pcm_substream *substream,
 			    struct snd_soc_dai *dai)
 {
+	struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream);
 	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+	struct mca_cluster *fe_cl;
 	struct mca_data *mca = cl->host;
 
 	if (cl->clocks_in_use[substream->stream] &&
@@ -786,11 +846,18 @@ static void mca_be_shutdown(struct snd_pcm_substream *substream,
 			mca_fe_disable_clocks(fe_cl);
 	}
 
+	if (!fe)
+		return;
+	fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0));
+
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
 		mca_modify(cl, REG_PORT_ENABLES, PORT_ENABLES_TX_DATA, 0);
 		writel_relaxed(0, cl->base + REG_PORT_DATA_SEL);
 	}
 
+	if (!fe_cl->clk_provider)
+		return;
+
 	cl->port_clk_started[substream->stream] = false;
 	if (!mca_be_clk_started(cl)) {
 		/*

From cbd9a07e4d20b5a729731b95e2e3b4eb3e921b00 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 14 Dec 2022 13:07:14 +0100
Subject: [PATCH 0302/1027] ASoC: apple: mca: Fix SYNCGEN enable on FE clock
 consumers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 89320430afc424..d54d071270d21c 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -141,6 +141,9 @@ struct mca_cluster {
 	bool clocks_in_use[SNDRV_PCM_STREAM_LAST + 1];
 	struct device_link *pd_link;
 
+	/* In case of clock consumer FE */
+	int syncgen_in_use;
+
 	unsigned int bclk_ratio;
 
 	/* Masks etc. picked up via the set_tdm_slot method */
@@ -387,14 +390,24 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream,
 	if (cl->clk_provider)
 		return 0;
 
-	if (!mca_fe_clocks_in_use(cl)) {
+	if (!cl->syncgen_in_use) {
 		int port = mca_fe_get_port(substream);
+
+		cl->pd_link = device_link_add(mca->dev, cl->pd_dev,
+					      DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+						DL_FLAG_RPM_ACTIVE);
+		if (!cl->pd_link) {
+			dev_err(mca->dev,
+				"cluster %d: unable to prop-up power domain\n", cl->no);
+			return -EINVAL;
+		}
+
 		writel_relaxed(port + 6 + 1,
 			       cl->base + REG_SYNCGEN_MCLK_SEL);
 		mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN,
 			   SYNCGEN_STATUS_EN);
 	}
-	cl->clocks_in_use[substream->stream] = true;
+	cl->syncgen_in_use |= 1 << substream->stream;
 
 	return 0;
 }
@@ -407,11 +420,13 @@ static int mca_fe_hw_free(struct snd_pcm_substream *substream,
 	if (cl->clk_provider)
 		return 0;
 
-	cl->clocks_in_use[substream->stream] = false;
-	if (mca_fe_clocks_in_use(cl))
+	cl->syncgen_in_use &= ~(1 << substream->stream);
+	if (cl->syncgen_in_use)
 		return 0;
 
 	mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0);
+	if (cl->pd_link)
+		device_link_del(cl->pd_link);
 
 	return 0;
 }

From 1ec86d500b02618508c944aa81839e557035facd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 14 Dec 2022 13:06:26 +0100
Subject: [PATCH 0303/1027] ASoC: macaudio: Start speaker sense capture support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 83cef7eda7f1bd..36c88235c233a1 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -57,6 +57,7 @@ struct macaudio_snd_data {
 	struct macaudio_link_props {
 		/* frontend props */
 		unsigned int bclk_ratio;
+		bool is_sense;
 
 		/* backend props */
 		bool is_speakers;
@@ -82,6 +83,11 @@ SND_SOC_DAILINK_DEFS(secondary,
 	DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
 	DAILINK_COMP_ARRAY(COMP_EMPTY()));
 
+SND_SOC_DAILINK_DEFS(sense,
+	DAILINK_COMP_ARRAY(COMP_CPU("mca-pcm-2")), // CPU
+	DAILINK_COMP_ARRAY(COMP_DUMMY()), // CODEC
+	DAILINK_COMP_ARRAY(COMP_EMPTY()));
+
 static struct snd_soc_dai_link macaudio_fe_links[] = {
 	{
 		.name = "Primary",
@@ -106,6 +112,17 @@ static struct snd_soc_dai_link macaudio_fe_links[] = {
 		.dai_fmt = MACAUDIO_DAI_FMT,
 		SND_SOC_DAILINK_REG(secondary),
 	},
+	{
+		.name = "Speaker Sense",
+		.stream_name = "Speaker Sense",
+		.dynamic = 1,
+		.dpcm_capture = 1,
+		.dai_fmt = (SND_SOC_DAIFMT_I2S | \
+					SND_SOC_DAIFMT_CBP_CFP | \
+					SND_SOC_DAIFMT_GATED | \
+					SND_SOC_DAIFMT_IB_IF),
+		SND_SOC_DAILINK_REG(sense),
+	},
 };
 
 static struct macaudio_link_props macaudio_fe_link_props[] = {
@@ -133,6 +150,9 @@ static struct macaudio_link_props macaudio_fe_link_props[] = {
 		 * those fancy speaker arrays.
 		 */
 		.bclk_ratio = 256,
+	},
+	{
+		.is_sense = 1,
 	}
 };
 
@@ -626,6 +646,9 @@ static int macaudio_fe_init(struct snd_soc_pcm_runtime *rtd)
 	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
 	int nslots = props->bclk_ratio / MACAUDIO_SLOTWIDTH;
 
+	if (props->is_sense)
+		return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), 0, 0xffff, 16, 16);
+
 	return snd_soc_dai_set_tdm_slot(snd_soc_rtd_to_cpu(rtd, 0), (1 << nslots) - 1,
 					(1 << nslots) - 1, nslots, MACAUDIO_SLOTWIDTH);
 }
@@ -686,6 +709,13 @@ static int macaudio_add_backend_dai_route(struct snd_soc_card *card, struct snd_
 		r->sink = "Headset Capture";
 	}
 
+	/* If speakers, add sense capture path */
+	if (is_speakers) {
+		r = &routes[nroutes++];
+		r->source = dai->stream[SNDRV_PCM_STREAM_CAPTURE].widget->name;
+		r->sink = "Speaker Sense Capture";
+	}
+
 	ret = snd_soc_dapm_add_routes(&card->dapm, routes, nroutes);
 	if (ret)
 		dev_err(card->dev, "failed adding dynamic DAPM routes for %s\n",
@@ -929,6 +959,7 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 	SND_SOC_DAPM_AIF_OUT("Headphone Playback", NULL, 0, SND_SOC_NOPM, 0, 0),
 
 	SND_SOC_DAPM_AIF_IN("Headset Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
+	SND_SOC_DAPM_AIF_IN("Speaker Sense Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
 };
 
 static const struct snd_kcontrol_new macaudio_controls[] = {
@@ -952,6 +983,9 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 
 	/* Capture paths */
 	{ "PCM0 RX", NULL, "Headset Capture" },
+
+	/* Sense paths */
+	{ "PCM2 RX", NULL, "Speaker Sense Capture" },	
 };
 
 static const struct of_device_id macaudio_snd_device_id[]  = {

From 4e2248a8773e144a12d1dcf5345e7ed97e56eb6b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 14 Dec 2022 13:07:51 +0100
Subject: [PATCH 0304/1027] ASoC: macaudio: Tweak "no audio route" message
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 36c88235c233a1..e002327a39ab25 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -527,7 +527,7 @@ static int macaudio_fe_hw_params(struct snd_pcm_substream *substream,
 	}
 
 	if (!be) {
-		dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured (bad settings applied to the sound card)\n",
+		dev_err(rtd->dev, "opening PCM device '%s' with no audio route configured by the user\n",
 				rtd->dai_link->name);
 		return -EINVAL;
 	}

From 6e3525370089ca3dd7f26b15808beac55402a108 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 19 Jan 2023 07:43:56 +0100
Subject: [PATCH 0305/1027] ASoC: macaudio: Do not constrain sense PCM
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index e002327a39ab25..ae9a8a977c09ce 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -499,8 +499,12 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream)
 
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
 	int ret;
 
+	if (props->is_sense)
+		return 0;
+
 	/* The FEs must never have more channels than the hardware */
 	ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 					SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels);

From c22bc3d9c8ba42ab99e82452d3977c523e1f7558 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 19 Jan 2023 07:22:14 +0100
Subject: [PATCH 0306/1027] ASoC: tas2770: Factor out set_ivsense_slots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a new explicit function for the setting of I/V sense TDM slots.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2770.c | 40 +++++++++++++++++++++++++-------------
 1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 5601fba17c9607..0ff0b62206583f 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -190,6 +190,31 @@ static int tas2770_mute(struct snd_soc_dai *dai, int mute, int direction)
 	return tas2770_update_pwr_ctrl(tas2770);
 }
 
+static int tas2770_set_ivsense_transmit(struct tas2770_priv *tas2770,
+					int i_slot, int v_slot)
+{
+	struct snd_soc_component *component = tas2770->component;
+	int ret;
+
+	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG5,
+					    TAS2770_TDM_CFG_REG5_VSNS_MASK |
+					    TAS2770_TDM_CFG_REG5_50_MASK,
+					    TAS2770_TDM_CFG_REG5_VSNS_ENABLE |
+					    v_slot);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG6,
+					    TAS2770_TDM_CFG_REG6_ISNS_MASK |
+					    TAS2770_TDM_CFG_REG6_50_MASK,
+					    TAS2770_TDM_CFG_REG6_ISNS_ENABLE |
+					    i_slot);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth)
 {
 	int ret;
@@ -222,19 +247,8 @@ static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth)
 	if (ret < 0)
 		return ret;
 
-	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG5,
-					    TAS2770_TDM_CFG_REG5_VSNS_MASK |
-					    TAS2770_TDM_CFG_REG5_50_MASK,
-					    TAS2770_TDM_CFG_REG5_VSNS_ENABLE |
-		tas2770->v_sense_slot);
-	if (ret < 0)
-		return ret;
-
-	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG6,
-					    TAS2770_TDM_CFG_REG6_ISNS_MASK |
-					    TAS2770_TDM_CFG_REG6_50_MASK,
-					    TAS2770_TDM_CFG_REG6_ISNS_ENABLE |
-					    tas2770->i_sense_slot);
+	ret = tas2770_set_ivsense_transmit(tas2770, tas2770->i_sense_slot,
+					   tas2770->v_sense_slot);
 	if (ret < 0)
 		return ret;
 

From bbac7f42eec951c2703bd872e44dfb3f18d66eb7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 19 Jan 2023 08:08:30 +0100
Subject: [PATCH 0307/1027] ASoC: tas2770: Fix and redo I/V sense TDM slot
 setting logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The former code sets the V slot from inside set_bitwidth according to
the bitwidth of the PCM format. That's wrong, since:

 * It overrides the V slot parsed from DT binding.

 * The V slot is set shifted behind the I slot by the length of the PCM
   bitwidth, but the PCM bitwidth has no assured relation to the TDM
   slot width.

Replace the former logic by setting up the I/V sense transmission only
in case of both I/V slots being specified in devicetree, and never
override those values. In case the slots are left unspecified, disable
the transmission completely.

There's an improbable case someone is relying on the old behavior, but
if so, that's a setup that only works by accident, and cannot be sanely
supported going forward. There's no indication anyone is consuming the
I/V sense data up to today, so break the former behavior.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2770.c | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 0ff0b62206583f..18e6d5103abc5b 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -225,19 +225,16 @@ static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth)
 		ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG2,
 						    TAS2770_TDM_CFG_REG2_RXW_MASK,
 						    TAS2770_TDM_CFG_REG2_RXW_16BITS);
-		tas2770->v_sense_slot = tas2770->i_sense_slot + 2;
 		break;
 	case SNDRV_PCM_FORMAT_S24_LE:
 		ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG2,
 						    TAS2770_TDM_CFG_REG2_RXW_MASK,
 						    TAS2770_TDM_CFG_REG2_RXW_24BITS);
-		tas2770->v_sense_slot = tas2770->i_sense_slot + 4;
 		break;
 	case SNDRV_PCM_FORMAT_S32_LE:
 		ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG2,
 						    TAS2770_TDM_CFG_REG2_RXW_MASK,
 						    TAS2770_TDM_CFG_REG2_RXW_32BITS);
-		tas2770->v_sense_slot = tas2770->i_sense_slot + 4;
 		break;
 
 	default:
@@ -247,11 +244,6 @@ static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth)
 	if (ret < 0)
 		return ret;
 
-	ret = tas2770_set_ivsense_transmit(tas2770, tas2770->i_sense_slot,
-					   tas2770->v_sense_slot);
-	if (ret < 0)
-		return ret;
-
 	return 0;
 }
 
@@ -506,6 +498,7 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 {
 	struct tas2770_priv *tas2770 =
 			snd_soc_component_get_drvdata(component);
+	int ret;
 
 	tas2770->component = component;
 
@@ -517,6 +510,14 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 	tas2770_reset(tas2770);
 	regmap_reinit_cache(tas2770->regmap, &tas2770_i2c_regmap);
 
+	if (tas2770->i_sense_slot != -1 && tas2770->v_sense_slot != -1) {
+		ret = tas2770_set_ivsense_transmit(tas2770, tas2770->i_sense_slot,
+						   tas2770->v_sense_slot);
+
+		if (ret < 0)
+			return ret;
+	}
+
 	return 0;
 }
 
@@ -644,7 +645,7 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770)
 		dev_info(tas2770->dev, "Property %s is missing setting default slot\n",
 			 "ti,imon-slot-no");
 
-		tas2770->i_sense_slot = 0;
+		tas2770->i_sense_slot = -1;
 	}
 
 	rc = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no",
@@ -653,7 +654,7 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770)
 		dev_info(tas2770->dev, "Property %s is missing setting default slot\n",
 			 "ti,vmon-slot-no");
 
-		tas2770->v_sense_slot = 2;
+		tas2770->v_sense_slot = -1;
 	}
 
 	tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);

From cc4d6971aaaa9870db50048c964cb4b35402218d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 20 Jan 2023 12:17:09 +0100
Subject: [PATCH 0308/1027] ASoC: tas2764: Reinit cache on part reset
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When the part is reset in component_probe, do not forget to reinit the
regcache, otherwise the cache can get out of sync with the part's
actual state. (This fix is similar to commit 0a0342ede303 which
concerned the tas2770 driver.)

Fixes: 827ed8a0fa50 ("ASoC: tas2764: Add the driver for the TAS2764")
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index a81a66890f3c6c..b4831f6ad7dc2c 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -540,6 +540,8 @@ static uint8_t sn012776_bop_presets[] = {
 	0x06, 0x3e, 0x37, 0x30, 0xff, 0xe6
 };
 
+static const struct regmap_config tas2764_i2c_regmap;
+
 static int tas2764_codec_probe(struct snd_soc_component *component)
 {
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
@@ -553,6 +555,7 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 	}
 
 	tas2764_reset(tas2764);
+	regmap_reinit_cache(tas2764->regmap, &tas2764_i2c_regmap);
 
 	if (tas2764->irq) {
 		ret = snd_soc_component_write(tas2764->component, TAS2764_INT_MASK0, 0xff);

From a08db3de8f96b6814e0607c70df9f89b03e1ef7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 20 Jan 2023 12:31:53 +0100
Subject: [PATCH 0309/1027] NOT UPSTREAMABLE: ASoC: tas2764: Redo I/V sense
 logic
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

 * Only set up I/V sense transmission in case the slots are described in
   devicetree, never use defaults.

 * Move the enablement of I/V sense transmission away from hw_params up
   into component probe, do not condition it on the measurements itself
   being enabled.

 * Move the slot configuration from set_tdm_slot into component probe,
   so it's not separate from other configuration.

Since this makes I/V sense unavailable in some configurations where it
formerly was, and it also changes behavior depending on the pairing with
a machine-level driver (depending on set_tdm_slot calls), it's probably
not upstreamable as is.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 61 ++++++++++++++------------------------
 1 file changed, 23 insertions(+), 38 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index b4831f6ad7dc2c..2f6041269dedea 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -257,7 +257,6 @@ static int tas2764_mute(struct snd_soc_dai *dai, int mute, int direction)
 static int tas2764_set_bitwidth(struct tas2764_priv *tas2764, int bitwidth)
 {
 	struct snd_soc_component *component = tas2764->component;
-	int sense_en;
 	int val;
 	int ret;
 
@@ -292,28 +291,6 @@ static int tas2764_set_bitwidth(struct tas2764_priv *tas2764, int bitwidth)
 	if (val < 0)
 		return val;
 
-	if (val & (1 << TAS2764_VSENSE_POWER_EN))
-		sense_en = 0;
-	else
-		sense_en = TAS2764_TDM_CFG5_VSNS_ENABLE;
-
-	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5,
-					    TAS2764_TDM_CFG5_VSNS_ENABLE,
-					    sense_en);
-	if (ret < 0)
-		return ret;
-
-	if (val & (1 << TAS2764_ISENSE_POWER_EN))
-		sense_en = 0;
-	else
-		sense_en = TAS2764_TDM_CFG6_ISNS_ENABLE;
-
-	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6,
-					    TAS2764_TDM_CFG6_ISNS_ENABLE,
-					    sense_en);
-	if (ret < 0)
-		return ret;
-
 	return 0;
 }
 
@@ -435,7 +412,6 @@ static int tas2764_set_dai_tdm_slot(struct snd_soc_dai *dai,
 				int slots, int slot_width)
 {
 	struct snd_soc_component *component = dai->component;
-	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
 	int left_slot, right_slot;
 	int slots_cfg;
 	int slot_size;
@@ -482,15 +458,26 @@ static int tas2764_set_dai_tdm_slot(struct snd_soc_dai *dai,
 	if (ret < 0)
 		return ret;
 
-	ret = snd_soc_component_update_bits(component, TAS2764_TDM_CFG5,
+	return 0;
+}
+
+static int tas2764_set_ivsense_transmit(struct tas2764_priv *tas2764, int i_slot, int v_slot)
+{
+	int ret;
+
+	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5,
+					    TAS2764_TDM_CFG5_VSNS_ENABLE |
 					    TAS2764_TDM_CFG5_50_MASK,
-					    tas2764->v_sense_slot);
+					    TAS2764_TDM_CFG5_VSNS_ENABLE |
+					    v_slot);
 	if (ret < 0)
 		return ret;
 
-	ret = snd_soc_component_update_bits(component, TAS2764_TDM_CFG6,
+	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6,
+					    TAS2764_TDM_CFG6_ISNS_ENABLE |
 					    TAS2764_TDM_CFG6_50_MASK,
-					    tas2764->i_sense_slot);
+					    TAS2764_TDM_CFG6_ISNS_ENABLE |
+					    i_slot);
 	if (ret < 0)
 		return ret;
 
@@ -585,15 +572,13 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 			dev_warn(tas2764->dev, "failed to request IRQ: %d\n", ret);
 	}
 
-	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG5,
-					    TAS2764_TDM_CFG5_VSNS_ENABLE, 0);
-	if (ret < 0)
-		return ret;
+	if (tas2764->i_sense_slot != -1 && tas2764->v_sense_slot != -1) {
+		ret = tas2764_set_ivsense_transmit(tas2764, tas2764->i_sense_slot,
+						   tas2764->v_sense_slot);
 
-	ret = snd_soc_component_update_bits(tas2764->component, TAS2764_TDM_CFG6,
-					    TAS2764_TDM_CFG6_ISNS_ENABLE, 0);
-	if (ret < 0)
-		return ret;
+		if (ret < 0)
+			return ret;
+	}
 
 	if (tas2764->devid == DEVID_SN012776) {
 		ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
@@ -730,12 +715,12 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)
 	ret = fwnode_property_read_u32(dev->fwnode, "ti,imon-slot-no",
 				       &tas2764->i_sense_slot);
 	if (ret)
-		tas2764->i_sense_slot = 0;
+		tas2764->i_sense_slot = -1;
 
 	ret = fwnode_property_read_u32(dev->fwnode, "ti,vmon-slot-no",
 				       &tas2764->v_sense_slot);
 	if (ret)
-		tas2764->v_sense_slot = 2;
+		tas2764->v_sense_slot = -1;
 
 	return 0;
 }

From a2e4510138209178a37416993a92dba647dd58e6 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 23 Jan 2023 10:47:01 +0100
Subject: [PATCH 0310/1027] ASoC: macaudio: Tune constraining of FEs, add BCLK
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 25 +++++++++++++++++++------
 1 file changed, 19 insertions(+), 6 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index ae9a8a977c09ce..93cf3b3a2e63c9 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -45,6 +45,15 @@
 				 SND_SOC_DAIFMT_IB_IF)
 #define MACAUDIO_JACK_MASK	(SND_JACK_HEADSET | SND_JACK_HEADPHONE)
 #define MACAUDIO_SLOTWIDTH	32
+/*
+ * Maximum BCLK frequency
+ *
+ * Codec maximums:
+ *  CS42L42  26.0 MHz
+ *  TAS2770  27.1 MHz
+ *  TAS2764  24.576 MHz
+ */
+#define MACAUDIO_MAX_BCLK_FREQ	24576000
 
 struct macaudio_snd_data {
 	struct snd_soc_card card;
@@ -500,19 +509,23 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream)
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
 	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
-	int ret;
+	int max_rate, ret;
 
 	if (props->is_sense)
 		return 0;
 
-	/* The FEs must never have more channels than the hardware */
 	ret = snd_pcm_hw_constraint_minmax(substream->runtime,
-					SNDRV_PCM_HW_PARAM_CHANNELS, 0, ma->max_channels);
+					   SNDRV_PCM_HW_PARAM_CHANNELS,
+					   0, ma->max_channels);
+	if (ret < 0)
+		return ret;
 
-	if (ret < 0) {
-		dev_err(rtd->dev, "Failed to constrain FE %d! %d", rtd->dai_link->id, ret);
+	max_rate = MACAUDIO_MAX_BCLK_FREQ / props->bclk_ratio;
+	ret = snd_pcm_hw_constraint_minmax(substream->runtime,
+					   SNDRV_PCM_HW_PARAM_RATE,
+					   0, max_rate);
+	if (ret < 0)
 		return ret;
-		}
 
 	return 0;
 }

From ea166a951855fa87bd2140cc47de9680f848179e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 24 Jan 2023 15:14:53 +0100
Subject: [PATCH 0311/1027] ASoC: apple: mca: Support capture on multiples BEs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When multiple BEs are linked to a FE, the former behavior was to source
the data line from the DIN pin of the first BE only. Change this to
ORing the DIN inputs of all linked BEs.

As long as the unused slots on each BE's line are zeroed out and the
slots on the BEs don't overlap, this will work out well.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/mca.c | 30 +++++++++++++-----------------
 1 file changed, 13 insertions(+), 17 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index d54d071270d21c..0b1cea66e2abaf 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -261,22 +261,18 @@ static int mca_fe_trigger(struct snd_pcm_substream *substream, int cmd,
 	return 0;
 }
 
-static int mca_fe_get_port(struct snd_pcm_substream *substream)
+static int mca_fe_get_portmask(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *fe = snd_soc_substream_to_rtd(substream);
-	struct snd_soc_pcm_runtime *be;
 	struct snd_soc_dpcm *dpcm;
+	int mask = 0;
 
-	be = NULL;
 	for_each_dpcm_be(fe, substream->stream, dpcm) {
-		be = dpcm->be;
-		break;
+		int no = mca_dai_to_cluster(snd_soc_rtd_to_cpu(dpcm->be, 0))->no;
+		mask |= 1 << no;
 	}
 
-	if (!be)
-		return -EINVAL;
-
-	return mca_dai_to_cluster(snd_soc_rtd_to_cpu(be, 0))->no;
+	return mask;
 }
 
 static int mca_fe_enable_clocks(struct mca_cluster *cl)
@@ -391,7 +387,7 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream,
 		return 0;
 
 	if (!cl->syncgen_in_use) {
-		int port = mca_fe_get_port(substream);
+		int port = ffs(mca_fe_get_portmask(substream)) - 1;
 
 		cl->pd_link = device_link_add(mca->dev, cl->pd_dev,
 					      DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
@@ -441,7 +437,7 @@ static unsigned int mca_crop_mask(unsigned int mask, int nchans)
 
 static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit,
 				unsigned int mask, int slots, int nchans,
-				int slot_width, bool is_tx, int port)
+				int slot_width, bool is_tx, int portmask)
 {
 	__iomem void *serdes_base = cl->base + serdes_unit;
 	u32 serdes_conf, serdes_conf_mask;
@@ -500,7 +496,7 @@ static int mca_configure_serdes(struct mca_cluster *cl, int serdes_unit,
 			       serdes_base + REG_RX_SERDES_SLOTMASK);
 		writel_relaxed(~((u32)mca_crop_mask(mask, nchans)),
 			       serdes_base + REG_RX_SERDES_SLOTMASK + 0x4);
-		writel_relaxed(1 << port,
+		writel_relaxed(portmask,
 			       serdes_base + REG_RX_SERDES_PORT);
 	}
 
@@ -637,7 +633,7 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream,
 	unsigned long bclk_ratio;
 	unsigned int tdm_slots, tdm_slot_width, tdm_mask;
 	u32 regval, pad;
-	int ret, port, nchans_ceiled;
+	int ret, portmask, nchans_ceiled;
 
 	if (!cl->tdm_slot_width) {
 		/*
@@ -686,13 +682,13 @@ static int mca_fe_hw_params(struct snd_pcm_substream *substream,
 		tdm_mask = (1 << tdm_slots) - 1;
 	}
 
-	port = mca_fe_get_port(substream);
-	if (port < 0)
-		return port;
+	portmask = mca_fe_get_portmask(substream);
+	if (!portmask)
+		return -EINVAL;
 
 	ret = mca_configure_serdes(cl, is_tx ? CLUSTER_TX_OFF : CLUSTER_RX_OFF,
 				   tdm_mask, tdm_slots, params_channels(params),
-				   tdm_slot_width, is_tx, port);
+				   tdm_slot_width, is_tx, portmask);
 	if (ret)
 		return ret;
 

From 780bed49f806714a0359b54edd85fd36b1009479 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 24 Jan 2023 15:22:40 +0100
Subject: [PATCH 0312/1027] ASoC: tas2764: Configure zeroing of SDOUT slots
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The codec has an option to zero out certain TDM slots on its SDOUT
output according to a preconfigured mask (otherwise the output is, for
the duration of unused slots, in a Hi-Z state). Configure this feature
based on a mask read from the devicetree.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 23 +++++++++++++++++++++++
 sound/soc/codecs/tas2764.h | 11 +++++++++++
 2 files changed, 34 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 2f6041269dedea..7966d15e173ea6 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -41,6 +41,7 @@ struct tas2764_priv {
 
 	int v_sense_slot;
 	int i_sense_slot;
+	u32 sdout_zero_mask;
 
 	bool dac_powered;
 	bool unmuted;
@@ -580,6 +581,23 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 			return ret;
 	}
 
+	if (tas2764->sdout_zero_mask) {
+		for (i = 0; i < 4; i++) {
+			ret = snd_soc_component_write(component, TAS2764_SDOUT_HIZ_1 + i,
+						      (tas2764->sdout_zero_mask >> (i * 8)) & 0xff);
+
+			if (ret < 0)
+				return ret;
+		}
+
+		ret = snd_soc_component_update_bits(component, TAS2764_SDOUT_HIZ_9,
+						    TAS2764_SDOUT_HIZ_9_FORCE_0_EN,
+						    TAS2764_SDOUT_HIZ_9_FORCE_0_EN);
+
+		if (ret < 0)
+			return ret;
+	}
+
 	if (tas2764->devid == DEVID_SN012776) {
 		ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
 					TAS2764_PWR_CTRL_BOP_SRC,
@@ -722,6 +740,11 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)
 	if (ret)
 		tas2764->v_sense_slot = -1;
 
+	ret = fwnode_property_read_u32(dev->fwnode, "ti,sdout-force-zero-mask",
+				       &tas2764->sdout_zero_mask);
+	if (ret)
+		tas2764->sdout_zero_mask = 0;
+
 	return 0;
 }
 
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 20628e51bf94f0..10ef7d4a490e1d 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -117,4 +117,15 @@
 
 #define TAS2764_BOP_CFG0                TAS2764_REG(0X0, 0x1d)
 
+#define TAS2764_SDOUT_HIZ_1		TAS2764_REG(0x1, 0x3d)
+#define TAS2764_SDOUT_HIZ_2		TAS2764_REG(0x1, 0x3e)
+#define TAS2764_SDOUT_HIZ_3		TAS2764_REG(0x1, 0x3f)
+#define TAS2764_SDOUT_HIZ_4		TAS2764_REG(0x1, 0x40)
+#define TAS2764_SDOUT_HIZ_5		TAS2764_REG(0x1, 0x41)
+#define TAS2764_SDOUT_HIZ_6		TAS2764_REG(0x1, 0x42)
+#define TAS2764_SDOUT_HIZ_7		TAS2764_REG(0x1, 0x43)
+#define TAS2764_SDOUT_HIZ_8		TAS2764_REG(0x1, 0x44)
+#define TAS2764_SDOUT_HIZ_9		TAS2764_REG(0x1, 0x45)
+#define TAS2764_SDOUT_HIZ_9_FORCE_0_EN	BIT(7)
+
 #endif /* __TAS2764__ */

From 5e68b6fabe991418b21331fbdb7e0984475e196e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 24 Jan 2023 15:24:28 +0100
Subject: [PATCH 0313/1027] WIP: ASoC: tas2764: Apply unknown Apple quirk
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This maybe, possibly, fixes some fault states on the codec...

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 38 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 38 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 7966d15e173ea6..15809b14880f79 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -530,6 +530,36 @@ static uint8_t sn012776_bop_presets[] = {
 
 static const struct regmap_config tas2764_i2c_regmap;
 
+static int tas2764_apply_unk_apple_quirk(struct snd_soc_component *component)
+{
+	int ret;
+
+	ret = snd_soc_component_write(component, 0xfd0d, 0xd);
+
+	if (ret < 0)
+		return ret;
+
+
+	ret = snd_soc_component_write(component, 0xfd6c, 0x2);
+
+	if (ret < 0)
+		return ret;
+
+
+	ret = snd_soc_component_write(component, 0xfd6d, 0xf);
+
+	if (ret < 0)
+		return ret;
+
+
+	ret = snd_soc_component_write(component, 0xfd0d, 0x0);
+
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
 static int tas2764_codec_probe(struct snd_soc_component *component)
 {
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
@@ -599,6 +629,11 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 	}
 
 	if (tas2764->devid == DEVID_SN012776) {
+		ret = tas2764_apply_unk_apple_quirk(component);
+
+		if (ret < 0)
+			return ret;
+
 		ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
 					TAS2764_PWR_CTRL_BOP_SRC,
 					TAS2764_PWR_CTRL_BOP_SRC);
@@ -692,6 +727,9 @@ static bool tas2764_volatile_register(struct device *dev, unsigned int reg)
 	case TAS2764_INT_LTCH0 ... TAS2764_INT_LTCH4:
 	case TAS2764_INT_CLK_CFG:
 		return true;
+	case TAS2764_REG(0xf0, 0x0) ... TAS2764_REG(0xff, 0x0):
+		/* TI's undocumented registers for the application of quirks */
+		return true;
 	default:
 		return false;
 	}

From c81c8b09b2ad0222fae770f31ef0902bddfd177a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Tue, 24 Jan 2023 15:25:07 +0100
Subject: [PATCH 0314/1027] ASoC: tas2764: Raise regmap range maximum
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 15809b14880f79..8b4926b7987199 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -712,7 +712,7 @@ static const struct reg_default tas2764_reg_defaults[] = {
 static const struct regmap_range_cfg tas2764_regmap_ranges[] = {
 	{
 		.range_min = 0,
-		.range_max = 1 * 128,
+		.range_max = 0xffff,
 		.selector_reg = TAS2764_PAGE,
 		.selector_mask = 0xff,
 		.selector_shift = 0,
@@ -744,7 +744,7 @@ static const struct regmap_config tas2764_i2c_regmap = {
 	.cache_type = REGCACHE_RBTREE,
 	.ranges = tas2764_regmap_ranges,
 	.num_ranges = ARRAY_SIZE(tas2764_regmap_ranges),
-	.max_register = 1 * 128,
+	.max_register = 0xffff,
 };
 
 static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)

From 8c59e4c2e21a375b1e24fd15a9e0fa689de50439 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 25 Jan 2023 11:08:59 +0100
Subject: [PATCH 0315/1027] ASoC: tas2770: Export 'die_temp' to sysfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Export a file for the readout of die temperature measurements.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2770.c | 47 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 47 insertions(+)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 18e6d5103abc5b..f64d44ac71fc13 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -21,6 +21,7 @@
 #include <linux/regmap.h>
 #include <linux/of.h>
 #include <linux/slab.h>
+#include <linux/sysfs.h>
 #include <sound/soc.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -492,6 +493,47 @@ static struct snd_soc_dai_driver tas2770_dai_driver[] = {
 	},
 };
 
+static int tas2770_read_die_temp(struct tas2770_priv *tas2770, int *result)
+{
+	int ret, reading;
+
+	ret = snd_soc_component_read(tas2770->component, TAS2770_TEMP_MSB);
+	if (ret < 0)
+		return ret;
+	reading = ret << 4;
+
+	ret = snd_soc_component_read(tas2770->component, TAS2770_TEMP_LSB);
+	if (ret < 0)
+		return ret;
+	reading |= ret >> 4;
+
+	*result = reading - (93 * 16);
+	return 0;
+}
+
+static ssize_t die_temp_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct tas2770_priv *tas2770 = i2c_get_clientdata(to_i2c_client(dev));
+	int ret, temp;
+
+	ret = tas2770_read_die_temp(tas2770, &temp);
+
+	if (ret < 0)
+		return ret;
+
+	return sysfs_emit(buf, "%d.%03d C\n", temp / 16,
+			  (temp * 1000 / 16) % 1000);
+}
+
+static DEVICE_ATTR_RO(die_temp);
+
+static struct attribute *tas2770_sysfs_attrs[] = {
+	&dev_attr_die_temp.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(tas2770_sysfs);
+
 static const struct regmap_config tas2770_i2c_regmap;
 
 static int tas2770_codec_probe(struct snd_soc_component *component)
@@ -518,6 +560,11 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 			return ret;
 	}
 
+	ret = sysfs_create_groups(&component->dev->kobj, tas2770_sysfs_groups);
+
+	if (ret < 0)
+		return ret;
+
 	return 0;
 }
 

From ebd334cb1267c0cc5d8e9d695605cd8317deaa7e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 25 Jan 2023 11:10:20 +0100
Subject: [PATCH 0316/1027] ASoC: tas2764: Export 'die_temp' to sysfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Export a file for the readout of die temperature measurements.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 45 ++++++++++++++++++++++++++++++++++++++
 sound/soc/codecs/tas2764.h |  3 +++
 2 files changed, 48 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 8b4926b7987199..bacd3914ccca88 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -17,6 +17,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/slab.h>
+#include <linux/sysfs.h>
 #include <sound/soc.h>
 #include <sound/pcm.h>
 #include <sound/pcm_params.h>
@@ -560,6 +561,39 @@ static int tas2764_apply_unk_apple_quirk(struct snd_soc_component *component)
 	return 0;
 }
 
+static int tas2764_read_die_temp(struct tas2764_priv *tas2764, int *result)
+{
+	int ret;
+
+	ret = snd_soc_component_read(tas2764->component, TAS2764_TEMP);
+	if (ret < 0)
+		return ret;
+	*result = ret - 93;
+	return 0;
+}
+
+static ssize_t die_temp_show(struct device *dev,
+			 struct device_attribute *attr, char *buf)
+{
+	struct tas2764_priv *tas2764 = i2c_get_clientdata(to_i2c_client(dev));
+	int ret, temp;
+
+	ret = tas2764_read_die_temp(tas2764, &temp);
+
+	if (ret < 0)
+		return ret;
+
+	return sysfs_emit(buf, "%d C\n", temp);
+}
+
+static DEVICE_ATTR_RO(die_temp);
+
+static struct attribute *tas2764_sysfs_attrs[] = {
+	&dev_attr_die_temp.attr,
+	NULL
+};
+ATTRIBUTE_GROUPS(tas2764_sysfs);
+
 static int tas2764_codec_probe(struct snd_soc_component *component)
 {
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
@@ -650,9 +684,19 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 		}
 	}
 
+	ret = sysfs_create_groups(&component->dev->kobj, tas2764_sysfs_groups);
+
+	if (ret < 0)
+		return ret;
+
 	return 0;
 }
 
+static void tas2764_codec_remove(struct snd_soc_component *component)
+{
+	sysfs_remove_groups(&component->dev->kobj, tas2764_sysfs_groups);
+}
+
 static DECLARE_TLV_DB_SCALE(tas2764_digital_tlv, 1100, 50, 0);
 static DECLARE_TLV_DB_SCALE(tas2764_playback_volume, -10050, 50, 1);
 
@@ -684,6 +728,7 @@ static const struct snd_kcontrol_new tas2764_snd_controls[] = {
 
 static const struct snd_soc_component_driver soc_component_driver_tas2764 = {
 	.probe			= tas2764_codec_probe,
+	.remove			= tas2764_codec_remove,
 	.suspend		= tas2764_codec_suspend,
 	.resume			= tas2764_codec_resume,
 	.controls		= tas2764_snd_controls,
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 10ef7d4a490e1d..dbe3f7fa901879 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -111,6 +111,9 @@
 #define TAS2764_INT_LTCH3               TAS2764_REG(0x0, 0x50)
 #define TAS2764_INT_LTCH4               TAS2764_REG(0x0, 0x51)
 
+/* Readout Registers */
+#define TAS2764_TEMP                    TAS2764_REG(0x0, 0x56)
+
 /* Clock/IRQ Settings */
 #define TAS2764_INT_CLK_CFG             TAS2764_REG(0x0, 0x5c)
 #define TAS2764_INT_CLK_CFG_IRQZ_CLR    BIT(2)

From 5e06ee42600cfaa1ed9983d490d74a3743817e1b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 25 Jan 2023 11:14:05 +0100
Subject: [PATCH 0317/1027] ASoC: tas2764: Crop SDOUT zero-out mask based on
 BCLK ratio
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764.c | 56 ++++++++++++++++++++++++++------------
 1 file changed, 39 insertions(+), 17 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index bacd3914ccca88..84e0e88d505fdc 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -348,6 +348,44 @@ static int tas2764_hw_params(struct snd_pcm_substream *substream,
 	return tas2764_set_samplerate(tas2764, params_rate(params));
 }
 
+static int tas2764_write_sdout_zero_mask(struct tas2764_priv *tas2764, int bclk_ratio)
+{
+	struct snd_soc_component *component = tas2764->component;
+	int nsense_slots = bclk_ratio / 8;
+	u32 cropped_mask;
+	int i, ret;
+
+	if (!tas2764->sdout_zero_mask)
+		return 0;
+
+	cropped_mask = tas2764->sdout_zero_mask & GENMASK(nsense_slots - 1, 0);
+
+	for (i = 0; i < 4; i++) {
+		ret = snd_soc_component_write(component, TAS2764_SDOUT_HIZ_1 + i,
+					      (cropped_mask >> (i * 8)) & 0xff);
+
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = snd_soc_component_update_bits(component, TAS2764_SDOUT_HIZ_9,
+					    TAS2764_SDOUT_HIZ_9_FORCE_0_EN,
+					    TAS2764_SDOUT_HIZ_9_FORCE_0_EN);
+
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int tas2764_set_bclk_ratio(struct snd_soc_dai *dai, unsigned int ratio)
+{
+	struct snd_soc_component *component = dai->component;
+	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
+
+	return tas2764_write_sdout_zero_mask(tas2764, ratio);
+}
+
 static int tas2764_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 {
 	struct snd_soc_component *component = dai->component;
@@ -489,6 +527,7 @@ static int tas2764_set_ivsense_transmit(struct tas2764_priv *tas2764, int i_slot
 static const struct snd_soc_dai_ops tas2764_dai_ops = {
 	.mute_stream = tas2764_mute,
 	.hw_params  = tas2764_hw_params,
+	.set_bclk_ratio = tas2764_set_bclk_ratio,
 	.set_fmt    = tas2764_set_fmt,
 	.set_tdm_slot = tas2764_set_dai_tdm_slot,
 	.no_capture_mute = 1,
@@ -645,23 +684,6 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 			return ret;
 	}
 
-	if (tas2764->sdout_zero_mask) {
-		for (i = 0; i < 4; i++) {
-			ret = snd_soc_component_write(component, TAS2764_SDOUT_HIZ_1 + i,
-						      (tas2764->sdout_zero_mask >> (i * 8)) & 0xff);
-
-			if (ret < 0)
-				return ret;
-		}
-
-		ret = snd_soc_component_update_bits(component, TAS2764_SDOUT_HIZ_9,
-						    TAS2764_SDOUT_HIZ_9_FORCE_0_EN,
-						    TAS2764_SDOUT_HIZ_9_FORCE_0_EN);
-
-		if (ret < 0)
-			return ret;
-	}
-
 	if (tas2764->devid == DEVID_SN012776) {
 		ret = tas2764_apply_unk_apple_quirk(component);
 

From 25410d8005ce176dcf9d85aba78c8f16693280fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 25 Jan 2023 13:41:42 +0100
Subject: [PATCH 0318/1027] ASoC: macaudio: Remove stale 'speaker_nchans'
 fields
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 93cf3b3a2e63c9..5be2f204d9f9e6 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -73,9 +73,6 @@ struct macaudio_snd_data {
 		bool is_headphones;
 		unsigned int tdm_mask;
 	} *link_props;
-
-	unsigned int speaker_nchans_array[2];
-	struct snd_pcm_hw_constraint_list speaker_nchans_list;
 };
 
 static bool please_blow_up_my_speakers;

From 325dcb84a3b20f8c8d0a40bdc479737d572ac14a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 25 Jan 2023 16:16:13 +0100
Subject: [PATCH 0319/1027] ASoC: macaudio: Add 'Speakers Up Indicator' control
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This control is there for userspace convenience, so that daemons
watching I/V sense data know when to open the sense PCM. If they open
the PCM without playback in progress, there will be no clocks on the
bus and the sense capture PCM will be stuck.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 69 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 69 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 5be2f204d9f9e6..52f0fe9c271ffd 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -73,6 +73,9 @@ struct macaudio_snd_data {
 		bool is_headphones;
 		unsigned int tdm_mask;
 	} *link_props;
+
+	bool speakers_streaming;
+	struct snd_kcontrol *speakers_streaming_kctl;
 };
 
 static bool please_blow_up_my_speakers;
@@ -566,6 +569,36 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
 	}
 }
 
+static int macaudio_be_prepare(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+	if (props->is_speakers) {
+		ma->speakers_streaming = true;
+		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ma->speakers_streaming_kctl->id);
+	}
+
+	return 0;
+}
+
+static int macaudio_be_hw_free(struct snd_pcm_substream *substream)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+	if (props->is_speakers) {
+		ma->speakers_streaming = false;
+		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ma->speakers_streaming_kctl->id);
+	}
+
+	return 0;
+}
+
 static const struct snd_soc_ops macaudio_fe_ops = {
 	.startup	= macaudio_fe_startup,
 	.shutdown	= macaudio_dpcm_shutdown,
@@ -573,6 +606,8 @@ static const struct snd_soc_ops macaudio_fe_ops = {
 };
 
 static const struct snd_soc_ops macaudio_be_ops = {
+	.prepare	= macaudio_be_prepare,
+	.hw_free	= macaudio_be_hw_free,
 	.shutdown	= macaudio_dpcm_shutdown,
 	.hw_params	= macaudio_dpcm_hw_params,
 };
@@ -803,6 +838,8 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 		}
 	}
 
+	ma->speakers_streaming_kctl = snd_soc_card_get_kcontrol(card, "Speakers Up Indicator");
+
 	return 0;
 }
 
@@ -976,10 +1013,42 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 	SND_SOC_DAPM_AIF_IN("Speaker Sense Capture", NULL, 0, SND_SOC_NOPM, 0, 0),
 };
 
+static int macaudio_sss_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+
+	return 0;
+}
+
+static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	/*
+	 * TODO: Check if any locking is in order here. I would
+	 * assume there is some ALSA-level lock, but DAPM implementations
+	 * of kcontrol ops do explicit locking, so look into it.
+	 */
+	uvalue->value.integer.value[0] = ma->speakers_streaming;
+
+	return 0;
+}
+
 static const struct snd_kcontrol_new macaudio_controls[] = {
 	SOC_DAPM_PIN_SWITCH("Speaker"),
 	SOC_DAPM_PIN_SWITCH("Headphone"),
 	SOC_DAPM_PIN_SWITCH("Headset Mic"),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
+		.name = "Speakers Up Indicator",
+		.info = macaudio_sss_info, .get = macaudio_sss_get,
+	},
 };
 
 static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {

From 66c0d7debfbd226ce8106b1219091143205e656d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 26 Jan 2023 14:24:24 +0100
Subject: [PATCH 0320/1027] ASoC: tas2764: Add optional 'Apple quirks'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Apple's SN012776 driver has some peculiar aspects to its behavior that
are suspected to work around issues in the codec part. Add a module
parameter for enabling individual quirks that should be imitated after
the Apple driver.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/codecs/tas2764-quirks.h | 185 ++++++++++++++++++++++++++++++
 sound/soc/codecs/tas2764.c        |  54 ++++-----
 2 files changed, 213 insertions(+), 26 deletions(-)
 create mode 100644 sound/soc/codecs/tas2764-quirks.h

diff --git a/sound/soc/codecs/tas2764-quirks.h b/sound/soc/codecs/tas2764-quirks.h
new file mode 100644
index 00000000000000..7cb860e7e9c252
--- /dev/null
+++ b/sound/soc/codecs/tas2764-quirks.h
@@ -0,0 +1,185 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef __TAS2764_QUIRKS__
+#define __TAS2764_QUIRKS__
+
+#include <linux/regmap.h>
+
+#include "tas2764.h"
+
+/*
+ * Disable noise gate and flip down reserved bit in NS_CFG0
+ */
+#define TAS2764_NOISE_GATE_DISABLE	BIT(0)
+
+struct reg_sequence tas2764_noise_gate_dis_seq[] = {
+	REG_SEQ0(TAS2764_REG(0x0, 0x35), 0xb0)
+};
+
+/*
+ * CONV_VBAT_PVDD_MODE=1
+ */
+#define TAS2764_CONV_VBAT_PVDD_MODE	BIT(1)
+
+struct reg_sequence tas2764_conv_vbat_pvdd_mode_seq[] = {
+	REG_SEQ0(TAS2764_REG(0x0, 0x6b), 0x41)
+};
+
+/*
+ * Reset of DAC modulator when DSP is OFF
+ */
+#define TAS2764_DMOD_RST		BIT(2)
+
+struct reg_sequence tas2764_dmod_rst_seq[] = {
+	REG_SEQ0(TAS2764_REG(0x0, 0x76), 0x0)
+};
+
+/*
+ * Unknown 0x133/0x137 writes (maybe TDM related)
+ */
+#define TAS2764_UNK_SEQ0		BIT(3)
+
+struct reg_sequence tas2764_unk_seq0[] = {
+	REG_SEQ0(TAS2764_REG(0x1, 0x33), 0x80),
+ 	REG_SEQ0(TAS2764_REG(0x1, 0x37), 0x3a),
+};
+
+/*
+ * Unknown 0x614 - 0x61f writes
+ */
+#define TAS2764_APPLE_UNK_SEQ1		BIT(4)
+
+struct reg_sequence tas2764_unk_seq1[] = {
+	REG_SEQ0(TAS2764_REG(0x6, 0x14), 0x0),
+	REG_SEQ0(TAS2764_REG(0x6, 0x15), 0x13),
+	REG_SEQ0(TAS2764_REG(0x6, 0x16), 0x52),
+	REG_SEQ0(TAS2764_REG(0x6, 0x17), 0x0),
+	REG_SEQ0(TAS2764_REG(0x6, 0x18), 0xe4),
+	REG_SEQ0(TAS2764_REG(0x6, 0x19), 0xc),
+	REG_SEQ0(TAS2764_REG(0x6, 0x16), 0xaa),
+	REG_SEQ0(TAS2764_REG(0x6, 0x1b), 0x0),
+	REG_SEQ0(TAS2764_REG(0x6, 0x1c), 0x12),
+	REG_SEQ0(TAS2764_REG(0x6, 0x1d), 0xa0),
+	REG_SEQ0(TAS2764_REG(0x6, 0x1e), 0xd8),
+	REG_SEQ0(TAS2764_REG(0x6, 0x1f), 0x0),
+};
+
+/*
+ * Unknown writes in the 0xfd page (with secondary paging inside)
+ */
+#define TAS2764_APPLE_UNK_SEQ2		BIT(5)
+
+struct reg_sequence tas2764_unk_seq2[] = {
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd),
+	REG_SEQ0(TAS2764_REG(0xfd, 0x6c), 0x2),
+	REG_SEQ0(TAS2764_REG(0xfd, 0x6d), 0xf),
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0),
+};
+
+/*
+ * Disable 'Thermal Threshold 1'
+ */
+#define TAS2764_THERMAL_TH1_DISABLE	BIT(6)
+
+struct reg_sequence tas2764_thermal_th1_dis_seq[] = {
+	REG_SEQ0(TAS2764_REG(0x1, 0x47), 0x2),
+};
+
+/*
+ * Imitate Apple's shutdown dance
+ */
+#define TAS2764_SHUTDOWN_DANCE		BIT(7)
+
+struct reg_sequence tas2764_shutdown_dance_init_seq[] = {
+	/*
+	 * SDZ_MODE=01 (immediate)
+	 *
+	 * We want the shutdown to happen under the influence of
+	 * the magic writes in the 0xfdXX region, so make sure
+	 * the shutdown is immediate and there's no grace period
+	 * followed by the codec part.
+	 */
+	REG_SEQ0(TAS2764_REG(0x0, 0x7), 0x60),
+};
+
+struct reg_sequence tas2764_pre_shutdown_seq[] = {
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd), /* switch hidden page */
+	REG_SEQ0(TAS2764_REG(0xfd, 0x64), 0x4), /* do write (unknown semantics) */
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0), /* switch hidden page back */
+};
+
+struct reg_sequence tas2764_post_shutdown_seq[] = {
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0xd),
+	REG_SEQ0(TAS2764_REG(0xfd, 0x64), 0x0), /* revert write from pre sequence */
+	REG_SEQ0(TAS2764_REG(0xfd, 0x0d), 0x0),
+};
+
+static int tas2764_do_quirky_pwr_ctrl_change(struct tas2764_priv *tas2764,
+					     unsigned int target)
+{
+	unsigned int curr;
+	int ret;
+
+	curr = snd_soc_component_read_field(tas2764->component,
+					       TAS2764_PWR_CTRL,
+					       TAS2764_PWR_CTRL_MASK);
+
+	if (target == curr)
+		return 0;
+
+#define TRANSITION(new, old) ((new) << 8 | (old))
+	switch (TRANSITION(target, curr)) {
+	case TRANSITION(TAS2764_PWR_CTRL_SHUTDOWN, TAS2764_PWR_CTRL_MUTE):
+	case TRANSITION(TAS2764_PWR_CTRL_SHUTDOWN, TAS2764_PWR_CTRL_ACTIVE):
+		ret = regmap_multi_reg_write(tas2764->regmap, tas2764_pre_shutdown_seq,
+					     ARRAY_SIZE(tas2764_pre_shutdown_seq));
+		if (ret < 0)
+			break;
+
+		ret = snd_soc_component_update_bits(tas2764->component,
+						    TAS2764_PWR_CTRL,
+					    	    TAS2764_PWR_CTRL_MASK,
+					    	    TAS2764_PWR_CTRL_SHUTDOWN);
+		if (ret > 0)
+			break;
+
+		ret = regmap_multi_reg_write(tas2764->regmap, tas2764_post_shutdown_seq,
+					     ARRAY_SIZE(tas2764_post_shutdown_seq));
+
+	default:
+		ret = snd_soc_component_update_bits(tas2764->component, TAS2764_PWR_CTRL,
+					    	    TAS2764_PWR_CTRL_MASK, target);
+	}
+#undef TRANSITION
+
+	if (ret < 0)
+		return ret;
+	return 0;
+}
+
+/*
+ * Via devicetree (TODO):
+ *  - switch from spread spectrum to class-D switching
+ *  - disable edge control
+ *  - set BOP settings (the BOP config bits *and* BOP_SRC)
+ */
+
+/*
+ * Other setup TODOs:
+ *  - DVC ramp rate
+ */
+
+static struct tas2764_quirk_init_sequence {
+	struct reg_sequence *seq;
+	int len;
+} tas2764_quirk_init_sequences[] = {
+	{ tas2764_noise_gate_dis_seq, ARRAY_SIZE(tas2764_noise_gate_dis_seq) },
+	{ tas2764_dmod_rst_seq, ARRAY_SIZE(tas2764_dmod_rst_seq) },
+	{ tas2764_conv_vbat_pvdd_mode_seq, ARRAY_SIZE(tas2764_conv_vbat_pvdd_mode_seq) },
+	{ tas2764_unk_seq0, ARRAY_SIZE(tas2764_unk_seq0) },
+	{ tas2764_unk_seq1, ARRAY_SIZE(tas2764_unk_seq1) },
+	{ tas2764_unk_seq2, ARRAY_SIZE(tas2764_unk_seq2) },
+	{ tas2764_thermal_th1_dis_seq, ARRAY_SIZE(tas2764_thermal_th1_dis_seq) },
+	{ tas2764_shutdown_dance_init_seq, ARRAY_SIZE(tas2764_shutdown_dance_init_seq) },
+};
+
+#endif /* __TAS2764_QUIRKS__ */
diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 84e0e88d505fdc..de618ddd742a3e 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -48,6 +48,12 @@ struct tas2764_priv {
 	bool unmuted;
 };
 
+static int apple_quirks;
+module_param(apple_quirks, int, 0644);
+MODULE_PARM_DESC(apple_quirks, "Mask of quirks to mimic after Apple's SN012776 driver");
+
+#include "tas2764-quirks.h"
+
 static const char *tas2764_int_ltch0_msgs[8] = {
 	"fault: over temperature", /* INT_LTCH0 & BIT(0) */
 	"fault: over current",
@@ -125,6 +131,9 @@ static int tas2764_update_pwr_ctrl(struct tas2764_priv *tas2764)
 	else
 		val = TAS2764_PWR_CTRL_SHUTDOWN;
 
+	if (apple_quirks & TAS2764_SHUTDOWN_DANCE)
+		return tas2764_do_quirky_pwr_ctrl_change(tas2764, val);
+
 	ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
 					    TAS2764_PWR_CTRL_MASK, val);
 	if (ret < 0)
@@ -570,32 +579,25 @@ static uint8_t sn012776_bop_presets[] = {
 
 static const struct regmap_config tas2764_i2c_regmap;
 
-static int tas2764_apply_unk_apple_quirk(struct snd_soc_component *component)
+static int tas2764_apply_init_quirks(struct tas2764_priv * tas2764)
 {
-	int ret;
-
-	ret = snd_soc_component_write(component, 0xfd0d, 0xd);
-
-	if (ret < 0)
-		return ret;
-
-
-	ret = snd_soc_component_write(component, 0xfd6c, 0x2);
-
-	if (ret < 0)
-		return ret;
+	int ret, i;
 
+	for (i = 0; i < ARRAY_SIZE(tas2764_quirk_init_sequences); i++) {
+		struct tas2764_quirk_init_sequence *init_seq = \
+					&tas2764_quirk_init_sequences[i];
+		if (!init_seq->seq)
+			continue;
 
-	ret = snd_soc_component_write(component, 0xfd6d, 0xf);
+		if (!(BIT(i) & apple_quirks))
+			continue;
 
-	if (ret < 0)
-		return ret;
+		ret = regmap_multi_reg_write(tas2764->regmap, init_seq->seq,
+					     init_seq->len);
 
-
-	ret = snd_soc_component_write(component, 0xfd0d, 0x0);
-
-	if (ret < 0)
-		return ret;
+		if (ret < 0)
+			return ret;
+	}
 
 	return 0;
 }
@@ -685,11 +687,6 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 	}
 
 	if (tas2764->devid == DEVID_SN012776) {
-		ret = tas2764_apply_unk_apple_quirk(component);
-
-		if (ret < 0)
-			return ret;
-
 		ret = snd_soc_component_update_bits(component, TAS2764_PWR_CTRL,
 					TAS2764_PWR_CTRL_BOP_SRC,
 					TAS2764_PWR_CTRL_BOP_SRC);
@@ -704,6 +701,11 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 			if (ret < 0)
 				return ret;
 		}
+
+		ret = tas2764_apply_init_quirks(tas2764);
+
+		if (ret < 0)
+			return ret;
 	}
 
 	ret = sysfs_create_groups(&component->dev->kobj, tas2764_sysfs_groups);

From df13311199ef3e2a158f2bbbf67ac32c4b8c145c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 5 Feb 2023 22:53:20 +0100
Subject: [PATCH 0321/1027] ASoC: macaudio: Do not disable ISENSE/VSENSE
 switches on j314
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/soc/apple/macaudio.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 52f0fe9c271ffd..4806cb665e78ea 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -924,8 +924,10 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 		 * samples from the codecs back to us, disable the
 		 * controls.
 		 */
+#if 0
 		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
 		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+#endif
 	}
 
 	return 0;

From df64b2d52350384a8fedfbfa18d7564c55c8b8f0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 9 May 2023 19:04:18 +0900
Subject: [PATCH 0322/1027] ASoC: macaudio: Fix PD link double-frees?

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 0b1cea66e2abaf..50745d2d41bdcd 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -296,6 +296,7 @@ static int mca_fe_enable_clocks(struct mca_cluster *cl)
 	 * the power state driver would error out on seeing the device
 	 * as clock-gated.
 	 */
+	WARN_ON(cl->pd_link);
 	cl->pd_link = device_link_add(mca->dev, cl->pd_dev,
 				      DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
 					      DL_FLAG_RPM_ACTIVE);
@@ -319,7 +320,11 @@ static void mca_fe_disable_clocks(struct mca_cluster *cl)
 	mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0);
 	mca_modify(cl, REG_STATUS, STATUS_MCLK_EN, 0);
 
-	device_link_del(cl->pd_link);
+	if (cl->pd_link) {
+		device_link_del(cl->pd_link);
+		cl->pd_link = NULL;
+	}
+
 	clk_disable_unprepare(cl->clk_parent);
 }
 
@@ -389,6 +394,7 @@ static int mca_fe_prepare(struct snd_pcm_substream *substream,
 	if (!cl->syncgen_in_use) {
 		int port = ffs(mca_fe_get_portmask(substream)) - 1;
 
+		WARN_ON(cl->pd_link);
 		cl->pd_link = device_link_add(mca->dev, cl->pd_dev,
 					      DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
 						DL_FLAG_RPM_ACTIVE);
@@ -421,8 +427,10 @@ static int mca_fe_hw_free(struct snd_pcm_substream *substream,
 		return 0;
 
 	mca_modify(cl, REG_SYNCGEN_STATUS, SYNCGEN_STATUS_EN, 0);
-	if (cl->pd_link)
+	if (cl->pd_link) {
 		device_link_del(cl->pd_link);
+		cl->pd_link = NULL;
+	}
 
 	return 0;
 }
@@ -1108,8 +1116,10 @@ static void apple_mca_release(struct mca_data *mca)
 			dev_pm_domain_detach(cl->pd_dev, true);
 	}
 
-	if (mca->pd_link)
+	if (mca->pd_link) {
 		device_link_del(mca->pd_link);
+		mca->pd_link = NULL;
+	}
 
 	if (!IS_ERR_OR_NULL(mca->pd_dev))
 		dev_pm_domain_detach(mca->pd_dev, true);

From 2f82985e6fcb030aaab96b6a52f5cce8e3659d38 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 9 May 2023 19:05:29 +0900
Subject: [PATCH 0323/1027] ASoC: macaudio: Sense improvements

- Export speakers sample rate via mixer control
- Sense device open does not force the sample rate
- No more timeouts on the sense device

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 83 +++++++++++++++++++++++++-------------
 1 file changed, 56 insertions(+), 27 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 4806cb665e78ea..46476e234c299e 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -74,8 +74,8 @@ struct macaudio_snd_data {
 		unsigned int tdm_mask;
 	} *link_props;
 
-	bool speakers_streaming;
-	struct snd_kcontrol *speakers_streaming_kctl;
+	int speaker_sample_rate;
+	struct snd_kcontrol *speaker_sample_rate_kctl;
 };
 
 static bool please_blow_up_my_speakers;
@@ -483,10 +483,37 @@ static int macaudio_dpcm_hw_params(struct snd_pcm_substream *substream,
 				   struct snd_pcm_hw_params *params)
 {
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
 	struct snd_soc_dai *cpu_dai = snd_soc_rtd_to_cpu(rtd, 0);
+	struct snd_interval *rate = hw_param_interval(params,
+						      SNDRV_PCM_HW_PARAM_RATE);
 	int bclk_ratio = macaudio_get_runtime_bclk_ratio(substream);
 	int i;
 
+	if (props->is_sense) {
+		rate->min = rate->max = cpu_dai->rate;
+		return 0;
+	}
+
+	/* Speakers BE */
+	if (props->is_speakers) {
+		if (substream->stream == SNDRV_PCM_STREAM_CAPTURE) {
+			/* Sense PCM: keep the existing BE rate (0 if not already running) */
+			rate->min = rate->max = cpu_dai->rate;
+
+			return 0;
+		} else {
+			/*
+			 * Set the sense PCM rate control to inform userspace of the
+			 * new sample rate.
+			 */
+			ma->speaker_sample_rate = params_rate(params);
+			snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+				       &ma->speaker_sample_rate_kctl->id);
+		}
+	}
+
 	if (bclk_ratio) {
 		struct snd_soc_dai *dai;
 		int mclk = params_rate(params) * bclk_ratio;
@@ -511,8 +538,14 @@ static int macaudio_fe_startup(struct snd_pcm_substream *substream)
 	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
 	int max_rate, ret;
 
-	if (props->is_sense)
+	if (props->is_sense) {
+		/*
+		 * Sense stream will not return data while playback is inactive,
+		 * so do not time out.
+		 */
+		substream->wait_time = MAX_SCHEDULE_TIMEOUT;
 		return 0;
+	}
 
 	ret = snd_pcm_hw_constraint_minmax(substream->runtime,
 					   SNDRV_PCM_HW_PARAM_CHANNELS,
@@ -569,31 +602,28 @@ static void macaudio_dpcm_shutdown(struct snd_pcm_substream *substream)
 	}
 }
 
-static int macaudio_be_prepare(struct snd_pcm_substream *substream)
-{
-	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
-	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
-	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
-
-	if (props->is_speakers) {
-		ma->speakers_streaming = true;
-		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
-			       &ma->speakers_streaming_kctl->id);
-	}
-
-	return 0;
-}
-
 static int macaudio_be_hw_free(struct snd_pcm_substream *substream)
 {
 	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
 	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+	struct snd_soc_dai *dai;
+	int i;
 
-	if (props->is_speakers) {
-		ma->speakers_streaming = false;
+	if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		/*
+		 * Clear the DAI rates, so the next open can change the sample rate.
+		 * This won't happen automatically if the sense PCM is open.
+		 */
+		for_each_rtd_dais(rtd, i, dai) {
+			dai->rate = 0;
+		}
+
+		/* Notify userspace that the speakers are closed */
+		ma->speaker_sample_rate = 0;
 		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
-			       &ma->speakers_streaming_kctl->id);
+			       &ma->speaker_sample_rate_kctl->id);
+
 	}
 
 	return 0;
@@ -606,7 +636,6 @@ static const struct snd_soc_ops macaudio_fe_ops = {
 };
 
 static const struct snd_soc_ops macaudio_be_ops = {
-	.prepare	= macaudio_be_prepare,
 	.hw_free	= macaudio_be_hw_free,
 	.shutdown	= macaudio_dpcm_shutdown,
 	.hw_params	= macaudio_dpcm_hw_params,
@@ -838,7 +867,7 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 		}
 	}
 
-	ma->speakers_streaming_kctl = snd_soc_card_get_kcontrol(card, "Speakers Up Indicator");
+	ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate");
 
 	return 0;
 }
@@ -1017,10 +1046,10 @@ static const struct snd_soc_dapm_widget macaudio_snd_widgets[] = {
 
 static int macaudio_sss_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
 {
-	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
 	uinfo->count = 1;
 	uinfo->value.integer.min = 0;
-	uinfo->value.integer.max = 1;
+	uinfo->value.integer.max = 192000;
 
 	return 0;
 }
@@ -1035,7 +1064,7 @@ static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
 	 * assume there is some ALSA-level lock, but DAPM implementations
 	 * of kcontrol ops do explicit locking, so look into it.
 	 */
-	uvalue->value.integer.value[0] = ma->speakers_streaming;
+	uvalue->value.integer.value[0] = ma->speaker_sample_rate;
 
 	return 0;
 }
@@ -1048,7 +1077,7 @@ static const struct snd_kcontrol_new macaudio_controls[] = {
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.access = SNDRV_CTL_ELEM_ACCESS_READ |
 			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
-		.name = "Speakers Up Indicator",
+		.name = "Speaker Sample Rate",
 		.info = macaudio_sss_info, .get = macaudio_sss_get,
 	},
 };

From 2841b258cdd65745f186672f3020bd69b1a92652 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 9 Oct 2023 22:31:23 +0900
Subject: [PATCH 0324/1027] ASoC: ops: Export snd_soc_control_matches()

This helper is useful for drivers that want to do their own control
lookups and matching as part of more complex logic than the existing
operations.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 include/sound/soc.h | 2 ++
 sound/soc/soc-ops.c | 9 +++++----
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/include/sound/soc.h b/include/sound/soc.h
index 455b81a268f982..678a865fb84721 100644
--- a/include/sound/soc.h
+++ b/include/sound/soc.h
@@ -607,6 +607,8 @@ int snd_soc_put_volsw_range(struct snd_kcontrol *kcontrol,
 	struct snd_ctl_elem_value *ucontrol);
 int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 	struct snd_ctl_elem_value *ucontrol);
+bool snd_soc_control_matches(struct snd_kcontrol *kcontrol,
+	const char *pattern);
 int snd_soc_limit_volume(struct snd_soc_card *card,
 	const char *name, int max);
 int snd_soc_deactivate_kctl(struct snd_soc_card *card,
diff --git a/sound/soc/soc-ops.c b/sound/soc/soc-ops.c
index 096918a73a3633..8cc779a5c7d4c6 100644
--- a/sound/soc/soc-ops.c
+++ b/sound/soc/soc-ops.c
@@ -632,7 +632,7 @@ int snd_soc_get_volsw_range(struct snd_kcontrol *kcontrol,
 }
 EXPORT_SYMBOL_GPL(snd_soc_get_volsw_range);
 
-static bool soc_control_matches(struct snd_kcontrol *kctl,
+bool snd_soc_control_matches(struct snd_kcontrol *kctl,
 	const char *pattern)
 {
 	const char *name = kctl->id.name;
@@ -654,6 +654,7 @@ static bool soc_control_matches(struct snd_kcontrol *kctl,
 
 	return !strcmp(name, pattern);
 }
+EXPORT_SYMBOL_GPL(snd_soc_control_matches);
 
 static int soc_clip_to_platform_max(struct snd_kcontrol *kctl)
 {
@@ -719,7 +720,7 @@ int snd_soc_limit_volume(struct snd_soc_card *card,
 		return -EINVAL;
 
 	list_for_each_entry(kctl, &card->snd_card->controls, list) {
-		if (!soc_control_matches(kctl, name))
+		if (!snd_soc_control_matches(kctl, name))
 			continue;
 
 		ret = soc_limit_volume(kctl, max);
@@ -757,7 +758,7 @@ int snd_soc_deactivate_kctl(struct snd_soc_card *card,
 		return -EINVAL;
 
 	list_for_each_entry(kctl, &card->snd_card->controls, list) {
-		if (!soc_control_matches(kctl, name))
+		if (!snd_soc_control_matches(kctl, name))
 			continue;
 
 		ret = snd_ctl_activate_id(card->snd_card, &kctl->id, active);
@@ -827,7 +828,7 @@ int snd_soc_set_enum_kctl(struct snd_soc_card *card,
 		return -EINVAL;
 
 	list_for_each_entry(kctl, &card->snd_card->controls, list) {
-		if (!soc_control_matches(kctl, name))
+		if (!snd_soc_control_matches(kctl, name))
 			continue;
 
 		ret = soc_set_enum_kctl(kctl, value);

From 952c50e9482bf6857e5bb8ee8dae490965f62723 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 Dec 2021 20:40:04 +0100
Subject: [PATCH 0325/1027] HID: add device IDs for Apple SPI HID devices

Apple Silicon based laptop use SPI as transport for HID. Add support for
SPI-based HID devices and and Apple keyboard and trackpad devices.
Intel based laptops using the keyboard input driver applespi use the
same HID over SPI protocol and can be supported later.

This requires SPI keyboard/mouse HID types since Apple's intenal
keyboards/trackpads use the same product id.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-core.c | 3 +++
 drivers/hid/hid-ids.h  | 5 +++++
 include/linux/hid.h    | 6 +++++-
 3 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 988d0acbdf04dd..98e6cd1b636d68 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2264,6 +2264,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
 	case BUS_I2C:
 		bus = "I2C";
 		break;
+	case BUS_SPI:
+		bus = "SPI";
+		break;
 	case BUS_VIRTUAL:
 		bus = "VIRTUAL";
 		break;
diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 781c5aa298598a..2bb8183ed122d0 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -89,6 +89,7 @@
 
 #define USB_VENDOR_ID_APPLE		0x05ac
 #define BT_VENDOR_ID_APPLE		0x004c
+#define SPI_VENDOR_ID_APPLE		0x05ac
 #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE	0x030d
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE2	0x0269
@@ -187,6 +188,10 @@
 #define USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021   0x029f
 #define USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT 0x8102
 #define USB_DEVICE_ID_APPLE_TOUCHBAR_DISPLAY 0x8302
+#define SPI_DEVICE_ID_APPLE_MACBOOK_AIR_2020	0x0281
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020	0x0341
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021	0x0342
+#define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021	0x0343
 
 #define USB_VENDOR_ID_ASUS		0x0486
 #define USB_DEVICE_ID_ASUS_T91MT	0x0185
diff --git a/include/linux/hid.h b/include/linux/hid.h
index 1533c9dcd3a67f..545fb1d182a813 100644
--- a/include/linux/hid.h
+++ b/include/linux/hid.h
@@ -587,7 +587,9 @@ struct hid_input {
 enum hid_type {
 	HID_TYPE_OTHER = 0,
 	HID_TYPE_USBMOUSE,
-	HID_TYPE_USBNONE
+	HID_TYPE_USBNONE,
+	HID_TYPE_SPI_KEYBOARD,
+	HID_TYPE_SPI_MOUSE,
 };
 
 enum hid_battery_status {
@@ -745,6 +747,8 @@ struct hid_descriptor {
 	.bus = BUS_BLUETOOTH, .vendor = (ven), .product = (prod)
 #define HID_I2C_DEVICE(ven, prod)				\
 	.bus = BUS_I2C, .vendor = (ven), .product = (prod)
+#define HID_SPI_DEVICE(ven, prod)				\
+	.bus = BUS_SPI, .vendor = (ven), .product = (prod)
 
 #define HID_REPORT_ID(rep) \
 	.report_type = (rep)

From 7ba7a701b3778eea78058b32825e27e7926e91c5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 21:15:31 +0100
Subject: [PATCH 0326/1027] HID: apple: add support for internal keyboards

Apple MacBook keyboards started using HID over SPI in 2015. With the
addition of the SPI HID transport they can be supported by this driver.
Support all product ids over with the Apple SPI vendor id for now.
Individual product ids will have to be added for a correct Fn/function
key mapping.

Enable by default on the Apple Arm platform.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/Kconfig     | 2 +-
 drivers/hid/hid-apple.c | 6 ++++++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 08446c89eff6e4..28362bd4e6467a 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -129,7 +129,7 @@ config HID_APPLE
 	tristate "Apple {i,Power,Mac}Books"
 	depends on LEDS_CLASS
 	depends on NEW_LEDS
-	default !EXPERT
+	default !EXPERT || SPI_HID_APPLE
 	help
 	Support for some Apple devices which less or more break
 	HID specification.
diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index af5cf94f9dea3a..3c76e53e2ef5f4 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -903,6 +903,10 @@ static int apple_probe(struct hid_device *hdev,
 	struct apple_sc *asc;
 	int ret;
 
+	if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE &&
+	    hdev->type != HID_TYPE_SPI_KEYBOARD)
+		return -ENODEV;
+
 	asc = devm_kzalloc(&hdev->dev, sizeof(*asc), GFP_KERNEL);
 	if (asc == NULL) {
 		hid_err(hdev, "can't alloc apple descriptor\n");
@@ -1158,6 +1162,8 @@ static const struct hid_device_id apple_devices[] = {
 		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK | APPLE_RDESC_BATTERY },
 	{ HID_BLUETOOTH_DEVICE(BT_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_MAGIC_KEYBOARD_NUMPAD_2021),
 		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+	{ HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
 		.driver_data = APPLE_MAGIC_BACKLIGHT },
 

From e0b7864d35d2b34085b13898b04acd0f50947f1c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 19 Dec 2021 18:08:15 +0100
Subject: [PATCH 0327/1027] HID: apple: add Fn key mapping for Apple silicon
 MacBooks

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-apple.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 3c76e53e2ef5f4..5b79787bdbcb5f 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -276,6 +276,28 @@ static const struct apple_key_translation apple_fn_keys[] = {
 	{ }
 };
 
+static const struct apple_key_translation apple_fn_keys_spi[] = {
+	{ KEY_BACKSPACE, KEY_DELETE },
+	{ KEY_ENTER,	KEY_INSERT },
+	{ KEY_F1,	KEY_BRIGHTNESSDOWN, APPLE_FLAG_FKEY },
+	{ KEY_F2,	KEY_BRIGHTNESSUP,   APPLE_FLAG_FKEY },
+	{ KEY_F3,	KEY_SCALE,          APPLE_FLAG_FKEY },
+	{ KEY_F4,	KEY_SEARCH,         APPLE_FLAG_FKEY },
+	{ KEY_F5,	KEY_RECORD,         APPLE_FLAG_FKEY },
+	{ KEY_F6,	KEY_SLEEP,          APPLE_FLAG_FKEY },
+	{ KEY_F7,	KEY_PREVIOUSSONG,   APPLE_FLAG_FKEY },
+	{ KEY_F8,	KEY_PLAYPAUSE,      APPLE_FLAG_FKEY },
+	{ KEY_F9,	KEY_NEXTSONG,       APPLE_FLAG_FKEY },
+	{ KEY_F10,	KEY_MUTE,           APPLE_FLAG_FKEY },
+	{ KEY_F11,	KEY_VOLUMEDOWN,     APPLE_FLAG_FKEY },
+	{ KEY_F12,	KEY_VOLUMEUP,       APPLE_FLAG_FKEY },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_LEFT,	KEY_HOME },
+	{ KEY_RIGHT,	KEY_END },
+	{ }
+};
+
 static const struct apple_key_translation powerbook_fn_keys[] = {
 	{ KEY_BACKSPACE, KEY_DELETE },
 	{ KEY_F1,	KEY_BRIGHTNESSDOWN,     APPLE_FLAG_FKEY },
@@ -491,6 +513,8 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
 		else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
 				hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
 			table = macbookair_fn_keys;
+		else if (hid->vendor == SPI_VENDOR_ID_APPLE)
+			table = apple_fn_keys_spi;
 		else if (hid->product < 0x21d || hid->product >= 0x300)
 			table = powerbook_fn_keys;
 		else
@@ -670,6 +694,7 @@ static void apple_setup_input(struct input_dev *input)
 
 	/* Enable all needed keys */
 	apple_setup_key_translation(input, apple_fn_keys);
+	apple_setup_key_translation(input, apple_fn_keys_spi);
 	apple_setup_key_translation(input, powerbook_fn_keys);
 	apple_setup_key_translation(input, powerbook_numlock_keys);
 	apple_setup_key_translation(input, apple_iso_keyboard);

From e21bd7d2a678c9b1ea9b90b773f56d7eee609559 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 5 Jan 2022 23:27:34 +0100
Subject: [PATCH 0328/1027] HID: apple: add Fn key mapping for Macbook Pro with
 touchbar

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-apple.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 5b79787bdbcb5f..70a9bf969c6d53 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -298,6 +298,28 @@ static const struct apple_key_translation apple_fn_keys_spi[] = {
 	{ }
 };
 
+static const struct apple_key_translation apple_fn_keys_mbp13[] = {
+	{ KEY_BACKSPACE, KEY_DELETE },
+	{ KEY_ENTER,	KEY_INSERT },
+	{ KEY_UP,	KEY_PAGEUP },
+	{ KEY_DOWN,	KEY_PAGEDOWN },
+	{ KEY_LEFT,	KEY_HOME },
+	{ KEY_RIGHT,	KEY_END },
+	{ KEY_1,	KEY_F1 },
+	{ KEY_2,	KEY_F2 },
+	{ KEY_3,	KEY_F3 },
+	{ KEY_4,	KEY_F4 },
+	{ KEY_5,	KEY_F5 },
+	{ KEY_6,	KEY_F6 },
+	{ KEY_7,	KEY_F7 },
+	{ KEY_8,	KEY_F8 },
+	{ KEY_9,	KEY_F9 },
+	{ KEY_0,	KEY_F10 },
+	{ KEY_MINUS,	KEY_F11 },
+	{ KEY_EQUAL,	KEY_F12 },
+	{ }
+};
+
 static const struct apple_key_translation powerbook_fn_keys[] = {
 	{ KEY_BACKSPACE, KEY_DELETE },
 	{ KEY_F1,	KEY_BRIGHTNESSDOWN,     APPLE_FLAG_FKEY },
@@ -513,6 +535,9 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
 		else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
 				hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
 			table = macbookair_fn_keys;
+		else if (hid->vendor == SPI_VENDOR_ID_APPLE &&
+			hid->product == SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020)
+			table = apple_fn_keys_mbp13;
 		else if (hid->vendor == SPI_VENDOR_ID_APPLE)
 			table = apple_fn_keys_spi;
 		else if (hid->product < 0x21d || hid->product >= 0x300)
@@ -695,6 +720,7 @@ static void apple_setup_input(struct input_dev *input)
 	/* Enable all needed keys */
 	apple_setup_key_translation(input, apple_fn_keys);
 	apple_setup_key_translation(input, apple_fn_keys_spi);
+	apple_setup_key_translation(input, apple_fn_keys_mbp13);
 	apple_setup_key_translation(input, powerbook_fn_keys);
 	apple_setup_key_translation(input, powerbook_numlock_keys);
 	apple_setup_key_translation(input, apple_iso_keyboard);

From 94c62d5ac2f5cc976ec5705abb36cd3c5471c2c2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:10:51 +0100
Subject: [PATCH 0329/1027] HID: magicmouse: use a define of the max number of
 touch contacts

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-magicmouse.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 2eb285b97fc011..2483462be34cba 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -61,6 +61,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define DOUBLE_REPORT_ID   0xf7
 #define USB_BATTERY_TIMEOUT_MS 60000
 
+#define MAX_CONTACTS 16
+
 /* These definitions are not precise, but they're close enough.  (Bits
  * 0x03 seem to indicate the aspect ratio of the touch, bits 0x70 seem
  * to be some kind of bit mask -- 0x20 may be a near-field reading,
@@ -142,8 +144,8 @@ struct magicmouse_sc {
 		u8 size;
 		bool scroll_x_active;
 		bool scroll_y_active;
-	} touches[16];
-	int tracking_ids[16];
+	} touches[MAX_CONTACTS];
+	int tracking_ids[MAX_CONTACTS];
 
 	struct hid_device *hdev;
 	struct delayed_work work;
@@ -595,7 +597,7 @@ static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hd
 
 	__set_bit(EV_ABS, input->evbit);
 
-	error = input_mt_init_slots(input, 16, mt_flags);
+	error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
 	if (error)
 		return error;
 	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 255 << 2,

From 5b59b2099d767f7721e455f1317ad2f2f230f95d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:12:35 +0100
Subject: [PATCH 0330/1027] HID: magicmouse: use struct input_mt_pos for X/Y

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-magicmouse.c | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 2483462be34cba..bd0cf50cc41b1c 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -134,9 +134,8 @@ struct magicmouse_sc {
 	int scroll_accel;
 	unsigned long scroll_jiffies;
 
+	struct input_mt_pos pos[MAX_CONTACTS];
 	struct {
-		short x;
-		short y;
 		short scroll_x;
 		short scroll_y;
 		short scroll_x_hr;
@@ -193,7 +192,7 @@ static void magicmouse_emit_buttons(struct magicmouse_sc *msc, int state)
 		} else if (last_state != 0) {
 			state = last_state;
 		} else if ((id = magicmouse_firm_touch(msc)) >= 0) {
-			int x = msc->touches[id].x;
+			int x = msc->pos[id].x;
 			if (x < middle_button_start)
 				state = 1;
 			else if (x > middle_button_stop)
@@ -254,8 +253,8 @@ static void magicmouse_emit_touch(struct magicmouse_sc *msc, int raw_id, u8 *tda
 
 	/* Store tracking ID and other fields. */
 	msc->tracking_ids[raw_id] = id;
-	msc->touches[id].x = x;
-	msc->touches[id].y = y;
+	msc->pos[id].x = x;
+	msc->pos[id].y = y;
 	msc->touches[id].size = size;
 
 	/* If requested, emulate a scroll wheel by detecting small

From 77e83e085b2b7534e23ea16e2f5cc6bcd29655b8 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 00:15:30 +0100
Subject: [PATCH 0331/1027] HID: magicmouse: use ops function pointers for
 input functionality

Will be used for supporting MacBook trackpads connected via SPI.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-magicmouse.c | 31 ++++++++++++++++++++++++++++++-
 1 file changed, 30 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index bd0cf50cc41b1c..3b6089748a58a9 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -113,6 +113,13 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define TRACKPAD2_RES_Y \
 	((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
 
+
+struct magicmouse_input_ops {
+	int (*raw_event)(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size);
+	int (*setup_input)(struct input_dev *input, struct hid_device *hdev);
+};
+
 /**
  * struct magicmouse_sc - Tracks Magic Mouse-specific data.
  * @input: Input device through which we report events.
@@ -149,6 +156,7 @@ struct magicmouse_sc {
 	struct hid_device *hdev;
 	struct delayed_work work;
 	struct timer_list battery_timer;
+	struct magicmouse_input_ops input_ops;
 };
 
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
@@ -378,6 +386,14 @@ static int magicmouse_raw_event(struct hid_device *hdev,
 		struct hid_report *report, u8 *data, int size)
 {
 	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+	return msc->input_ops.raw_event(hdev, report, data, size);
+}
+
+static int magicmouse_raw_event_usb(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
 	struct input_dev *input = msc->input;
 	int x = 0, y = 0, ii, clicks = 0, npoints;
 
@@ -523,7 +539,17 @@ static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
 	return 0;
 }
 
-static int magicmouse_setup_input(struct input_dev *input, struct hid_device *hdev)
+
+static int magicmouse_setup_input(struct input_dev *input,
+				  struct hid_device *hdev)
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+
+	return msc->input_ops.setup_input(input, hdev);
+}
+
+static int magicmouse_setup_input_usb(struct input_dev *input,
+				      struct hid_device *hdev)
 {
 	int error;
 	int mt_flags = 0;
@@ -810,6 +836,9 @@ static int magicmouse_probe(struct hid_device *hdev,
 		return -ENOMEM;
 	}
 
+	msc->input_ops.raw_event = magicmouse_raw_event_usb;
+	msc->input_ops.setup_input = magicmouse_setup_input_usb;
+
 	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
 	msc->hdev = hdev;
 	INIT_DEFERRABLE_WORK(&msc->work, magicmouse_enable_mt_work);

From 78f07e4961f67b53567b2d2ac98f50b94d6b95a5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 16 Dec 2021 01:17:48 +0100
Subject: [PATCH 0332/1027] HID: magicmouse: add support for Macbook trackpads

The trackpads in Macbooks beginning in 2015 are HID devices connected
over SPI. On Intel Macbooks they are currently supported by applespi.c.
This chang adds support for the trackpads on Apple Silicon Macbooks
starting in late 2020. They use a new HID over SPI transport driver.
The touch report format differs from USB/BT Magic Trackpads. It is the
same format as the type 4 format supported by bcm5974.c.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/Kconfig          |   4 +-
 drivers/hid/hid-magicmouse.c | 259 ++++++++++++++++++++++++++++++++++-
 2 files changed, 260 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 28362bd4e6467a..63cd66efb979aa 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -669,11 +669,13 @@ config LOGIWHEELS_FF
 
 config HID_MAGICMOUSE
 	tristate "Apple Magic Mouse/Trackpad multi-touch support"
+	default SPI_HID_APPLE
 	help
 	Support for the Apple Magic Mouse/Trackpad multi-touch.
 
 	Say Y here if you want support for the multi-touch features of the
-	Apple Wireless "Magic" Mouse and the Apple Wireless "Magic" Trackpad.
+	Apple Wireless "Magic" Mouse, the Apple Wireless "Magic" Trackpad and
+	force touch Trackpads in Macbooks starting from 2015.
 
 config HID_MALTRON
 	tristate "Maltron L90 keyboard"
diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 3b6089748a58a9..e7c39b3f818979 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -113,6 +113,18 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define TRACKPAD2_RES_Y \
 	((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
 
+#define J314_TP_DIMENSION_X (float)13000
+#define J314_TP_MIN_X -5900
+#define J314_TP_MAX_X 6500
+#define J314_TP_RES_X \
+	((J314_TP_MAX_X - J314_TP_MIN_X) / (J314_TP_DIMENSION_X / 100))
+#define J314_TP_DIMENSION_Y (float)8100
+#define J314_TP_MIN_Y -200
+#define J314_TP_MAX_Y 7400
+#define J314_TP_RES_Y \
+	((J314_TP_MAX_Y - J314_TP_MIN_Y) / (J314_TP_DIMENSION_Y / 100))
+
+#define J314_TP_MAX_FINGER_ORIENTATION 16384
 
 struct magicmouse_input_ops {
 	int (*raw_event)(struct hid_device *hdev,
@@ -522,6 +534,157 @@ static int magicmouse_raw_event_usb(struct hid_device *hdev,
 	return 1;
 }
 
+/**
+ * struct tp_finger - single trackpad finger structure, le16-aligned
+ *
+ * @unknown1:		unknown
+ * @unknown2:		unknown
+ * @abs_x:		absolute x coordinate
+ * @abs_y:		absolute y coordinate
+ * @rel_x:		relative x coordinate
+ * @rel_y:		relative y coordinate
+ * @tool_major:		tool area, major axis
+ * @tool_minor:		tool area, minor axis
+ * @orientation:	16384 when point, else 15 bit angle
+ * @touch_major:	touch area, major axis
+ * @touch_minor:	touch area, minor axis
+ * @unused:		zeros
+ * @pressure:		pressure on forcetouch touchpad
+ * @multi:		one finger: varies, more fingers: constant
+ * @crc16:		on last finger: crc over the whole message struct
+ *			(i.e. message header + this struct) minus the last
+ *			@crc16 field; unknown on all other fingers.
+ */
+struct tp_finger {
+	__le16 unknown1;
+	__le16 unknown2;
+	__le16 abs_x;
+	__le16 abs_y;
+	__le16 rel_x;
+	__le16 rel_y;
+	__le16 tool_major;
+	__le16 tool_minor;
+	__le16 orientation;
+	__le16 touch_major;
+	__le16 touch_minor;
+	__le16 unused[2];
+	__le16 pressure;
+	__le16 multi;
+} __attribute__((packed, aligned(2)));
+
+/**
+ * struct trackpad report
+ *
+ * @report_id:		reportid
+ * @buttons:		HID Usage Buttons 3 1-bit reports
+ * @num_fingers:	the number of fingers being reported in @fingers
+ * @clicked:		same as @buttons
+ */
+struct tp_header {
+	// HID mouse report
+	u8 report_id;
+	u8 buttons;
+	u8 rel_x;
+	u8 rel_y;
+	u8 padding[4];
+	// HID vendor part, up to 1751 bytes
+	u8 unknown[22];
+	u8 num_fingers;
+	u8 clicked;
+	u8 unknown3[14];
+};
+
+static inline int le16_to_int(__le16 x)
+{
+	return (signed short)le16_to_cpu(x);
+}
+
+static void report_finger_data(struct input_dev *input, int slot,
+			       const struct input_mt_pos *pos,
+			       const struct tp_finger *f)
+{
+	input_mt_slot(input, slot);
+	input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+	input_report_abs(input, ABS_MT_TOUCH_MAJOR,
+			 le16_to_int(f->touch_major) << 1);
+	input_report_abs(input, ABS_MT_TOUCH_MINOR,
+			 le16_to_int(f->touch_minor) << 1);
+	input_report_abs(input, ABS_MT_WIDTH_MAJOR,
+			 le16_to_int(f->tool_major) << 1);
+	input_report_abs(input, ABS_MT_WIDTH_MINOR,
+			 le16_to_int(f->tool_minor) << 1);
+	input_report_abs(input, ABS_MT_ORIENTATION,
+			 J314_TP_MAX_FINGER_ORIENTATION - le16_to_int(f->orientation));
+	input_report_abs(input, ABS_MT_PRESSURE, le16_to_int(f->pressure));
+	input_report_abs(input, ABS_MT_POSITION_X, pos->x);
+	input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
+}
+
+static int magicmouse_raw_event_spi(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+	struct input_dev *input = msc->input;
+	struct tp_header *tp_hdr;
+	struct tp_finger *f;
+	int i, n;
+	u32 npoints;
+	const size_t hdr_sz = sizeof(struct tp_header);
+	const size_t touch_sz = sizeof(struct tp_finger);
+	u8 map_contacs[MAX_CONTACTS];
+
+	// hid_warn(hdev, "%s\n", __func__);
+	// print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data,
+	// 		     size, false);
+
+	if (data[0] != TRACKPAD2_USB_REPORT_ID)
+		return 0;
+
+	/* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */
+	if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0)
+		return 0;
+
+	tp_hdr = (struct tp_header *)data;
+
+	npoints = (size - hdr_sz) / touch_sz;
+	if (npoints < tp_hdr->num_fingers || npoints > MAX_CONTACTS) {
+		hid_warn(hdev,
+			 "unexpected number of touches (%u) for "
+			 "report\n",
+			 npoints);
+		return 0;
+	}
+
+	n = 0;
+	for (i = 0; i < tp_hdr->num_fingers; i++) {
+		f = (struct tp_finger *)(data + hdr_sz + i * touch_sz);
+		if (le16_to_int(f->touch_major) == 0)
+			continue;
+
+		hid_dbg(hdev, "ev x:%04x y:%04x\n", le16_to_int(f->abs_x),
+			le16_to_int(f->abs_y));
+		msc->pos[n].x = le16_to_int(f->abs_x);
+		msc->pos[n].y = -le16_to_int(f->abs_y);
+		map_contacs[n] = i;
+		n++;
+	}
+
+	input_mt_assign_slots(input, msc->tracking_ids, msc->pos, n, 0);
+
+	for (i = 0; i < n; i++) {
+		int idx = map_contacs[i];
+		f = (struct tp_finger *)(data + hdr_sz + idx * touch_sz);
+		report_finger_data(input, msc->tracking_ids[i], &msc->pos[i], f);
+	}
+
+	input_mt_sync_frame(input);
+	input_report_key(input, BTN_MOUSE, data[1] & 1);
+
+	input_sync(input);
+	return 1;
+}
+
 static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
 		struct hid_usage *usage, __s32 value)
 {
@@ -701,6 +864,79 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
 	return 0;
 }
 
+static int magicmouse_setup_input_spi(struct input_dev *input,
+				      struct hid_device *hdev)
+{
+	int error;
+	int mt_flags = 0;
+
+	__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
+	__clear_bit(BTN_0, input->keybit);
+	__clear_bit(BTN_RIGHT, input->keybit);
+	__clear_bit(BTN_MIDDLE, input->keybit);
+	__clear_bit(EV_REL, input->evbit);
+	__clear_bit(REL_X, input->relbit);
+	__clear_bit(REL_Y, input->relbit);
+
+	mt_flags = INPUT_MT_POINTER | INPUT_MT_DROP_UNUSED | INPUT_MT_TRACK;
+
+	/* finger touch area */
+	input_set_abs_params(input, ABS_MT_TOUCH_MAJOR, 0, 5000, 0, 0);
+	input_set_abs_params(input, ABS_MT_TOUCH_MINOR, 0, 5000, 0, 0);
+
+	/* finger approach area */
+	input_set_abs_params(input, ABS_MT_WIDTH_MAJOR, 0, 5000, 0, 0);
+	input_set_abs_params(input, ABS_MT_WIDTH_MINOR, 0, 5000, 0, 0);
+
+	/* Note: Touch Y position from the device is inverted relative
+	 * to how pointer motion is reported (and relative to how USB
+	 * HID recommends the coordinates work).  This driver keeps
+	 * the origin at the same position, and just uses the additive
+	 * inverse of the reported Y.
+	 */
+
+	input_set_abs_params(input, ABS_MT_PRESSURE, 0, 6000, 0, 0);
+
+	/*
+	 * This makes libinput recognize this as a PressurePad and
+	 * stop trying to use pressure for touch size. Pressure unit
+	 * seems to be ~grams on these touchpads.
+	 */
+	input_abs_set_res(input, ABS_MT_PRESSURE, 1);
+
+	/* finger orientation */
+	input_set_abs_params(input, ABS_MT_ORIENTATION, -J314_TP_MAX_FINGER_ORIENTATION,
+			     J314_TP_MAX_FINGER_ORIENTATION, 0, 0);
+
+	/* finger position */
+	input_set_abs_params(input, ABS_MT_POSITION_X, J314_TP_MIN_X, J314_TP_MAX_X,
+			     0, 0);
+	/* Y axis is inverted */
+	input_set_abs_params(input, ABS_MT_POSITION_Y, -J314_TP_MAX_Y, -J314_TP_MIN_Y,
+			     0, 0);
+
+	/* X/Y resolution */
+	input_abs_set_res(input, ABS_MT_POSITION_X, J314_TP_RES_X);
+	input_abs_set_res(input, ABS_MT_POSITION_Y, J314_TP_RES_Y);
+
+	input_set_events_per_packet(input, 60);
+
+	/* touchpad button */
+	input_set_capability(input, EV_KEY, BTN_MOUSE);
+
+	/*
+	 * hid-input may mark device as using autorepeat, but the trackpad does
+	 * not actually want it.
+	 */
+	__clear_bit(EV_REP, input->evbit);
+
+	error = input_mt_init_slots(input, MAX_CONTACTS, mt_flags);
+	if (error)
+		return error;
+
+	return 0;
+}
+
 static int magicmouse_input_mapping(struct hid_device *hdev,
 		struct hid_input *hi, struct hid_field *field,
 		struct hid_usage *usage, unsigned long **bit, int *max)
@@ -756,6 +992,9 @@ static int magicmouse_enable_multitouch(struct hid_device *hdev)
 			feature_size = sizeof(feature_mt_trackpad2_usb);
 			feature = feature_mt_trackpad2_usb;
 		}
+	} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
+		feature_size = sizeof(feature_mt_trackpad2_usb);
+		feature = feature_mt_trackpad2_usb;
 	} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
 		feature_size = sizeof(feature_mt_mouse2);
 		feature = feature_mt_mouse2;
@@ -830,14 +1069,26 @@ static int magicmouse_probe(struct hid_device *hdev,
 	struct hid_report *report;
 	int ret;
 
+	if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE &&
+	    hdev->type != HID_TYPE_SPI_MOUSE)
+		return -ENODEV;
+
 	msc = devm_kzalloc(&hdev->dev, sizeof(*msc), GFP_KERNEL);
 	if (msc == NULL) {
 		hid_err(hdev, "can't alloc magicmouse descriptor\n");
 		return -ENOMEM;
 	}
 
-	msc->input_ops.raw_event = magicmouse_raw_event_usb;
-	msc->input_ops.setup_input = magicmouse_setup_input_usb;
+	// internal trackpad use a data format use input ops to avoid
+	// conflicts with the report ID.
+	if (id->vendor == SPI_VENDOR_ID_APPLE) {
+		msc->input_ops.raw_event = magicmouse_raw_event_spi;
+		msc->input_ops.setup_input = magicmouse_setup_input_spi;
+
+	} else {
+		msc->input_ops.raw_event = magicmouse_raw_event_usb;
+		msc->input_ops.setup_input = magicmouse_setup_input_usb;
+	}
 
 	msc->scroll_accel = SCROLL_ACCEL_DEFAULT;
 	msc->hdev = hdev;
@@ -887,6 +1138,8 @@ static int magicmouse_probe(struct hid_device *hdev,
 		else /* USB_VENDOR_ID_APPLE */
 			report = hid_register_report(hdev, HID_INPUT_REPORT,
 				TRACKPAD2_USB_REPORT_ID, 0);
+	} else if (id->vendor == SPI_VENDOR_ID_APPLE) {
+		report = hid_register_report(hdev, HID_INPUT_REPORT, 2, 0);
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
 			TRACKPAD_REPORT_ID, 0);
@@ -981,6 +1234,8 @@ static const struct hid_device_id magic_mice[] = {
 		USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE,
 		USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
+	{ HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
+	  .driver_data = 0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, magic_mice);

From 4113733b8df9eb6537cb331faffb6882726e7696 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 10 Dec 2021 19:38:43 +0100
Subject: [PATCH 0333/1027] WIP: HID: transport: spi: add Apple SPI transport

Keyboard and trackpad of Apple Sillicon SoCs (M1, M1 Pro/Max) laptops
are are HID devices connected via SPI.

This is the same protocol as implemented by applespi.c. It was not
noticed that protocol is a transport for HID. Adding support for ACPI
based Intel MacBooks will be done in a separate commit.

How HID is mapped in this protocol is not yet fully understood.

Microsoft has a specification for HID over SPI [1] incompatible with the
transport protocol used by Apple.

[1] https://docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-over-spi

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/Kconfig                      |    2 +
 drivers/hid/Makefile                     |    2 +
 drivers/hid/spi-hid/Kconfig              |   26 +
 drivers/hid/spi-hid/Makefile             |   10 +
 drivers/hid/spi-hid/spi-hid-apple-core.c | 1030 ++++++++++++++++++++++
 drivers/hid/spi-hid/spi-hid-apple-of.c   |  136 +++
 drivers/hid/spi-hid/spi-hid-apple.h      |   31 +
 7 files changed, 1237 insertions(+)
 create mode 100644 drivers/hid/spi-hid/Kconfig
 create mode 100644 drivers/hid/spi-hid/Makefile
 create mode 100644 drivers/hid/spi-hid/spi-hid-apple-core.c
 create mode 100644 drivers/hid/spi-hid/spi-hid-apple-of.c
 create mode 100644 drivers/hid/spi-hid/spi-hid-apple.h

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 63cd66efb979aa..8a356b24bb6705 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1369,4 +1369,6 @@ source "drivers/hid/amd-sfh-hid/Kconfig"
 
 source "drivers/hid/surface-hid/Kconfig"
 
+source "drivers/hid/spi-hid/Kconfig"
+
 endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index e40f1ddebbb713..019c845cd24610 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -169,3 +169,5 @@ obj-$(INTEL_ISH_FIRMWARE_DOWNLOADER)	+= intel-ish-hid/
 obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
 
 obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
+
+obj-$(CONFIG_SPI_HID_APPLE_CORE)	+= spi-hid/
diff --git a/drivers/hid/spi-hid/Kconfig b/drivers/hid/spi-hid/Kconfig
new file mode 100644
index 00000000000000..8e37f0fec28ac9
--- /dev/null
+++ b/drivers/hid/spi-hid/Kconfig
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menu "SPI HID support"
+	depends on SPI
+
+config SPI_HID_APPLE_OF
+	tristate "HID over SPI transport layer for Apple Silicon SoCs"
+	default ARCH_APPLE
+	depends on SPI && INPUT && OF
+	help
+	  Say Y here if you use Apple Silicon based laptop. The keyboard and
+	  touchpad are HID based devices connected via SPI.
+
+	  If unsure, say N.
+
+	  This support is also available as a module.  If so, the module
+	  will be called spi-hid-apple-of. It will also build/depend on the
+	  module spi-hid-apple.
+
+endmenu
+
+config SPI_HID_APPLE_CORE
+	tristate
+	default y if SPI_HID_APPLE_OF=y
+	default m if SPI_HID_APPLE_OF=m
+	select HID
+	select CRC16
diff --git a/drivers/hid/spi-hid/Makefile b/drivers/hid/spi-hid/Makefile
new file mode 100644
index 00000000000000..f276ee12cb94fc
--- /dev/null
+++ b/drivers/hid/spi-hid/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+#
+# Makefile for SPI HID tarnsport drivers
+#
+
+obj-$(CONFIG_SPI_HID_APPLE_CORE)		+= spi-hid-apple.o
+
+spi-hid-apple-objs				=  spi-hid-apple-core.o
+
+obj-$(CONFIG_SPI_HID_APPLE_OF)			+= spi-hid-apple-of.o
diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
new file mode 100644
index 00000000000000..cd018df4f38715
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -0,0 +1,1030 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * Apple SPI HID transport driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on: drivers/input/applespi.c
+ *
+ * MacBook (Pro) SPI keyboard and touchpad driver
+ *
+ * Copyright (c) 2015-2018 Federico Lorenzi
+ * Copyright (c) 2017-2018 Ronald Tschalär
+ *
+ */
+
+//#define DEBUG 2
+
+#include <asm/unaligned.h>
+#include <linux/crc16.h>
+#include <linux/delay.h>
+#include <linux/device/driver.h>
+#include <linux/hid.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/printk.h>
+#include <linux/platform_device.h>
+#include <linux/spi/spi.h>
+#include <linux/wait.h>
+
+#include "spi-hid-apple.h"
+
+#define SPIHID_DEF_WAIT msecs_to_jiffies(1000)
+
+#define SPIHID_MAX_INPUT_REPORT_SIZE 0x800
+
+/* support only keyboard, trackpad and management dev for now */
+#define SPIHID_MAX_DEVICES 3
+
+#define SPIHID_DEVICE_ID_MNGT 0x0
+#define SPIHID_DEVICE_ID_KBD 0x1
+#define SPIHID_DEVICE_ID_TP 0x2
+#define SPIHID_DEVICE_ID_INFO 0xd0
+
+#define SPIHID_READ_PACKET 0x20
+#define SPIHID_WRITE_PACKET 0x40
+
+#define SPIHID_DESC_MAX 512
+
+#define SPIHID_SET_LEDS 0x0151 /* caps lock */
+
+#define SPI_RW_CHG_DELAY_US 200 /* 'Inter Stage Us'? */
+
+static const u8 spi_hid_apple_booted[4] = { 0xa0, 0x80, 0x00, 0x00 };
+static const u8 spi_hid_apple_status_ok[4] = { 0xac, 0x27, 0x68, 0xd5 };
+
+struct spihid_interface {
+	struct hid_device *hid;
+	u8 *hid_desc;
+	u32 hid_desc_len;
+	u32 id;
+	unsigned country;
+	u32 max_control_report_len;
+	u32 max_input_report_len;
+	u32 max_output_report_len;
+	u8 name[32];
+	bool ready;
+};
+
+struct spihid_input_report {
+	u8 *buf;
+	u32 length;
+	u32 offset;
+	u8 device;
+	u8 flags;
+};
+
+struct spihid_apple {
+	struct spi_device *spidev;
+
+	struct spihid_apple_ops *ops;
+
+	struct spihid_interface mngt;
+	struct spihid_interface kbd;
+	struct spihid_interface tp;
+
+	wait_queue_head_t wait;
+	struct mutex tx_lock; //< protects against concurrent SPI writes
+
+	struct spi_message rx_msg;
+	struct spi_message tx_msg;
+	struct spi_transfer rx_transfer;
+	struct spi_transfer tx_transfer;
+	struct spi_transfer status_transfer;
+
+	u8 *rx_buf;
+	u8 *tx_buf;
+	u8 *status_buf;
+
+	u8 vendor[32];
+	u8 product[64];
+	u8 serial[32];
+
+	u32 num_devices;
+
+	u32 vendor_id;
+	u32 product_id;
+	u32 version_number;
+
+	u8 msg_id;
+
+	/* fragmented HID report */
+	struct spihid_input_report report;
+
+	/* state tracking flags */
+	bool status_booted;
+};
+
+/**
+ * struct spihid_msg_hdr - common header of protocol messages.
+ *
+ * Each message begins with fixed header, followed by a message-type specific
+ * payload, and ends with a 16-bit crc. Because of the varying lengths of the
+ * payload, the crc is defined at the end of each payload struct, rather than
+ * in this struct.
+ *
+ * @unknown0:	request type? output, input (0x10), feature, protocol
+ * @unknown1:	maybe report id?
+ * @unknown2:	mostly zero, in info request maybe device num
+ * @msgid:	incremented on each message, rolls over after 255; there is a
+ *		separate counter for each message type.
+ * @rsplen:	response length (the exact nature of this field is quite
+ *		speculative). On a request/write this is often the same as
+ *		@length, though in some cases it has been seen to be much larger
+ *		(e.g. 0x400); on a response/read this the same as on the
+ *		request; for reads that are not responses it is 0.
+ * @length:	length of the remainder of the data in the whole message
+ *		structure (after re-assembly in case of being split over
+ *		multiple spi-packets), minus the trailing crc. The total size
+ *		of a message is therefore @length + 10.
+ */
+
+struct spihid_msg_hdr {
+	u8 unknown0;
+	u8 unknown1;
+	u8 unknown2;
+	u8 id;
+	__le16 rsplen;
+	__le16 length;
+};
+
+/**
+ * struct spihid_transfer_packet - a complete spi packet; always 256 bytes. This carries
+ * the (parts of the) message in the data. But note that this does not
+ * necessarily contain a complete message, as in some cases (e.g. many
+ * fingers pressed) the message is split over multiple packets (see the
+ * @offset, @remain, and @length fields). In general the data parts in
+ * spihid_transfer_packet's are concatenated until @remaining is 0, and the
+ * result is an message.
+ *
+ * @flags:	0x40 = write (to device), 0x20 = read (from device); note that
+ *		the response to a write still has 0x40.
+ * @device:	1 = keyboard, 2 = touchpad
+ * @offset:	specifies the offset of this packet's data in the complete
+ *		message; i.e. > 0 indicates this is a continuation packet (in
+ *		the second packet for a message split over multiple packets
+ *		this would then be the same as the @length in the first packet)
+ * @remain:	number of message bytes remaining in subsequents packets (in
+ *		the first packet of a message split over two packets this would
+ *		then be the same as the @length in the second packet)
+ * @length:	length of the valid data in the @data in this packet
+ * @data:	all or part of a message
+ * @crc16:	crc over this whole structure minus this @crc16 field. This
+ *		covers just this packet, even on multi-packet messages (in
+ *		contrast to the crc in the message).
+ */
+struct spihid_transfer_packet {
+	u8 flags;
+	u8 device;
+	__le16 offset;
+	__le16 remain;
+	__le16 length;
+	u8 data[246];
+	__le16 crc16;
+};
+
+/*
+ * how HID is mapped onto the protocol is not fully clear. This are the known
+ * reports/request:
+ *
+ *			pkt.flags	pkt.dev?	msg.u0	msg.u1	msg.u2
+ * info			0x40		0xd0		0x20	0x01	0xd0
+ *
+ * info mngt:		0x40		0xd0		0x20	0x10	0x00
+ * info kbd:		0x40		0xd0		0x20	0x10	0x01
+ * info tp:		0x40		0xd0		0x20	0x10	0x02
+ *
+ * desc kbd:		0x40		0xd0		0x20	0x10	0x01
+ * desc trackpad:	0x40		0xd0		0x20	0x10	0x02
+ *
+ * mt mode:		0x40		0x02		0x52	0x02	0x00	set protocol?
+ * capslock led		0x40		0x01		0x51	0x01	0x00	output report
+ *
+ * report kbd:		0x20		0x01		0x10	0x01	0x00	input report
+ * report tp:		0x20		0x02		0x10	0x02	0x00	input report
+ *
+ */
+
+
+static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0,
+				u8 unk1, u8 unk2, u16 resp_len, u8 *buf,
+				    size_t len)
+{
+	struct spihid_transfer_packet *pkt;
+	struct spihid_msg_hdr *hdr;
+	u16 crc;
+	int err;
+
+	/* know reports are small enoug to fit in a single packet */
+	if (len > sizeof(pkt->data) - sizeof(*hdr) - sizeof(__le16))
+		return -EINVAL;
+
+	err = mutex_lock_interruptible(&spihid->tx_lock);
+	if (err < 0)
+		return err;
+
+	pkt = (struct spihid_transfer_packet *)spihid->tx_buf;
+
+	memset(pkt, 0, sizeof(*pkt));
+	pkt->flags = SPIHID_WRITE_PACKET;
+	pkt->device = target;
+	pkt->length = cpu_to_le16(sizeof(*hdr) + len + sizeof(__le16));
+
+	hdr = (struct spihid_msg_hdr *)&pkt->data[0];
+	hdr->unknown0 = unk0;
+	hdr->unknown1 = unk1;
+	hdr->unknown2 = unk2;
+	hdr->id = spihid->msg_id++;
+	hdr->rsplen = cpu_to_le16(resp_len);
+	hdr->length = cpu_to_le16(len);
+
+	if (len)
+		memcpy(pkt->data + sizeof(*hdr), buf, len);
+	crc = crc16(0, &pkt->data[0], sizeof(*hdr) + len);
+	put_unaligned_le16(crc, pkt->data + sizeof(*hdr) + len);
+
+	pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf,
+				 offsetof(struct spihid_transfer_packet, crc16)));
+
+	err = spi_sync(spihid->spidev, &spihid->tx_msg);
+	mutex_unlock(&spihid->tx_lock);
+	if (err < 0)
+		return err;
+
+	return (int)len;
+}
+
+static struct spihid_apple *spihid_get_data(struct spihid_interface *idev)
+{
+	switch (idev->id) {
+	case SPIHID_DEVICE_ID_KBD:
+		return container_of(idev, struct spihid_apple, kbd);
+	case SPIHID_DEVICE_ID_TP:
+		return container_of(idev, struct spihid_apple, tp);
+	default:
+		return NULL;
+	}
+}
+
+static int apple_ll_start(struct hid_device *hdev)
+{
+	/* no-op SPI transport is already setup */
+	return 0;
+};
+
+static void apple_ll_stop(struct hid_device *hdev)
+{
+	/* no-op, devices will be desstroyed on driver destruction */
+}
+
+static int apple_ll_open(struct hid_device *hdev)
+{
+	struct spihid_apple *spihid;
+	struct spihid_interface *idev = hdev->driver_data;
+
+	if (idev->hid_desc_len == 0) {
+		spihid = spihid_get_data(idev);
+		dev_warn(&spihid->spidev->dev,
+			 "HID descriptor missing for dev %u", idev->id);
+	} else
+		idev->ready = true;
+
+	return 0;
+}
+
+static void apple_ll_close(struct hid_device *hdev)
+{
+	struct spihid_interface *idev = hdev->driver_data;
+	idev->ready = false;
+}
+
+static int apple_ll_parse(struct hid_device *hdev)
+{
+	struct spihid_interface *idev = hdev->driver_data;
+
+	return hid_parse_report(hdev, idev->hid_desc, idev->hid_desc_len);
+}
+
+static int apple_ll_raw_request(struct hid_device *hdev,
+				unsigned char reportnum, __u8 *buf, size_t len,
+				unsigned char rtype, int reqtype)
+{
+	struct spihid_interface *idev = hdev->driver_data;
+	struct spihid_apple *spihid = spihid_get_data(idev);
+
+	dev_dbg(&spihid->spidev->dev,
+		"apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu",
+		idev->id, reportnum, rtype);
+
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		return -EINVAL; // spihid_get_raw_report();
+	case HID_REQ_SET_REPORT:
+		if (buf[0] != reportnum)
+			return -EINVAL;
+		if (reportnum != idev->id) {
+			dev_warn(&spihid->spidev->dev,
+				 "device:%u reportnum:"
+				 "%hhu mismatch",
+				 idev->id, reportnum);
+			return -EINVAL;
+		}
+		return spihid_apple_request(spihid, idev->id, 0x52, reportnum, 0x00, 2, buf, len);
+	default:
+		return -EIO;
+	}
+}
+
+static int apple_ll_output_report(struct hid_device *hdev, __u8 *buf,
+				  size_t len)
+{
+	struct spihid_interface *idev = hdev->driver_data;
+	struct spihid_apple *spihid = spihid_get_data(idev);
+	if (!spihid)
+		return -1;
+
+	dev_dbg(&spihid->spidev->dev,
+		"apple_ll_output_report: device:%u len:%zu:",
+		idev->id, len);
+	// second idev->id should maybe be buf[0]?
+	return spihid_apple_request(spihid, idev->id, 0x51, idev->id, 0x00, 0, buf, len);
+}
+
+static struct hid_ll_driver apple_hid_ll = {
+	.start = &apple_ll_start,
+	.stop = &apple_ll_stop,
+	.open = &apple_ll_open,
+	.close = &apple_ll_close,
+	.parse = &apple_ll_parse,
+	.raw_request = &apple_ll_raw_request,
+	.output_report = &apple_ll_output_report,
+};
+
+static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid,
+						 u32 iface)
+{
+	switch (iface) {
+	case SPIHID_DEVICE_ID_MNGT:
+		return &spihid->mngt;
+	case SPIHID_DEVICE_ID_KBD:
+		return &spihid->kbd;
+	case SPIHID_DEVICE_ID_TP:
+		return &spihid->tp;
+	default:
+		return NULL;
+	}
+}
+
+static int spihid_verify_msg(struct spihid_apple *spihid, u8 *buf, size_t len)
+{
+	u16 msg_crc, crc;
+	struct device *dev = &spihid->spidev->dev;
+
+	crc = crc16(0, buf, len - sizeof(__le16));
+	msg_crc = get_unaligned_le16(buf + len - sizeof(__le16));
+	if (crc != msg_crc) {
+		dev_warn_ratelimited(dev, "Read message crc mismatch\n");
+		return 0;
+	}
+	return 1;
+}
+
+static bool spihid_status_report(struct spihid_apple *spihid, u8 *pl,
+				 size_t len)
+{
+	struct device *dev = &spihid->spidev->dev;
+	dev_dbg(dev, "%s: len: %zu", __func__, len);
+	if (len == 5 && pl[0] == 0xe0)
+		return true;
+
+	return false;
+}
+
+static bool spihid_process_input_report(struct spihid_apple *spihid, u32 device,
+					struct spihid_msg_hdr *hdr, u8 *payload,
+					size_t len)
+{
+	//dev_dbg(&spihid>spidev->dev, "input report: req:%hx iface:%u ", hdr->unknown0, device);
+	if (hdr->unknown0 != 0x10)
+		return false;
+
+	/* HID device as well but Vendor usage only, handle it internally for now */
+	if (device == 0) {
+		if (hdr->unknown1 == 0xe0) {
+			return spihid_status_report(spihid, payload, len);
+		}
+	} else if (device < SPIHID_MAX_DEVICES) {
+		struct spihid_interface *iface =
+			spihid_get_iface(spihid, device);
+		if (iface && iface->hid && iface->ready) {
+			hid_input_report(iface->hid, HID_INPUT_REPORT, payload,
+					 len, 1);
+			return true;
+		}
+	} else
+		dev_dbg(&spihid->spidev->dev,
+			"unexpected iface:%u for input report", device);
+
+	return false;
+}
+
+struct spihid_device_info {
+	__le16 u0[2];
+	__le16 num_devices;
+	__le16 vendor_id;
+	__le16 product_id;
+	__le16 version_number;
+	__le16 vendor_str[2]; //< offset and string length
+	__le16 product_str[2]; //< offset and string length
+	__le16 serial_str[2]; //< offset and string length
+};
+
+static bool spihid_process_device_info(struct spihid_apple *spihid, u32 iface,
+				       u8 *payload, size_t len)
+{
+	struct device *dev = &spihid->spidev->dev;
+
+	if (iface != SPIHID_DEVICE_ID_INFO)
+		return false;
+
+	if (spihid->vendor_id == 0 &&
+	    len >= sizeof(struct spihid_device_info)) {
+		struct spihid_device_info *info =
+			(struct spihid_device_info *)payload;
+		u16 voff, vlen, poff, plen, soff, slen;
+		u32 num_devices;
+
+		num_devices = __le16_to_cpu(info->num_devices);
+
+		if (num_devices < SPIHID_MAX_DEVICES) {
+			dev_err(dev,
+				"Device info reports %u devices, expecting at least 3",
+				num_devices);
+			return false;
+		}
+		spihid->num_devices = num_devices;
+
+		if (spihid->num_devices > SPIHID_MAX_DEVICES) {
+			dev_info(
+				dev,
+				"limiting the number of devices to mngt, kbd and mouse");
+			spihid->num_devices = SPIHID_MAX_DEVICES;
+		}
+
+		spihid->vendor_id = __le16_to_cpu(info->vendor_id);
+		spihid->product_id = __le16_to_cpu(info->product_id);
+		spihid->version_number = __le16_to_cpu(info->version_number);
+
+		voff = __le16_to_cpu(info->vendor_str[0]);
+		vlen = __le16_to_cpu(info->vendor_str[1]);
+
+		if (voff < len && vlen <= len - voff &&
+		    vlen < sizeof(spihid->vendor)) {
+			memcpy(spihid->vendor, payload + voff, vlen);
+			spihid->vendor[vlen] = '\0';
+		}
+
+		poff = __le16_to_cpu(info->product_str[0]);
+		plen = __le16_to_cpu(info->product_str[1]);
+
+		if (poff < len && plen <= len - poff &&
+		    plen < sizeof(spihid->product)) {
+			memcpy(spihid->product, payload + poff, plen);
+			spihid->product[plen] = '\0';
+		}
+
+		soff = __le16_to_cpu(info->serial_str[0]);
+		slen = __le16_to_cpu(info->serial_str[1]);
+
+		if (soff < len && slen <= len - soff &&
+		    slen < sizeof(spihid->serial)) {
+			memcpy(spihid->vendor, payload + soff, slen);
+			spihid->serial[slen] = '\0';
+		}
+
+		wake_up_interruptible(&spihid->wait);
+	}
+	return true;
+}
+
+struct spihid_iface_info {
+	u8 u_0;
+	u8 interface_num;
+	u8 u_2;
+	u8 u_3;
+	u8 u_4;
+	u8 country_code;
+	__le16 max_input_report_len;
+	__le16 max_output_report_len;
+	__le16 max_control_report_len;
+	__le16 name_offset;
+	__le16 name_length;
+};
+
+static bool spihid_process_iface_info(struct spihid_apple *spihid, u32 num,
+				      u8 *payload, size_t len)
+{
+	struct spihid_iface_info *info;
+	struct spihid_interface *iface = spihid_get_iface(spihid, num);
+	u32 name_off, name_len;
+
+	if (!iface)
+		return false;
+
+	if (!iface->max_input_report_len) {
+		if (len < sizeof(*info))
+			return false;
+
+		info = (struct spihid_iface_info *)payload;
+
+		iface->max_input_report_len =
+			le16_to_cpu(info->max_input_report_len);
+		iface->max_output_report_len =
+			le16_to_cpu(info->max_output_report_len);
+		iface->max_control_report_len =
+			le16_to_cpu(info->max_control_report_len);
+		iface->country = info->country_code;
+
+		name_off = le16_to_cpu(info->name_offset);
+		name_len = le16_to_cpu(info->name_length);
+
+		if (name_off < len && name_len <= len - name_off &&
+		    name_len < sizeof(iface->name)) {
+			memcpy(iface->name, payload + name_off, name_len);
+			iface->name[name_len] = '\0';
+		}
+
+		dev_dbg(&spihid->spidev->dev, "Info for %s, country code: 0x%x",
+			iface->name, iface->country);
+
+		wake_up_interruptible(&spihid->wait);
+	}
+
+	return true;
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+				      struct spihid_interface *idev, u8 device);
+
+static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid,
+						 u32 num, u8 *payload,
+						 size_t len)
+{
+	struct spihid_interface *iface = spihid_get_iface(spihid, num);
+
+	if (!iface)
+		return false;
+
+	if (iface->hid_desc_len == 0) {
+		if (len > SPIHID_DESC_MAX)
+			return false;
+		memcpy(iface->hid_desc, payload, len);
+		iface->hid_desc_len = len;
+
+		/* do not register the mngt iface as HID device */
+		if (num > 0)
+			spihid_register_hid_device(spihid, iface, num);
+
+		wake_up_interruptible(&spihid->wait);
+	}
+	return true;
+}
+
+static bool spihid_process_response(struct spihid_apple *spihid,
+				    struct spihid_msg_hdr *hdr, u8 *payload,
+				    size_t len)
+{
+	if (hdr->unknown0 == 0x20) {
+		switch (hdr->unknown1) {
+		case 0x01:
+			return spihid_process_device_info(spihid, hdr->unknown2,
+							  payload, len);
+		case 0x02:
+			return spihid_process_iface_info(spihid, hdr->unknown2,
+							 payload, len);
+		case 0x10:
+			return spihid_process_iface_hid_report_desc(
+				spihid, hdr->unknown2, payload, len);
+		default:
+			break;
+		}
+	}
+
+	return false;
+}
+
+static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
+				   size_t length, u8 device, u8 flags)
+{
+	struct device *dev = &spihid->spidev->dev;
+	struct spihid_msg_hdr *hdr;
+	bool handled = false;
+	u8 *payload;
+
+	if (!spihid_verify_msg(spihid, data, length))
+		return;
+
+	hdr = (struct spihid_msg_hdr *)data;
+
+	if (hdr->length == 0)
+		return;
+
+	payload = data + sizeof(struct spihid_msg_hdr);
+
+	switch (flags) {
+	case SPIHID_READ_PACKET:
+		handled = spihid_process_input_report(spihid, device, hdr,
+						      payload, le16_to_cpu(hdr->length));
+		break;
+	case SPIHID_WRITE_PACKET:
+		handled = spihid_process_response(spihid, hdr, payload,
+						  le16_to_cpu(hdr->length));
+		break;
+	default:
+		break;
+	}
+
+#if defined(DEBUG) && DEBUG > 1
+	{
+		dev_dbg(dev,
+			"R msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n",
+			hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id,
+			hdr->length);
+		print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1,
+				     payload, le16_to_cpu(hdr->length), true);
+	}
+#else
+	if (!handled) {
+		dev_dbg(dev,
+			"R unhandled msg: req:%02hhx rep:%02hhx dev:%02hhx id:%hu len:%hu\n",
+			hdr->unknown0, hdr->unknown1, hdr->unknown2, hdr->id,
+			hdr->length);
+		print_hex_dump_debug("spihid msg: ", DUMP_PREFIX_OFFSET, 16, 1,
+				     payload, le16_to_cpu(hdr->length), true);
+	}
+#endif
+}
+
+static void spihid_assemble_message(struct spihid_apple *spihid,
+				    struct spihid_transfer_packet *pkt)
+{
+	size_t length, offset, remain;
+	struct device *dev = &spihid->spidev->dev;
+	struct spihid_input_report *rep = &spihid->report;
+
+	length = le16_to_cpu(pkt->length);
+	remain = le16_to_cpu(pkt->remain);
+	offset = le16_to_cpu(pkt->offset);
+
+	if (offset + length + remain > U16_MAX) {
+		return;
+	}
+
+	if (pkt->device != rep->device || pkt->flags != rep->flags ||
+	    offset != rep->offset) {
+		rep->device = 0;
+		rep->flags = 0;
+		rep->offset = 0;
+		rep->length = 0;
+	}
+
+	if (offset == 0) {
+		if (rep->offset != 0) {
+			dev_warn(dev, "incomplete report off:%u len:%u",
+				 rep->offset, rep->length);
+		}
+		memcpy(rep->buf, pkt->data, length);
+		rep->offset = length;
+		rep->length = length + remain;
+		rep->device = pkt->device;
+		rep->flags = pkt->flags;
+	} else if (offset == rep->offset) {
+		if (offset + length + remain != rep->length) {
+			dev_warn(dev, "incomplete report off:%u len:%u",
+				 rep->offset, rep->length);
+			return;
+		}
+		memcpy(rep->buf + offset, pkt->data, length);
+		rep->offset += length;
+
+		if (rep->offset == rep->length) {
+			spihid_process_message(spihid, rep->buf, rep->length,
+					       rep->device, rep->flags);
+			rep->device = 0;
+			rep->flags = 0;
+			rep->offset = 0;
+			rep->length = 0;
+		}
+	}
+}
+
+static void spihid_process_read(struct spihid_apple *spihid)
+{
+	u16 crc;
+	size_t length;
+	struct device *dev = &spihid->spidev->dev;
+	struct spihid_transfer_packet *pkt;
+
+	pkt = (struct spihid_transfer_packet *)spihid->rx_buf;
+
+	/* check transfer packet crc */
+	crc = crc16(0, spihid->rx_buf,
+		    offsetof(struct spihid_transfer_packet, crc16));
+	if (crc != le16_to_cpu(pkt->crc16)) {
+		dev_warn_ratelimited(dev, "Read package crc mismatch\n");
+		return;
+	}
+
+	length = le16_to_cpu(pkt->length);
+
+	if (length < sizeof(struct spihid_msg_hdr) + 2) {
+		if (length == sizeof(spi_hid_apple_booted) &&
+		    !memcmp(pkt->data, spi_hid_apple_booted, length)) {
+			if (!spihid->status_booted) {
+				spihid->status_booted = true;
+				wake_up_interruptible(&spihid->wait);
+			}
+		} else {
+			dev_info(dev, "R short packet: len:%zu\n", length);
+			print_hex_dump(KERN_INFO, "spihid pkt:",
+				       DUMP_PREFIX_OFFSET, 16, 1, pkt->data,
+				       length, false);
+		}
+		return;
+	}
+
+#if defined(DEBUG) && DEBUG > 1
+	dev_dbg(dev,
+		"R pkt: flags:%02hhx dev:%02hhx off:%hu remain:%hu, len:%zu\n",
+		pkt->flags, pkt->device, pkt->offset, pkt->remain, length);
+#if defined(DEBUG) && DEBUG > 2
+	print_hex_dump_debug("spihid pkt: ", DUMP_PREFIX_OFFSET, 16, 1,
+			     spihid->rx_buf,
+			     sizeof(struct spihid_transfer_packet), true);
+#endif
+#endif
+
+	if (length > sizeof(pkt->data)) {
+		dev_warn_ratelimited(dev, "Invalid pkt len:%zu", length);
+		return;
+	}
+
+	/* short message */
+	if (pkt->offset == 0 && pkt->remain == 0) {
+		spihid_process_message(spihid, pkt->data, length, pkt->device,
+				       pkt->flags);
+	} else {
+		spihid_assemble_message(spihid, pkt);
+	}
+}
+
+static void spihid_read_packet_sync(struct spihid_apple *spihid)
+{
+	int err;
+
+	err = spi_sync(spihid->spidev, &spihid->rx_msg);
+	if (!err) {
+		spihid_process_read(spihid);
+	} else {
+		dev_warn(&spihid->spidev->dev, "RX failed: %d\n", err);
+	}
+}
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data)
+{
+	struct spi_device *spi = data;
+	struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+	spihid_read_packet_sync(spihid);
+
+	return IRQ_HANDLED;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_irq);
+
+static void spihid_apple_setup_spi_msgs(struct spihid_apple *spihid)
+{
+	memset(&spihid->rx_transfer, 0, sizeof(spihid->rx_transfer));
+
+	spihid->rx_transfer.rx_buf = spihid->rx_buf;
+	spihid->rx_transfer.len = sizeof(struct spihid_transfer_packet);
+
+	spi_message_init(&spihid->rx_msg);
+	spi_message_add_tail(&spihid->rx_transfer, &spihid->rx_msg);
+
+	memset(&spihid->tx_transfer, 0, sizeof(spihid->rx_transfer));
+	memset(&spihid->status_transfer, 0, sizeof(spihid->status_transfer));
+
+	spihid->tx_transfer.tx_buf = spihid->tx_buf;
+	spihid->tx_transfer.len = sizeof(struct spihid_transfer_packet);
+	spihid->tx_transfer.delay.unit = SPI_DELAY_UNIT_USECS;
+	spihid->tx_transfer.delay.value = SPI_RW_CHG_DELAY_US;
+
+	spihid->status_transfer.rx_buf = spihid->status_buf;
+	spihid->status_transfer.len = sizeof(spi_hid_apple_status_ok);
+
+	spi_message_init(&spihid->tx_msg);
+	spi_message_add_tail(&spihid->tx_transfer, &spihid->tx_msg);
+	spi_message_add_tail(&spihid->status_transfer, &spihid->tx_msg);
+}
+
+static int spihid_apple_setup_spi(struct spihid_apple *spihid)
+{
+	spihid_apple_setup_spi_msgs(spihid);
+
+	return spihid->ops->power_on(spihid->ops);
+}
+
+static int spihid_register_hid_device(struct spihid_apple *spihid,
+				      struct spihid_interface *iface, u8 device)
+{
+	int ret;
+	struct hid_device *hid;
+
+	iface->id = device;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid))
+		return PTR_ERR(hid);
+
+	strscpy(hid->name, spihid->product, sizeof(hid->name));
+	snprintf(hid->phys, sizeof(hid->phys), "%s (%hhx)",
+		 dev_name(&spihid->spidev->dev), device);
+	strscpy(hid->uniq, spihid->serial, sizeof(hid->uniq));
+
+	hid->ll_driver = &apple_hid_ll;
+	hid->bus = BUS_SPI;
+	hid->vendor = spihid->vendor_id;
+	hid->product = spihid->product_id;
+	hid->version = spihid->version_number;
+
+	if (device == SPIHID_DEVICE_ID_KBD)
+		hid->type = HID_TYPE_SPI_KEYBOARD;
+	else if (device == SPIHID_DEVICE_ID_TP)
+		hid->type = HID_TYPE_SPI_MOUSE;
+
+	hid->country = iface->country;
+	hid->dev.parent = &spihid->spidev->dev;
+	hid->driver_data = iface;
+
+	ret = hid_add_device(hid);
+	if (ret < 0) {
+		hid_destroy_device(hid);
+		dev_warn(&spihid->spidev->dev,
+			 "Failed to register hid device %hhu", device);
+		return ret;
+	}
+
+	iface->hid = hid;
+
+	return 0;
+}
+
+static void spihid_destroy_hid_device(struct spihid_interface *iface)
+{
+	if (iface->hid) {
+		hid_destroy_device(iface->hid);
+		iface->hid = NULL;
+	}
+	iface->ready = false;
+}
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops)
+{
+	struct device *dev = &spi->dev;
+	struct spihid_apple *spihid;
+	int err, i;
+
+	if (!ops || !ops->power_on || !ops->power_off || !ops->enable_irq || !ops->disable_irq)
+		return -EINVAL;
+
+	spihid = devm_kzalloc(dev, sizeof(*spihid), GFP_KERNEL);
+	if (!spihid)
+		return -ENOMEM;
+
+	spihid->ops = ops;
+	spihid->spidev = spi;
+
+	// init spi
+	spi_set_drvdata(spi, spihid);
+
+	/* allocate SPI buffers */
+	spihid->rx_buf = devm_kmalloc(
+		&spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+	spihid->tx_buf = devm_kmalloc(
+		&spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+	spihid->status_buf = devm_kmalloc(
+		&spi->dev, sizeof(spi_hid_apple_status_ok), GFP_KERNEL);
+
+	if (!spihid->rx_buf || !spihid->tx_buf || !spihid->status_buf)
+		return -ENOMEM;
+
+	spihid->report.buf =
+		devm_kmalloc(dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL);
+
+	spihid->kbd.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+	spihid->tp.hid_desc = devm_kmalloc(dev, SPIHID_DESC_MAX, GFP_KERNEL);
+
+	if (!spihid->report.buf || !spihid->kbd.hid_desc ||
+	    !spihid->tp.hid_desc)
+		return -ENOMEM;
+
+	init_waitqueue_head(&spihid->wait);
+
+	mutex_init(&spihid->tx_lock);
+
+	/* Init spi transfer buffers and power device on */
+	err = spihid_apple_setup_spi(spihid);
+	if (err < 0)
+		goto error;
+
+	/* enable HID irq */
+	spihid->ops->enable_irq(spihid->ops);
+
+	// wait for boot message
+	err = wait_event_interruptible_timeout(spihid->wait,
+					       spihid->status_booted,
+					       msecs_to_jiffies(1000));
+	if (err == 0)
+		err = -ENODEV;
+	if (err < 0) {
+		dev_err(dev, "waiting for device boot failed: %d", err);
+		goto error;
+	}
+
+	/* request device information */
+	dev_dbg(dev, "request device info");
+	spihid_apple_request(spihid, 0xd0, 0x20, 0x01, 0xd0, 0, NULL, 0);
+	err = wait_event_interruptible_timeout(spihid->wait, spihid->vendor_id,
+					       SPIHID_DEF_WAIT);
+	if (err == 0)
+		err = -ENODEV;
+	if (err < 0) {
+		dev_err(dev, "waiting for device info failed: %d", err);
+		goto error;
+	}
+
+	/* request interface information */
+	for (i = 0; i < spihid->num_devices; i++) {
+		struct spihid_interface *iface = spihid_get_iface(spihid, i);
+		if (!iface)
+			continue;
+		dev_dbg(dev, "request interface info 0x%02x", i);
+		spihid_apple_request(spihid, 0xd0, 0x20, 0x02, i,
+				     SPIHID_DESC_MAX, NULL, 0);
+		err = wait_event_interruptible_timeout(
+			spihid->wait, iface->max_input_report_len,
+			SPIHID_DEF_WAIT);
+	}
+
+	/* request HID report descriptors */
+	for (i = 1; i < spihid->num_devices; i++) {
+		struct spihid_interface *iface = spihid_get_iface(spihid, i);
+		if (!iface)
+			continue;
+		dev_dbg(dev, "request hid report desc 0x%02x", i);
+		spihid_apple_request(spihid, 0xd0, 0x20, 0x10, i,
+				     SPIHID_DESC_MAX, NULL, 0);
+		wait_event_interruptible_timeout(
+			spihid->wait, iface->hid_desc_len, SPIHID_DEF_WAIT);
+	}
+
+	return 0;
+error:
+	return err;
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_probe);
+
+void spihid_apple_core_remove(struct spi_device *spi)
+{
+	struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+	/* destroy input devices */
+
+	spihid_destroy_hid_device(&spihid->tp);
+	spihid_destroy_hid_device(&spihid->kbd);
+
+	/* disable irq */
+	spihid->ops->disable_irq(spihid->ops);
+
+	/* power SPI device down */
+	spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_remove);
+
+void spihid_apple_core_shutdown(struct spi_device *spi)
+{
+	struct spihid_apple *spihid = spi_get_drvdata(spi);
+
+	/* disable irq */
+	spihid->ops->disable_irq(spihid->ops);
+
+	/* power SPI device down */
+	spihid->ops->power_off(spihid->ops);
+}
+EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown);
+
+MODULE_DESCRIPTION("Apple SPI HID transport driver");
+MODULE_AUTHOR("Janne Grunau <j@jannau.net>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c
new file mode 100644
index 00000000000000..f1380bfc52672e
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple-of.c
@@ -0,0 +1,136 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0
+ *
+ * Apple SPI HID transport driver - Open Firmware
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+
+#include "spi-hid-apple.h"
+
+
+struct spihid_apple_of {
+	struct spihid_apple_ops ops;
+
+	struct gpio_desc *enable_gpio;
+	int irq;
+};
+
+static int spihid_apple_of_power_on(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	/* reset the controller on boot */
+	gpiod_direction_output(sh_of->enable_gpio, 1);
+	msleep(5);
+	gpiod_direction_output(sh_of->enable_gpio, 0);
+	msleep(5);
+	/* turn SPI device on */
+	gpiod_direction_output(sh_of->enable_gpio, 1);
+	msleep(50);
+
+	return 0;
+}
+
+static int spihid_apple_of_power_off(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	/* turn SPI device off */
+	gpiod_direction_output(sh_of->enable_gpio, 0);
+
+	return 0;
+}
+
+static int spihid_apple_of_enable_irq(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	enable_irq(sh_of->irq);
+
+	return 0;
+}
+
+static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	disable_irq(sh_of->irq);
+
+	return 0;
+}
+
+static int spihid_apple_of_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct spihid_apple_of *spihid_of;
+	int err;
+
+	spihid_of = devm_kzalloc(dev, sizeof(*spihid_of), GFP_KERNEL);
+	if (!spihid_of)
+		return -ENOMEM;
+
+	spihid_of->ops.power_on = spihid_apple_of_power_on;
+	spihid_of->ops.power_off = spihid_apple_of_power_off;
+	spihid_of->ops.enable_irq = spihid_apple_of_enable_irq;
+	spihid_of->ops.disable_irq = spihid_apple_of_disable_irq;
+
+	spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0);
+	if (IS_ERR(spihid_of->enable_gpio)) {
+		err = PTR_ERR(spihid_of->enable_gpio);
+		dev_err(dev, "failed to get 'spien' gpio pin: %d", err);
+		return err;
+	}
+
+	spihid_of->irq = of_irq_get(dev->of_node, 0);
+	if (spihid_of->irq < 0) {
+		err = spihid_of->irq;
+		dev_err(dev, "failed to get 'extended-irq': %d", err);
+		return err;
+	}
+	err = devm_request_threaded_irq(dev, spihid_of->irq, NULL,
+					spihid_apple_core_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+					"spi-hid-apple-irq", spi);
+	if (err < 0) {
+		dev_err(dev, "failed to request extended-irq %d: %d",
+			spihid_of->irq, err);
+		return err;
+	}
+
+	return spihid_apple_core_probe(spi, &spihid_of->ops);
+}
+
+static const struct of_device_id spihid_apple_of_match[] = {
+	{ .compatible = "apple,spi-hid-transport" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, spihid_apple_of_match);
+
+static struct spi_device_id spihid_apple_of_id[] = {
+	{ "spi-hid-transport", 0 },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, spihid_apple_of_id);
+
+static struct spi_driver spihid_apple_of_driver = {
+	.driver = {
+		.name	= "spi-hid-apple-of",
+		//.pm	= &spi_hid_apple_of_pm,
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(spihid_apple_of_match),
+	},
+
+	.id_table	= spihid_apple_of_id,
+	.probe		= spihid_apple_of_probe,
+	.remove		= spihid_apple_core_remove,
+	.shutdown	= spihid_apple_core_shutdown,
+};
+
+module_spi_driver(spihid_apple_of_driver);
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h
new file mode 100644
index 00000000000000..2d9554e8a5f819
--- /dev/null
+++ b/drivers/hid/spi-hid/spi-hid-apple.h
@@ -0,0 +1,31 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+
+#ifndef SPI_HID_APPLE_H
+#define SPI_HID_APPLE_H
+
+#include <linux/interrupt.h>
+#include <linux/spi/spi.h>
+
+/**
+ * struct spihid_apple_ops - Ops to control the device from the core driver.
+ *
+ * @power_on: reset and power the device on.
+ * @power_off: power the device off.
+ * @enable_irq: enable irq or ACPI gpe.
+ * @disable_irq: disable irq or ACPI gpe.
+ */
+
+struct spihid_apple_ops {
+    int (*power_on)(struct spihid_apple_ops *ops);
+    int (*power_off)(struct spihid_apple_ops *ops);
+    int (*enable_irq)(struct spihid_apple_ops *ops);
+    int (*disable_irq)(struct spihid_apple_ops *ops);
+};
+
+irqreturn_t spihid_apple_core_irq(int irq, void *data);
+
+int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops);
+void spihid_apple_core_remove(struct spi_device *spi);
+void spihid_apple_core_shutdown(struct spi_device *spi);
+
+#endif /* SPI_HID_APPLE_H */

From 704449c0eca50f8da681b4d07939f7c5ecdfd47e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 00:29:43 +0900
Subject: [PATCH 0334/1027] HID: add HOST vendor/device IDs for Apple MTP
 devices

Apple M2 chips have an embedded MTP processor that handles all HID
functions, and does not go over a traditional bus like SPI. The devices
still have real IDs, so add them here.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-ids.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h
index 2bb8183ed122d0..51d7a9d93029a7 100644
--- a/drivers/hid/hid-ids.h
+++ b/drivers/hid/hid-ids.h
@@ -90,6 +90,7 @@
 #define USB_VENDOR_ID_APPLE		0x05ac
 #define BT_VENDOR_ID_APPLE		0x004c
 #define SPI_VENDOR_ID_APPLE		0x05ac
+#define HOST_VENDOR_ID_APPLE		0x05ac
 #define USB_DEVICE_ID_APPLE_MIGHTYMOUSE	0x0304
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE	0x030d
 #define USB_DEVICE_ID_APPLE_MAGICMOUSE2	0x0269
@@ -192,6 +193,8 @@
 #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020	0x0341
 #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO14_2021	0x0342
 #define SPI_DEVICE_ID_APPLE_MACBOOK_PRO16_2021	0x0343
+#define HOST_DEVICE_ID_APPLE_MACBOOK_AIR13_2022	0x0351
+#define HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022	0x0354
 
 #define USB_VENDOR_ID_ASUS		0x0486
 #define USB_DEVICE_ID_ASUS_T91MT	0x0185

From becc07142fe53ac720979184f95dbf9e477d8d05 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:06:15 +0900
Subject: [PATCH 0335/1027] HID: core: Handle HOST bus type when announcing
 devices

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-core.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 98e6cd1b636d68..403c2b2522d3e3 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -2267,6 +2267,9 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask)
 	case BUS_SPI:
 		bus = "SPI";
 		break;
+	case BUS_HOST:
+		bus = "HOST";
+		break;
 	case BUS_VIRTUAL:
 		bus = "VIRTUAL";
 		break;

From 61c7d97e22ba757c0720f5a53e4b2428f2d2f4b0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:12:24 +0900
Subject: [PATCH 0336/1027] hid: apple: Bind to HOST devices for MTP

We use BUS_HOST for MTP HID subdevices

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-apple.c | 19 +++++++++++++------
 1 file changed, 13 insertions(+), 6 deletions(-)

diff --git a/drivers/hid/hid-apple.c b/drivers/hid/hid-apple.c
index 70a9bf969c6d53..2e4abe9e7b0f21 100644
--- a/drivers/hid/hid-apple.c
+++ b/drivers/hid/hid-apple.c
@@ -535,11 +535,16 @@ static int hidinput_apple_event(struct hid_device *hid, struct input_dev *input,
 		else if (hid->product >= USB_DEVICE_ID_APPLE_WELLSPRING4_ANSI &&
 				hid->product <= USB_DEVICE_ID_APPLE_WELLSPRING4A_JIS)
 			table = macbookair_fn_keys;
-		else if (hid->vendor == SPI_VENDOR_ID_APPLE &&
-			hid->product == SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020)
-			table = apple_fn_keys_mbp13;
-		else if (hid->vendor == SPI_VENDOR_ID_APPLE)
-			table = apple_fn_keys_spi;
+		else if (hid->bus == BUS_HOST || hid->bus == BUS_SPI)
+			switch (hid->product) {
+			case SPI_DEVICE_ID_APPLE_MACBOOK_PRO13_2020:
+			case HOST_DEVICE_ID_APPLE_MACBOOK_PRO13_2022:
+				table = apple_fn_keys_mbp13;
+				break;
+			default:
+				table = apple_fn_keys_spi;
+				break;
+			}
 		else if (hid->product < 0x21d || hid->product >= 0x300)
 			table = powerbook_fn_keys;
 		else
@@ -954,7 +959,7 @@ static int apple_probe(struct hid_device *hdev,
 	struct apple_sc *asc;
 	int ret;
 
-	if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE &&
+	if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
 	    hdev->type != HID_TYPE_SPI_KEYBOARD)
 		return -ENODEV;
 
@@ -1215,6 +1220,8 @@ static const struct hid_device_id apple_devices[] = {
 		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
 	{ HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
 		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
+	{ HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE, HID_ANY_ID),
+		.driver_data = APPLE_HAS_FN | APPLE_ISO_TILDE_QUIRK },
 	{ HID_USB_DEVICE(USB_VENDOR_ID_APPLE, USB_DEVICE_ID_APPLE_TOUCHBAR_BACKLIGHT),
 		.driver_data = APPLE_MAGIC_BACKLIGHT },
 

From a7d29649ef0cea4ec6f49436c7417951df589f35 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:12:57 +0900
Subject: [PATCH 0337/1027] hid: magicmouse: Add MTP multi-touch device support

Apple M2 devices expose the multi-touch device over the HID over
DockChannel transport, which we represent as the HOST bus type. The
report format is the same, except the legacy mouse header is gone and
there is no enable request needed.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-magicmouse.c | 67 ++++++++++++++++++++++++++----------
 1 file changed, 49 insertions(+), 18 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index e7c39b3f818979..07038054f06e1d 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -59,6 +59,8 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define MOUSE_REPORT_ID    0x29
 #define MOUSE2_REPORT_ID   0x12
 #define DOUBLE_REPORT_ID   0xf7
+#define SPI_REPORT_ID      0x02
+#define MTP_REPORT_ID      0x75
 #define USB_BATTERY_TIMEOUT_MS 60000
 
 #define MAX_CONTACTS 16
@@ -573,25 +575,32 @@ struct tp_finger {
 } __attribute__((packed, aligned(2)));
 
 /**
- * struct trackpad report
+ * vendor trackpad report
  *
- * @report_id:		reportid
- * @buttons:		HID Usage Buttons 3 1-bit reports
  * @num_fingers:	the number of fingers being reported in @fingers
- * @clicked:		same as @buttons
+ * @buttons:		same as HID buttons
  */
 struct tp_header {
+	// HID vendor part, up to 1751 bytes
+	u8 unknown[22];
+	u8 num_fingers;
+	u8 buttons;
+	u8 unknown3[14];
+};
+
+/**
+ * standard HID mouse report
+ *
+ * @report_id:		reportid
+ * @buttons:		HID Usage Buttons 3 1-bit reports
+ */
+struct tp_mouse_report {
 	// HID mouse report
 	u8 report_id;
 	u8 buttons;
 	u8 rel_x;
 	u8 rel_y;
 	u8 padding[4];
-	// HID vendor part, up to 1751 bytes
-	u8 unknown[22];
-	u8 num_fingers;
-	u8 clicked;
-	u8 unknown3[14];
 };
 
 static inline int le16_to_int(__le16 x)
@@ -621,7 +630,7 @@ static void report_finger_data(struct input_dev *input, int slot,
 	input_report_abs(input, ABS_MT_POSITION_Y, pos->y);
 }
 
-static int magicmouse_raw_event_spi(struct hid_device *hdev,
+static int magicmouse_raw_event_mtp(struct hid_device *hdev,
 		struct hid_report *report, u8 *data, int size)
 {
 	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
@@ -638,9 +647,6 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev,
 	// print_hex_dump_debug("appleft ev: ", DUMP_PREFIX_OFFSET, 16, 1, data,
 	// 		     size, false);
 
-	if (data[0] != TRACKPAD2_USB_REPORT_ID)
-		return 0;
-
 	/* Expect 46 bytes of prefix, and N * 30 bytes of touch data. */
 	if (size < hdr_sz || ((size - hdr_sz) % touch_sz) != 0)
 		return 0;
@@ -679,12 +685,26 @@ static int magicmouse_raw_event_spi(struct hid_device *hdev,
 	}
 
 	input_mt_sync_frame(input);
-	input_report_key(input, BTN_MOUSE, data[1] & 1);
+	input_report_key(input, BTN_MOUSE, tp_hdr->buttons & 1);
 
 	input_sync(input);
 	return 1;
 }
 
+static int magicmouse_raw_event_spi(struct hid_device *hdev,
+		struct hid_report *report, u8 *data, int size)
+{
+	const size_t hdr_sz = sizeof(struct tp_mouse_report);
+
+	if (size < hdr_sz)
+		return 0;
+
+	if (data[0] != TRACKPAD2_USB_REPORT_ID)
+		return 0;
+
+	return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
+}
+
 static int magicmouse_event(struct hid_device *hdev, struct hid_field *field,
 		struct hid_usage *usage, __s32 value)
 {
@@ -1069,7 +1089,7 @@ static int magicmouse_probe(struct hid_device *hdev,
 	struct hid_report *report;
 	int ret;
 
-	if (id->bus == BUS_SPI && id->vendor == SPI_VENDOR_ID_APPLE &&
+	if ((id->bus == BUS_SPI || id->bus == BUS_HOST) && id->vendor == SPI_VENDOR_ID_APPLE &&
 	    hdev->type != HID_TYPE_SPI_MOUSE)
 		return -ENODEV;
 
@@ -1081,7 +1101,10 @@ static int magicmouse_probe(struct hid_device *hdev,
 
 	// internal trackpad use a data format use input ops to avoid
 	// conflicts with the report ID.
-	if (id->vendor == SPI_VENDOR_ID_APPLE) {
+	if (id->bus == BUS_HOST) {
+		msc->input_ops.raw_event = magicmouse_raw_event_mtp;
+		msc->input_ops.setup_input = magicmouse_setup_input_spi;
+	} else if (id->bus == BUS_SPI) {
 		msc->input_ops.raw_event = magicmouse_raw_event_spi;
 		msc->input_ops.setup_input = magicmouse_setup_input_spi;
 
@@ -1138,8 +1161,10 @@ static int magicmouse_probe(struct hid_device *hdev,
 		else /* USB_VENDOR_ID_APPLE */
 			report = hid_register_report(hdev, HID_INPUT_REPORT,
 				TRACKPAD2_USB_REPORT_ID, 0);
-	} else if (id->vendor == SPI_VENDOR_ID_APPLE) {
-		report = hid_register_report(hdev, HID_INPUT_REPORT, 2, 0);
+	} else if (id->bus == BUS_SPI) {
+		report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_REPORT_ID, 0);
+	} else if (id->bus == BUS_HOST) {
+		report = hid_register_report(hdev, HID_INPUT_REPORT, MTP_REPORT_ID, 0);
 	} else { /* USB_DEVICE_ID_APPLE_MAGICTRACKPAD */
 		report = hid_register_report(hdev, HID_INPUT_REPORT,
 			TRACKPAD_REPORT_ID, 0);
@@ -1154,6 +1179,10 @@ static int magicmouse_probe(struct hid_device *hdev,
 	}
 	report->size = 6;
 
+	/* MTP devices do not need the MT enable, this is handled by the MTP driver */
+	if (id->bus == BUS_HOST)
+		return 0;
+
 	/*
 	 * Some devices repond with 'invalid report id' when feature
 	 * report switching it into multitouch mode is sent to it.
@@ -1236,6 +1265,8 @@ static const struct hid_device_id magic_mice[] = {
 		USB_DEVICE_ID_APPLE_MAGICTRACKPAD2), .driver_data = 0 },
 	{ HID_SPI_DEVICE(SPI_VENDOR_ID_APPLE, HID_ANY_ID),
 	  .driver_data = 0 },
+	{ HID_DEVICE(BUS_HOST, HID_GROUP_ANY, HOST_VENDOR_ID_APPLE,
+                     HID_ANY_ID), .driver_data = 0 },
 	{ }
 };
 MODULE_DEVICE_TABLE(hid, magic_mice);

From a67d73b28b112a427fad98da583d9549ec204ef1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 1 Feb 2022 00:40:51 +0900
Subject: [PATCH 0338/1027] lib/vsprintf: Add support for generic FOURCCs by
 extending %p4cc

%p4cc is designed for DRM/V4L2 FOURCCs with their specific quirks, but
it's useful to be able to print generic 4-character codes formatted as
an integer. Extend it to add format specifiers for printing generic
32-bit FOURCCs with various endian semantics:

%p4ch   Host-endian
%p4cl	Little-endian
%p4cb	Big-endian
%p4cr	Reverse-endian

The endianness determines how bytes are interpreted as a u32, and the
FOURCC is then always printed MSByte-first (this is the opposite of
V4L/DRM FOURCCs). This covers most practical cases, e.g. %p4cr would
allow printing LSByte-first FOURCCs stored in host endian order
(other than the hex form being in character order, not the integer
value).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 Documentation/core-api/printk-formats.rst | 32 +++++++++++++++++++++
 lib/vsprintf.c                            | 35 +++++++++++++++++++----
 2 files changed, 61 insertions(+), 6 deletions(-)

diff --git a/Documentation/core-api/printk-formats.rst b/Documentation/core-api/printk-formats.rst
index 4451ef50193613..c726a846f752e9 100644
--- a/Documentation/core-api/printk-formats.rst
+++ b/Documentation/core-api/printk-formats.rst
@@ -632,6 +632,38 @@ Examples::
 	%p4cc	Y10  little-endian (0x20303159)
 	%p4cc	NV12 big-endian (0xb231564e)
 
+Generic FourCC code
+-------------------
+
+::
+	%p4c[hnbl]	gP00 (0x67503030)
+
+Print a generic FourCC code, as both ASCII characters and its numerical
+value as hexadecimal.
+
+The additional ``h``, ``r``, ``b``, and ``l`` specifiers are used to specify
+host, reversed, big or little endian order data respectively. Host endian
+order means the data is interpreted as a 32-bit integer and the most
+significant byte is printed first; that is, the character code as printed
+matches the byte order stored in memory on big-endian systems, and is reversed
+on little-endian systems.
+
+Passed by reference.
+
+Examples for a little-endian machine, given &(u32)0x67503030::
+
+	%p4ch	gP00 (0x67503030)
+	%p4cl	gP00 (0x67503030)
+	%p4cb	00Pg (0x30305067)
+	%p4cr	00Pg (0x30305067)
+
+Examples for a big-endian machine, given &(u32)0x67503030::
+
+	%p4ch	gP00 (0x67503030)
+	%p4cl	00Pg (0x30305067)
+	%p4cb	gP00 (0x67503030)
+	%p4cr	00Pg (0x30305067)
+
 Rust
 ----
 
diff --git a/lib/vsprintf.c b/lib/vsprintf.c
index 2d71b11159161b..58022584c55a30 100644
--- a/lib/vsprintf.c
+++ b/lib/vsprintf.c
@@ -1760,27 +1760,50 @@ char *fourcc_string(char *buf, char *end, const u32 *fourcc,
 	char output[sizeof("0123 little-endian (0x01234567)")];
 	char *p = output;
 	unsigned int i;
+	bool pix_fmt = false;
 	u32 orig, val;
 
-	if (fmt[1] != 'c' || fmt[2] != 'c')
+	if (fmt[1] != 'c')
 		return error_string(buf, end, "(%p4?)", spec);
 
 	if (check_pointer(&buf, end, fourcc, spec))
 		return buf;
 
 	orig = get_unaligned(fourcc);
-	val = orig & ~BIT(31);
+	switch (fmt[2]) {
+	case 'h':
+		val = orig;
+		break;
+	case 'r':
+		val = orig = swab32(orig);
+		break;
+	case 'l':
+		val = orig = le32_to_cpu(orig);
+		break;
+	case 'b':
+		val = orig = be32_to_cpu(orig);
+		break;
+	case 'c':
+		/* Pixel formats are printed LSB-first */
+		val = swab32(orig & ~BIT(31));
+		pix_fmt = true;
+		break;
+	default:
+		return error_string(buf, end, "(%p4?)", spec);
+	}
 
 	for (i = 0; i < sizeof(u32); i++) {
-		unsigned char c = val >> (i * 8);
+		unsigned char c = val >> ((3 - i) * 8);
 
 		/* Print non-control ASCII characters as-is, dot otherwise */
 		*p++ = isascii(c) && isprint(c) ? c : '.';
 	}
 
-	*p++ = ' ';
-	strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian");
-	p += strlen(p);
+	if (pix_fmt) {
+		*p++ = ' ';
+		strcpy(p, orig & BIT(31) ? "big-endian" : "little-endian");
+		p += strlen(p);
+	}
 
 	*p++ = ' ';
 	*p++ = '(';

From 7294e62ee55a995f4c3271f75a222dd57131252e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Apr 2023 21:50:43 +0900
Subject: [PATCH 0339/1027] cpufreq: apple-soc: Drop setting the PS2 field on
 M2+

Newer devices don't use this. We still don't know what it does, but
let's keep to the same behavior macOS has here.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/cpufreq/apple-soc-cpufreq.c | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/drivers/cpufreq/apple-soc-cpufreq.c b/drivers/cpufreq/apple-soc-cpufreq.c
index af34c22fa273da..15b6e8a04e4ad0 100644
--- a/drivers/cpufreq/apple-soc-cpufreq.c
+++ b/drivers/cpufreq/apple-soc-cpufreq.c
@@ -25,7 +25,7 @@
 #define APPLE_DVFS_CMD			0x20
 #define APPLE_DVFS_CMD_BUSY		BIT(31)
 #define APPLE_DVFS_CMD_SET		BIT(25)
-#define APPLE_DVFS_CMD_PS2		GENMASK(16, 12)
+#define APPLE_DVFS_CMD_PS2		GENMASK(15, 12)
 #define APPLE_DVFS_CMD_PS1		GENMASK(4, 0)
 
 /* Same timebase as CPU counter (24MHz) */
@@ -55,6 +55,7 @@
 #define APPLE_DVFS_TRANSITION_TIMEOUT 100
 
 struct apple_soc_cpufreq_info {
+	bool has_ps2;
 	u64 max_pstate;
 	u64 cur_pstate_mask;
 	u64 cur_pstate_shift;
@@ -69,18 +70,21 @@ struct apple_cpu_priv {
 static struct cpufreq_driver apple_soc_cpufreq_driver;
 
 static const struct apple_soc_cpufreq_info soc_t8103_info = {
+	.has_ps2 = true,
 	.max_pstate = 15,
 	.cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8103,
 	.cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8103,
 };
 
 static const struct apple_soc_cpufreq_info soc_t8112_info = {
+	.has_ps2 = false,
 	.max_pstate = 31,
 	.cur_pstate_mask = APPLE_DVFS_STATUS_CUR_PS_T8112,
 	.cur_pstate_shift = APPLE_DVFS_STATUS_CUR_PS_SHIFT_T8112,
 };
 
 static const struct apple_soc_cpufreq_info soc_default_info = {
+	.has_ps2 = false,
 	.max_pstate = 15,
 	.cur_pstate_mask = 0, /* fallback */
 };
@@ -148,9 +152,12 @@ static int apple_soc_cpufreq_set_target(struct cpufreq_policy *policy,
 		return -EIO;
 	}
 
-	reg &= ~(APPLE_DVFS_CMD_PS1 | APPLE_DVFS_CMD_PS2);
+	reg &= ~APPLE_DVFS_CMD_PS1;
 	reg |= FIELD_PREP(APPLE_DVFS_CMD_PS1, pstate);
-	reg |= FIELD_PREP(APPLE_DVFS_CMD_PS2, pstate);
+	if (priv->info->has_ps2) {
+		reg &= ~APPLE_DVFS_CMD_PS2;
+		reg |= FIELD_PREP(APPLE_DVFS_CMD_PS2, pstate);
+	}
 	reg |= APPLE_DVFS_CMD_SET;
 
 	writeq_relaxed(reg, priv->reg_base + APPLE_DVFS_CMD);

From 6c752523ba1ad894951a5e12d9b5279420506aed Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 2 May 2022 21:17:41 +0900
Subject: [PATCH 0340/1027] dt-bindings: pci: apple,pcie: Add subnode binding,
 pwren-gpios property

We weren't properly validating root port subnodes, so let's do that. Then,
also add the new `pwren-gpios` property there to handle device power-up.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../devicetree/bindings/pci/apple,pcie.yaml   | 51 +++++++++++++++++--
 1 file changed, 48 insertions(+), 3 deletions(-)

diff --git a/Documentation/devicetree/bindings/pci/apple,pcie.yaml b/Documentation/devicetree/bindings/pci/apple,pcie.yaml
index c8775f9cb07133..510959e12d792e 100644
--- a/Documentation/devicetree/bindings/pci/apple,pcie.yaml
+++ b/Documentation/devicetree/bindings/pci/apple,pcie.yaml
@@ -72,6 +72,27 @@ properties:
   power-domains:
     maxItems: 1
 
+patternProperties:
+  "^pci@":
+    $ref: /schemas/pci/pci-bus.yaml#
+    type: object
+    description: A single PCI root port
+
+    properties:
+      reg:
+        maxItems: 1
+
+      pwren-gpios:
+        description: Optional GPIO to power on the device
+        maxItems: 1
+
+    required:
+      - reset-gpios
+      - interrupt-controller
+      - "#interrupt-cells"
+      - interrupt-map-mask
+      - interrupt-map
+
 required:
   - compatible
   - reg
@@ -142,7 +163,7 @@ examples:
         pinctrl-0 = <&pcie_pins>;
         pinctrl-names = "default";
 
-        pci@0,0 {
+        port00: pci@0,0 {
           device_type = "pci";
           reg = <0x0 0x0 0x0 0x0 0x0>;
           reset-gpios = <&pinctrl_ap 152 0>;
@@ -150,9 +171,17 @@ examples:
           #address-cells = <3>;
           #size-cells = <2>;
           ranges;
+
+          interrupt-controller;
+          #interrupt-cells = <1>;
+          interrupt-map-mask = <0 0 0 7>;
+          interrupt-map = <0 0 0 1 &port00 0 0 0 0>,
+                          <0 0 0 2 &port00 0 0 0 1>,
+                          <0 0 0 3 &port00 0 0 0 2>,
+                          <0 0 0 4 &port00 0 0 0 3>;
         };
 
-        pci@1,0 {
+        port01: pci@1,0 {
           device_type = "pci";
           reg = <0x800 0x0 0x0 0x0 0x0>;
           reset-gpios = <&pinctrl_ap 153 0>;
@@ -160,9 +189,17 @@ examples:
           #address-cells = <3>;
           #size-cells = <2>;
           ranges;
+
+          interrupt-controller;
+          #interrupt-cells = <1>;
+          interrupt-map-mask = <0 0 0 7>;
+          interrupt-map = <0 0 0 1 &port01 0 0 0 0>,
+                          <0 0 0 2 &port01 0 0 0 1>,
+                          <0 0 0 3 &port01 0 0 0 2>,
+                          <0 0 0 4 &port01 0 0 0 3>;
         };
 
-        pci@2,0 {
+        port02: pci@2,0 {
           device_type = "pci";
           reg = <0x1000 0x0 0x0 0x0 0x0>;
           reset-gpios = <&pinctrl_ap 33 0>;
@@ -170,6 +207,14 @@ examples:
           #address-cells = <3>;
           #size-cells = <2>;
           ranges;
+
+          interrupt-controller;
+          #interrupt-cells = <1>;
+          interrupt-map-mask = <0 0 0 7>;
+          interrupt-map = <0 0 0 1 &port02 0 0 0 0>,
+                          <0 0 0 2 &port02 0 0 0 1>,
+                          <0 0 0 3 &port02 0 0 0 2>,
+                          <0 0 0 4 &port02 0 0 0 3>;
         };
       };
     };

From 62fd5049b62cf3d654804b9297c77b45bf0dffe9 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 2 May 2022 21:22:46 +0900
Subject: [PATCH 0341/1027] PCI: apple: Use gpiod_set_value_cansleep in probe
 flow

We're allowed to sleep here, so tell the GPIO core by using
gpiod_set_value_cansleep instead of gpiod_set_value.

Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up")
Acked-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index fefab2758a0646..ddc65368e77d19 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -541,7 +541,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK);
 
 	/* Assert PERST# before setting up the clock */
-	gpiod_set_value(reset, 1);
+	gpiod_set_value_cansleep(reset, 1);
 
 	ret = apple_pcie_setup_refclk(pcie, port);
 	if (ret < 0)
@@ -552,7 +552,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 
 	/* Deassert PERST# */
 	rmw_set(PORT_PERST_OFF, port->base + PORT_PERST);
-	gpiod_set_value(reset, 0);
+	gpiod_set_value_cansleep(reset, 0);
 
 	/* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */
 	msleep(100);

From 7de4e5f78708b56a2c471a7b6a71578ddd1afc8e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 6 Feb 2022 21:15:39 +0900
Subject: [PATCH 0342/1027] PCI: apple: Probe all GPIOs for availability first

If we're probing the PCI controller and some GPIOs are not available and
cause a probe defer, we can end up leaving some ports initialized and
not others and making a mess.

Check for PERST# GPIOs for all ports first, and just return
-EPROBE_DEFER if any are not ready yet, without bringing anything up.

Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up")
Cc: stable@vger.kernel.org
Acked-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index ddc65368e77d19..60eacadc99d4fe 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -507,6 +507,20 @@ static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port,
 	return readl_relaxed(port->base + PORT_RID2SID(idx));
 }
 
+static int apple_pcie_probe_port(struct device_node *np)
+{
+	struct gpio_desc *gd;
+
+	gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "reset", 0,
+				    GPIOD_OUT_LOW, "PERST#");
+	if (IS_ERR(gd)) {
+		return PTR_ERR(gd);
+	}
+
+	gpiod_put(gd);
+	return 0;
+}
+
 static int apple_pcie_setup_port(struct apple_pcie *pcie,
 				 struct device_node *np)
 {
@@ -801,8 +815,19 @@ static int apple_pcie_init(struct pci_config_window *cfg)
 
 static int apple_pcie_probe(struct platform_device *pdev)
 {
+	struct device *dev = &pdev->dev;
+	struct device_node *of_port;
 	int ret;
 
+	/* Check for probe dependencies for all ports first */
+	for_each_available_child_of_node(dev->of_node, of_port) {
+		ret = apple_pcie_probe_port(of_port);
+		if (ret) {
+			of_node_put(of_port);
+			return dev_err_probe(dev, ret, "Port %pOF probe fail\n", of_port);
+		}
+	}
+
 	ret = bus_register_notifier(&pci_bus_type, &apple_pcie_nb);
 	if (ret)
 		return ret;

From f4988b08aaee0f298972d7f68b9e339f47965bb7 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 6 Feb 2022 21:18:18 +0900
Subject: [PATCH 0343/1027] PCI: apple: Add support for optional PWREN GPIO

WiFi and SD card devices on M1 Macs have a separate power enable GPIO.
Add support for this to the PCIe controller. This is modeled after how
pcie-fu740 does it.

Acked-by: Marc Zyngier <maz@kernel.org>
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 34 ++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 60eacadc99d4fe..ab03bebaedd057 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -518,6 +518,16 @@ static int apple_pcie_probe_port(struct device_node *np)
 	}
 
 	gpiod_put(gd);
+
+	gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "pwren", 0,
+				    GPIOD_OUT_LOW, "PWREN");
+	if (IS_ERR(gd)) {
+		if (PTR_ERR(gd) != -ENOENT)
+			return PTR_ERR(gd);
+	} else {
+		gpiod_put(gd);
+	}
+
 	return 0;
 }
 
@@ -526,7 +536,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 {
 	struct platform_device *platform = to_platform_device(pcie->dev);
 	struct apple_pcie_port *port;
-	struct gpio_desc *reset;
+	struct gpio_desc *reset, *pwren = NULL;
 	u32 stat, idx;
 	int ret, i;
 
@@ -535,6 +545,15 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	if (IS_ERR(reset))
 		return PTR_ERR(reset);
 
+	pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "pwren",
+					    GPIOD_OUT_LOW, "PWREN");
+	if (IS_ERR(pwren)) {
+		if (PTR_ERR(pwren) == -ENOENT)
+			pwren = NULL;
+		else
+			return PTR_ERR(pwren);
+	}
+
 	port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL);
 	if (!port)
 		return -ENOMEM;
@@ -557,12 +576,21 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	/* Assert PERST# before setting up the clock */
 	gpiod_set_value_cansleep(reset, 1);
 
+	/* Power on the device if required */
+	gpiod_set_value_cansleep(pwren, 1);
+
 	ret = apple_pcie_setup_refclk(pcie, port);
 	if (ret < 0)
 		return ret;
 
-	/* The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2) */
-	usleep_range(100, 200);
+	/*
+	 * The minimal Tperst-clk value is 100us (PCIe CEM r5.0, 2.9.2)
+	 * If powering up, the minimal Tpvperl is 100ms
+	 */
+	if (pwren)
+		msleep(100);
+	else
+		usleep_range(100, 200);
 
 	/* Deassert PERST# */
 	rmw_set(PORT_PERST_OFF, port->base + PORT_PERST);

From a615eeed382e05cdf5b9c24551e7cce969d11062 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 5 Dec 2022 18:13:40 +0900
Subject: [PATCH 0344/1027] PCI: apple: Fix missing OF node reference in
 apple_pcie_setup_port

In the success path we hang onto a reference to the node, so make sure
to grab one. The caller iterator puts our borrowed reference when we
return.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index ab03bebaedd057..e19f1769d18c5d 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -627,6 +627,9 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	list_add_tail(&port->entry, &pcie->ports);
 	init_completion(&pcie->event);
 
+	/* In the success path, we keep a reference to np around */
+	of_node_get(np);
+
 	ret = apple_pcie_port_register_irqs(port);
 	WARN_ON(ret);
 

From c99868be8c23c4a4c30d6eed0c60849066c94930 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 Feb 2023 21:48:15 +0100
Subject: [PATCH 0345/1027] PCI: apple: Set only available ports up

Fixes "interrupt-map" parsing in of_irq_parse_raw() which takes the
node's availability into account.

This became apparent after disabling unused PCIe ports in the Apple
silicon device trees instead of disabling them.

Link: https://lore.kernel.org/asahi/20230214-apple_dts_pcie_disable_unused-v1-0-5ea0d3ddcde3@jannau.net/
Link: https://lore.kernel.org/asahi/1ea2107a-bb86-8c22-0bbc-82c453ab08ce@linaro.org/
Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up")
Cc: stable@vger.kernel.org
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/pcie-apple.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index e19f1769d18c5d..a6947640274bd5 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -832,7 +832,7 @@ static int apple_pcie_init(struct pci_config_window *cfg)
 	if (ret)
 		return ret;
 
-	for_each_child_of_node(dev->of_node, of_port) {
+	for_each_available_child_of_node(dev->of_node, of_port) {
 		ret = apple_pcie_setup_port(pcie, of_port);
 		if (ret) {
 			dev_err(pcie->dev, "Port %pOF setup fail: %d\n", of_port, ret);

From 3056dc5e70ea569b00327ba78fba254e9c9a5d90 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 19:35:27 +0900
Subject: [PATCH 0346/1027] PCI: apple: Move port PHY registers to their own
 reg items

T602x PCIe cores move these registers around. Instead of hardcoding in
another offset, let's move them into their own reg entries. This matches
what Apple does on macOS device trees too.

Maintains backwards compatibility with old DTs by using the old offsets.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 51 +++++++++++++++++++----------
 1 file changed, 33 insertions(+), 18 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index a6947640274bd5..5fc1b405715ae6 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -40,14 +40,18 @@
 #define   CORE_RC_STAT_READY		BIT(0)
 #define CORE_FABRIC_STAT		0x04000
 #define   CORE_FABRIC_STAT_MASK		0x001F001F
-#define CORE_LANE_CFG(port)		(0x84000 + 0x4000 * (port))
-#define   CORE_LANE_CFG_REFCLK0REQ	BIT(0)
-#define   CORE_LANE_CFG_REFCLK1REQ	BIT(1)
-#define   CORE_LANE_CFG_REFCLK0ACK	BIT(2)
-#define   CORE_LANE_CFG_REFCLK1ACK	BIT(3)
-#define   CORE_LANE_CFG_REFCLKEN	(BIT(9) | BIT(10))
-#define CORE_LANE_CTL(port)		(0x84004 + 0x4000 * (port))
-#define   CORE_LANE_CTL_CFGACC		BIT(15)
+
+#define CORE_PHY_DEFAULT_BASE(port)	(0x84000 + 0x4000 * (port))
+
+#define PHY_LANE_CFG			0x00000
+#define   PHY_LANE_CFG_REFCLK0REQ	BIT(0)
+#define   PHY_LANE_CFG_REFCLK1REQ	BIT(1)
+#define   PHY_LANE_CFG_REFCLK0ACK	BIT(2)
+#define   PHY_LANE_CFG_REFCLK1ACK	BIT(3)
+#define   PHY_LANE_CFG_REFCLKEN		(BIT(9) | BIT(10))
+#define   PHY_LANE_CFG_REFCLKCGEN	(BIT(30) | BIT(31))
+#define PHY_LANE_CTL			0x00004
+#define   PHY_LANE_CTL_CFGACC		BIT(15)
 
 #define PORT_LTSSMCTL			0x00080
 #define   PORT_LTSSMCTL_START		BIT(0)
@@ -146,6 +150,7 @@ struct apple_pcie_port {
 	struct apple_pcie	*pcie;
 	struct device_node	*np;
 	void __iomem		*base;
+	void __iomem		*phy;
 	struct irq_domain	*domain;
 	struct list_head	entry;
 	DECLARE_BITMAP(sid_map, MAX_RID2SID);
@@ -474,26 +479,26 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
 	if (res < 0)
 		return res;
 
-	rmw_set(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx));
-	rmw_set(CORE_LANE_CFG_REFCLK0REQ, pcie->base + CORE_LANE_CFG(port->idx));
+	rmw_set(PHY_LANE_CTL_CFGACC, port->phy + PHY_LANE_CTL);
+	rmw_set(PHY_LANE_CFG_REFCLK0REQ, port->phy + PHY_LANE_CFG);
 
-	res = readl_relaxed_poll_timeout(pcie->base + CORE_LANE_CFG(port->idx),
-					 stat, stat & CORE_LANE_CFG_REFCLK0ACK,
+	res = readl_relaxed_poll_timeout(port->phy + PHY_LANE_CFG,
+					 stat, stat & PHY_LANE_CFG_REFCLK0ACK,
 					 100, 50000);
 	if (res < 0)
 		return res;
 
-	rmw_set(CORE_LANE_CFG_REFCLK1REQ, pcie->base + CORE_LANE_CFG(port->idx));
-	res = readl_relaxed_poll_timeout(pcie->base + CORE_LANE_CFG(port->idx),
-					 stat, stat & CORE_LANE_CFG_REFCLK1ACK,
+	rmw_set(PHY_LANE_CFG_REFCLK1REQ, port->phy + PHY_LANE_CFG);
+	res = readl_relaxed_poll_timeout(port->phy + PHY_LANE_CFG,
+					 stat, stat & PHY_LANE_CFG_REFCLK1ACK,
 					 100, 50000);
 
 	if (res < 0)
 		return res;
 
-	rmw_clear(CORE_LANE_CTL_CFGACC, pcie->base + CORE_LANE_CTL(port->idx));
+	rmw_clear(PHY_LANE_CTL_CFGACC, port->phy + PHY_LANE_CTL);
 
-	rmw_set(CORE_LANE_CFG_REFCLKEN, pcie->base + CORE_LANE_CFG(port->idx));
+	rmw_set(PHY_LANE_CFG_REFCLKEN, port->phy + PHY_LANE_CFG);
 	rmw_set(PORT_REFCLK_EN, port->base + PORT_REFCLK);
 
 	return 0;
@@ -539,6 +544,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	struct gpio_desc *reset, *pwren = NULL;
 	u32 stat, idx;
 	int ret, i;
+	char name[16];
 
 	reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
 				      GPIOD_OUT_LOW, "PERST#");
@@ -567,9 +573,18 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	port->pcie = pcie;
 	port->np = np;
 
-	port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+	snprintf(name, sizeof(name), "port%d", port->idx);
+	port->base = devm_platform_ioremap_resource_byname(platform, name);
 	if (IS_ERR(port->base))
+		port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+	if (IS_ERR(port->base)) {
 		return PTR_ERR(port->base);
+	}
+
+	snprintf(name, sizeof(name), "phy%d", port->idx);
+	port->phy = devm_platform_ioremap_resource_byname(platform, name);
+	if (IS_ERR(port->phy))
+		port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx);
 
 	rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK);
 

From 8f2c9a2e723e0c1fe0623f64f3c2e7c8c4e71846 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 19:37:52 +0900
Subject: [PATCH 0347/1027] PCI: apple: Drop poll for CORE_RC_PHYIF_STAT_REFCLK

This is checking a core refclk in per-port setup which doesn't make a
lot of sense, and the bootloader needs to have gone through this anyway.

It doesn't work on T602x, so just drop it across the board.
---
 drivers/pci/controller/pcie-apple.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 5fc1b405715ae6..494cd0f2d21f7b 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -473,12 +473,6 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
 	u32 stat;
 	int res;
 
-	res = readl_relaxed_poll_timeout(pcie->base + CORE_RC_PHYIF_STAT, stat,
-					 stat & CORE_RC_PHYIF_STAT_REFCLK,
-					 100, 50000);
-	if (res < 0)
-		return res;
-
 	rmw_set(PHY_LANE_CTL_CFGACC, port->phy + PHY_LANE_CTL);
 	rmw_set(PHY_LANE_CFG_REFCLK0REQ, port->phy + PHY_LANE_CFG);
 

From fb2de8b4d47a54cd57e50cc6afa03383fa6ced8a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 19:38:37 +0900
Subject: [PATCH 0348/1027] PCIE: apple: Add T602x PCIe support

This version of the hardware moved around a bunch of registers, so we
drop the old compatible for these and introduce register offset
structures to handle the differences.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 127 +++++++++++++++++++++++-----
 1 file changed, 106 insertions(+), 21 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 494cd0f2d21f7b..02d4cc372a7637 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -27,6 +27,7 @@
 #include <linux/module.h>
 #include <linux/msi.h>
 #include <linux/notifier.h>
+#include <linux/of_device.h>
 #include <linux/of_irq.h>
 #include <linux/pci-ecam.h>
 
@@ -105,7 +106,7 @@
 #define   PORT_REFCLK_CGDIS		BIT(8)
 #define PORT_PERST			0x00814
 #define   PORT_PERST_OFF		BIT(0)
-#define PORT_RID2SID(i16)		(0x00828 + 4 * (i16))
+#define PORT_RID2SID			0x00828
 #define   PORT_RID2SID_VALID		BIT(31)
 #define   PORT_RID2SID_SID_SHIFT	16
 #define   PORT_RID2SID_BUS_SHIFT	8
@@ -123,7 +124,7 @@
 #define   PORT_TUNSTAT_PERST_ACK_PEND	BIT(1)
 #define PORT_PREFMEM_ENABLE		0x00994
 
-#define MAX_RID2SID			64
+#define MAX_RID2SID			512
 
 /*
  * The doorbell address is set to 0xfffff000, which by convention
@@ -134,6 +135,57 @@
  */
 #define DOORBELL_ADDR		CONFIG_PCIE_APPLE_MSI_DOORBELL_ADDR
 
+struct reg_info {
+	u32 phy_lane_ctl;
+	u32 port_msiaddr;
+	u32 port_msiaddr_hi;
+	u32 port_refclk;
+	u32 port_perst;
+	u32 port_rid2sid;
+	u32 port_msimap;
+	u32 max_rid2sid;
+	u32 max_msimap;
+};
+
+const struct reg_info t8103_hw = {
+	.phy_lane_ctl = PHY_LANE_CTL,
+	.port_msiaddr = PORT_MSIADDR,
+	.port_msiaddr_hi = 0,
+	.port_refclk = PORT_REFCLK,
+	.port_perst = PORT_PERST,
+	.port_rid2sid = PORT_RID2SID,
+	.port_msimap = 0,
+	.max_rid2sid = 64,
+	.max_msimap = 0,
+};
+
+#define PORT_T602X_MSIADDR		0x016c
+#define PORT_T602X_MSIADDR_HI		0x0170
+#define PORT_T602X_PERST		0x082c
+#define PORT_T602X_RID2SID		0x3000
+#define PORT_T602X_MSIMAP		0x3800
+
+#define PORT_MSIMAP_ENABLE		BIT(31)
+#define PORT_MSIMAP_TARGET		GENMASK(7, 0)
+
+const struct reg_info t602x_hw = {
+	.phy_lane_ctl = 0,
+	.port_msiaddr = PORT_T602X_MSIADDR,
+	.port_msiaddr_hi = PORT_T602X_MSIADDR_HI,
+	.port_refclk = 0,
+	.port_perst = PORT_T602X_PERST,
+	.port_rid2sid = PORT_T602X_RID2SID,
+	.port_msimap = PORT_T602X_MSIMAP,
+	.max_rid2sid = 512, /* 16 on t602x, guess for autodetect on future HW */
+	.max_msimap = 512, /* 96 on t602x, guess for autodetect on future HW */
+};
+
+static const struct of_device_id apple_pcie_of_match_hw[] = {
+	{ .compatible = "apple,t6020-pcie", .data = &t602x_hw },
+	{ .compatible = "apple,pcie", .data = &t8103_hw },
+	{ }
+};
+
 struct apple_pcie {
 	struct mutex		lock;
 	struct device		*dev;
@@ -144,6 +196,7 @@ struct apple_pcie {
 	struct completion	event;
 	struct irq_fwspec	fwspec;
 	u32			nvecs;
+	const struct reg_info	*hw;
 };
 
 struct apple_pcie_port {
@@ -267,14 +320,14 @@ static void apple_port_irq_mask(struct irq_data *data)
 {
 	struct apple_pcie_port *port = irq_data_get_irq_chip_data(data);
 
-	writel_relaxed(BIT(data->hwirq), port->base + PORT_INTMSKSET);
+	rmw_set(BIT(data->hwirq), port->base + PORT_INTMSK);
 }
 
 static void apple_port_irq_unmask(struct irq_data *data)
 {
 	struct apple_pcie_port *port = irq_data_get_irq_chip_data(data);
 
-	writel_relaxed(BIT(data->hwirq), port->base + PORT_INTMSKCLR);
+	rmw_clear(BIT(data->hwirq), port->base + PORT_INTMSK);
 }
 
 static bool hwirq_is_intx(unsigned int hwirq)
@@ -378,6 +431,7 @@ static void apple_port_irq_handler(struct irq_desc *desc)
 static int apple_pcie_port_setup_irq(struct apple_pcie_port *port)
 {
 	struct fwnode_handle *fwnode = &port->np->fwnode;
+	struct apple_pcie *pcie = port->pcie;
 	unsigned int irq;
 
 	/* FIXME: consider moving each interrupt under each port */
@@ -393,19 +447,35 @@ static int apple_pcie_port_setup_irq(struct apple_pcie_port *port)
 		return -ENOMEM;
 
 	/* Disable all interrupts */
-	writel_relaxed(~0, port->base + PORT_INTMSKSET);
+	writel_relaxed(~0, port->base + PORT_INTMSK);
 	writel_relaxed(~0, port->base + PORT_INTSTAT);
+	writel_relaxed(~0, port->base + PORT_LINKCMDSTS);
 
 	irq_set_chained_handler_and_data(irq, apple_port_irq_handler, port);
 
 	/* Configure MSI base address */
-	BUILD_BUG_ON(upper_32_bits(DOORBELL_ADDR));
-	writel_relaxed(lower_32_bits(DOORBELL_ADDR), port->base + PORT_MSIADDR);
+	BUG_ON(upper_32_bits(DOORBELL_ADDR));
+	writel_relaxed(lower_32_bits(DOORBELL_ADDR),
+		       port->base + pcie->hw->port_msiaddr);
+	if (pcie->hw->port_msiaddr_hi)
+		writel_relaxed(0, port->base + pcie->hw->port_msiaddr_hi);
 
 	/* Enable MSIs, shared between all ports */
-	writel_relaxed(0, port->base + PORT_MSIBASE);
-	writel_relaxed((ilog2(port->pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT) |
-		       PORT_MSICFG_EN, port->base + PORT_MSICFG);
+	if (pcie->hw->port_msimap) {
+		int i;
+
+		for (i = 0; i < pcie->nvecs; i++) {
+			writel_relaxed(FIELD_PREP(PORT_MSIMAP_TARGET, i) |
+				       PORT_MSIMAP_ENABLE,
+				       port->base + pcie->hw->port_msimap + 4 * i);
+		}
+
+		writel_relaxed(PORT_MSICFG_EN, port->base + PORT_MSICFG);
+	} else {
+		writel_relaxed(0, port->base + PORT_MSIBASE);
+		writel_relaxed((ilog2(pcie->nvecs) << PORT_MSICFG_L2MSINUM_SHIFT) |
+			PORT_MSICFG_EN, port->base + PORT_MSICFG);
+	}
 
 	return 0;
 }
@@ -473,7 +543,9 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
 	u32 stat;
 	int res;
 
-	rmw_set(PHY_LANE_CTL_CFGACC, port->phy + PHY_LANE_CTL);
+	if (pcie->hw->phy_lane_ctl)
+		rmw_set(PHY_LANE_CTL_CFGACC, port->phy + pcie->hw->phy_lane_ctl);
+
 	rmw_set(PHY_LANE_CFG_REFCLK0REQ, port->phy + PHY_LANE_CFG);
 
 	res = readl_relaxed_poll_timeout(port->phy + PHY_LANE_CFG,
@@ -490,10 +562,13 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
 	if (res < 0)
 		return res;
 
-	rmw_clear(PHY_LANE_CTL_CFGACC, port->phy + PHY_LANE_CTL);
+	if (pcie->hw->phy_lane_ctl)
+		rmw_clear(PHY_LANE_CTL_CFGACC, port->phy + pcie->hw->phy_lane_ctl);
 
 	rmw_set(PHY_LANE_CFG_REFCLKEN, port->phy + PHY_LANE_CFG);
-	rmw_set(PORT_REFCLK_EN, port->base + PORT_REFCLK);
+
+	if (pcie->hw->port_refclk)
+		rmw_set(PORT_REFCLK_EN, port->base + pcie->hw->port_refclk);
 
 	return 0;
 }
@@ -501,9 +576,9 @@ static int apple_pcie_setup_refclk(struct apple_pcie *pcie,
 static u32 apple_pcie_rid2sid_write(struct apple_pcie_port *port,
 				    int idx, u32 val)
 {
-	writel_relaxed(val, port->base + PORT_RID2SID(idx));
+	writel_relaxed(val, port->base + port->pcie->hw->port_rid2sid + 4 * idx);
 	/* Read back to ensure completion of the write */
-	return readl_relaxed(port->base + PORT_RID2SID(idx));
+	return readl_relaxed(port->base + port->pcie->hw->port_rid2sid + 4 * idx);
 }
 
 static int apple_pcie_probe_port(struct device_node *np)
@@ -602,7 +677,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 		usleep_range(100, 200);
 
 	/* Deassert PERST# */
-	rmw_set(PORT_PERST_OFF, port->base + PORT_PERST);
+	rmw_set(PORT_PERST_OFF, port->base + pcie->hw->port_perst);
 	gpiod_set_value_cansleep(reset, 0);
 
 	/* Wait for 100ms after PERST# deassertion (PCIe r5.0, 6.6.1) */
@@ -615,15 +690,12 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 		return ret;
 	}
 
-	rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK);
-	rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK);
-
 	ret = apple_pcie_port_setup_irq(port);
 	if (ret)
 		return ret;
 
 	/* Reset all RID/SID mappings, and check for RAZ/WI registers */
-	for (i = 0; i < MAX_RID2SID; i++) {
+	for (i = 0; i < pcie->hw->max_rid2sid; i++) {
 		if (apple_pcie_rid2sid_write(port, i, 0xbad1d) != 0xbad1d)
 			break;
 		apple_pcie_rid2sid_write(port, i, 0);
@@ -647,6 +719,12 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	if (!wait_for_completion_timeout(&pcie->event, HZ / 10))
 		dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
 
+	if (pcie->hw->port_refclk)
+		rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK);
+	else
+		rmw_set(PHY_LANE_CFG_REFCLKCGEN, port->phy + PHY_LANE_CFG);
+	rmw_clear(PORT_APPCLK_CGDIS, port->base + PORT_APPCLK);
+
 	return 0;
 }
 
@@ -763,7 +841,7 @@ static void apple_pcie_release_device(struct apple_pcie_port *port,
 	for_each_set_bit(idx, port->sid_map, port->sid_map_sz) {
 		u32 val;
 
-		val = readl_relaxed(port->base + PORT_RID2SID(idx));
+		val = readl_relaxed(port->base + port->pcie->hw->port_rid2sid + 4 * idx);
 		if ((val & 0xffff) == rid) {
 			apple_pcie_rid2sid_write(port, idx, 0);
 			bitmap_release_region(port->sid_map, idx, 0);
@@ -820,13 +898,19 @@ static int apple_pcie_init(struct pci_config_window *cfg)
 	struct platform_device *platform = to_platform_device(dev);
 	struct device_node *of_port;
 	struct apple_pcie *pcie;
+	const struct of_device_id *match;
 	int ret;
 
+	match = of_match_device(apple_pcie_of_match_hw, dev);
+	if (!match)
+		return -ENODEV;
+
 	pcie = devm_kzalloc(dev, sizeof(*pcie), GFP_KERNEL);
 	if (!pcie)
 		return -ENOMEM;
 
 	pcie->dev = dev;
+	pcie->hw = match->data;
 
 	mutex_init(&pcie->lock);
 
@@ -889,6 +973,7 @@ static const struct pci_ecam_ops apple_pcie_cfg_ecam_ops = {
 };
 
 static const struct of_device_id apple_pcie_of_match[] = {
+	{ .compatible = "apple,t6020-pcie", .data = &apple_pcie_cfg_ecam_ops },
 	{ .compatible = "apple,pcie", .data = &apple_pcie_cfg_ecam_ops },
 	{ }
 };

From 9cda2791039e02df004cec44629ff43b1d001fd8 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 20 Apr 2023 23:24:59 +0200
Subject: [PATCH 0349/1027] PCI: apple: Skip controller port setup for online
 links

U-boot gained recently support for PCIe controller on Apple silicon
devices. It is currently unkown how to reset / retrain already brought
up ports. Redoing the controller level setup breaks the links.
Check the link status before performing controller level port/link
setup.

Link: https://lore.kernel.org/u-boot/20230121192800.82428-1-kettenis@openbsd.org/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/pcie-apple.c | 88 ++++++++++++++++++-----------
 1 file changed, 55 insertions(+), 33 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 02d4cc372a7637..bc8c8f7184d0e0 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -605,15 +605,13 @@ static int apple_pcie_probe_port(struct device_node *np)
 	return 0;
 }
 
-static int apple_pcie_setup_port(struct apple_pcie *pcie,
+static int apple_pcie_setup_link(struct apple_pcie *pcie,
+				 struct apple_pcie_port *port,
 				 struct device_node *np)
 {
-	struct platform_device *platform = to_platform_device(pcie->dev);
-	struct apple_pcie_port *port;
-	struct gpio_desc *reset, *pwren = NULL;
-	u32 stat, idx;
-	int ret, i;
-	char name[16];
+	struct gpio_desc *reset, *pwren;
+	u32 stat;
+	int ret;
 
 	reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
 				      GPIOD_OUT_LOW, "PERST#");
@@ -629,32 +627,6 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 			return PTR_ERR(pwren);
 	}
 
-	port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL);
-	if (!port)
-		return -ENOMEM;
-
-	ret = of_property_read_u32_index(np, "reg", 0, &idx);
-	if (ret)
-		return ret;
-
-	/* Use the first reg entry to work out the port index */
-	port->idx = idx >> 11;
-	port->pcie = pcie;
-	port->np = np;
-
-	snprintf(name, sizeof(name), "port%d", port->idx);
-	port->base = devm_platform_ioremap_resource_byname(platform, name);
-	if (IS_ERR(port->base))
-		port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
-	if (IS_ERR(port->base)) {
-		return PTR_ERR(port->base);
-	}
-
-	snprintf(name, sizeof(name), "phy%d", port->idx);
-	port->phy = devm_platform_ioremap_resource_byname(platform, name);
-	if (IS_ERR(port->phy))
-		port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx);
-
 	rmw_set(PORT_APPCLK_EN, port->base + PORT_APPCLK);
 
 	/* Assert PERST# before setting up the clock */
@@ -690,6 +662,52 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 		return ret;
 	}
 
+	return 0;
+}
+
+static int apple_pcie_setup_port(struct apple_pcie *pcie,
+				 struct device_node *np)
+{
+	struct platform_device *platform = to_platform_device(pcie->dev);
+	struct apple_pcie_port *port;
+	u32 link_stat, idx;
+	int ret, i;
+	char name[16];
+
+	port = devm_kzalloc(pcie->dev, sizeof(*port), GFP_KERNEL);
+	if (!port)
+		return -ENOMEM;
+
+	ret = of_property_read_u32_index(np, "reg", 0, &idx);
+	if (ret)
+		return ret;
+
+	/* Use the first reg entry to work out the port index */
+	port->idx = idx >> 11;
+	port->pcie = pcie;
+	port->np = np;
+
+	snprintf(name, sizeof(name), "port%d", port->idx);
+	port->base = devm_platform_ioremap_resource_byname(platform, name);
+	if (IS_ERR(port->base))
+		port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+	if (IS_ERR(port->base)) {
+		return PTR_ERR(port->base);
+	}
+
+	snprintf(name, sizeof(name), "phy%d", port->idx);
+	port->phy = devm_platform_ioremap_resource_byname(platform, name);
+	if (IS_ERR(port->phy))
+		port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx);
+
+	/* link might be already brought up by u-boot, skip setup then */
+	link_stat = readl_relaxed(port->base + PORT_LINKSTS);
+	if (!(link_stat & PORT_LINKSTS_UP)) {
+		ret = apple_pcie_setup_link(pcie, port, np);
+		if (ret)
+			return ret;
+	}
+
 	ret = apple_pcie_port_setup_irq(port);
 	if (ret)
 		return ret;
@@ -714,6 +732,10 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	ret = apple_pcie_port_register_irqs(port);
 	WARN_ON(ret);
 
+	if (link_stat & PORT_LINKSTS_UP)
+		return 0;
+
+	/* start link training */
 	writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
 
 	if (!wait_for_completion_timeout(&pcie->event, HZ / 10))

From 94de61a02ff22d6be4d2989a17a40309846d3589 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 18 May 2023 16:12:39 +0900
Subject: [PATCH 0350/1027] PCI: apple: Make link up timeout configurable,
 default to 500ms

We're seeing link up timeouts and it looks like devices are just too
slow. Let's just increase this.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index bc8c8f7184d0e0..4f120987cb4897 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -31,6 +31,10 @@
 #include <linux/of_irq.h>
 #include <linux/pci-ecam.h>
 
+static int link_up_timeout = 500;
+module_param(link_up_timeout, int, 0644);
+MODULE_PARM_DESC(link_up_timeout, "PCIe link training timeout in milliseconds");
+
 #define CORE_RC_PHYIF_CTL		0x00024
 #define   CORE_RC_PHYIF_CTL_RUN		BIT(0)
 #define CORE_RC_PHYIF_STAT		0x00028
@@ -738,7 +742,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	/* start link training */
 	writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
 
-	if (!wait_for_completion_timeout(&pcie->event, HZ / 10))
+	if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000))
 		dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
 
 	if (pcie->hw->port_refclk)

From 141d489b9bd17ce8deb23347de76019e23d59598 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 18 May 2023 16:18:29 +0900
Subject: [PATCH 0351/1027] PCI: apple: Reorder & improve link-up logic

Always re-check LINKSTS right before deciding whether to start the link
training and wait for it, just in case the link happened to come up
while we were setting up IRQs. Also, always do the clock-gate disable
even if the link is already up.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 4f120987cb4897..5ea0a80a1a2f03 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -736,14 +736,14 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	ret = apple_pcie_port_register_irqs(port);
 	WARN_ON(ret);
 
-	if (link_stat & PORT_LINKSTS_UP)
-		return 0;
-
-	/* start link training */
-	writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
+	link_stat = readl_relaxed(port->base + PORT_LINKSTS);
+	if (!(link_stat & PORT_LINKSTS_UP)) {
+		/* start link training */
+		writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
 
-	if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000))
-		dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
+		if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000))
+			dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
+	}
 
 	if (pcie->hw->port_refclk)
 		rmw_clear(PORT_REFCLK_CGDIS, port->base + PORT_REFCLK);

From 6e5fef72607acd7b32ca98406602a63dabb55c24 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 18 May 2023 17:03:32 +0900
Subject: [PATCH 0352/1027] PCI: apple: Log the time it takes for links to come
 up

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 5ea0a80a1a2f03..2ede9c9c921947 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -738,11 +738,18 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 
 	link_stat = readl_relaxed(port->base + PORT_LINKSTS);
 	if (!(link_stat & PORT_LINKSTS_UP)) {
+		unsigned long timeout, left;
 		/* start link training */
 		writel_relaxed(PORT_LTSSMCTL_START, port->base + PORT_LTSSMCTL);
 
-		if (!wait_for_completion_timeout(&pcie->event, link_up_timeout * HZ / 1000))
+		timeout = link_up_timeout * HZ / 1000;
+		left = wait_for_completion_timeout(&pcie->event, timeout);
+		if (!left)
 			dev_warn(pcie->dev, "%pOF link didn't come up\n", np);
+		else
+			dev_info(pcie->dev, "%pOF link up after %ldms\n", np,
+				 (timeout - left) * 1000 / HZ);
+
 	}
 
 	if (pcie->hw->port_refclk)

From 77fd34bba7dd77381e96ed81f512fd6bcf9d4858 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 6 Jun 2023 15:31:01 +0900
Subject: [PATCH 0353/1027] PCI: apple: Do not power down devices on port setup

If a device is already powered, leave it powered. Otherwise port setup
done by u-boot breaks.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/pci/controller/pcie-apple.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 2ede9c9c921947..e89306054e2c5f 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -598,7 +598,7 @@ static int apple_pcie_probe_port(struct device_node *np)
 	gpiod_put(gd);
 
 	gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "pwren", 0,
-				    GPIOD_OUT_LOW, "PWREN");
+				    GPIOD_ASIS, "PWREN");
 	if (IS_ERR(gd)) {
 		if (PTR_ERR(gd) != -ENOENT)
 			return PTR_ERR(gd);
@@ -623,7 +623,7 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie,
 		return PTR_ERR(reset);
 
 	pwren = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "pwren",
-					    GPIOD_OUT_LOW, "PWREN");
+					    GPIOD_ASIS, "PWREN");
 	if (IS_ERR(pwren)) {
 		if (PTR_ERR(pwren) == -ENOENT)
 			pwren = NULL;

From 334bfcc128d4e0161b91cd93987854d7e86dbd57 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 13 Apr 2024 12:23:26 +0200
Subject: [PATCH 0354/1027] fixup! PCI: apple: Move port PHY registers to their
 own reg items

open code devm_platform_ioremap_resource_byname() to avoid error
messages on older platforms with missing resources in the pcie node.

Avoids "pcie-apple 590000000.pcie: invalid resource (null)" on probe.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/pcie-apple.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index e89306054e2c5f..e1c18ab8a500af 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -674,6 +674,7 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 {
 	struct platform_device *platform = to_platform_device(pcie->dev);
 	struct apple_pcie_port *port;
+	struct resource *res;
 	u32 link_stat, idx;
 	int ret, i;
 	char name[16];
@@ -692,16 +693,21 @@ static int apple_pcie_setup_port(struct apple_pcie *pcie,
 	port->np = np;
 
 	snprintf(name, sizeof(name), "port%d", port->idx);
-	port->base = devm_platform_ioremap_resource_byname(platform, name);
-	if (IS_ERR(port->base))
+	res = platform_get_resource_byname(platform, IORESOURCE_MEM, name);
+	if (res) {
+		port->base = devm_ioremap_resource(&platform->dev, res);
+	} else {
 		port->base = devm_platform_ioremap_resource(platform, port->idx + 2);
+	}
 	if (IS_ERR(port->base)) {
 		return PTR_ERR(port->base);
 	}
 
 	snprintf(name, sizeof(name), "phy%d", port->idx);
-	port->phy = devm_platform_ioremap_resource_byname(platform, name);
-	if (IS_ERR(port->phy))
+	res = platform_get_resource_byname(platform, IORESOURCE_MEM, name);
+	if (res)
+		port->phy = devm_ioremap_resource(&platform->dev, res);
+	else
 		port->phy = pcie->base + CORE_PHY_DEFAULT_BASE(port->idx);
 
 	/* link might be already brought up by u-boot, skip setup then */

From fe508234cc165a39efbd6247418edfd99551ef88 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 9 Sep 2024 18:22:24 +0200
Subject: [PATCH 0355/1027] fixup! PCI: apple: Probe all GPIOs for availability
 first

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/pcie-apple.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index e1c18ab8a500af..6558d530a799b7 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -589,8 +589,9 @@ static int apple_pcie_probe_port(struct device_node *np)
 {
 	struct gpio_desc *gd;
 
+	/* check whether the GPPIO pin exists but leave it as is */
 	gd = fwnode_gpiod_get_index(of_fwnode_handle(np), "reset", 0,
-				    GPIOD_OUT_LOW, "PERST#");
+				    GPIOD_ASIS, "PERST#");
 	if (IS_ERR(gd)) {
 		return PTR_ERR(gd);
 	}

From faeb1a1d196a658b41825d887eb16e166a3f0946 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 9 Sep 2024 18:23:04 +0200
Subject: [PATCH 0356/1027] PCI: apple: Avoid PERST# deassertion through gpiod
 initialization

The Aquantia AQC113 10GB ethernet device used in Apple silicon
Mac Studio, Mac Pro and as option in Mac mini is sensitive to PERST#
deassertion before clock setup. The perst pins are defined as
GPIO_ACTIVE_LOW in the device tree. GPIOD_OUT_LOW will deassert the
PERST# pin. This breaks the link setup reliably under m1n1's hypervisor
on a M1 Ultra Mac Studio. There might have been reports of unavailable
10GB NICs before u-boot took over the PCIe link setup.

Signed-off-by: Janne Grunau <j@jannau.net>
Fixes: a6b9ede1f3df ("PCI: apple: Do not leak reset GPIO on unbind/unload/error")
Fixes: 1e33888fbe44 ("PCI: apple: Add initial hardware bring-up")
---
 drivers/pci/controller/pcie-apple.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index 6558d530a799b7..d37e954fcd20e6 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -618,8 +618,13 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie,
 	u32 stat;
 	int ret;
 
+	/*
+	 * Leave PERST# as is. The Aquantia AQC113 10GB nic used desktop macs is
+	 * sensitive to deasserting it without prior clock setup.
+	 * Observed on M1 Max/Ultra Mac Studios under m1n1's hypervisor.
+	 */
 	reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
-				      GPIOD_OUT_LOW, "PERST#");
+				      GPIOD_ASIS, "PERST#");
 	if (IS_ERR(reset))
 		return PTR_ERR(reset);
 

From f84db17a7e0b91ada04ecec762e4e7a951b32594 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 14 May 2024 00:26:54 +0200
Subject: [PATCH 0357/1027] Revert "nvmem: core: Print error on wrong bits DT
 property"

This reverts commit def3173d4f17b37cecbd74d7c269a080b0b01598.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/nvmem/core.c | 5 -----
 1 file changed, 5 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 33ffa2aa4c1152..4def14b4c60dbe 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -824,11 +824,6 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod
 		if (addr && len == (2 * sizeof(u32))) {
 			info.bit_offset = be32_to_cpup(addr++);
 			info.nbits = be32_to_cpup(addr);
-			if (info.bit_offset >= BITS_PER_BYTE || info.nbits < 1) {
-				dev_err(dev, "nvmem: invalid bits on %pOF\n", child);
-				of_node_put(child);
-				return -EINVAL;
-			}
 		}
 
 		info.np = of_node_get(child);

From 46ee93e3199d214fb3f4ba2e95ccbfce758dcfd6 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:11:02 +0100
Subject: [PATCH 0358/1027] nvmem: allow bit offset > 8

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/nvmem/core.c | 14 +++++++++++---
 1 file changed, 11 insertions(+), 3 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index 4def14b4c60dbe..cbc14e33514e93 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -1612,15 +1612,23 @@ EXPORT_SYMBOL_GPL(nvmem_cell_put);
 static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf)
 {
 	u8 *p, *b;
-	int i, extra, bit_offset = cell->bit_offset;
+	int i, padding, extra, bit_offset = cell->bit_offset;
+	int bytes = cell->bytes;
 
 	p = b = buf;
 	if (bit_offset) {
+		padding = bit_offset/8;
+		if (padding) {
+		      memmove(buf, buf + padding, bytes - padding);
+		      bit_offset -= BITS_PER_BYTE * padding;
+		      bytes -= padding;
+		}
+
 		/* First shift */
 		*b++ >>= bit_offset;
 
 		/* setup rest of the bytes if any */
-		for (i = 1; i < cell->bytes; i++) {
+		for (i = 1; i < bytes; i++) {
 			/* Get bits from next byte and shift them towards msb */
 			*p |= *b << (BITS_PER_BYTE - bit_offset);
 
@@ -1633,7 +1641,7 @@ static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void
 	}
 
 	/* result fits in less bytes */
-	extra = cell->bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
+	extra = bytes - DIV_ROUND_UP(cell->nbits, BITS_PER_BYTE);
 	while (--extra >= 0)
 		*p-- = 0;
 

From 2654eed93917229a7d00d89f7f8f3fa247141476 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:11:04 +0100
Subject: [PATCH 0359/1027] nvmem: round up to word_size

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/nvmem/core.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index cbc14e33514e93..edafbe1d638772 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -582,8 +582,8 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem,
 	cell->np = info->np;
 
 	if (cell->nbits)
-		cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset,
-					   BITS_PER_BYTE);
+		cell->bytes = round_up(DIV_ROUND_UP(cell->nbits + cell->bit_offset,
+					   BITS_PER_BYTE), nvmem->word_size);
 
 	if (!IS_ALIGNED(cell->offset, nvmem->stride)) {
 		dev_err(&nvmem->dev,

From 4f5b8aa35e68a33a1d20b6ce62014794768af7ad Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:11:05 +0100
Subject: [PATCH 0360/1027] WIP: phy: apple: Add Apple Type-C PHY

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/phy/Kconfig        |    1 +
 drivers/phy/Makefile       |    1 +
 drivers/phy/apple/Kconfig  |   11 +
 drivers/phy/apple/Makefile |    6 +
 drivers/phy/apple/atc.c    | 2404 ++++++++++++++++++++++++++++++++++++
 drivers/phy/apple/atc.h    |  139 +++
 drivers/phy/apple/trace.c  |    4 +
 drivers/phy/apple/trace.h  |  147 +++
 8 files changed, 2713 insertions(+)
 create mode 100644 drivers/phy/apple/Kconfig
 create mode 100644 drivers/phy/apple/Makefile
 create mode 100644 drivers/phy/apple/atc.c
 create mode 100644 drivers/phy/apple/atc.h
 create mode 100644 drivers/phy/apple/trace.c
 create mode 100644 drivers/phy/apple/trace.h

diff --git a/drivers/phy/Kconfig b/drivers/phy/Kconfig
index dfab1c66b3e510..f0fdad2f7e6733 100644
--- a/drivers/phy/Kconfig
+++ b/drivers/phy/Kconfig
@@ -84,6 +84,7 @@ config PHY_AIROHA_PCIE
 
 source "drivers/phy/allwinner/Kconfig"
 source "drivers/phy/amlogic/Kconfig"
+source "drivers/phy/apple/Kconfig"
 source "drivers/phy/broadcom/Kconfig"
 source "drivers/phy/cadence/Kconfig"
 source "drivers/phy/freescale/Kconfig"
diff --git a/drivers/phy/Makefile b/drivers/phy/Makefile
index 5fcbce5f9ab1f8..a6e26d367003d0 100644
--- a/drivers/phy/Makefile
+++ b/drivers/phy/Makefile
@@ -13,6 +13,7 @@ obj-$(CONFIG_USB_LGM_PHY)		+= phy-lgm-usb.o
 obj-$(CONFIG_PHY_AIROHA_PCIE)		+= phy-airoha-pcie.o
 obj-y					+= allwinner/	\
 					   amlogic/	\
+					   apple/	\
 					   broadcom/	\
 					   cadence/	\
 					   freescale/	\
diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig
new file mode 100644
index 00000000000000..090d8542651f86
--- /dev/null
+++ b/drivers/phy/apple/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+config PHY_APPLE_ATC
+	tristate "Apple Type-C PHY"
+	depends on ARCH_APPLE || COMPILE_TEST
+	default ARCH_APPLE
+	select GENERIC_PHY
+	select TYPEC
+	help
+	  Enable this to add support for the Apple Type-C PHY, switch
+	  and mux found in Apple SoCs such as the M1.
+	  This driver currently provides support for USB2 and USB3.
diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile
new file mode 100644
index 00000000000000..af863fa299dc5f
--- /dev/null
+++ b/drivers/phy/apple/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+CFLAGS_trace.o			:= -I$(src)
+
+obj-$(CONFIG_PHY_APPLE_ATC)		+= phy-apple-atc.o
+phy-apple-atc-y			:= atc.o
+phy-apple-atc-$(CONFIG_TRACING)	+= trace.o
diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
new file mode 100644
index 00000000000000..f95f9f9fe60a9c
--- /dev/null
+++ b/drivers/phy/apple/atc.c
@@ -0,0 +1,2404 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#include "atc.h"
+#include "trace.h"
+
+#include <dt-bindings/phy/phy.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/iopoll.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/phy/phy.h>
+#include <linux/platform_device.h>
+#include <linux/reset-controller.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/usb/typec_tbt.h>
+
+#define rcdev_to_apple_atcphy(_rcdev) \
+	container_of(_rcdev, struct apple_atcphy, rcdev)
+
+#define AUSPLL_APB_CMD_OVERRIDE 0x2000
+#define AUSPLL_APB_CMD_OVERRIDE_REQ BIT(0)
+#define AUSPLL_APB_CMD_OVERRIDE_ACK BIT(1)
+#define AUSPLL_APB_CMD_OVERRIDE_UNK28 BIT(28)
+#define AUSPLL_APB_CMD_OVERRIDE_CMD GENMASK(27, 3)
+
+#define AUSPLL_FREQ_DESC_A 0x2080
+#define AUSPLL_FD_FREQ_COUNT_TARGET GENMASK(9, 0)
+#define AUSPLL_FD_FBDIVN_HALF BIT(10)
+#define AUSPLL_FD_REV_DIVN GENMASK(13, 11)
+#define AUSPLL_FD_KI_MAN GENMASK(17, 14)
+#define AUSPLL_FD_KI_EXP GENMASK(21, 18)
+#define AUSPLL_FD_KP_MAN GENMASK(25, 22)
+#define AUSPLL_FD_KP_EXP GENMASK(29, 26)
+#define AUSPLL_FD_KPKI_SCALE_HBW GENMASK(31, 30)
+
+#define AUSPLL_FREQ_DESC_B 0x2084
+#define AUSPLL_FD_FBDIVN_FRAC_DEN GENMASK(13, 0)
+#define AUSPLL_FD_FBDIVN_FRAC_NUM GENMASK(27, 14)
+
+#define AUSPLL_FREQ_DESC_C 0x2088
+#define AUSPLL_FD_SDM_SSC_STEP GENMASK(7, 0)
+#define AUSPLL_FD_SDM_SSC_EN BIT(8)
+#define AUSPLL_FD_PCLK_DIV_SEL GENMASK(13, 9)
+#define AUSPLL_FD_LFSDM_DIV GENMASK(15, 14)
+#define AUSPLL_FD_LFCLK_CTRL GENMASK(19, 16)
+#define AUSPLL_FD_VCLK_OP_DIVN GENMASK(21, 20)
+#define AUSPLL_FD_VCLK_PRE_DIVN BIT(22)
+
+#define AUSPLL_DCO_EFUSE_SPARE 0x222c
+#define AUSPLL_RODCO_ENCAP_EFUSE GENMASK(10, 9)
+#define AUSPLL_RODCO_BIAS_ADJUST_EFUSE GENMASK(14, 12)
+
+#define AUSPLL_FRACN_CAN 0x22a4
+#define AUSPLL_DLL_START_CAPCODE GENMASK(18, 17)
+
+#define AUSPLL_CLKOUT_MASTER 0x2200
+#define AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN BIT(2)
+#define AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN BIT(4)
+#define AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN BIT(6)
+
+#define AUSPLL_CLKOUT_DIV 0x2208
+#define AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI GENMASK(20, 16)
+
+#define AUSPLL_BGR 0x2214
+#define AUSPLL_BGR_CTRL_AVAIL BIT(0)
+
+#define AUSPLL_CLKOUT_DTC_VREG 0x2220
+#define AUSPLL_DTC_VREG_ADJUST GENMASK(16, 14)
+#define AUSPLL_DTC_VREG_BYPASS BIT(7)
+
+#define AUSPLL_FREQ_CFG 0x2224
+#define AUSPLL_FREQ_REFCLK GENMASK(1, 0)
+
+#define AUS_COMMON_SHIM_BLK_VREG 0x0a04
+#define AUS_VREG_TRIM GENMASK(6, 2)
+
+#define CIO3PLL_CLK_CTRL 0x2a00
+#define CIO3PLL_CLK_PCLK_EN BIT(1)
+#define CIO3PLL_CLK_REFCLK_EN BIT(5)
+
+#define CIO3PLL_DCO_NCTRL 0x2a38
+#define CIO3PLL_DCO_COARSEBIN_EFUSE0 GENMASK(6, 0)
+#define CIO3PLL_DCO_COARSEBIN_EFUSE1 GENMASK(23, 17)
+
+#define CIO3PLL_FRACN_CAN 0x2aa4
+#define CIO3PLL_DLL_CAL_START_CAPCODE GENMASK(18, 17)
+
+#define CIO3PLL_DTC_VREG 0x2a20
+#define CIO3PLL_DTC_VREG_ADJUST GENMASK(16, 14)
+
+#define ACIOPHY_CROSSBAR 0x4c
+#define ACIOPHY_CROSSBAR_PROTOCOL GENMASK(4, 0)
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB4 0x0
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED 0x1
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3 0xa
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED 0xb
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP 0x10
+#define ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED 0x11
+#define ACIOPHY_CROSSBAR_PROTOCOL_DP 0x14
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA GENMASK(16, 5)
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE 0x0000
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100 0x100
+#define ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008 0x008
+#define ACIOPHY_CROSSBAR_DP_BOTH_PMA BIT(17)
+
+#define ACIOPHY_LANE_MODE 0x48
+#define ACIOPHY_LANE_MODE_RX0 GENMASK(2, 0)
+#define ACIOPHY_LANE_MODE_TX0 GENMASK(5, 3)
+#define ACIOPHY_LANE_MODE_RX1 GENMASK(8, 6)
+#define ACIOPHY_LANE_MODE_TX1 GENMASK(11, 9)
+#define ACIOPHY_LANE_MODE_USB4 0
+#define ACIOPHY_LANE_MODE_USB3 1
+#define ACIOPHY_LANE_MODE_DP 2
+#define ACIOPHY_LANE_MODE_OFF 3
+
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1 0x84
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN BIT(27)
+#define ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN BIT(28)
+
+#define ACIOPHY_TOP_BIST_OV_CFG 0x8c
+#define ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV BIT(13)
+#define ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV BIT(25)
+
+#define ACIOPHY_TOP_BIST_READ_CTRL 0x90
+#define ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE BIT(2)
+
+#define ACIOPHY_TOP_PHY_STAT 0x9c
+#define ACIOPHY_TOP_PHY_STAT_LN0_UNK0 BIT(0)
+#define ACIOPHY_TOP_PHY_STAT_LN0_UNK23 BIT(23)
+
+#define ACIOPHY_TOP_BIST_PHY_CFG0 0xa8
+#define ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N BIT(0)
+
+#define ACIOPHY_TOP_BIST_PHY_CFG1 0xac
+#define ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN GENMASK(13, 10)
+
+#define ACIOPHY_PLL_COMMON_CTRL 0x1028
+#define ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT BIT(24)
+
+#define ATCPHY_POWER_CTRL 0x20000
+#define ATCPHY_POWER_STAT 0x20004
+#define ATCPHY_POWER_SLEEP_SMALL BIT(0)
+#define ATCPHY_POWER_SLEEP_BIG BIT(1)
+#define ATCPHY_POWER_CLAMP_EN BIT(2)
+#define ATCPHY_POWER_APB_RESET_N BIT(3)
+#define ATCPHY_POWER_PHY_RESET_N BIT(4)
+
+#define ATCPHY_MISC 0x20008
+#define ATCPHY_MISC_RESET_N BIT(0)
+#define ATCPHY_MISC_LANE_SWAP BIT(2)
+
+#define ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0 0x7000
+#define DP_PMA_BYTECLK_RESET BIT(0)
+#define DP_MAC_DIV20_CLK_SEL BIT(1)
+#define DPTXPHY_PMA_LANE_RESET_N BIT(2)
+#define DPTXPHY_PMA_LANE_RESET_N_OV BIT(3)
+#define DPTX_PCLK1_SELECT GENMASK(6, 4)
+#define DPTX_PCLK2_SELECT GENMASK(9, 7)
+#define DPRX_PCLK_SELECT GENMASK(12, 10)
+#define DPTX_PCLK1_ENABLE BIT(13)
+#define DPTX_PCLK2_ENABLE BIT(14)
+#define DPRX_PCLK_ENABLE BIT(15)
+
+#define ACIOPHY_DP_PCLK_STAT 0x7044
+#define ACIOPHY_AUSPLL_LOCK BIT(3)
+
+#define LN0_AUSPMA_RX_TOP 0x9000
+#define LN0_AUSPMA_RX_EQ 0xA000
+#define LN0_AUSPMA_RX_SHM 0xB000
+#define LN0_AUSPMA_TX_TOP 0xC000
+#define LN0_AUSPMA_TX_SHM 0xD000
+
+#define LN1_AUSPMA_RX_TOP 0x10000
+#define LN1_AUSPMA_RX_EQ 0x11000
+#define LN1_AUSPMA_RX_SHM 0x12000
+#define LN1_AUSPMA_TX_TOP 0x13000
+#define LN1_AUSPMA_TX_SHM 0x14000
+
+#define LN_AUSPMA_RX_TOP_PMAFSM 0x0010
+#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV BIT(0)
+#define LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ BIT(9)
+
+#define LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE 0x00F0
+#define LN_RX_TXMODE BIT(0)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0 0x00
+#define LN_TX_CLK_EN BIT(20)
+#define LN_TX_CLK_EN_OV BIT(21)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1 0x04
+#define LN_RX_DIV20_RESET_N_OV BIT(29)
+#define LN_RX_DIV20_RESET_N BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL2 0x08
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL3 0x0C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL4 0x10
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL5 0x14
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL6 0x18
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL7 0x1C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL8 0x20
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL9 0x24
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10 0x28
+#define LN_DTVREG_ADJUST GENMASK(31, 27)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11 0x2C
+#define LN_DTVREG_BIG_EN BIT(23)
+#define LN_DTVREG_BIG_EN_OV BIT(24)
+#define LN_DTVREG_SML_EN BIT(25)
+#define LN_DTVREG_SML_EN_OV BIT(26)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12 0x30
+#define LN_TX_BYTECLK_RESET_SYNC_CLR BIT(22)
+#define LN_TX_BYTECLK_RESET_SYNC_CLR_OV BIT(23)
+#define LN_TX_BYTECLK_RESET_SYNC_EN BIT(24)
+#define LN_TX_BYTECLK_RESET_SYNC_EN_OV BIT(25)
+#define LN_TX_HRCLK_SEL BIT(28)
+#define LN_TX_HRCLK_SEL_OV BIT(29)
+#define LN_TX_PBIAS_EN BIT(30)
+#define LN_TX_PBIAS_EN_OV BIT(31)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13 0x34
+#define LN_TX_PRE_EN BIT(0)
+#define LN_TX_PRE_EN_OV BIT(1)
+#define LN_TX_PST1_EN BIT(2)
+#define LN_TX_PST1_EN_OV BIT(3)
+#define LN_DTVREG_ADJUST_OV BIT(15)
+
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14A 0x38
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL14B 0x3C
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15A 0x40
+#define LN_AUSPMA_RX_SHM_TJ_UNK_CTRL15B 0x44
+#define LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16 0x48
+#define LN_RXTERM_EN BIT(21)
+#define LN_RXTERM_EN_OV BIT(22)
+#define LN_RXTERM_PULLUP_LEAK_EN BIT(23)
+#define LN_RXTERM_PULLUP_LEAK_EN_OV BIT(24)
+#define LN_TX_CAL_CODE GENMASK(29, 25)
+#define LN_TX_CAL_CODE_OV BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17 0x4C
+#define LN_TX_MARGIN GENMASK(19, 15)
+#define LN_TX_MARGIN_OV BIT(20)
+#define LN_TX_MARGIN_LSB BIT(21)
+#define LN_TX_MARGIN_LSB_OV BIT(22)
+#define LN_TX_MARGIN_P1 GENMASK(26, 23)
+#define LN_TX_MARGIN_P1_OV BIT(27)
+#define LN_TX_MARGIN_P1_LSB GENMASK(29, 28)
+#define LN_TX_MARGIN_P1_LSB_OV BIT(30)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18 0x50
+#define LN_TX_P1_CODE GENMASK(3, 0)
+#define LN_TX_P1_CODE_OV BIT(4)
+#define LN_TX_P1_LSB_CODE GENMASK(6, 5)
+#define LN_TX_P1_LSB_CODE_OV BIT(7)
+#define LN_TX_MARGIN_PRE GENMASK(10, 8)
+#define LN_TX_MARGIN_PRE_OV BIT(11)
+#define LN_TX_MARGIN_PRE_LSB GENMASK(13, 12)
+#define LN_TX_MARGIN_PRE_LSB_OV BIT(14)
+#define LN_TX_PRE_LSB_CODE GENMASK(16, 15)
+#define LN_TX_PRE_LSB_CODE_OV BIT(17)
+#define LN_TX_PRE_CODE GENMASK(21, 18)
+#define LN_TX_PRE_CODE_OV BIT(22)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19 0x54
+#define LN_TX_TEST_EN BIT(21)
+#define LN_TX_TEST_EN_OV BIT(22)
+#define LN_TX_EN BIT(23)
+#define LN_TX_EN_OV BIT(24)
+#define LN_TX_CLK_DLY_CTRL_TAPGEN GENMASK(27, 25)
+#define LN_TX_CLK_DIV2_EN BIT(28)
+#define LN_TX_CLK_DIV2_EN_OV BIT(29)
+#define LN_TX_CLK_DIV2_RST BIT(30)
+#define LN_TX_CLK_DIV2_RST_OV BIT(31)
+
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL20 0x58
+#define LN_AUSPMA_RX_SHM_TJ_RXA_UNK_CTRL21 0x5C
+#define LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22 0x60
+#define LN_VREF_ADJUST_GRAY GENMASK(11, 7)
+#define LN_VREF_ADJUST_GRAY_OV BIT(12)
+#define LN_VREF_BIAS_SEL GENMASK(14, 13)
+#define LN_VREF_BIAS_SEL_OV BIT(15)
+#define LN_VREF_BOOST_EN BIT(16)
+#define LN_VREF_BOOST_EN_OV BIT(17)
+#define LN_VREF_EN BIT(18)
+#define LN_VREF_EN_OV BIT(19)
+#define LN_VREF_LPBKIN_DATA GENMASK(29, 28)
+#define LN_VREF_TEST_RXLPBKDT_EN BIT(30)
+#define LN_VREF_TEST_RXLPBKDT_EN_OV BIT(31)
+
+#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0 0x00
+#define LN_BYTECLK_RESET_SYNC_EN_OV BIT(2)
+#define LN_BYTECLK_RESET_SYNC_EN BIT(3)
+#define LN_BYTECLK_RESET_SYNC_CLR_OV BIT(4)
+#define LN_BYTECLK_RESET_SYNC_CLR BIT(5)
+#define LN_BYTECLK_RESET_SYNC_SEL_OV BIT(6)
+
+#define LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1 0x04
+#define LN_TXA_DIV2_EN_OV BIT(8)
+#define LN_TXA_DIV2_EN BIT(9)
+#define LN_TXA_DIV2_RESET_OV BIT(10)
+#define LN_TXA_DIV2_RESET BIT(11)
+#define LN_TXA_CLK_EN_OV BIT(22)
+#define LN_TXA_CLK_EN BIT(23)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG0 0x08
+#define LN_TXA_CAL_CTRL_OV BIT(0)
+#define LN_TXA_CAL_CTRL GENMASK(18, 1)
+#define LN_TXA_CAL_CTRL_BASE_OV BIT(19)
+#define LN_TXA_CAL_CTRL_BASE GENMASK(23, 20)
+#define LN_TXA_HIZ_OV BIT(29)
+#define LN_TXA_HIZ BIT(30)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG1 0x0C
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG2 0x10
+#define LN_TXA_MARGIN_OV BIT(0)
+#define LN_TXA_MARGIN GENMASK(18, 1)
+#define LN_TXA_MARGIN_2R_OV BIT(19)
+#define LN_TXA_MARGIN_2R BIT(20)
+
+#define LN_AUSPMA_TX_SHM_TXA_IMP_REG3 0x14
+#define LN_TXA_MARGIN_POST_OV BIT(0)
+#define LN_TXA_MARGIN_POST GENMASK(10, 1)
+#define LN_TXA_MARGIN_POST_2R_OV BIT(11)
+#define LN_TXA_MARGIN_POST_2R BIT(12)
+#define LN_TXA_MARGIN_POST_4R_OV BIT(13)
+#define LN_TXA_MARGIN_POST_4R BIT(14)
+#define LN_TXA_MARGIN_PRE_OV BIT(15)
+#define LN_TXA_MARGIN_PRE GENMASK(21, 16)
+#define LN_TXA_MARGIN_PRE_2R_OV BIT(22)
+#define LN_TXA_MARGIN_PRE_2R BIT(23)
+#define LN_TXA_MARGIN_PRE_4R_OV BIT(24)
+#define LN_TXA_MARGIN_PRE_4R BIT(25)
+
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG0 0x18
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG1 0x1C
+#define LN_AUSPMA_TX_SHM_TXA_UNK_REG2 0x20
+
+#define LN_AUSPMA_TX_SHM_TXA_LDOCLK 0x24
+#define LN_LDOCLK_BYPASS_SML_OV BIT(8)
+#define LN_LDOCLK_BYPASS_SML BIT(9)
+#define LN_LDOCLK_BYPASS_BIG_OV BIT(10)
+#define LN_LDOCLK_BYPASS_BIG BIT(11)
+#define LN_LDOCLK_EN_SML_OV BIT(12)
+#define LN_LDOCLK_EN_SML BIT(13)
+#define LN_LDOCLK_EN_BIG_OV BIT(14)
+#define LN_LDOCLK_EN_BIG BIT(15)
+
+/* LPDPTX registers */
+#define LPDPTX_AUX_CFG_BLK_AUX_CTRL 0x0000
+#define LPDPTX_BLK_AUX_CTRL_PWRDN BIT(4)
+#define LPDPTX_BLK_AUX_RXOFFSET GENMASK(25, 22)
+
+#define LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL 0x0008
+
+#define LPDPTX_AUX_CFG_BLK_AUX_MARGIN 0x000c
+#define LPDPTX_MARGIN_RCAL_RXOFFSET_EN BIT(5)
+#define LPDPTX_AUX_MARGIN_RCAL_TXSWING GENMASK(10, 6)
+
+#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0 0x0204
+#define LPDPTX_CFG_PMA_AUX_SEL_LF_DATA BIT(15)
+
+#define LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1 0x0208
+#define LPDPTX_CFG_PMA_PHYS_ADJ GENMASK(22, 20)
+#define LPDPTX_CFG_PMA_PHYS_ADJ_OV BIT(19)
+
+#define LPDPTX_AUX_CONTROL 0x4000
+#define LPDPTX_AUX_PWN_DOWN 0x10
+#define LPDPTX_AUX_CLAMP_EN 0x04
+#define LPDPTX_SLEEP_B_BIG_IN 0x02
+#define LPDPTX_SLEEP_B_SML_IN 0x01
+#define LPDPTX_TXTERM_CODEMSB 0x400
+#define LPDPTX_TXTERM_CODE GENMASK(9, 5)
+
+/* pipehandler registers */
+#define PIPEHANDLER_OVERRIDE 0x00
+#define PIPEHANDLER_OVERRIDE_RXVALID BIT(0)
+#define PIPEHANDLER_OVERRIDE_RXDETECT BIT(2)
+
+#define PIPEHANDLER_OVERRIDE_VALUES 0x04
+
+#define PIPEHANDLER_MUX_CTRL 0x0c
+#define PIPEHANDLER_MUX_MODE GENMASK(1, 0)
+#define PIPEHANDLER_MUX_MODE_USB3PHY 0
+#define PIPEHANDLER_MUX_MODE_DUMMY_PHY 2
+#define PIPEHANDLER_CLK_SELECT GENMASK(5, 3)
+#define PIPEHANDLER_CLK_USB3PHY 1
+#define PIPEHANDLER_CLK_DUMMY_PHY 4
+#define PIPEHANDLER_LOCK_REQ 0x10
+#define PIPEHANDLER_LOCK_ACK 0x14
+#define PIPEHANDLER_LOCK_EN BIT(0)
+
+#define PIPEHANDLER_AON_GEN 0x1C
+#define PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN BIT(4)
+#define PIPEHANDLER_AON_GEN_DWC3_RESET_N BIT(0)
+
+#define PIPEHANDLER_NONSELECTED_OVERRIDE 0x20
+#define PIPEHANDLER_NONSELECTED_NATIVE_RESET BIT(12)
+#define PIPEHANDLER_DUMMY_PHY_EN BIT(15)
+#define PIPEHANDLER_NONSELECTED_NATIVE_POWER_DOWN GENMASK(3, 0)
+
+/* USB2 PHY regs */
+#define USB2PHY_USBCTL 0x00
+#define USB2PHY_USBCTL_HOST_EN BIT(1)
+
+#define USB2PHY_CTL 0x04
+#define USB2PHY_CTL_RESET BIT(0)
+#define USB2PHY_CTL_PORT_RESET BIT(1)
+#define USB2PHY_CTL_APB_RESET_N BIT(2)
+#define USB2PHY_CTL_SIDDQ BIT(3)
+
+#define USB2PHY_SIG 0x08
+#define USB2PHY_SIG_VBUSDET_FORCE_VAL BIT(0)
+#define USB2PHY_SIG_VBUSDET_FORCE_EN BIT(1)
+#define USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL BIT(2)
+#define USB2PHY_SIG_VBUSVLDEXT_FORCE_EN BIT(3)
+#define USB2PHY_SIG_HOST (7 << 12)
+
+static const struct {
+	const struct atcphy_mode_configuration normal;
+	const struct atcphy_mode_configuration swapped;
+	bool enable_dp_aux;
+	enum atcphy_pipehandler_state pipehandler_state;
+} atcphy_modes[] = {
+	[APPLE_ATCPHY_MODE_OFF] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+			.dp_lane = {false, false},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+			.dp_lane = {false, false},
+			.set_swap = false, /* doesn't matter since the SS lanes are off */
+		},
+		.enable_dp_aux = false,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+	},
+	[APPLE_ATCPHY_MODE_USB2] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+			.dp_lane = {false, false},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_OFF},
+			.dp_lane = {false, false},
+			.set_swap = false, /* doesn't matter since the SS lanes are off */
+		},
+		.enable_dp_aux = false,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+	},
+	[APPLE_ATCPHY_MODE_USB3] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_OFF},
+			.dp_lane = {false, false},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_SWAPPED,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_OFF, ACIOPHY_LANE_MODE_USB3},
+			.dp_lane = {false, false},
+			.set_swap = true,
+		},
+		.enable_dp_aux = false,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3,
+	},
+	[APPLE_ATCPHY_MODE_USB3_DP] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_USB3, ACIOPHY_LANE_MODE_DP},
+			.dp_lane = {false, true},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB3_DP_SWAPPED,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_USB3},
+			.dp_lane = {true, false},
+			.set_swap = true,
+		},
+		.enable_dp_aux = true,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB3,
+	},
+	[APPLE_ATCPHY_MODE_USB4] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4},
+			.dp_lane = {false, false},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_USB4_SWAPPED,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_NONE,
+			.crossbar_dp_both_pma = false,
+			.lane_mode = {ACIOPHY_LANE_MODE_USB4, ACIOPHY_LANE_MODE_USB4},
+			.dp_lane = {false, false},
+			.set_swap = false, /* intentionally false */
+		},
+		.enable_dp_aux = false,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+	},
+	[APPLE_ATCPHY_MODE_DP] = {
+		.normal = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK100,
+			.crossbar_dp_both_pma = true,
+			.lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP},
+			.dp_lane = {true, true},
+			.set_swap = false,
+		},
+		.swapped = {
+			.crossbar = ACIOPHY_CROSSBAR_PROTOCOL_DP,
+			.crossbar_dp_single_pma = ACIOPHY_CROSSBAR_DP_SINGLE_PMA_UNK008,
+			.crossbar_dp_both_pma = false, /* intentionally false */
+			.lane_mode = {ACIOPHY_LANE_MODE_DP, ACIOPHY_LANE_MODE_DP},
+			.dp_lane = {true, true},
+			.set_swap = false, /* intentionally false */
+		},
+		.enable_dp_aux = true,
+		.pipehandler_state = ATCPHY_PIPEHANDLER_STATE_USB2,
+	},
+};
+
+static const struct atcphy_dp_link_rate_configuration dp_lr_config[] = {
+	[ATCPHY_DP_LINK_RATE_RBR] = {
+		.freqinit_count_target = 0x21c,
+		.fbdivn_frac_den = 0x0,
+		.fbdivn_frac_num = 0x0,
+		.pclk_div_sel = 0x13,
+		.lfclk_ctrl = 0x5,
+		.vclk_op_divn = 0x2,
+		.plla_clkout_vreg_bypass = true,
+		.bypass_txa_ldoclk = true,
+		.txa_div2_en = true,
+	},
+	[ATCPHY_DP_LINK_RATE_HBR] = {
+		.freqinit_count_target = 0x1c2,
+		.fbdivn_frac_den = 0x3ffe,
+		.fbdivn_frac_num = 0x1fff,
+		.pclk_div_sel = 0x9,
+		.lfclk_ctrl = 0x5,
+		.vclk_op_divn = 0x2,
+		.plla_clkout_vreg_bypass = true,
+		.bypass_txa_ldoclk = true,
+		.txa_div2_en = false,
+	},
+	[ATCPHY_DP_LINK_RATE_HBR2] = {
+		.freqinit_count_target = 0x1c2,
+		.fbdivn_frac_den = 0x3ffe,
+		.fbdivn_frac_num = 0x1fff,
+		.pclk_div_sel = 0x4,
+		.lfclk_ctrl = 0x5,
+		.vclk_op_divn = 0x0,
+		.plla_clkout_vreg_bypass = true,
+		.bypass_txa_ldoclk = true,
+		.txa_div2_en = false,
+	},
+	[ATCPHY_DP_LINK_RATE_HBR3] = {
+		.freqinit_count_target = 0x2a3,
+		.fbdivn_frac_den = 0x3ffc,
+		.fbdivn_frac_num = 0x2ffd,
+		.pclk_div_sel = 0x4,
+		.lfclk_ctrl = 0x6,
+		.vclk_op_divn = 0x0,
+		.plla_clkout_vreg_bypass = false,
+		.bypass_txa_ldoclk = false,
+		.txa_div2_en = false,
+	},
+};
+
+static inline void mask32(void __iomem *reg, u32 mask, u32 set)
+{
+	u32 value = readl(reg);
+	value &= ~mask;
+	value |= set;
+	writel(value, reg);
+}
+
+static inline void core_mask32(struct apple_atcphy *atcphy, u32 reg, u32 mask,
+			       u32 set)
+{
+	mask32(atcphy->regs.core + reg, mask, set);
+}
+
+static inline void set32(void __iomem *reg, u32 set)
+{
+	mask32(reg, 0, set);
+}
+
+static inline void core_set32(struct apple_atcphy *atcphy, u32 reg, u32 set)
+{
+	core_mask32(atcphy, reg, 0, set);
+}
+
+static inline void clear32(void __iomem *reg, u32 clear)
+{
+	mask32(reg, clear, 0);
+}
+
+static inline void core_clear32(struct apple_atcphy *atcphy, u32 reg, u32 clear)
+{
+	core_mask32(atcphy, reg, clear, 0);
+}
+
+static void atcphy_apply_tunable(struct apple_atcphy *atcphy,
+				 void __iomem *regs,
+				 struct atcphy_tunable *tunable)
+{
+	size_t i;
+
+	for (i = 0; i < tunable->sz; ++i)
+		mask32(regs + tunable->values[i].offset,
+		       tunable->values[i].mask, tunable->values[i].value);
+}
+
+static void atcphy_apply_tunables(struct apple_atcphy *atcphy,
+				  enum atcphy_mode mode)
+{
+	int lane0 = atcphy->swap_lanes ? 1 : 0;
+	int lane1 = atcphy->swap_lanes ? 0 : 1;
+
+	atcphy_apply_tunable(atcphy, atcphy->regs.axi2af,
+			     &atcphy->tunables.axi2af);
+	atcphy_apply_tunable(atcphy, atcphy->regs.core,
+			     &atcphy->tunables.common);
+
+	switch (mode) {
+	case APPLE_ATCPHY_MODE_USB3:
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_usb3[lane0]);
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_usb3[lane1]);
+		break;
+
+	case APPLE_ATCPHY_MODE_USB3_DP:
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_usb3[lane0]);
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_displayport[lane1]);
+		break;
+
+	case APPLE_ATCPHY_MODE_DP:
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_displayport[lane0]);
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_displayport[lane1]);
+		break;
+
+	case APPLE_ATCPHY_MODE_USB4:
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_usb4[lane0]);
+		atcphy_apply_tunable(atcphy, atcphy->regs.core,
+				     &atcphy->tunables.lane_usb4[lane1]);
+		break;
+
+	default:
+		dev_warn(atcphy->dev,
+			 "Unknown mode %d in atcphy_apply_tunables\n", mode);
+		fallthrough;
+	case APPLE_ATCPHY_MODE_OFF:
+	case APPLE_ATCPHY_MODE_USB2:
+		break;
+	}
+}
+
+static void atcphy_setup_pll_fuses(struct apple_atcphy *atcphy)
+{
+	void __iomem *regs = atcphy->regs.core;
+
+	if (!atcphy->fuses.present)
+		return;
+
+	/* CIO3PLL fuses */
+	mask32(regs + CIO3PLL_DCO_NCTRL, CIO3PLL_DCO_COARSEBIN_EFUSE0,
+	       FIELD_PREP(CIO3PLL_DCO_COARSEBIN_EFUSE0,
+			  atcphy->fuses.cio3pll_dco_coarsebin[0]));
+	mask32(regs + CIO3PLL_DCO_NCTRL, CIO3PLL_DCO_COARSEBIN_EFUSE1,
+	       FIELD_PREP(CIO3PLL_DCO_COARSEBIN_EFUSE1,
+			  atcphy->fuses.cio3pll_dco_coarsebin[1]));
+	mask32(regs + CIO3PLL_FRACN_CAN, CIO3PLL_DLL_CAL_START_CAPCODE,
+	       FIELD_PREP(CIO3PLL_DLL_CAL_START_CAPCODE,
+			  atcphy->fuses.cio3pll_dll_start_capcode[0]));
+
+	if (atcphy->quirks.t8103_cio3pll_workaround) {
+		mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+		       FIELD_PREP(AUS_VREG_TRIM,
+				  atcphy->fuses.aus_cmn_shm_vreg_trim));
+		mask32(regs + CIO3PLL_FRACN_CAN, CIO3PLL_DLL_CAL_START_CAPCODE,
+		       FIELD_PREP(CIO3PLL_DLL_CAL_START_CAPCODE,
+				  atcphy->fuses.cio3pll_dll_start_capcode[1]));
+		mask32(regs + CIO3PLL_DTC_VREG, CIO3PLL_DTC_VREG_ADJUST,
+		       FIELD_PREP(CIO3PLL_DTC_VREG_ADJUST,
+				  atcphy->fuses.cio3pll_dtc_vreg_adjust));
+	} else {
+		mask32(regs + CIO3PLL_DTC_VREG, CIO3PLL_DTC_VREG_ADJUST,
+		       FIELD_PREP(CIO3PLL_DTC_VREG_ADJUST,
+				  atcphy->fuses.cio3pll_dtc_vreg_adjust));
+		mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+		       FIELD_PREP(AUS_VREG_TRIM,
+				  atcphy->fuses.aus_cmn_shm_vreg_trim));
+	}
+
+	/* AUSPLL fuses */
+	mask32(regs + AUSPLL_DCO_EFUSE_SPARE, AUSPLL_RODCO_ENCAP_EFUSE,
+	       FIELD_PREP(AUSPLL_RODCO_ENCAP_EFUSE,
+			  atcphy->fuses.auspll_rodco_encap));
+	mask32(regs + AUSPLL_DCO_EFUSE_SPARE, AUSPLL_RODCO_BIAS_ADJUST_EFUSE,
+	       FIELD_PREP(AUSPLL_RODCO_BIAS_ADJUST_EFUSE,
+			  atcphy->fuses.auspll_rodco_bias_adjust));
+	mask32(regs + AUSPLL_FRACN_CAN, AUSPLL_DLL_START_CAPCODE,
+	       FIELD_PREP(AUSPLL_DLL_START_CAPCODE,
+			  atcphy->fuses.auspll_fracn_dll_start_capcode));
+	mask32(regs + AUSPLL_CLKOUT_DTC_VREG, AUSPLL_DTC_VREG_ADJUST,
+	       FIELD_PREP(AUSPLL_DTC_VREG_ADJUST,
+			  atcphy->fuses.auspll_dtc_vreg_adjust));
+
+	/* TODO: is this actually required again? */
+	mask32(regs + AUS_COMMON_SHIM_BLK_VREG, AUS_VREG_TRIM,
+	       FIELD_PREP(AUS_VREG_TRIM, atcphy->fuses.aus_cmn_shm_vreg_trim));
+}
+
+static int atcphy_cio_power_off(struct apple_atcphy *atcphy)
+{
+	u32 reg;
+	int ret;
+
+	/* enable all reset lines */
+	core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N);
+	core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N);
+	core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN);
+	core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N);
+
+	// TODO: why clear? is this SLEEP_N? or do we enable some power management here?
+	core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG);
+	ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+				 !(reg & ATCPHY_POWER_SLEEP_BIG), 100, 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "failed to sleep atcphy \"big\"\n");
+		return ret;
+	}
+
+	core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL);
+	ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+				 !(reg & ATCPHY_POWER_SLEEP_SMALL), 100,
+				 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "failed to sleep atcphy \"small\"\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int atcphy_cio_power_on(struct apple_atcphy *atcphy)
+{
+	u32 reg;
+	int ret;
+
+	core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_RESET_N);
+
+	// TODO: why set?! see above
+	core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_SMALL);
+	ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+				 reg & ATCPHY_POWER_SLEEP_SMALL, 100, 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "failed to wakeup atcphy \"small\"\n");
+		return ret;
+	}
+
+	core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_SLEEP_BIG);
+	ret = readl_poll_timeout(atcphy->regs.core + ATCPHY_POWER_STAT, reg,
+				 reg & ATCPHY_POWER_SLEEP_BIG, 100, 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "failed to wakeup atcphy \"big\"\n");
+		return ret;
+	}
+
+	core_clear32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_CLAMP_EN);
+	core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_APB_RESET_N);
+
+	return 0;
+}
+
+static void atcphy_configure_lanes(struct apple_atcphy *atcphy,
+				   enum atcphy_mode mode)
+{
+	const struct atcphy_mode_configuration *mode_cfg;
+
+	if (atcphy->swap_lanes)
+		mode_cfg = &atcphy_modes[mode].swapped;
+	else
+		mode_cfg = &atcphy_modes[mode].normal;
+
+	trace_atcphy_configure_lanes(mode, mode_cfg);
+
+	if (mode_cfg->set_swap)
+		core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+	else
+		core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+
+	if (mode_cfg->dp_lane[0]) {
+		core_set32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+			   LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV);
+		core_clear32(atcphy,
+			     LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+			     LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ);
+	}
+	if (mode_cfg->dp_lane[1]) {
+		core_set32(atcphy, LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+			   LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV);
+		core_clear32(atcphy,
+			     LN1_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
+			     LN_AUSPMA_RX_TOP_PMAFSM_PCS_REQ);
+	}
+
+	core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX0,
+		    FIELD_PREP(ACIOPHY_LANE_MODE_RX0, mode_cfg->lane_mode[0]));
+	core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX0,
+		    FIELD_PREP(ACIOPHY_LANE_MODE_TX0, mode_cfg->lane_mode[0]));
+	core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_RX1,
+		    FIELD_PREP(ACIOPHY_LANE_MODE_RX1, mode_cfg->lane_mode[1]));
+	core_mask32(atcphy, ACIOPHY_LANE_MODE, ACIOPHY_LANE_MODE_TX1,
+		    FIELD_PREP(ACIOPHY_LANE_MODE_TX1, mode_cfg->lane_mode[1]));
+	core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_PROTOCOL,
+		    FIELD_PREP(ACIOPHY_CROSSBAR_PROTOCOL, mode_cfg->crossbar));
+
+	core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+		    FIELD_PREP(ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+			       mode_cfg->crossbar_dp_single_pma));
+	if (mode_cfg->crossbar_dp_both_pma)
+		core_set32(atcphy, ACIOPHY_CROSSBAR,
+			   ACIOPHY_CROSSBAR_DP_BOTH_PMA);
+	else
+		core_clear32(atcphy, ACIOPHY_CROSSBAR,
+			     ACIOPHY_CROSSBAR_DP_BOTH_PMA);
+}
+
+static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy)
+{
+	int ret;
+	u32 reg;
+
+	if (readl_relaxed(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ) &
+	    PIPEHANDLER_LOCK_EN)
+		dev_warn(atcphy->dev, "pipehandler already locked\n");
+
+	set32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ,
+	      PIPEHANDLER_LOCK_EN);
+
+	ret = readl_poll_timeout(atcphy->regs.pipehandler +
+					 PIPEHANDLER_LOCK_ACK,
+				 reg, reg & PIPEHANDLER_LOCK_EN, 1000, 1000000);
+	if (ret) {
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ, 1);
+		dev_err(atcphy->dev,
+			"pipehandler lock not acked, this type-c port is probably dead until the next reboot.\n");
+	}
+
+	return ret;
+}
+
+static int atcphy_pipehandler_unlock(struct apple_atcphy *atcphy)
+{
+	int ret;
+	u32 reg;
+
+	clear32(atcphy->regs.pipehandler + PIPEHANDLER_LOCK_REQ,
+		PIPEHANDLER_LOCK_EN);
+	ret = readl_poll_timeout(
+		atcphy->regs.pipehandler + PIPEHANDLER_LOCK_ACK, reg,
+		!(reg & PIPEHANDLER_LOCK_EN), 1000, 1000000);
+	if (ret)
+		dev_err(atcphy->dev,
+			"pipehandler lock release not acked, this type-c port is probably dead until the next reboot.\n");
+
+	return ret;
+}
+
+static int atcphy_configure_pipehandler(struct apple_atcphy *atcphy,
+					enum atcphy_pipehandler_state state)
+{
+	int ret;
+	u32 reg;
+
+	if (atcphy->pipehandler_state == state)
+		return 0;
+
+	clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE_VALUES,
+		14); // TODO: why 14?
+	set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+	      PIPEHANDLER_OVERRIDE_RXVALID | PIPEHANDLER_OVERRIDE_RXDETECT);
+
+	ret = atcphy_pipehandler_lock(atcphy);
+	if (ret)
+		return ret;
+
+	switch (state) {
+	case ATCPHY_PIPEHANDLER_STATE_USB3:
+		core_set32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG0,
+			   ACIOPHY_TOP_BIST_PHY_CFG0_LN0_RESET_N);
+		core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG,
+			   ACIOPHY_TOP_BIST_OV_CFG_LN0_RESET_N_OV);
+		ret = readl_poll_timeout(
+			atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+			!(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 100, 100000);
+		if (ret)
+			dev_warn(
+				atcphy->dev,
+				"timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n");
+
+			// TODO: macOS does this but this breaks waiting for
+			//       ACIOPHY_TOP_PHY_STAT_LN0_UNK0 then for some reason :/
+			//       this is probably status reset which clears the ln0
+			//       ready status but then the ready status never comes
+			//       up again
+#if 0
+		core_set32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL,
+			   ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE);
+		core_clear32(atcphy, ACIOPHY_TOP_BIST_READ_CTRL,
+			     ACIOPHY_TOP_BIST_READ_CTRL_LN0_PHY_STATUS_RE);
+#endif
+		core_mask32(atcphy, ACIOPHY_TOP_BIST_PHY_CFG1,
+			    ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN,
+			    FIELD_PREP(ACIOPHY_TOP_BIST_PHY_CFG1_LN0_PWR_DOWN,
+				       3));
+		core_set32(atcphy, ACIOPHY_TOP_BIST_OV_CFG,
+			   ACIOPHY_TOP_BIST_OV_CFG_LN0_PWR_DOWN_OV);
+		core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+			   ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN);
+		core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+			   ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN);
+		writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_CIOPHY_CFG1);
+
+		ret = readl_poll_timeout(
+			atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+			(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK0), 100, 100000);
+		if (ret)
+			dev_warn(
+				atcphy->dev,
+				"timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK0\n");
+
+		ret = readl_poll_timeout(
+			atcphy->regs.core + ACIOPHY_TOP_PHY_STAT, reg,
+			!(reg & ACIOPHY_TOP_PHY_STAT_LN0_UNK23), 100, 100000);
+		if (ret)
+			dev_warn(
+				atcphy->dev,
+				"timed out waiting for ACIOPHY_TOP_PHY_STAT_LN0_UNK23\n");
+
+		writel(0, atcphy->regs.core + ACIOPHY_TOP_BIST_OV_CFG);
+		core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+			   ACIOPHY_TOP_BIST_CIOPHY_CFG1_CLK_EN);
+		core_set32(atcphy, ACIOPHY_TOP_BIST_CIOPHY_CFG1,
+			   ACIOPHY_TOP_BIST_CIOPHY_CFG1_BIST_EN);
+
+		/* switch dwc3's superspeed PHY to the real physical PHY */
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+			PIPEHANDLER_CLK_SELECT);
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+			PIPEHANDLER_MUX_MODE);
+		mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+		       PIPEHANDLER_CLK_SELECT,
+		       FIELD_PREP(PIPEHANDLER_CLK_SELECT,
+				  PIPEHANDLER_CLK_USB3PHY));
+		mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+		       PIPEHANDLER_MUX_MODE,
+		       FIELD_PREP(PIPEHANDLER_MUX_MODE,
+				  PIPEHANDLER_MUX_MODE_USB3PHY));
+
+		/* use real rx detect/valid values again */
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+			PIPEHANDLER_OVERRIDE_RXVALID |
+				PIPEHANDLER_OVERRIDE_RXDETECT);
+		break;
+	default:
+		dev_warn(
+			atcphy->dev,
+			"unknown mode in pipehandler_configure: %d, switching to safe state\n",
+			state);
+		fallthrough;
+	case ATCPHY_PIPEHANDLER_STATE_USB2:
+		/* switch dwc3's superspeed PHY back to the dummy (and also USB4 PHY?) */
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+			PIPEHANDLER_CLK_SELECT);
+		clear32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+			PIPEHANDLER_MUX_MODE);
+		mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+		       PIPEHANDLER_CLK_SELECT,
+		       FIELD_PREP(PIPEHANDLER_CLK_SELECT,
+				  PIPEHANDLER_CLK_DUMMY_PHY));
+		mask32(atcphy->regs.pipehandler + PIPEHANDLER_MUX_CTRL,
+		       PIPEHANDLER_MUX_MODE,
+		       FIELD_PREP(PIPEHANDLER_MUX_MODE,
+				  PIPEHANDLER_MUX_MODE_DUMMY_PHY));
+
+		/* keep ignoring rx detect and valid values from the USB3/4 PHY? */
+		set32(atcphy->regs.pipehandler + PIPEHANDLER_OVERRIDE,
+		      PIPEHANDLER_OVERRIDE_RXVALID |
+			      PIPEHANDLER_OVERRIDE_RXDETECT);
+		break;
+	}
+
+	ret = atcphy_pipehandler_unlock(atcphy);
+	if (ret)
+		return ret;
+
+	// TODO: macos seems to always clear it for USB3 - what about USB2/4?
+	clear32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE,
+		PIPEHANDLER_NONSELECTED_NATIVE_RESET);
+
+	// TODO: why? without this superspeed devices sometimes come up as highspeed
+	msleep(500);
+
+	atcphy->pipehandler_state = state;
+
+	return 0;
+}
+
+static void atcphy_enable_dp_aux(struct apple_atcphy *atcphy)
+{
+	core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		   DPTXPHY_PMA_LANE_RESET_N);
+	core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		   DPTXPHY_PMA_LANE_RESET_N_OV);
+
+	core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		    DPRX_PCLK_SELECT, FIELD_PREP(DPRX_PCLK_SELECT, 1));
+	core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		   DPRX_PCLK_ENABLE);
+
+	core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		    DPTX_PCLK1_SELECT, FIELD_PREP(DPTX_PCLK1_SELECT, 1));
+	core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		   DPTX_PCLK1_ENABLE);
+
+	core_mask32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		    DPTX_PCLK2_SELECT, FIELD_PREP(DPTX_PCLK2_SELECT, 1));
+	core_set32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		   DPTX_PCLK2_ENABLE);
+
+	core_set32(atcphy, ACIOPHY_PLL_COMMON_CTRL,
+		   ACIOPHY_PLL_WAIT_FOR_CMN_READY_BEFORE_RESET_EXIT);
+
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_SML_IN);
+	udelay(2);
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_SLEEP_B_BIG_IN);
+	udelay(2);
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN);
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+		LPDPTX_TXTERM_CODEMSB);
+	mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_TXTERM_CODE,
+	       FIELD_PREP(LPDPTX_TXTERM_CODE, 0x16));
+
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_LDO_CTRL, 0x1c00);
+	mask32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1,
+	       LPDPTX_CFG_PMA_PHYS_ADJ, FIELD_PREP(LPDPTX_CFG_PMA_PHYS_ADJ, 5));
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG1,
+	      LPDPTX_CFG_PMA_PHYS_ADJ_OV);
+
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN,
+		LPDPTX_MARGIN_RCAL_RXOFFSET_EN);
+
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+		LPDPTX_BLK_AUX_CTRL_PWRDN);
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_SHM_CFG_BLK_AUX_CTRL_REG0,
+	      LPDPTX_CFG_PMA_AUX_SEL_LF_DATA);
+	mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+	       LPDPTX_BLK_AUX_RXOFFSET, FIELD_PREP(LPDPTX_BLK_AUX_RXOFFSET, 3));
+
+	mask32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_MARGIN,
+	       LPDPTX_AUX_MARGIN_RCAL_TXSWING,
+	       FIELD_PREP(LPDPTX_AUX_MARGIN_RCAL_TXSWING, 12));
+
+	atcphy->dp_link_rate = -1;
+}
+
+static void atcphy_disable_dp_aux(struct apple_atcphy *atcphy)
+{
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_PWN_DOWN);
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CFG_BLK_AUX_CTRL,
+	      LPDPTX_BLK_AUX_CTRL_PWRDN);
+	set32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL, LPDPTX_AUX_CLAMP_EN);
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+		LPDPTX_SLEEP_B_SML_IN);
+	udelay(2);
+	clear32(atcphy->regs.lpdptx + LPDPTX_AUX_CONTROL,
+		LPDPTX_SLEEP_B_BIG_IN);
+	udelay(2);
+
+	// TODO: maybe?
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DPTXPHY_PMA_LANE_RESET_N);
+	// _OV?
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DPRX_PCLK_ENABLE);
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DPTX_PCLK1_ENABLE);
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DPTX_PCLK2_ENABLE);
+
+	// clear 0x1000000 / BIT(24) maybe
+	// writel(0x1830630, atcphy->regs.core + 0x1028);
+}
+
+static int
+atcphy_dp_configure_lane(struct apple_atcphy *atcphy, unsigned int lane,
+			 const struct atcphy_dp_link_rate_configuration *cfg)
+{
+	void __iomem *tx_shm, *rx_shm, *rx_top;
+
+	switch (lane) {
+	case 0:
+		tx_shm = atcphy->regs.core + LN0_AUSPMA_TX_SHM;
+		rx_shm = atcphy->regs.core + LN0_AUSPMA_RX_SHM;
+		rx_top = atcphy->regs.core + LN0_AUSPMA_RX_TOP;
+		break;
+	case 1:
+		tx_shm = atcphy->regs.core + LN1_AUSPMA_TX_SHM;
+		rx_shm = atcphy->regs.core + LN1_AUSPMA_RX_SHM;
+		rx_top = atcphy->regs.core + LN1_AUSPMA_RX_TOP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_SML_OV);
+	udelay(2);
+
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK, LN_LDOCLK_EN_BIG_OV);
+	udelay(2);
+
+	if (cfg->bypass_txa_ldoclk) {
+		set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+		      LN_LDOCLK_BYPASS_SML);
+		set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+		      LN_LDOCLK_BYPASS_SML_OV);
+		udelay(2);
+
+		set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+		      LN_LDOCLK_BYPASS_BIG);
+		set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+		      LN_LDOCLK_BYPASS_BIG_OV);
+		udelay(2);
+	} else {
+		clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+			LN_LDOCLK_BYPASS_SML);
+		clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+			LN_LDOCLK_BYPASS_SML_OV);
+		udelay(2);
+
+		clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+			LN_LDOCLK_BYPASS_BIG);
+		clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_LDOCLK,
+			LN_LDOCLK_BYPASS_BIG_OV);
+		udelay(2);
+	}
+
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+	      LN_BYTECLK_RESET_SYNC_SEL_OV);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+	      LN_BYTECLK_RESET_SYNC_EN);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+	      LN_BYTECLK_RESET_SYNC_EN_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+		LN_BYTECLK_RESET_SYNC_CLR);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG0,
+	      LN_BYTECLK_RESET_SYNC_CLR_OV);
+
+	if (cfg->txa_div2_en)
+		set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+		      LN_TXA_DIV2_EN);
+	else
+		clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+			LN_TXA_DIV2_EN);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_EN_OV);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_CLK_EN_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1, LN_TXA_DIV2_RESET);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_CFG_MAIN_REG1,
+	      LN_TXA_DIV2_RESET_OV);
+
+	mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE,
+	       FIELD_PREP(LN_TXA_CAL_CTRL_BASE, 0xf));
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_BASE_OV);
+	mask32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL,
+	       FIELD_PREP(LN_TXA_CAL_CTRL, 0x3f)); // TODO: 3f?
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_CAL_CTRL_OV);
+
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG2, LN_TXA_MARGIN_2R_OV);
+
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_2R_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_POST_4R_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_2R_OV);
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG3, LN_TXA_MARGIN_PRE_4R_OV);
+
+	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ);
+	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
+		LN_RX_DIV20_RESET_N);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
+	      LN_RX_DIV20_RESET_N_OV);
+	udelay(2);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1, LN_RX_DIV20_RESET_N);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+	      LN_TX_BYTECLK_RESET_SYNC_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+	      LN_TX_BYTECLK_RESET_SYNC_EN_OV);
+
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE,
+	       FIELD_PREP(LN_TX_CAL_CODE, 6)); // TODO 6?
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_TX_CAL_CODE_OV);
+
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+	       LN_TX_CLK_DLY_CTRL_TAPGEN,
+	       FIELD_PREP(LN_TX_CLK_DLY_CTRL_TAPGEN, 3));
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16, LN_RXTERM_EN_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_TEST_EN_OV);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_TEST_RXLPBKDT_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_TEST_RXLPBKDT_EN_OV);
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	       LN_VREF_LPBKIN_DATA, FIELD_PREP(LN_VREF_LPBKIN_DATA, 3));
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BIAS_SEL,
+	       FIELD_PREP(LN_VREF_BIAS_SEL, 2));
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_BIAS_SEL_OV);
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	       LN_VREF_ADJUST_GRAY, FIELD_PREP(LN_VREF_ADJUST_GRAY, 0x18));
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_ADJUST_GRAY_OV);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_EN_OV);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_BOOST_EN_OV);
+	udelay(2);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22, LN_VREF_BOOST_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_VREF_CTRL22,
+	      LN_VREF_BOOST_EN_OV);
+	udelay(2);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PRE_EN_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_TX_PST1_EN_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_PBIAS_EN_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16,
+		LN_RXTERM_PULLUP_LEAK_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_SAVOS_CTRL16,
+	      LN_RXTERM_PULLUP_LEAK_EN_OV);
+
+	set32(rx_top + LN_AUSPMA_RX_TOP_TJ_CFG_RX_TXMODE, LN_RX_TXMODE);
+
+	if (cfg->txa_div2_en)
+		set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+		      LN_TX_CLK_DIV2_EN);
+	else
+		clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+			LN_TX_CLK_DIV2_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+	      LN_TX_CLK_DIV2_EN_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+		LN_TX_CLK_DIV2_RST);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19,
+	      LN_TX_CLK_DIV2_RST_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12, LN_TX_HRCLK_SEL_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_LSB_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17, LN_TX_MARGIN_P1_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17,
+		LN_TX_MARGIN_P1_LSB);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL17,
+	      LN_TX_MARGIN_P1_LSB_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_CODE_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_P1_LSB_CODE_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_MARGIN_PRE_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+		LN_TX_MARGIN_PRE_LSB);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+	      LN_TX_MARGIN_PRE_LSB_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_LSB_CODE);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18,
+	      LN_TX_PRE_LSB_CODE_OV);
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TX_CTRL18, LN_TX_PRE_CODE_OV);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_SML_EN_OV);
+	udelay(2);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL11, LN_DTVREG_BIG_EN_OV);
+	udelay(2);
+
+	mask32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL10, LN_DTVREG_ADJUST,
+	       FIELD_PREP(LN_DTVREG_ADJUST, 0xa));
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL13, LN_DTVREG_ADJUST_OV);
+	udelay(2);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_TERM_CTRL19, LN_TX_EN_OV);
+	udelay(2);
+
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_CTLE_CTRL0, LN_TX_CLK_EN_OV);
+
+	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+		LN_TX_BYTECLK_RESET_SYNC_CLR);
+	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_DFE_CTRL12,
+	      LN_TX_BYTECLK_RESET_SYNC_CLR_OV);
+
+	return 0;
+}
+
+static int atcphy_auspll_apb_command(struct apple_atcphy *atcphy, u32 command)
+{
+	int ret;
+	u32 reg;
+
+	reg = readl(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE);
+	reg &= ~AUSPLL_APB_CMD_OVERRIDE_CMD;
+	reg |= FIELD_PREP(AUSPLL_APB_CMD_OVERRIDE_CMD, command);
+	reg |= AUSPLL_APB_CMD_OVERRIDE_REQ;
+	reg |= AUSPLL_APB_CMD_OVERRIDE_UNK28;
+	writel(reg, atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE);
+
+	ret = readl_poll_timeout(atcphy->regs.core + AUSPLL_APB_CMD_OVERRIDE,
+				 reg, (reg & AUSPLL_APB_CMD_OVERRIDE_ACK), 100,
+				 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "AUSPLL APB command was not acked.\n");
+		return ret;
+	}
+
+	core_clear32(atcphy, AUSPLL_APB_CMD_OVERRIDE,
+		     AUSPLL_APB_CMD_OVERRIDE_REQ);
+
+	return 0;
+}
+
+static int atcphy_dp_configure(struct apple_atcphy *atcphy,
+			       enum atcphy_dp_link_rate lr)
+{
+	const struct atcphy_dp_link_rate_configuration *cfg = &dp_lr_config[lr];
+	const struct atcphy_mode_configuration *mode_cfg;
+	int ret;
+	u32 reg;
+
+	trace_atcphy_dp_configure(atcphy, lr);
+
+	if (atcphy->dp_link_rate == lr)
+		return 0;
+
+	if (atcphy->swap_lanes)
+		mode_cfg = &atcphy_modes[atcphy->mode].swapped;
+	else
+		mode_cfg = &atcphy_modes[atcphy->mode].normal;
+
+	core_clear32(atcphy, AUSPLL_FREQ_CFG, AUSPLL_FREQ_REFCLK);
+
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FREQ_COUNT_TARGET,
+		    FIELD_PREP(AUSPLL_FD_FREQ_COUNT_TARGET,
+			       cfg->freqinit_count_target));
+	core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_FBDIVN_HALF);
+	core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_REV_DIVN);
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_MAN,
+		    FIELD_PREP(AUSPLL_FD_KI_MAN, 8));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KI_EXP,
+		    FIELD_PREP(AUSPLL_FD_KI_EXP, 3));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_MAN,
+		    FIELD_PREP(AUSPLL_FD_KP_MAN, 8));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KP_EXP,
+		    FIELD_PREP(AUSPLL_FD_KP_EXP, 7));
+	core_clear32(atcphy, AUSPLL_FREQ_DESC_A, AUSPLL_FD_KPKI_SCALE_HBW);
+
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_DEN,
+		    FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_DEN,
+			       cfg->fbdivn_frac_den));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_B, AUSPLL_FD_FBDIVN_FRAC_NUM,
+		    FIELD_PREP(AUSPLL_FD_FBDIVN_FRAC_NUM,
+			       cfg->fbdivn_frac_num));
+
+	core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_STEP);
+	core_clear32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_SDM_SSC_EN);
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_PCLK_DIV_SEL,
+		    FIELD_PREP(AUSPLL_FD_PCLK_DIV_SEL, cfg->pclk_div_sel));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFSDM_DIV,
+		    FIELD_PREP(AUSPLL_FD_LFSDM_DIV, 1));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_LFCLK_CTRL,
+		    FIELD_PREP(AUSPLL_FD_LFCLK_CTRL, cfg->lfclk_ctrl));
+	core_mask32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_OP_DIVN,
+		    FIELD_PREP(AUSPLL_FD_VCLK_OP_DIVN, cfg->vclk_op_divn));
+	core_set32(atcphy, AUSPLL_FREQ_DESC_C, AUSPLL_FD_VCLK_PRE_DIVN);
+
+	core_mask32(atcphy, AUSPLL_CLKOUT_DIV, AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI,
+		    FIELD_PREP(AUSPLL_CLKOUT_PLLA_REFBUFCLK_DI, 7));
+
+	if (cfg->plla_clkout_vreg_bypass)
+		core_set32(atcphy, AUSPLL_CLKOUT_DTC_VREG,
+			   AUSPLL_DTC_VREG_BYPASS);
+	else
+		core_clear32(atcphy, AUSPLL_CLKOUT_DTC_VREG,
+			     AUSPLL_DTC_VREG_BYPASS);
+
+	core_set32(atcphy, AUSPLL_BGR, AUSPLL_BGR_CTRL_AVAIL);
+
+	core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+		   AUSPLL_CLKOUT_MASTER_PCLK_DRVR_EN);
+	core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+		   AUSPLL_CLKOUT_MASTER_PCLK2_DRVR_EN);
+	core_set32(atcphy, AUSPLL_CLKOUT_MASTER,
+		   AUSPLL_CLKOUT_MASTER_REFBUFCLK_DRVR_EN);
+
+	ret = atcphy_auspll_apb_command(atcphy, 0);
+	if (ret)
+		return ret;
+
+	ret = readl_poll_timeout(atcphy->regs.core + ACIOPHY_DP_PCLK_STAT, reg,
+				 (reg & ACIOPHY_AUSPLL_LOCK), 100, 100000);
+	if (ret) {
+		dev_err(atcphy->dev, "ACIOPHY_DP_PCLK did not lock.\n");
+		return ret;
+	}
+
+	ret = atcphy_auspll_apb_command(atcphy, 0x2800);
+	if (ret)
+		return ret;
+
+	if (mode_cfg->dp_lane[0]) {
+		ret = atcphy_dp_configure_lane(atcphy, 0, cfg);
+		if (ret)
+			return ret;
+	}
+
+	if (mode_cfg->dp_lane[1]) {
+		ret = atcphy_dp_configure_lane(atcphy, 1, cfg);
+		if (ret)
+			return ret;
+	}
+
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DP_PMA_BYTECLK_RESET);
+	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
+		     DP_MAC_DIV20_CLK_SEL);
+
+	atcphy->dp_link_rate = lr;
+	return 0;
+}
+
+static int atcphy_cio_configure(struct apple_atcphy *atcphy,
+				enum atcphy_mode mode)
+{
+	int ret;
+
+	BUG_ON(!mutex_is_locked(&atcphy->lock));
+
+	ret = atcphy_cio_power_on(atcphy);
+	if (ret)
+		return ret;
+
+	atcphy_setup_pll_fuses(atcphy);
+	atcphy_apply_tunables(atcphy, mode);
+
+	// TODO: without this sometimes device aren't recognized but no idea what it does
+	// ACIOPHY_PLL_TOP_BLK_AUSPLL_PCTL_FSM_CTRL1.APB_REQ_OV_SEL = 255
+	core_set32(atcphy, 0x1014, 255 << 13);
+	core_set32(atcphy, AUSPLL_APB_CMD_OVERRIDE,
+		   AUSPLL_APB_CMD_OVERRIDE_UNK28);
+
+	writel(0x10000cef, atcphy->regs.core + 0x8); // ACIOPHY_CFG0
+	writel(0x15570cff, atcphy->regs.core + 0x1b0); // ACIOPHY_SLEEP_CTRL
+	writel(0x11833fef, atcphy->regs.core + 0x8); // ACIOPHY_CFG0
+
+	/* enable clocks and configure lanes */
+	core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_PCLK_EN);
+	core_set32(atcphy, CIO3PLL_CLK_CTRL, CIO3PLL_CLK_REFCLK_EN);
+	atcphy_configure_lanes(atcphy, mode);
+
+	/* take the USB3 PHY out of reset */
+	core_set32(atcphy, ATCPHY_POWER_CTRL, ATCPHY_POWER_PHY_RESET_N);
+
+	/* setup AUX channel if DP altmode is requested */
+	if (atcphy_modes[mode].enable_dp_aux)
+		atcphy_enable_dp_aux(atcphy);
+
+	atcphy->mode = mode;
+	return 0;
+}
+
+static int atcphy_usb3_power_on(struct phy *phy)
+{
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+	enum atcphy_pipehandler_state state;
+	int ret = 0;
+
+	/*
+	 * Both usb role switch and mux set work will be running concurrently.
+	 * Make sure atcphy_mux_set_work is done bringing up ATCPHY before
+	 * trying to switch dwc3 to the correct PHY.
+	 */
+	mutex_lock(&atcphy->lock);
+	if (atcphy->mode != atcphy->target_mode) {
+		reinit_completion(&atcphy->atcphy_online_event);
+		mutex_unlock(&atcphy->lock);
+		wait_for_completion_timeout(&atcphy->atcphy_online_event,
+					msecs_to_jiffies(1000));
+		mutex_lock(&atcphy->lock);
+	}
+
+	if (atcphy->mode != atcphy->target_mode) {
+		dev_err(atcphy->dev, "ATCPHY did not come up; won't allow dwc3 to come up.\n");
+		return -EINVAL;
+	}
+
+	atcphy->dwc3_online = true;
+	state = atcphy_modes[atcphy->mode].pipehandler_state;
+	switch (state) {
+	case ATCPHY_PIPEHANDLER_STATE_USB2:
+	case ATCPHY_PIPEHANDLER_STATE_USB3:
+		ret = atcphy_configure_pipehandler(atcphy, state);
+		break;
+
+	case ATCPHY_PIPEHANDLER_STATE_INVALID:
+	default:
+		dev_warn(atcphy->dev, "Invalid state %d in usb3_set_phy\n",
+			 state);
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static int atcphy_usb3_power_off(struct phy *phy)
+{
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+	mutex_lock(&atcphy->lock);
+
+	atcphy_configure_pipehandler(atcphy, ATCPHY_PIPEHANDLER_STATE_USB2);
+
+	atcphy->dwc3_online = false;
+	complete(&atcphy->dwc3_shutdown_event);
+
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static const struct phy_ops apple_atc_usb3_phy_ops = {
+	.owner = THIS_MODULE,
+	.power_on = atcphy_usb3_power_on,
+	.power_off = atcphy_usb3_power_off,
+};
+
+static int atcphy_usb2_power_on(struct phy *phy)
+{
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+	mutex_lock(&atcphy->lock);
+
+	/* take the PHY out of its low power state */
+	clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ);
+	udelay(10);
+
+	/* reset the PHY for good measure */
+	clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+	set32(atcphy->regs.usb2phy + USB2PHY_CTL,
+	      USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+	udelay(10);
+	set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+	clear32(atcphy->regs.usb2phy + USB2PHY_CTL,
+		USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+
+	set32(atcphy->regs.usb2phy + USB2PHY_SIG,
+	      USB2PHY_SIG_VBUSDET_FORCE_VAL | USB2PHY_SIG_VBUSDET_FORCE_EN |
+		      USB2PHY_SIG_VBUSVLDEXT_FORCE_VAL |
+		      USB2PHY_SIG_VBUSVLDEXT_FORCE_EN);
+
+	/* enable the dummy PHY for the SS lanes */
+	set32(atcphy->regs.pipehandler + PIPEHANDLER_NONSELECTED_OVERRIDE,
+	      PIPEHANDLER_DUMMY_PHY_EN);
+
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static int atcphy_usb2_power_off(struct phy *phy)
+{
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+	mutex_lock(&atcphy->lock);
+
+	/* reset the PHY before transitioning to low power mode */
+	clear32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_APB_RESET_N);
+	set32(atcphy->regs.usb2phy + USB2PHY_CTL,
+	      USB2PHY_CTL_RESET | USB2PHY_CTL_PORT_RESET);
+
+	/* switch the PHY to low power mode */
+	set32(atcphy->regs.usb2phy + USB2PHY_CTL, USB2PHY_CTL_SIDDQ);
+
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static int atcphy_usb2_set_mode(struct phy *phy, enum phy_mode mode,
+				int submode)
+{
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+	int ret;
+
+	mutex_lock(&atcphy->lock);
+
+	switch (mode) {
+	case PHY_MODE_USB_HOST:
+	case PHY_MODE_USB_HOST_LS:
+	case PHY_MODE_USB_HOST_FS:
+	case PHY_MODE_USB_HOST_HS:
+	case PHY_MODE_USB_HOST_SS:
+		set32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST);
+		set32(atcphy->regs.usb2phy + USB2PHY_USBCTL,
+		      USB2PHY_USBCTL_HOST_EN);
+		ret = 0;
+		break;
+
+	case PHY_MODE_USB_DEVICE:
+	case PHY_MODE_USB_DEVICE_LS:
+	case PHY_MODE_USB_DEVICE_FS:
+	case PHY_MODE_USB_DEVICE_HS:
+	case PHY_MODE_USB_DEVICE_SS:
+		clear32(atcphy->regs.usb2phy + USB2PHY_SIG, USB2PHY_SIG_HOST);
+		clear32(atcphy->regs.usb2phy + USB2PHY_USBCTL,
+			USB2PHY_USBCTL_HOST_EN);
+		ret = 0;
+		break;
+
+	default:
+		dev_err(atcphy->dev, "Unknown mode for usb2 phy: %d\n", mode);
+		ret = -EINVAL;
+	}
+
+	mutex_unlock(&atcphy->lock);
+	return ret;
+}
+
+static const struct phy_ops apple_atc_usb2_phy_ops = {
+	.owner = THIS_MODULE,
+	.set_mode = atcphy_usb2_set_mode,
+	/*
+	 * This PHY is always matched with a dwc3 controller. Currently,
+	 * first dwc3 initializes the PHY and then soft-resets itself and
+	 * then finally powers on the PHY. This should be reasonable.
+	 * Annoyingly, the dwc3 soft reset is never completed when the USB2 PHY
+	 * is powered off so we have to pretend that these two are actually
+	 * init/exit here to ensure the PHY is powered on and out of reset
+	 * early enough.
+	 */
+	.init = atcphy_usb2_power_on,
+	.exit = atcphy_usb2_power_off,
+};
+
+static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode,
+				 int submode)
+{
+	/* nothing to do here since the setup already happened in mux_set */
+	if (mode == PHY_MODE_DP && submode == 0)
+		return 0;
+	return -EINVAL;
+}
+
+static int atcphy_dpphy_validate(struct phy *phy, enum phy_mode mode,
+				 int submode, union phy_configure_opts *opts_)
+{
+	struct phy_configure_opts_dp *opts = &opts_->dp;
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+	if (mode != PHY_MODE_DP)
+		return -EINVAL;
+	if (submode != 0)
+		return -EINVAL;
+
+	switch (atcphy->mode) {
+	case APPLE_ATCPHY_MODE_USB3_DP:
+		opts->lanes = 2;
+		break;
+	case APPLE_ATCPHY_MODE_DP:
+		opts->lanes = 4;
+		break;
+	default:
+		opts->lanes = 0;
+	}
+
+	opts->link_rate = 8100;
+
+	for (int i = 0; i < 4; ++i) {
+		opts->voltage[i] = 3;
+		opts->pre[i] = 3;
+	}
+
+	return 0;
+}
+
+static int atcphy_dpphy_configure(struct phy *phy,
+				  union phy_configure_opts *opts_)
+{
+	struct phy_configure_opts_dp *opts = &opts_->dp;
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+	enum atcphy_dp_link_rate link_rate;
+	int ret = 0;
+
+	/* might be possibly but we don't know how */
+	if (opts->set_voltages)
+		return -EINVAL;
+
+	/* TODO? or maybe just ack since this mux_set should've done this? */
+	if (opts->set_lanes)
+		return -EINVAL;
+
+	if (opts->set_rate) {
+		switch (opts->link_rate) {
+		case 1620:
+			link_rate = ATCPHY_DP_LINK_RATE_RBR;
+			break;
+		case 2700:
+			link_rate = ATCPHY_DP_LINK_RATE_HBR;
+			break;
+		case 5400:
+			link_rate = ATCPHY_DP_LINK_RATE_HBR2;
+			break;
+		case 8100:
+			link_rate = ATCPHY_DP_LINK_RATE_HBR3;
+			break;
+		case 0:
+			// TODO: disable!
+			return 0;
+			break;
+		default:
+			dev_err(atcphy->dev, "Unsupported link rate: %d\n",
+				opts->link_rate);
+			return -EINVAL;
+		}
+
+		mutex_lock(&atcphy->lock);
+		ret = atcphy_dp_configure(atcphy, link_rate);
+		mutex_unlock(&atcphy->lock);
+	}
+
+	return ret;
+}
+
+static const struct phy_ops apple_atc_dp_phy_ops = {
+	.owner = THIS_MODULE,
+	.configure = atcphy_dpphy_configure,
+	.validate = atcphy_dpphy_validate,
+	.set_mode = atcphy_dpphy_set_mode,
+};
+
+static struct phy *atcphy_xlate(struct device *dev,
+				const struct of_phandle_args *args)
+{
+	struct apple_atcphy *atcphy = dev_get_drvdata(dev);
+
+	switch (args->args[0]) {
+	case PHY_TYPE_USB2:
+		return atcphy->phy_usb2;
+	case PHY_TYPE_USB3:
+		return atcphy->phy_usb3;
+	case PHY_TYPE_DP:
+		return atcphy->phy_dp;
+	}
+	return ERR_PTR(-ENODEV);
+}
+
+static int atcphy_probe_phy(struct apple_atcphy *atcphy)
+{
+	atcphy->phy_usb2 =
+		devm_phy_create(atcphy->dev, NULL, &apple_atc_usb2_phy_ops);
+	if (IS_ERR(atcphy->phy_usb2))
+		return PTR_ERR(atcphy->phy_usb2);
+	phy_set_drvdata(atcphy->phy_usb2, atcphy);
+
+	atcphy->phy_usb3 =
+		devm_phy_create(atcphy->dev, NULL, &apple_atc_usb3_phy_ops);
+	if (IS_ERR(atcphy->phy_usb3))
+		return PTR_ERR(atcphy->phy_usb3);
+	phy_set_drvdata(atcphy->phy_usb3, atcphy);
+
+	atcphy->phy_dp =
+		devm_phy_create(atcphy->dev, NULL, &apple_atc_dp_phy_ops);
+	if (IS_ERR(atcphy->phy_dp))
+		return PTR_ERR(atcphy->phy_dp);
+	phy_set_drvdata(atcphy->phy_dp, atcphy);
+
+	atcphy->phy_provider =
+		devm_of_phy_provider_register(atcphy->dev, atcphy_xlate);
+	if (IS_ERR(atcphy->phy_provider))
+		return PTR_ERR(atcphy->phy_provider);
+
+	return 0;
+}
+
+static int atcphy_dwc3_reset_assert(struct reset_controller_dev *rcdev,
+				    unsigned long id)
+{
+	struct apple_atcphy *atcphy = rcdev_to_apple_atcphy(rcdev);
+
+	clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+		PIPEHANDLER_AON_GEN_DWC3_RESET_N);
+	set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+	      PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN);
+
+	return 0;
+}
+
+static int atcphy_dwc3_reset_deassert(struct reset_controller_dev *rcdev,
+				      unsigned long id)
+{
+	struct apple_atcphy *atcphy = rcdev_to_apple_atcphy(rcdev);
+
+	clear32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+		PIPEHANDLER_AON_GEN_DWC3_FORCE_CLAMP_EN);
+	set32(atcphy->regs.pipehandler + PIPEHANDLER_AON_GEN,
+	      PIPEHANDLER_AON_GEN_DWC3_RESET_N);
+
+	return 0;
+}
+
+const struct reset_control_ops atcphy_dwc3_reset_ops = {
+	.assert = atcphy_dwc3_reset_assert,
+	.deassert = atcphy_dwc3_reset_deassert,
+};
+
+static int atcphy_reset_xlate(struct reset_controller_dev *rcdev,
+			      const struct of_phandle_args *reset_spec)
+{
+	return 0;
+}
+
+static int atcphy_probe_rcdev(struct apple_atcphy *atcphy)
+{
+	atcphy->rcdev.owner = THIS_MODULE;
+	atcphy->rcdev.nr_resets = 1;
+	atcphy->rcdev.ops = &atcphy_dwc3_reset_ops;
+	atcphy->rcdev.of_node = atcphy->dev->of_node;
+	atcphy->rcdev.of_reset_n_cells = 0;
+	atcphy->rcdev.of_xlate = atcphy_reset_xlate;
+
+	return devm_reset_controller_register(atcphy->dev, &atcphy->rcdev);
+}
+
+static int atcphy_sw_set(struct typec_switch_dev *sw,
+			 enum typec_orientation orientation)
+{
+	struct apple_atcphy *atcphy = typec_switch_get_drvdata(sw);
+
+	trace_atcphy_sw_set(orientation);
+
+	mutex_lock(&atcphy->lock);
+	switch (orientation) {
+	case TYPEC_ORIENTATION_NONE:
+		break;
+	case TYPEC_ORIENTATION_NORMAL:
+		atcphy->swap_lanes = false;
+		break;
+	case TYPEC_ORIENTATION_REVERSE:
+		atcphy->swap_lanes = true;
+		break;
+	}
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static int atcphy_probe_switch(struct apple_atcphy *atcphy)
+{
+	struct typec_switch_desc sw_desc = {
+		.drvdata = atcphy,
+		.fwnode = atcphy->dev->fwnode,
+		.set = atcphy_sw_set,
+	};
+
+	return PTR_ERR_OR_ZERO(typec_switch_register(atcphy->dev, &sw_desc));
+}
+
+static void atcphy_mux_set_work(struct work_struct *work)
+{
+	struct apple_atcphy *atcphy = container_of(work, struct apple_atcphy, mux_set_work);
+
+	mutex_lock(&atcphy->lock);
+	/*
+	 * If we're transitiong to TYPEC_STATE_SAFE dwc3 will have gotten
+	 * a usb-role-switch event to ROLE_NONE which is deferred to a work
+	 * queue. dwc3 will try to switch the pipehandler mux to USB2 and
+	 * we have to make sure that has happened before we disable ATCPHY.
+	 * If we instead disable ATCPHY first dwc3 will get stuck and the
+	 * port won't work anymore until a full SoC reset.
+	 * We're guaranteed that no other role switch event will be generated
+	 * before we return because the mux_set callback runs in the same
+	 * thread that generates these. We can thus unlock the mutex, wait
+	 * for dwc3_shutdown_event from the usb3 phy's power_off callback after
+	 * it has taken the mutex and the lock again.
+	 */
+	if (atcphy->dwc3_online && atcphy->target_mode == APPLE_ATCPHY_MODE_OFF) {
+		reinit_completion(&atcphy->dwc3_shutdown_event);
+		mutex_unlock(&atcphy->lock);
+		wait_for_completion_timeout(&atcphy->dwc3_shutdown_event,
+					    msecs_to_jiffies(1000));
+		mutex_lock(&atcphy->lock);
+		WARN_ON(atcphy->dwc3_online);
+	}
+
+	switch (atcphy->target_mode) {
+	case APPLE_ATCPHY_MODE_DP:
+	case APPLE_ATCPHY_MODE_USB3_DP:
+	case APPLE_ATCPHY_MODE_USB3:
+	case APPLE_ATCPHY_MODE_USB4:
+		atcphy_cio_configure(atcphy, atcphy->target_mode);
+		break;
+	default:
+		dev_warn(atcphy->dev, "Unknown mode %d in atcphy_mux_set\n",
+			 atcphy->target_mode);
+		fallthrough;
+	case APPLE_ATCPHY_MODE_USB2:
+	case APPLE_ATCPHY_MODE_OFF:
+		atcphy->mode = APPLE_ATCPHY_MODE_OFF;
+		atcphy_disable_dp_aux(atcphy);
+		atcphy_cio_power_off(atcphy);
+	}
+
+	complete(&atcphy->atcphy_online_event);
+	mutex_unlock(&atcphy->lock);
+}
+
+static int atcphy_mux_set(struct typec_mux_dev *mux,
+			  struct typec_mux_state *state)
+{
+	struct apple_atcphy *atcphy = typec_mux_get_drvdata(mux);
+
+	// TODO: 
+	flush_work(&atcphy->mux_set_work);
+
+	mutex_lock(&atcphy->lock);
+	trace_atcphy_mux_set(state);
+
+	if (state->mode == TYPEC_STATE_SAFE) {
+		atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+	} else if (state->mode == TYPEC_STATE_USB) {
+		atcphy->target_mode = APPLE_ATCPHY_MODE_USB3;
+	} else if (state->alt && state->alt->svid == USB_TYPEC_DP_SID) {
+		switch (state->mode) {
+		case TYPEC_DP_STATE_C:
+		case TYPEC_DP_STATE_E:
+			atcphy->target_mode = APPLE_ATCPHY_MODE_DP;
+			break;
+		case TYPEC_DP_STATE_D:
+			atcphy->target_mode = APPLE_ATCPHY_MODE_USB3_DP;
+			break;
+		default:
+			dev_err(atcphy->dev,
+				"Unsupported DP pin assignment: 0x%lx.\n",
+				state->mode);
+			atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+		}
+	} else if (state->alt && state->alt->svid == USB_TYPEC_TBT_SID) {
+		dev_err(atcphy->dev, "USB4/TBT mode is not supported yet.\n");
+		atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+	} else if (state->alt) {
+		dev_err(atcphy->dev, "Unknown alternate mode SVID: 0x%x\n",
+			state->alt->svid);
+		atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+	} else {
+		dev_err(atcphy->dev, "Unknown mode: 0x%lx\n", state->mode);
+		atcphy->target_mode = APPLE_ATCPHY_MODE_OFF;
+	}
+
+	if (atcphy->mode != atcphy->target_mode)
+		WARN_ON(!schedule_work(&atcphy->mux_set_work));
+
+	mutex_unlock(&atcphy->lock);
+
+	return 0;
+}
+
+static int atcphy_probe_mux(struct apple_atcphy *atcphy)
+{
+	struct typec_mux_desc mux_desc = {
+		.drvdata = atcphy,
+		.fwnode = atcphy->dev->fwnode,
+		.set = atcphy_mux_set,
+	};
+
+	return PTR_ERR_OR_ZERO(typec_mux_register(atcphy->dev, &mux_desc));
+}
+
+static int atcphy_parse_legacy_tunable(struct apple_atcphy *atcphy,
+				       struct atcphy_tunable *tunable,
+				       const char *name)
+{
+	struct property *prop;
+	const __le32 *p = NULL;
+	int i;
+
+#if 0
+	WARN_TAINT_ONCE(1, TAINT_FIRMWARE_WORKAROUND,
+			"parsing legacy tunable; please update m1n1");
+#endif
+
+	prop = of_find_property(atcphy->np, name, NULL);
+	if (!prop) {
+		dev_err(atcphy->dev, "tunable %s not found\n", name);
+		return -ENOENT;
+	}
+
+	if (prop->length % (3 * sizeof(u32)))
+		return -EINVAL;
+
+	tunable->sz = prop->length / (3 * sizeof(u32));
+	tunable->values = devm_kcalloc(atcphy->dev, tunable->sz,
+				       sizeof(*tunable->values), GFP_KERNEL);
+	if (!tunable->values)
+		return -ENOMEM;
+
+	for (i = 0; i < tunable->sz; ++i) {
+		p = of_prop_next_u32(prop, p, &tunable->values[i].offset);
+		p = of_prop_next_u32(prop, p, &tunable->values[i].mask);
+		p = of_prop_next_u32(prop, p, &tunable->values[i].value);
+	}
+
+	trace_atcphy_parsed_tunable(name, tunable);
+
+	return 0;
+}
+
+static int atcphy_parse_new_tunable(struct apple_atcphy *atcphy,
+				    struct atcphy_tunable *tunable,
+				    const char *name)
+{
+	struct property *prop;
+	u64 *fdt_tunable;
+	int ret, i;
+
+	prop = of_find_property(atcphy->np, name, NULL);
+	if (!prop) {
+		dev_err(atcphy->dev, "tunable %s not found\n", name);
+		return -ENOENT;
+	}
+
+	if (prop->length % (4 * sizeof(u64)))
+		return -EINVAL;
+
+	fdt_tunable = kzalloc(prop->length, GFP_KERNEL);
+	if (!fdt_tunable)
+		return -ENOMEM;
+
+	tunable->sz = prop->length / (4 * sizeof(u64));
+	ret = of_property_read_variable_u64_array(atcphy->np, name, fdt_tunable,
+						  tunable->sz, tunable->sz);
+	if (ret < 0)
+		goto err_free_fdt;
+
+	tunable->values = devm_kcalloc(atcphy->dev, tunable->sz,
+				       sizeof(*tunable->values), GFP_KERNEL);
+	if (!tunable->values) {
+		ret = -ENOMEM;
+		goto err_free_fdt;
+	}
+
+	for (i = 0; i < tunable->sz; ++i) {
+		u32 offset, size, mask, value;
+
+		offset = fdt_tunable[4 * i];
+		size = fdt_tunable[4 * i + 1];
+		mask = fdt_tunable[4 * i + 2];
+		value = fdt_tunable[4 * i + 3];
+
+		if (offset > U32_MAX || size != 4 || mask > U32_MAX ||
+		    value > U32_MAX) {
+			ret = -EINVAL;
+			goto err_free_values;
+		}
+
+		tunable->values[i].offset = offset;
+		tunable->values[i].mask = mask;
+		tunable->values[i].value = value;
+	}
+
+	trace_atcphy_parsed_tunable(name, tunable);
+	kfree(fdt_tunable);
+
+	BUG_ON(1);
+	return 0;
+
+err_free_values:
+	devm_kfree(atcphy->dev, tunable->values);
+err_free_fdt:
+	kfree(fdt_tunable);
+	return ret;
+}
+
+static int atcphy_parse_tunable(struct apple_atcphy *atcphy,
+				struct atcphy_tunable *tunable,
+				const char *name)
+{
+	int ret;
+
+	if (!of_find_property(atcphy->np, name, NULL)) {
+		dev_err(atcphy->dev, "tunable %s not found\n", name);
+		return -ENOENT;
+	}
+
+	ret = atcphy_parse_new_tunable(atcphy, tunable, name);
+	if (ret)
+		ret = atcphy_parse_legacy_tunable(atcphy, tunable, name);
+
+	return ret;
+}
+
+static int atcphy_load_tunables(struct apple_atcphy *atcphy)
+{
+	int ret;
+
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.axi2af,
+				   "apple,tunable-axi2af");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.common,
+				   "apple,tunable-common");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb3[0],
+				   "apple,tunable-lane0-usb");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb3[1],
+				   "apple,tunable-lane1-usb");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb4[0],
+				   "apple,tunable-lane0-cio");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy, &atcphy->tunables.lane_usb4[1],
+				   "apple,tunable-lane1-cio");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy,
+				   &atcphy->tunables.lane_displayport[0],
+				   "apple,tunable-lane0-dp");
+	if (ret)
+		return ret;
+	ret = atcphy_parse_tunable(atcphy,
+				   &atcphy->tunables.lane_displayport[1],
+				   "apple,tunable-lane1-dp");
+	if (ret)
+		return ret;
+
+	return 0;
+}
+
+static int atcphy_load_fuses(struct apple_atcphy *atcphy)
+{
+	int ret;
+
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "aus_cmn_shm_vreg_trim",
+		&atcphy->fuses.aus_cmn_shm_vreg_trim);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "auspll_rodco_encap",
+		&atcphy->fuses.auspll_rodco_encap);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "auspll_rodco_bias_adjust",
+		&atcphy->fuses.auspll_rodco_bias_adjust);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "auspll_fracn_dll_start_capcode",
+		&atcphy->fuses.auspll_fracn_dll_start_capcode);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "auspll_dtc_vreg_adjust",
+		&atcphy->fuses.auspll_dtc_vreg_adjust);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "cio3pll_dco_coarsebin0",
+		&atcphy->fuses.cio3pll_dco_coarsebin[0]);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "cio3pll_dco_coarsebin1",
+		&atcphy->fuses.cio3pll_dco_coarsebin[1]);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "cio3pll_dll_start_capcode",
+		&atcphy->fuses.cio3pll_dll_start_capcode[0]);
+	if (ret)
+		return ret;
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "cio3pll_dtc_vreg_adjust",
+		&atcphy->fuses.cio3pll_dtc_vreg_adjust);
+	if (ret)
+		return ret;
+
+	/* 
+	 * Only one of the two t8103 PHYs requires the following additional fuse
+	 * and a slighly different configuration sequence if it's present.
+	 * The other t8103 instance and all t6000 instances don't which means
+	 * we must not fail here in case the fuse isn't present.
+	 */
+	ret = nvmem_cell_read_variable_le_u32(
+		atcphy->dev, "cio3pll_dll_start_capcode_workaround",
+		&atcphy->fuses.cio3pll_dll_start_capcode[1]);
+	switch (ret) {
+	case 0:
+		atcphy->quirks.t8103_cio3pll_workaround = true;
+		break;
+	case -ENOENT:
+		atcphy->quirks.t8103_cio3pll_workaround = false;
+		break;
+	default:
+		return ret;
+	}
+
+	atcphy->fuses.present = true;
+
+	trace_atcphy_fuses(atcphy);
+	return 0;
+}
+
+static int atcphy_probe(struct platform_device *pdev)
+{
+	struct apple_atcphy *atcphy;
+	struct device *dev = &pdev->dev;
+	int ret;
+
+	atcphy = devm_kzalloc(&pdev->dev, sizeof(*atcphy), GFP_KERNEL);
+	if (!atcphy)
+		return -ENOMEM;
+
+	atcphy->dev = dev;
+	atcphy->np = dev->of_node;
+	platform_set_drvdata(pdev, atcphy);
+
+	mutex_init(&atcphy->lock);
+	init_completion(&atcphy->dwc3_shutdown_event);
+	init_completion(&atcphy->atcphy_online_event);
+	INIT_WORK(&atcphy->mux_set_work, atcphy_mux_set_work);
+
+	atcphy->regs.core = devm_platform_ioremap_resource_byname(pdev, "core");
+	if (IS_ERR(atcphy->regs.core))
+		return PTR_ERR(atcphy->regs.core);
+	atcphy->regs.lpdptx =
+		devm_platform_ioremap_resource_byname(pdev, "lpdptx");
+	if (IS_ERR(atcphy->regs.lpdptx))
+		return PTR_ERR(atcphy->regs.lpdptx);
+	atcphy->regs.axi2af =
+		devm_platform_ioremap_resource_byname(pdev, "axi2af");
+	if (IS_ERR(atcphy->regs.axi2af))
+		return PTR_ERR(atcphy->regs.axi2af);
+	atcphy->regs.usb2phy =
+		devm_platform_ioremap_resource_byname(pdev, "usb2phy");
+	if (IS_ERR(atcphy->regs.usb2phy))
+		return PTR_ERR(atcphy->regs.usb2phy);
+	atcphy->regs.pipehandler =
+		devm_platform_ioremap_resource_byname(pdev, "pipehandler");
+	if (IS_ERR(atcphy->regs.pipehandler))
+		return PTR_ERR(atcphy->regs.pipehandler);
+
+	if (of_property_read_bool(dev->of_node, "nvmem-cells")) {
+		ret = atcphy_load_fuses(atcphy);
+		if (ret)
+			return ret;
+	}
+
+	ret = atcphy_load_tunables(atcphy);
+	if (ret)
+		return ret;
+
+	atcphy->mode = APPLE_ATCPHY_MODE_OFF;
+	atcphy->pipehandler_state = ATCPHY_PIPEHANDLER_STATE_INVALID;
+
+	ret = atcphy_probe_rcdev(atcphy);
+	if (ret)
+		return ret;
+	ret = atcphy_probe_mux(atcphy);
+	if (ret)
+		return ret;
+	ret = atcphy_probe_switch(atcphy);
+	if (ret)
+		return ret;
+	return atcphy_probe_phy(atcphy);
+}
+
+static const struct of_device_id atcphy_match[] = {
+	{
+		.compatible = "apple,t8103-atcphy",
+	},
+	{
+		.compatible = "apple,t6000-atcphy",
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, atcphy_match);
+
+static struct platform_driver atcphy_driver = {
+	.driver = {
+		.name = "phy-apple-atc",
+		.of_match_table = atcphy_match,
+	},
+	.probe = atcphy_probe,
+};
+
+module_platform_driver(atcphy_driver);
+
+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
+MODULE_DESCRIPTION("Apple Type-C PHY driver");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/apple/atc.h b/drivers/phy/apple/atc.h
new file mode 100644
index 00000000000000..6b020953965fa5
--- /dev/null
+++ b/drivers/phy/apple/atc.h
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#ifndef APPLE_PHY_ATC_H
+#define APPLE_PHY_ATC_H 1
+
+#include <linux/mutex.h>
+#include <linux/phy/phy.h>
+#include <linux/usb/typec_mux.h>
+#include <linux/reset-controller.h>
+#include <linux/types.h>
+#include <linux/usb/typec.h>
+#include <linux/usb/typec_altmode.h>
+#include <linux/usb/typec_dp.h>
+#include <linux/usb/typec_tbt.h>
+#include <linux/workqueue.h>
+
+enum atcphy_dp_link_rate {
+	ATCPHY_DP_LINK_RATE_RBR,
+	ATCPHY_DP_LINK_RATE_HBR,
+	ATCPHY_DP_LINK_RATE_HBR2,
+	ATCPHY_DP_LINK_RATE_HBR3,
+};
+
+enum atcphy_pipehandler_state {
+	ATCPHY_PIPEHANDLER_STATE_INVALID,
+	ATCPHY_PIPEHANDLER_STATE_USB2,
+	ATCPHY_PIPEHANDLER_STATE_USB3,
+};
+
+enum atcphy_mode {
+	APPLE_ATCPHY_MODE_OFF,
+	APPLE_ATCPHY_MODE_USB2,
+	APPLE_ATCPHY_MODE_USB3,
+	APPLE_ATCPHY_MODE_USB3_DP,
+	APPLE_ATCPHY_MODE_USB4,
+	APPLE_ATCPHY_MODE_DP,
+};
+
+struct atcphy_dp_link_rate_configuration {
+	u16 freqinit_count_target;
+	u16 fbdivn_frac_den;
+	u16 fbdivn_frac_num;
+	u16 pclk_div_sel;
+	u8 lfclk_ctrl;
+	u8 vclk_op_divn;
+	bool plla_clkout_vreg_bypass;
+	bool bypass_txa_ldoclk;
+	bool txa_div2_en;
+};
+
+struct atcphy_mode_configuration {
+	u32 crossbar;
+	u32 crossbar_dp_single_pma;
+	bool crossbar_dp_both_pma;
+	u32 lane_mode[2];
+	bool dp_lane[2];
+	bool set_swap;
+};
+
+struct atcphy_tunable {
+	size_t sz;
+	struct {
+		u32 offset;
+		u32 mask;
+		u32 value;
+	} * values;
+};
+
+struct apple_atcphy {
+	struct device_node *np;
+	struct device *dev;
+
+	struct {
+		unsigned int t8103_cio3pll_workaround : 1;
+	} quirks;
+
+	/* calibration fuse values */
+	struct {
+		bool present;
+		u32 aus_cmn_shm_vreg_trim;
+		u32 auspll_rodco_encap;
+		u32 auspll_rodco_bias_adjust;
+		u32 auspll_fracn_dll_start_capcode;
+		u32 auspll_dtc_vreg_adjust;
+		u32 cio3pll_dco_coarsebin[2];
+		u32 cio3pll_dll_start_capcode[2];
+		u32 cio3pll_dtc_vreg_adjust;
+	} fuses;
+
+	/* tunables provided by firmware through the device tree */
+	struct {
+		struct atcphy_tunable axi2af;
+		struct atcphy_tunable common;
+		struct atcphy_tunable lane_usb3[2];
+		struct atcphy_tunable lane_displayport[2];
+		struct atcphy_tunable lane_usb4[2];
+	} tunables;
+
+	bool usb3_power_on;
+	bool swap_lanes;
+
+	enum atcphy_mode mode;
+	int dp_link_rate;
+
+	struct {
+		void __iomem *core;
+		void __iomem *axi2af;
+		void __iomem *usb2phy;
+		void __iomem *pipehandler;
+		void __iomem *lpdptx;
+	} regs;
+
+	struct phy *phy_usb2;
+	struct phy *phy_usb3;
+	struct phy *phy_dp;
+	struct phy_provider *phy_provider;
+	struct reset_controller_dev rcdev;
+	struct typec_switch *sw;
+	struct typec_mux *mux;
+
+	bool dwc3_online;
+	struct completion dwc3_shutdown_event;
+	struct completion atcphy_online_event;
+
+	enum atcphy_pipehandler_state pipehandler_state;
+
+	struct mutex lock;
+
+	struct work_struct mux_set_work;
+	enum atcphy_mode target_mode;
+};
+
+#endif
diff --git a/drivers/phy/apple/trace.c b/drivers/phy/apple/trace.c
new file mode 100644
index 00000000000000..a82dc089f6caa8
--- /dev/null
+++ b/drivers/phy/apple/trace.c
@@ -0,0 +1,4 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include "trace.h"
+
diff --git a/drivers/phy/apple/trace.h b/drivers/phy/apple/trace.h
new file mode 100644
index 00000000000000..c4c21c84e8917c
--- /dev/null
+++ b/drivers/phy/apple/trace.h
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple Type-C PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM appletypecphy
+
+#if !defined(_APPLETYPECPHY_TRACE_H_) || defined(TRACE_HEADER_MULTI_READ)
+#define _APPLETYPECPHY_TRACE_H_
+
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+#include "atc.h"
+
+#define show_dp_lr(lr)                                  \
+	__print_symbolic(lr, { ATCPHY_DP_LINK_RATE_RBR, "RBR" }, \
+			 { ATCPHY_DP_LINK_RATE_HBR, "HBR" },          \
+			 { ATCPHY_DP_LINK_RATE_HBR2, "HBR2" },          \
+			 { ATCPHY_DP_LINK_RATE_HBR3, "HBR3" })
+
+#define show_sw_orientation(orientation)                                  \
+	__print_symbolic(orientation, { TYPEC_ORIENTATION_NONE, "none" }, \
+			 { TYPEC_ORIENTATION_NORMAL, "normal" },          \
+			 { TYPEC_ORIENTATION_REVERSE, "reverse" })
+
+TRACE_EVENT(atcphy_sw_set, TP_PROTO(enum typec_orientation orientation),
+	    TP_ARGS(orientation),
+
+	    TP_STRUCT__entry(__field(enum typec_orientation, orientation)),
+
+	    TP_fast_assign(__entry->orientation = orientation;),
+
+	    TP_printk("orientation: %s",
+		      show_sw_orientation(__entry->orientation)));
+
+#define show_mux_state(state)                                                 \
+	__print_symbolic(state.mode, { TYPEC_STATE_SAFE, "USB Safe State" }, \
+			 { TYPEC_STATE_USB, "USB" })
+
+#define show_atcphy_mode(mode)                                      \
+	__print_symbolic(mode, { APPLE_ATCPHY_MODE_OFF, "off" },    \
+			 { APPLE_ATCPHY_MODE_USB2, "USB2" },        \
+			 { APPLE_ATCPHY_MODE_USB3, "USB3" },        \
+			 { APPLE_ATCPHY_MODE_USB3_DP, "DP + USB" }, \
+			 { APPLE_ATCPHY_MODE_USB4, "USB4" },        \
+			 { APPLE_ATCPHY_MODE_DP, "DP-only" })
+
+TRACE_EVENT(atcphy_usb3_set_mode,
+	    TP_PROTO(struct apple_atcphy *atcphy, enum phy_mode mode,
+		     int submode),
+	    TP_ARGS(atcphy, mode, submode),
+
+	    TP_STRUCT__entry(__field(enum atcphy_mode, mode)
+					     __field(enum phy_mode, phy_mode)
+						     __field(int, submode)),
+
+	    TP_fast_assign(__entry->mode = atcphy->mode;
+			   __entry->phy_mode = mode;
+			   __entry->submode = submode;),
+
+	    TP_printk("mode: %s, phy_mode: %d, submode: %d",
+		      show_atcphy_mode(__entry->mode), __entry->phy_mode,
+		      __entry->submode));
+
+TRACE_EVENT(
+	atcphy_configure_lanes,
+	TP_PROTO(enum atcphy_mode mode,
+		 const struct atcphy_mode_configuration *cfg),
+	TP_ARGS(mode, cfg),
+
+	TP_STRUCT__entry(__field(enum atcphy_mode, mode) __field_struct(
+		struct atcphy_mode_configuration, cfg)),
+
+	TP_fast_assign(__entry->mode = mode; __entry->cfg = *cfg;),
+
+	TP_printk(
+		"mode: %s, crossbar: 0x%02x, lanes: {0x%02x, 0x%02x}, swap: %d",
+		show_atcphy_mode(__entry->mode), __entry->cfg.crossbar,
+		__entry->cfg.lane_mode[0], __entry->cfg.lane_mode[1],
+		__entry->cfg.set_swap));
+
+TRACE_EVENT(atcphy_mux_set, TP_PROTO(struct typec_mux_state *state),
+	    TP_ARGS(state),
+
+	    TP_STRUCT__entry(__field_struct(struct typec_mux_state, state)),
+
+	    TP_fast_assign(__entry->state = *state;),
+
+	    TP_printk("state: %s", show_mux_state(__entry->state)));
+
+TRACE_EVENT(atcphy_parsed_tunable,
+	    TP_PROTO(const char *name, struct atcphy_tunable *tunable),
+	    TP_ARGS(name, tunable),
+
+	    TP_STRUCT__entry(__field(const char *, name)
+				     __field(size_t, sz)),
+
+	    TP_fast_assign(__entry->name = name; __entry->sz = tunable->sz;),
+
+	    TP_printk("%s with %zu entries", __entry->name,
+		      __entry->sz));
+
+TRACE_EVENT(
+	atcphy_fuses, TP_PROTO(struct apple_atcphy *atcphy), TP_ARGS(atcphy),
+	TP_STRUCT__entry(__field(struct apple_atcphy *, atcphy)),
+	TP_fast_assign(__entry->atcphy = atcphy;),
+	TP_printk(
+		"aus_cmn_shm_vreg_trim: 0x%02x; auspll_rodco_encap: 0x%02x; auspll_rodco_bias_adjust: 0x%02x; auspll_fracn_dll_start_capcode: 0x%02x; auspll_dtc_vreg_adjust: 0x%02x; cio3pll_dco_coarsebin: 0x%02x, 0x%02x; cio3pll_dll_start_capcode: 0x%02x, 0x%02x; cio3pll_dtc_vreg_adjust: 0x%02x",
+		__entry->atcphy->fuses.aus_cmn_shm_vreg_trim,
+		__entry->atcphy->fuses.auspll_rodco_encap,
+		__entry->atcphy->fuses.auspll_rodco_bias_adjust,
+		__entry->atcphy->fuses.auspll_fracn_dll_start_capcode,
+		__entry->atcphy->fuses.auspll_dtc_vreg_adjust,
+		__entry->atcphy->fuses.cio3pll_dco_coarsebin[0],
+		__entry->atcphy->fuses.cio3pll_dco_coarsebin[1],
+		__entry->atcphy->fuses.cio3pll_dll_start_capcode[0],
+		__entry->atcphy->fuses.cio3pll_dll_start_capcode[1],
+		__entry->atcphy->fuses.cio3pll_dtc_vreg_adjust));
+
+
+
+TRACE_EVENT(atcphy_dp_configure,
+	    TP_PROTO(struct apple_atcphy *atcphy, enum atcphy_dp_link_rate lr),
+	    TP_ARGS(atcphy, lr),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(atcphy->dev))
+				     __field(enum atcphy_dp_link_rate, lr)),
+
+	    TP_fast_assign(__assign_str(devname, dev_name(atcphy->dev));
+	     		  __entry->lr = lr;),
+
+	    TP_printk("%s: link rate: %s", __get_str(devname),
+		      show_dp_lr(__entry->lr)));
+
+#endif /* _APPLETYPECPHY_TRACE_H_ */
+
+/* This part must be outside protection */
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+#include <trace/define_trace.h>

From 01fbfa6e641327464ed082a0aba90c1680442c57 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:11:14 +0100
Subject: [PATCH 0361/1027] usb: typec: tipd: Clear interrupts first

Right now the interrupt handler first reads all updated status registers
and only then clears the interrupts. It's possible that a duplicate
interrupt for a changed register or plug state comes in after the
interrupts have been processed but before they have been cleared:

* plug is inserted, TPS_REG_INT_PLUG_EVENT is set
* TPS_REG_INT_EVENT1 is read
* tps6598x_handle_plug_event() has run and registered the plug
* plug is removed again, TPS_REG_INT_PLUG_EVENT is set (again)
* TPS_REG_INT_CLEAR1 is written, TPS_REG_INT_PLUG_EVENT is cleared

We then have no plug connected and no pending interrupt but the tipd
core still thinks there is a plug. It's possible to trigger this with
e.g. a slightly broken Type-C to USB A converter.

Fix this by first clearing the interrupts and only then reading the
updated registers.

Fixes: 45188f27b3d0 ("usb: typec: tipd: Add support for Apple CD321X")
Fixes: 0a4c005bd171 ("usb: typec: driver for TI TPS6598x USB Power Delivery controllers")
Cc: stable <stable@kernel.org>
Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/usb/typec/tipd/core.c | 24 +++++++++++-------------
 1 file changed, 11 insertions(+), 13 deletions(-)

diff --git a/drivers/usb/typec/tipd/core.c b/drivers/usb/typec/tipd/core.c
index dd51a25480bfb9..287980dc303c96 100644
--- a/drivers/usb/typec/tipd/core.c
+++ b/drivers/usb/typec/tipd/core.c
@@ -550,24 +550,23 @@ static irqreturn_t cd321x_interrupt(int irq, void *data)
 	if (!event)
 		goto err_unlock;
 
+	tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
+
 	if (!tps6598x_read_status(tps, &status))
-		goto err_clear_ints;
+		goto err_unlock;
 
 	if (event & APPLE_CD_REG_INT_POWER_STATUS_UPDATE)
 		if (!tps6598x_read_power_status(tps))
-			goto err_clear_ints;
+			goto err_unlock;
 
 	if (event & APPLE_CD_REG_INT_DATA_STATUS_UPDATE)
 		if (!tps6598x_read_data_status(tps))
-			goto err_clear_ints;
+			goto err_unlock;
 
 	/* Handle plug insert or removal */
 	if (event & APPLE_CD_REG_INT_PLUG_EVENT)
 		tps6598x_handle_plug_event(tps, status);
 
-err_clear_ints:
-	tps6598x_write64(tps, TPS_REG_INT_CLEAR1, event);
-
 err_unlock:
 	mutex_unlock(&tps->lock);
 
@@ -673,25 +672,24 @@ static irqreturn_t tps6598x_interrupt(int irq, void *data)
 	if (!(event1[0] | event1[1] | event2[0] | event2[1]))
 		goto err_unlock;
 
+	tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len);
+	tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len);
+
 	if (!tps6598x_read_status(tps, &status))
-		goto err_clear_ints;
+		goto err_unlock;
 
 	if ((event1[0] | event2[0]) & TPS_REG_INT_POWER_STATUS_UPDATE)
 		if (!tps6598x_read_power_status(tps))
-			goto err_clear_ints;
+			goto err_unlock;
 
 	if ((event1[0] | event2[0]) & TPS_REG_INT_DATA_STATUS_UPDATE)
 		if (!tps6598x_read_data_status(tps))
-			goto err_clear_ints;
+			goto err_unlock;
 
 	/* Handle plug insert or removal */
 	if ((event1[0] | event2[0]) & TPS_REG_INT_PLUG_EVENT)
 		tps6598x_handle_plug_event(tps, status);
 
-err_clear_ints:
-	tps6598x_block_write(tps, TPS_REG_INT_CLEAR1, event1, intev_len);
-	tps6598x_block_write(tps, TPS_REG_INT_CLEAR2, event2, intev_len);
-
 err_unlock:
 	mutex_unlock(&tps->lock);
 

From 82afdf3140fb861cb25a68a38fc5ab366ada2e90 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 5 May 2023 17:40:26 +0200
Subject: [PATCH 0362/1027] phy: apple: Add DP TX phy driver

This driver is found on Apple's Mac mini (M2, 2023) and controls one
output of the main display controller. It is connected to a MCDP 29XX
(public known part is MCDP 2900) DP 1.4 to HDMI 2.0a protocol converter.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/Kconfig  |  11 +
 drivers/phy/apple/Makefile |   3 +
 drivers/phy/apple/dptx.c   | 686 +++++++++++++++++++++++++++++++++++++
 drivers/phy/apple/dptx.h   |  18 +
 4 files changed, 718 insertions(+)
 create mode 100644 drivers/phy/apple/dptx.c
 create mode 100644 drivers/phy/apple/dptx.h

diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig
index 090d8542651f86..d3b9148eaf7e08 100644
--- a/drivers/phy/apple/Kconfig
+++ b/drivers/phy/apple/Kconfig
@@ -9,3 +9,14 @@ config PHY_APPLE_ATC
 	  Enable this to add support for the Apple Type-C PHY, switch
 	  and mux found in Apple SoCs such as the M1.
 	  This driver currently provides support for USB2 and USB3.
+
+config PHY_APPLE_DPTX
+	tristate "Apple DPTX PHY"
+	depends on ARCH_APPLE || COMPILE_TEST
+	default ARCH_APPLE
+	select GENERIC_PHY
+	help
+	  Enable this to add support for the Apple DPTX PHY found on Apple SoCs
+	  such as the M2.
+	  This driver provides support for DisplayPort and is used on the
+	  Mac mini (M2, 2023).
diff --git a/drivers/phy/apple/Makefile b/drivers/phy/apple/Makefile
index af863fa299dc5f..f8900fef11610b 100644
--- a/drivers/phy/apple/Makefile
+++ b/drivers/phy/apple/Makefile
@@ -4,3 +4,6 @@ CFLAGS_trace.o			:= -I$(src)
 obj-$(CONFIG_PHY_APPLE_ATC)		+= phy-apple-atc.o
 phy-apple-atc-y			:= atc.o
 phy-apple-atc-$(CONFIG_TRACING)	+= trace.o
+
+obj-$(CONFIG_PHY_APPLE_DPTX)	+= phy-apple-dptx.o
+phy-apple-dptx-y		+= dptx.o
diff --git a/drivers/phy/apple/dptx.c b/drivers/phy/apple/dptx.c
new file mode 100644
index 00000000000000..4de826964d3b37
--- /dev/null
+++ b/drivers/phy/apple/dptx.c
@@ -0,0 +1,686 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple dptx PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Janne Grunau <j@jannau.net>
+ *
+ * based on drivers/phy/apple/atc.c
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#include "dptx.h"
+
+#include <asm/io.h>
+#include "linux/of.h"
+#include <dt-bindings/phy/phy.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/phy/phy.h>
+#include <linux/phy/phy-dp.h>
+#include <linux/platform_device.h>
+#include <linux/types.h>
+
+#define DPTX_MAX_LANES    4
+#define DPTX_LANE0_OFFSET 0x5000
+#define DPTX_LANE_STRIDE  0x1000
+#define DPTX_LANE_END     (DPTX_LANE0_OFFSET + DPTX_MAX_LANES * DPTX_LANE_STRIDE)
+
+enum apple_dptx_type {
+    DPTX_PHY_T8112,
+    DPTX_PHY_T6020,
+};
+
+struct apple_dptx_phy_hw {
+	enum apple_dptx_type type;
+};
+
+struct apple_dptx_phy {
+	struct device *dev;
+
+	struct apple_dptx_phy_hw hw;
+
+	int dp_link_rate;
+
+	struct {
+		void __iomem *core;
+		void __iomem *dptx;
+	} regs;
+
+	struct phy *phy_dp;
+	struct phy_provider *phy_provider;
+
+	struct mutex lock;
+
+	// TODO: m1n1 port things to clean up
+	u32 active_lanes;
+};
+
+
+static inline void mask32(void __iomem *reg, u32 mask, u32 set)
+{
+	u32 value = readl(reg);
+	value &= ~mask;
+	value |= set;
+	writel(value, reg);
+}
+
+static inline void set32(void __iomem *reg, u32 set)
+{
+	mask32(reg, 0, set);
+}
+
+static inline void clear32(void __iomem *reg, u32 clear)
+{
+	mask32(reg, clear, 0);
+}
+
+
+static int dptx_phy_set_active_lane_count(struct apple_dptx_phy *phy, u32 num_lanes)
+{
+	u32 l, ctrl;
+
+	dev_dbg(phy->dev, "set_active_lane_count(%u)\n", num_lanes);
+
+	if (num_lanes == 3 || num_lanes > DPTX_MAX_LANES)
+		return -1;
+
+	ctrl = readl(phy->regs.dptx + 0x4000);
+	writel(ctrl, phy->regs.dptx + 0x4000);
+
+	for (l = 0; l < num_lanes; l++) {
+		u64 offset = 0x5000 + 0x1000 * l;
+		readl(phy->regs.dptx + offset);
+		writel(0x100, phy->regs.dptx + offset);
+    }
+    for (; l < DPTX_MAX_LANES; l++) {
+        u64 offset = 0x5000 + 0x1000 * l;
+	readl(phy->regs.dptx + offset);
+	writel(0x300, phy->regs.dptx + offset);
+    }
+    for (l = 0; l < num_lanes; l++) {
+        u64 offset = 0x5000 + 0x1000 * l;
+	readl(phy->regs.dptx + offset);
+	writel(0x0, phy->regs.dptx + offset);
+    }
+    for (; l < DPTX_MAX_LANES; l++) {
+        u64 offset = 0x5000 + 0x1000 * l;
+	readl(phy->regs.dptx + offset);
+	writel(0x300, phy->regs.dptx + offset);
+    }
+
+    if (num_lanes > 0) {
+	// clear32(phy->regs.dptx + 0x4000, 0x4000000);
+	ctrl = readl(phy->regs.dptx + 0x4000);
+	ctrl &= ~0x4000000;
+	writel(ctrl, phy->regs.dptx + 0x4000);
+    }
+    phy->active_lanes = num_lanes;
+
+    return 0;
+}
+
+static int dptx_phy_activate(struct apple_dptx_phy *phy, u32 dcp_index)
+{
+	u32 val_2014;
+	u32 val_4008;
+	u32 val_4408;
+
+	dev_dbg(phy->dev, "activate(dcp:%u)\n", dcp_index);
+
+	// MMIO: R.4   0x23c500010 (dptx-phy[1], offset 0x10) = 0x0
+	// MMIO: W.4   0x23c500010 (dptx-phy[1], offset 0x10) = 0x0
+	readl(phy->regs.core + 0x10);
+	writel(dcp_index, phy->regs.core + 0x10);
+
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x444
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x454
+	set32(phy->regs.core + 0x48, 0x010);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x454
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x474
+	set32(phy->regs.core + 0x48, 0x020);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x474
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x434
+	clear32(phy->regs.core + 0x48, 0x040);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x434
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x534
+	set32(phy->regs.core + 0x48, 0x100);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x534
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x734
+	set32(phy->regs.core + 0x48, 0x200);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x734
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x334
+	clear32(phy->regs.core + 0x48, 0x400);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x334
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x335
+	set32(phy->regs.core + 0x48, 0x001);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x335
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x337
+	set32(phy->regs.core + 0x48, 0x002);
+	// MMIO: R.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x337
+	// MMIO: W.4   0x23c500048 (dptx-phy[1], offset 0x48) = 0x333
+	clear32(phy->regs.core + 0x48, 0x004);
+
+	// MMIO: R.4   0x23c542014 (dptx-phy[0], offset 0x2014) = 0x80a0c
+	val_2014 = readl(phy->regs.dptx + 0x2014);
+	// MMIO: W.4   0x23c542014 (dptx-phy[0], offset 0x2014) = 0x300a0c
+	writel((0x30 << 16) | (val_2014 & 0xffff), phy->regs.dptx + 0x2014);
+
+	// MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x644800
+	// MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+	set32(phy->regs.dptx + 0x20b8, 0x010000);
+
+	// MMIO: R.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a2
+	// MMIO: W.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a0
+	clear32(phy->regs.dptx + 0x2220, 0x0000002);
+
+	// MMIO: R.4   0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103003
+	// MMIO: W.4   0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103803
+	set32(phy->regs.dptx + 0x222c, 0x000800);
+	// MMIO: R.4   0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103803
+	// MMIO: W.4   0x23c54222c (dptx-phy[0], offset 0x222c) = 0x103903
+	set32(phy->regs.dptx + 0x222c, 0x000100);
+
+	// MMIO: R.4   0x23c542230 (dptx-phy[0], offset 0x2230) = 0x2308804
+	// MMIO: W.4   0x23c542230 (dptx-phy[0], offset 0x2230) = 0x2208804
+	clear32(phy->regs.dptx + 0x2230, 0x0100000);
+
+	// MMIO: R.4   0x23c542278 (dptx-phy[0], offset 0x2278) = 0x18300811
+	// MMIO: W.4   0x23c542278 (dptx-phy[0], offset 0x2278) = 0x10300811
+	clear32(phy->regs.dptx + 0x2278, 0x08000000);
+
+	// MMIO: R.4   0x23c5422a4 (dptx-phy[0], offset 0x22a4) = 0x1044200
+	// MMIO: W.4   0x23c5422a4 (dptx-phy[0], offset 0x22a4) = 0x1044201
+	set32(phy->regs.dptx + 0x22a4, 0x0000001);
+
+	// MMIO: R.4   0x23c544008 (dptx-phy[0], offset 0x4008) = 0x18030
+	val_4008 = readl(phy->regs.dptx + 0x4008);
+	// MMIO: W.4   0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30030
+	writel((0x6 << 15) | (val_4008 & 0x7fff), phy->regs.dptx + 0x4008);
+	// MMIO: R.4   0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30030
+	// MMIO: W.4   0x23c544008 (dptx-phy[0], offset 0x4008) = 0x30010
+	clear32(phy->regs.dptx + 0x4008, 0x00020);
+
+	// MMIO: R.4   0x23c54420c (dptx-phy[0], offset 0x420c) = 0x88e3
+	// MMIO: W.4   0x23c54420c (dptx-phy[0], offset 0x420c) = 0x88c3
+	clear32(phy->regs.dptx + 0x420c, 0x0020);
+
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x0
+	// MMIO: W.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000
+	set32(phy->regs.dptx + 0x4600, 0x8000000);
+
+	// MMIO: R.4   0x23c545040 (dptx-phy[0], offset 0x5040) = 0x21780
+	// MMIO: W.4   0x23c545040 (dptx-phy[0], offset 0x5040) = 0x221780
+	// MMIO: R.4   0x23c546040 (dptx-phy[0], offset 0x6040) = 0x21780
+	// MMIO: W.4   0x23c546040 (dptx-phy[0], offset 0x6040) = 0x221780
+	// MMIO: R.4   0x23c547040 (dptx-phy[0], offset 0x7040) = 0x21780
+	// MMIO: W.4   0x23c547040 (dptx-phy[0], offset 0x7040) = 0x221780
+	// MMIO: R.4   0x23c548040 (dptx-phy[0], offset 0x8040) = 0x21780
+	// MMIO: W.4   0x23c548040 (dptx-phy[0], offset 0x8040) = 0x221780
+	for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END;
+	     loff += DPTX_LANE_STRIDE)
+		set32(phy->regs.dptx + loff + 0x40, 0x200000);
+
+	// MMIO: R.4   0x23c545040 (dptx-phy[0], offset 0x5040) = 0x221780
+	// MMIO: W.4   0x23c545040 (dptx-phy[0], offset 0x5040) = 0x2a1780
+	// MMIO: R.4   0x23c546040 (dptx-phy[0], offset 0x6040) = 0x221780
+	// MMIO: W.4   0x23c546040 (dptx-phy[0], offset 0x6040) = 0x2a1780
+	// MMIO: R.4   0x23c547040 (dptx-phy[0], offset 0x7040) = 0x221780
+	// MMIO: W.4   0x23c547040 (dptx-phy[0], offset 0x7040) = 0x2a1780
+	// MMIO: R.4   0x23c548040 (dptx-phy[0], offset 0x8040) = 0x221780
+	// MMIO: W.4   0x23c548040 (dptx-phy[0], offset 0x8040) = 0x2a1780
+	for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END;
+	     loff += DPTX_LANE_STRIDE)
+		set32(phy->regs.dptx + loff + 0x40, 0x080000);
+
+	// MMIO: R.4   0x23c545244 (dptx-phy[0], offset 0x5244) = 0x18
+	// MMIO: W.4   0x23c545244 (dptx-phy[0], offset 0x5244) = 0x8
+	// MMIO: R.4   0x23c546244 (dptx-phy[0], offset 0x6244) = 0x18
+	// MMIO: W.4   0x23c546244 (dptx-phy[0], offset 0x6244) = 0x8
+	// MMIO: R.4   0x23c547244 (dptx-phy[0], offset 0x7244) = 0x18
+	// MMIO: W.4   0x23c547244 (dptx-phy[0], offset 0x7244) = 0x8
+	// MMIO: R.4   0x23c548244 (dptx-phy[0], offset 0x8244) = 0x18
+	// MMIO: W.4   0x23c548244 (dptx-phy[0], offset 0x8244) = 0x8
+	for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END;
+	     loff += DPTX_LANE_STRIDE)
+		clear32(phy->regs.dptx + loff + 0x244, 0x10);
+
+	// MMIO: R.4   0x23c542214 (dptx-phy[0], offset 0x2214) = 0x1e0
+	// MMIO: W.4   0x23c542214 (dptx-phy[0], offset 0x2214) = 0x1e1
+	set32(phy->regs.dptx + 0x2214, 0x001);
+
+	// MMIO: R.4   0x23c542224 (dptx-phy[0], offset 0x2224) = 0x20086001
+	// MMIO: W.4   0x23c542224 (dptx-phy[0], offset 0x2224) = 0x20086000
+	clear32(phy->regs.dptx + 0x2224, 0x00000001);
+
+	// MMIO: R.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000
+	// MMIO: W.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002
+	set32(phy->regs.dptx + 0x2200, 0x0002);
+
+	// MMIO: R.4   0x23c541000 (dptx-phy[0], offset 0x1000) = 0xe0000003
+	// MMIO: W.4   0x23c541000 (dptx-phy[0], offset 0x1000) = 0xe0000001
+	clear32(phy->regs.dptx + 0x1000, 0x00000002);
+
+	// MMIO: R.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41
+	// MMIO: W.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+	set32(phy->regs.dptx + 0x4004, 0x08);
+
+	/* TODO: no idea what happens here, supposedly setting/clearing some bits */
+	// MMIO: R.4   0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444
+	readl(phy->regs.dptx + 0x4404);
+	// MMIO: W.4   0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444
+	writel(0x555d444, phy->regs.dptx + 0x4404);
+	// MMIO: R.4   0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444
+	readl(phy->regs.dptx + 0x4404);
+	// MMIO: W.4   0x23c544404 (dptx-phy[0], offset 0x4404) = 0x555d444
+	writel(0x555d444, phy->regs.dptx + 0x4404);
+
+	dptx_phy_set_active_lane_count(phy, 0);
+
+	// MMIO: R.4   0x23c544200 (dptx-phy[0], offset 0x4200) = 0x4002430
+	// MMIO: W.4   0x23c544200 (dptx-phy[0], offset 0x4200) = 0x4002420
+	clear32(phy->regs.dptx + 0x4200, 0x0000010);
+
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000
+	// MMIO: W.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000
+	clear32(phy->regs.dptx + 0x4600, 0x0000001);
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000000
+	// MMIO: W.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000001
+	set32(phy->regs.dptx + 0x4600, 0x0000001);
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000001
+	// MMIO: W.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000003
+	set32(phy->regs.dptx + 0x4600, 0x0000002);
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000043
+	// MMIO: R.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000043
+	// MMIO: W.4   0x23c544600 (dptx-phy[0], offset 0x4600) = 0x8000041
+	/* TODO: read first to check if the previous set(...,0x2) sticked? */
+	readl(phy->regs.dptx + 0x4600);
+	clear32(phy->regs.dptx + 0x4600, 0x0000001);
+
+	// MMIO: R.4   0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482
+	// MMIO: W.4   0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482
+	/* TODO: probably a set32 of an already set bit */
+	val_4408 = readl(phy->regs.dptx + 0x4408);
+	if (val_4408 != 0x482 && val_4408 != 0x483)
+		dev_warn(
+			phy->dev,
+			"unexpected initial value at regs.dptx offset 0x4408: 0x%03x\n",
+			val_4408);
+	writel(val_4408, phy->regs.dptx + 0x4408);
+	// MMIO: R.4   0x23c544408 (dptx-phy[0], offset 0x4408) = 0x482
+	// MMIO: W.4   0x23c544408 (dptx-phy[0], offset 0x4408) = 0x483
+	set32(phy->regs.dptx + 0x4408, 0x001);
+
+	return 0;
+}
+
+static int dptx_phy_deactivate(struct apple_dptx_phy *phy)
+{
+	return 0;
+}
+
+static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate)
+{
+    u32 sts_1008, sts_1014, val_100c, val_20b0, val_20b4;
+
+	dev_dbg(phy->dev, "set_link_rate(%u)\n", link_rate);
+
+    // MMIO: R.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+    // MMIO: W.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+    set32(phy->regs.dptx + 0x4004, 0x08);
+
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    clear32(phy->regs.dptx + 0x4000, 0x0000040);
+
+    // MMIO: R.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+    // MMIO: W.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41
+    clear32(phy->regs.dptx + 0x4004, 0x08);
+
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    clear32(phy->regs.dptx + 0x4000, 0x2000000);
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    set32(phy->regs.dptx + 0x4000, 0x1000000);
+
+    // MMIO: R.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002
+    // MMIO: R.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002
+    // MMIO: W.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000
+    /* TODO: what is this read checking for? */
+    readl(phy->regs.dptx + 0x2200);
+    clear32(phy->regs.dptx + 0x2200, 0x0002);
+
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf008
+    /* TODO: what is the setting/clearing? */
+    val_100c = readl(phy->regs.dptx + 0x100c);
+    writel(val_100c, phy->regs.dptx + 0x100c);
+    set32(phy->regs.dptx + 0x100c, 0x0008);
+
+    // MMIO: R.4   0x23c541014 (dptx-phy[0], offset 0x1014) = 0x1
+    sts_1014 = readl(phy->regs.dptx + 0x1014);
+    /* TODO: assert(sts_1014 == 0x1); */
+
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf008
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
+    clear32(phy->regs.dptx + 0x100c, 0x0008);
+
+    // MMIO: R.4   0x23c541008 (dptx-phy[0], offset 0x1008) = 0x1
+    sts_1008 = readl(phy->regs.dptx + 0x1008);
+    /* TODO: assert(sts_1008 == 0x1); */
+
+    // MMIO: R.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a0
+    // MMIO: W.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x1109020
+    clear32(phy->regs.dptx + 0x2220, 0x0000080);
+
+    // MMIO: R.4   0x23c5420b0 (dptx-phy[0], offset 0x20b0) = 0x1e0e01c2
+    // MMIO: W.4   0x23c5420b0 (dptx-phy[0], offset 0x20b0) = 0x1e0e01c2
+    val_20b0 = readl(phy->regs.dptx + 0x20b0);
+    /* TODO: what happens on dptx-phy */
+    if (phy->hw.type == DPTX_PHY_T6020)
+	val_20b0 = (val_20b0 & ~0x3ff) | 0x2a3;
+    writel(val_20b0, phy->regs.dptx + 0x20b0);
+
+    // MMIO: R.4   0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe
+    // MMIO: W.4   0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe
+    val_20b4 = readl(phy->regs.dptx + 0x20b4);
+    /* TODO: what happens on dptx-phy */
+    if (phy->hw.type == DPTX_PHY_T6020)
+	val_20b4 = (val_20b4 | 0x4000000) & ~0x0008000;
+    writel(val_20b4, phy->regs.dptx + 0x20b4);
+
+    // MMIO: R.4   0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe
+    // MMIO: W.4   0x23c5420b4 (dptx-phy[0], offset 0x20b4) = 0x7fffffe
+    val_20b4 = readl(phy->regs.dptx + 0x20b4);
+    /* TODO: what happens on dptx-phy */
+    if (phy->hw.type == DPTX_PHY_T6020)
+	val_20b4 = (val_20b4 | 0x0000001) & ~0x0000004;
+    writel(val_20b4, phy->regs.dptx + 0x20b4);
+
+    // MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    // MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    /* TODO: unclear */
+    set32(phy->regs.dptx + 0x20b8, 0);
+    // MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    // MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    /* TODO: unclear */
+    set32(phy->regs.dptx + 0x20b8, 0);
+    // MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    // MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    /* TODO: unclear */
+    if (phy->hw.type == DPTX_PHY_T6020)
+	set32(phy->regs.dptx + 0x20b8, 0x010000);
+    else
+	set32(phy->regs.dptx + 0x20b8, 0);
+    // MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x654800
+    // MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800
+    clear32(phy->regs.dptx + 0x20b8, 0x200000);
+
+    // MMIO: R.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800
+    // MMIO: W.4   0x23c5420b8 (dptx-phy[0], offset 0x20b8) = 0x454800
+    /* TODO: unclear */
+    set32(phy->regs.dptx + 0x20b8, 0);
+
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x0
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x4000c
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x4000c
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8000c
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8000c
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0xc
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8
+    // MMIO: R.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x8
+    // MMIO: W.4   0x23c5000a0 (dptx-phy[1], offset 0xa0) = 0x0
+    set32(phy->regs.core + 0xa0, 0x8);
+    set32(phy->regs.core + 0xa0, 0x4);
+    set32(phy->regs.core + 0xa0, 0x40000);
+    clear32(phy->regs.core + 0xa0, 0x40000);
+    set32(phy->regs.core + 0xa0, 0x80000);
+    clear32(phy->regs.core + 0xa0, 0x80000);
+    clear32(phy->regs.core + 0xa0, 0x4);
+    clear32(phy->regs.core + 0xa0, 0x8);
+
+    // MMIO: R.4   0x23c542000 (dptx-phy[0], offset 0x2000) = 0x2
+    // MMIO: W.4   0x23c542000 (dptx-phy[0], offset 0x2000) = 0x2
+    /* TODO: unclear */
+    set32(phy->regs.dptx + 0x2000, 0x0);
+
+    // MMIO: R.4   0x23c542018 (dptx-phy[0], offset 0x2018) = 0x0
+    // MMIO: W.4   0x23c542018 (dptx-phy[0], offset 0x2018) = 0x0
+    clear32(phy->regs.dptx + 0x2018, 0x0);
+
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007
+    set32(phy->regs.dptx + 0x100c, 0x0007);
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf00f
+    set32(phy->regs.dptx + 0x100c, 0x0008);
+
+    // MMIO: R.4   0x23c541014 (dptx-phy[0], offset 0x1014) = 0x38f
+    sts_1014 = readl(phy->regs.dptx + 0x1014);
+    /* TODO: assert(sts_1014 == 0x38f); */
+
+    // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf00f
+    // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007
+    clear32(phy->regs.dptx + 0x100c, 0x0008);
+
+    // MMIO: R.4   0x23c541008 (dptx-phy[0], offset 0x1008) = 0x9
+    sts_1008 = readl(phy->regs.dptx + 0x1008);
+    /* TODO: assert(sts_1008 == 0x9); */
+
+    // MMIO: R.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000
+    // MMIO: W.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002
+    set32(phy->regs.dptx + 0x2200, 0x0002);
+
+    // MMIO: R.4   0x23c545010 (dptx-phy[0], offset 0x5010) = 0x18003000
+    // MMIO: W.4   0x23c545010 (dptx-phy[0], offset 0x5010) = 0x18003000
+    // MMIO: R.4   0x23c546010 (dptx-phy[0], offset 0x6010) = 0x18003000
+    // MMIO: W.4   0x23c546010 (dptx-phy[0], offset 0x6010) = 0x18003000
+    // MMIO: R.4   0x23c547010 (dptx-phy[0], offset 0x7010) = 0x18003000
+    // MMIO: W.4   0x23c547010 (dptx-phy[0], offset 0x7010) = 0x18003000
+    // MMIO: R.4   0x23c548010 (dptx-phy[0], offset 0x8010) = 0x18003000
+    // MMIO: W.4   0x23c548010 (dptx-phy[0], offset 0x8010) = 0x18003000
+    writel(0x18003000, phy->regs.dptx + 0x8010);
+    for (u32 loff = DPTX_LANE0_OFFSET; loff < DPTX_LANE_END; loff += DPTX_LANE_STRIDE) {
+	u32 val_l010 = readl(phy->regs.dptx + loff + 0x10);
+	writel(val_l010, phy->regs.dptx + loff + 0x10);
+    }
+
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x41021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x51021ac
+    set32(phy->regs.dptx + 0x4000, 0x1000000);
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x51021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ac
+    set32(phy->regs.dptx + 0x4000, 0x2000000);
+
+    // MMIO: R.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x41
+    // MMIO: W.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+    set32(phy->regs.dptx + 0x4004, 0x08);
+
+    // MMIO: R.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ac
+    // MMIO: W.4   0x23c544000 (dptx-phy[0], offset 0x4000) = 0x71021ec
+    set32(phy->regs.dptx + 0x4000, 0x0000040);
+
+    // MMIO: R.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x49
+    // MMIO: W.4   0x23c544004 (dptx-phy[0], offset 0x4004) = 0x48
+    clear32(phy->regs.dptx + 0x4004, 0x01);
+
+    return 0;
+}
+
+static int dptx_phy_set_mode(struct phy *phy, enum phy_mode mode, int submode)
+{
+	struct apple_dptx_phy *dptx_phy = phy_get_drvdata(phy);
+
+	switch (mode) {
+	case PHY_MODE_INVALID:
+		return dptx_phy_deactivate(dptx_phy);
+	case PHY_MODE_DP:
+		if (submode < 0 || submode > 5)
+			return -EINVAL;
+		return dptx_phy_activate(dptx_phy, submode);
+	default:
+		break;
+	}
+
+	return -EINVAL;
+}
+
+static int dptx_phy_validate(struct phy *phy, enum phy_mode mode, int submode,
+			     union phy_configure_opts *opts_)
+{
+	struct phy_configure_opts_dp *opts = &opts_->dp;
+
+	if (mode == PHY_MODE_INVALID) {
+		memset(opts, 0, sizeof(*opts));
+		return 0;
+	}
+
+	if (mode != PHY_MODE_DP)
+		return -EINVAL;
+	if (submode < 0 || submode > 5)
+		return -EINVAL;
+
+	opts->lanes = 4;
+	opts->link_rate = 8100;
+
+	for (int i = 0; i < 4; ++i) {
+		opts->voltage[i] = 3;
+		opts->pre[i] = 3;
+	}
+
+	return 0;
+}
+
+static int dptx_phy_configure(struct phy *phy, union phy_configure_opts *opts_)
+{
+	struct phy_configure_opts_dp *opts = &opts_->dp;
+	struct apple_dptx_phy *dptx_phy = phy_get_drvdata(phy);
+	enum dptx_phy_link_rate link_rate;
+	int ret = 0;
+
+	if (opts->set_lanes) {
+		mutex_lock(&dptx_phy->lock);
+		ret = dptx_phy_set_active_lane_count(dptx_phy, opts->lanes);
+		mutex_unlock(&dptx_phy->lock);
+	}
+
+	if (opts->set_rate) {
+		switch (opts->link_rate) {
+		case 1620:
+			link_rate = DPTX_PHY_LINK_RATE_RBR;
+			break;
+		case 2700:
+			link_rate = DPTX_PHY_LINK_RATE_HBR;
+			break;
+		case 5400:
+			link_rate = DPTX_PHY_LINK_RATE_HBR2;
+			break;
+		case 8100:
+			link_rate = DPTX_PHY_LINK_RATE_HBR3;
+			break;
+		case 0:
+			// TODO: disable!
+			return 0;
+			break;
+		default:
+			dev_err(dptx_phy->dev, "Unsupported link rate: %d\n",
+				opts->link_rate);
+			return -EINVAL;
+		}
+
+		mutex_lock(&dptx_phy->lock);
+		ret = dptx_phy_set_link_rate(dptx_phy, link_rate);
+		mutex_unlock(&dptx_phy->lock);
+	}
+
+	return ret;
+}
+
+static const struct phy_ops apple_atc_dp_phy_ops = {
+	.owner = THIS_MODULE,
+	.configure = dptx_phy_configure,
+	.validate = dptx_phy_validate,
+	.set_mode = dptx_phy_set_mode,
+};
+
+static int dptx_phy_probe(struct platform_device *pdev)
+{
+	struct apple_dptx_phy *dptx_phy;
+	struct device *dev = &pdev->dev;
+
+	dptx_phy = devm_kzalloc(dev, sizeof(*dptx_phy), GFP_KERNEL);
+	if (!dptx_phy)
+		return -ENOMEM;
+
+	dptx_phy->dev = dev;
+	dptx_phy->hw =
+		*(struct apple_dptx_phy_hw *)of_device_get_match_data(dev);
+	platform_set_drvdata(pdev, dptx_phy);
+
+	mutex_init(&dptx_phy->lock);
+
+	dptx_phy->regs.core =
+		devm_platform_ioremap_resource_byname(pdev, "core");
+	if (IS_ERR(dptx_phy->regs.core))
+		return PTR_ERR(dptx_phy->regs.core);
+	dptx_phy->regs.dptx =
+		devm_platform_ioremap_resource_byname(pdev, "dptx");
+	if (IS_ERR(dptx_phy->regs.dptx))
+		return PTR_ERR(dptx_phy->regs.dptx);
+
+	/* create phy */
+	dptx_phy->phy_dp =
+		devm_phy_create(dptx_phy->dev, NULL, &apple_atc_dp_phy_ops);
+	if (IS_ERR(dptx_phy->phy_dp))
+		return PTR_ERR(dptx_phy->phy_dp);
+	phy_set_drvdata(dptx_phy->phy_dp, dptx_phy);
+
+	dptx_phy->phy_provider =
+		devm_of_phy_provider_register(dev, of_phy_simple_xlate);
+	if (IS_ERR(dptx_phy->phy_provider))
+		return PTR_ERR(dptx_phy->phy_provider);
+
+	return 0;
+}
+
+static const struct apple_dptx_phy_hw apple_dptx_hw_t6020 = {
+	.type = DPTX_PHY_T6020,
+};
+
+static const struct apple_dptx_phy_hw apple_dptx_hw_t8112 = {
+	.type = DPTX_PHY_T8112,
+};
+
+static const struct of_device_id dptx_phy_match[] = {
+	{ .compatible = "apple,t6020-dptx-phy", .data = &apple_dptx_hw_t6020 },
+	{ .compatible = "apple,t8112-dptx-phy", .data = &apple_dptx_hw_t8112 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dptx_phy_match);
+
+static struct platform_driver dptx_phy_driver = {
+	.driver = {
+		.name = "phy-apple-dptx",
+		.of_match_table = dptx_phy_match,
+	},
+	.probe = dptx_phy_probe,
+};
+
+module_platform_driver(dptx_phy_driver);
+
+MODULE_AUTHOR("Janne Grunau <j@jananu.net>");
+MODULE_DESCRIPTION("Apple DP TX PHY driver");
+
+MODULE_LICENSE("GPL");
diff --git a/drivers/phy/apple/dptx.h b/drivers/phy/apple/dptx.h
new file mode 100644
index 00000000000000..2dd36d753eb357
--- /dev/null
+++ b/drivers/phy/apple/dptx.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: GPL-2.0 OR BSD-2-Clause
+/*
+ * Apple DP TX PHY driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ * Author: Janne Grunau <j@jannau.net>
+ */
+
+#ifndef PHY_APPLE_DPTX_H
+#define PHY_APPLE_DPTX_H
+
+enum dptx_phy_link_rate {
+	DPTX_PHY_LINK_RATE_RBR,
+	DPTX_PHY_LINK_RATE_HBR,
+	DPTX_PHY_LINK_RATE_HBR2,
+	DPTX_PHY_LINK_RATE_HBR3,
+};
+#endif /* PHY_APPLE_DPTX_H */

From 4f8e7f50f5e21f71fb9088b117921ef665e0f7dd Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 22:02:56 +0100
Subject: [PATCH 0363/1027] phy: apple: atc: Split atcphy_dp_configure_lane()

No functional change but orders the register writes in the same way as
macOS does. This makes the comparison of Linux and macOS traces much
simple. The output of `diff` is now mostly legible.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index f95f9f9fe60a9c..f3aa1765776380 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -1246,6 +1246,30 @@ atcphy_dp_configure_lane(struct apple_atcphy *atcphy, unsigned int lane,
 	clear32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ);
 	set32(tx_shm + LN_AUSPMA_TX_SHM_TXA_IMP_REG0, LN_TXA_HIZ_OV);
 
+	return 0;
+}
+
+static int
+atcphy_dp_configure_lane2(struct apple_atcphy *atcphy, unsigned int lane,
+			 const struct atcphy_dp_link_rate_configuration *cfg)
+{
+	void __iomem *tx_shm, *rx_shm, *rx_top;
+
+	switch (lane) {
+	case 0:
+		tx_shm = atcphy->regs.core + LN0_AUSPMA_TX_SHM;
+		rx_shm = atcphy->regs.core + LN0_AUSPMA_RX_SHM;
+		rx_top = atcphy->regs.core + LN0_AUSPMA_RX_TOP;
+		break;
+	case 1:
+		tx_shm = atcphy->regs.core + LN1_AUSPMA_TX_SHM;
+		rx_shm = atcphy->regs.core + LN1_AUSPMA_RX_SHM;
+		rx_top = atcphy->regs.core + LN1_AUSPMA_RX_TOP;
+		break;
+	default:
+		return -EINVAL;
+	}
+
 	clear32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
 		LN_RX_DIV20_RESET_N);
 	set32(rx_shm + LN_AUSPMA_RX_SHM_TJ_RXA_AFE_CTRL1,
@@ -1515,6 +1539,18 @@ static int atcphy_dp_configure(struct apple_atcphy *atcphy,
 			return ret;
 	}
 
+	if (mode_cfg->dp_lane[0]) {
+		ret = atcphy_dp_configure_lane2(atcphy, 0, cfg);
+		if (ret)
+			return ret;
+	}
+
+	if (mode_cfg->dp_lane[1]) {
+		ret = atcphy_dp_configure_lane2(atcphy, 1, cfg);
+		if (ret)
+			return ret;
+	}
+
 	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,
 		     DP_PMA_BYTECLK_RESET);
 	core_clear32(atcphy, ACIOPHY_LANE_DP_CFG_BLK_TX_DP_CTRL0,

From eebfc3087d508385feeb1511f4d03a112c861c63 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 22:09:35 +0100
Subject: [PATCH 0364/1027] phy: apple: atc: Reorder ACIOPHY_CROSSBAR and
 ACIOPHY_MISC ops

Use the same order of operations as macOS 13.5 for simpler comparisons
of Linux and macOS traces.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index f3aa1765776380..2af1bf40b1286b 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -828,11 +828,6 @@ static void atcphy_configure_lanes(struct apple_atcphy *atcphy,
 
 	trace_atcphy_configure_lanes(mode, mode_cfg);
 
-	if (mode_cfg->set_swap)
-		core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
-	else
-		core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
-
 	if (mode_cfg->dp_lane[0]) {
 		core_set32(atcphy, LN0_AUSPMA_RX_TOP + LN_AUSPMA_RX_TOP_PMAFSM,
 			   LN_AUSPMA_RX_TOP_PMAFSM_PCS_OV);
@@ -859,15 +854,21 @@ static void atcphy_configure_lanes(struct apple_atcphy *atcphy,
 	core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_PROTOCOL,
 		    FIELD_PREP(ACIOPHY_CROSSBAR_PROTOCOL, mode_cfg->crossbar));
 
-	core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
-		    FIELD_PREP(ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
-			       mode_cfg->crossbar_dp_single_pma));
+	if (mode_cfg->set_swap)
+		core_set32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+	else
+		core_clear32(atcphy, ATCPHY_MISC, ATCPHY_MISC_LANE_SWAP);
+
 	if (mode_cfg->crossbar_dp_both_pma)
 		core_set32(atcphy, ACIOPHY_CROSSBAR,
 			   ACIOPHY_CROSSBAR_DP_BOTH_PMA);
 	else
 		core_clear32(atcphy, ACIOPHY_CROSSBAR,
 			     ACIOPHY_CROSSBAR_DP_BOTH_PMA);
+
+	core_mask32(atcphy, ACIOPHY_CROSSBAR, ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+		    FIELD_PREP(ACIOPHY_CROSSBAR_DP_SINGLE_PMA,
+			       mode_cfg->crossbar_dp_single_pma));
 }
 
 static int atcphy_pipehandler_lock(struct apple_atcphy *atcphy)

From 57ab3ff09ce9001dcdc497115f65e89e13eea813 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 22:13:09 +0100
Subject: [PATCH 0365/1027] phy: apple: atc: Support DisplayPort only operation

atc3 on is used to drive the HDMI port on 14/16 inch Macbook Pros with
M1/M2 Pro/Max via a DP2HMDMI converter.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 32 ++++++++++++++++++++++++--------
 drivers/phy/apple/atc.h |  1 +
 2 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index 2af1bf40b1286b..f6b6277853ddad 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -2399,19 +2399,35 @@ static int atcphy_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	atcphy->dp_only = of_property_read_bool(dev->of_node, "apple,mode-fixed-dp");
+
 	atcphy->mode = APPLE_ATCPHY_MODE_OFF;
 	atcphy->pipehandler_state = ATCPHY_PIPEHANDLER_STATE_INVALID;
 
-	ret = atcphy_probe_rcdev(atcphy);
-	if (ret)
-		return ret;
-	ret = atcphy_probe_mux(atcphy);
-	if (ret)
-		return ret;
-	ret = atcphy_probe_switch(atcphy);
+	if (!atcphy->dp_only) {
+		ret = atcphy_probe_rcdev(atcphy);
+		if (ret)
+			return ret;
+		ret = atcphy_probe_mux(atcphy);
+		if (ret)
+			return ret;
+		ret = atcphy_probe_switch(atcphy);
+		if (ret)
+			return ret;
+	}
+
+	ret = atcphy_probe_phy(atcphy);
 	if (ret)
 		return ret;
-	return atcphy_probe_phy(atcphy);
+
+	if (atcphy->dp_only) {
+		atcphy->target_mode = APPLE_ATCPHY_MODE_DP;
+		WARN_ON(!schedule_work(&atcphy->mux_set_work));
+		wait_for_completion_timeout(&atcphy->atcphy_online_event,
+					msecs_to_jiffies(1000));
+	}
+
+	return 0;
 }
 
 static const struct of_device_id atcphy_match[] = {
diff --git a/drivers/phy/apple/atc.h b/drivers/phy/apple/atc.h
index 6b020953965fa5..922f68c0100782 100644
--- a/drivers/phy/apple/atc.h
+++ b/drivers/phy/apple/atc.h
@@ -134,6 +134,7 @@ struct apple_atcphy {
 
 	struct work_struct mux_set_work;
 	enum atcphy_mode target_mode;
+	bool dp_only;
 };
 
 #endif

From 055fac0e1588e1bff33a40e09b31c8fee31f10cb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 22:20:08 +0100
Subject: [PATCH 0366/1027] phy: apple: atc: Support 'set_lanes' in DP mode

Does not actually switch the number of lanes used for DisplayPort.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index f6b6277853ddad..acd604cae8f361 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -1829,9 +1829,18 @@ static int atcphy_dpphy_configure(struct phy *phy,
 	if (opts->set_voltages)
 		return -EINVAL;
 
-	/* TODO? or maybe just ack since this mux_set should've done this? */
-	if (opts->set_lanes)
-		return -EINVAL;
+	/*
+	 * Just ack set_lanes for compatibility with (lp)dptx-phy
+	 * The mux_set should've done this anyway
+	 */
+	if (opts->set_lanes) {
+		if (((atcphy->mode == APPLE_ATCPHY_MODE_DP && opts->lanes != 4) ||
+		     (atcphy->mode == APPLE_ATCPHY_MODE_USB3_DP && opts->lanes != 2)) &&
+	            opts->lanes != 0)
+		dev_warn(atcphy->dev, "Unexpected lane count %u for mode %u\n",
+			 opts->lanes, atcphy->mode);
+
+	}
 
 	if (opts->set_rate) {
 		switch (opts->link_rate) {

From e4ecbf77da067568d2e64ab5ea935fd92cbb46f8 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 7 Nov 2023 23:16:38 +0100
Subject: [PATCH 0367/1027] HACK: phy: apple: atc: Ignore fake submodes

For compatibility with dptx-phy.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index acd604cae8f361..5d92782655fcd2 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -1780,7 +1780,7 @@ static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode,
 				 int submode)
 {
 	/* nothing to do here since the setup already happened in mux_set */
-	if (mode == PHY_MODE_DP && submode == 0)
+	if (mode == PHY_MODE_DP && submode >= 0 && submode <= 5)
 		return 0;
 	return -EINVAL;
 }

From 45a13ccb81e9545fc5d84956a7eded04d822cfd3 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 20 Nov 2023 22:49:41 +0100
Subject: [PATCH 0368/1027] phy: apple: atc: support mode switches in
 atcphy_dpphy_set_mode()

Required for the DP2HDMI only atc3 port on 14/16 inch Macbook Pros.

Fixes: 9ca3958ee18b ("phy: apple: atc: Support DisplayPort only operation")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 68 +++++++++++++++++++++++++++++++++--------
 1 file changed, 55 insertions(+), 13 deletions(-)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index 5d92782655fcd2..57f96f7ceae295 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -9,6 +9,7 @@
 #include "atc.h"
 #include "trace.h"
 
+#include <asm-generic/errno.h>
 #include <dt-bindings/phy/phy.h>
 #include <linux/bitfield.h>
 #include <linux/delay.h>
@@ -1776,12 +1777,55 @@ static const struct phy_ops apple_atc_usb2_phy_ops = {
 	.exit = atcphy_usb2_power_off,
 };
 
+static int atcphy_dpphy_mux_set(struct apple_atcphy *atcphy, enum atcphy_mode target)
+{
+	int ret = 0;
+
+	// TODO:
+	flush_work(&atcphy->mux_set_work);
+
+	mutex_lock(&atcphy->lock);
+
+	if (atcphy->mode == target)
+		goto out_unlock;
+
+	atcphy->target_mode = target;
+
+	WARN_ON(!schedule_work(&atcphy->mux_set_work));
+	ret = wait_for_completion_timeout(&atcphy->atcphy_online_event,
+					  msecs_to_jiffies(1000));
+	if (ret == 0)
+		ret = -ETIMEDOUT;
+	else if (ret > 0)
+		ret = 0;
+
+out_unlock:
+	mutex_unlock(&atcphy->lock);
+	return ret;
+}
+
 static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode,
 				 int submode)
 {
-	/* nothing to do here since the setup already happened in mux_set */
-	if (mode == PHY_MODE_DP && submode >= 0 && submode <= 5)
-		return 0;
+	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
+
+	dev_info(atcphy->dev, "%s(mode=%u, submode=%d)\n", __func__, mode, submode);
+
+	switch (mode) {
+	case PHY_MODE_INVALID:
+		if (atcphy->mode == APPLE_ATCPHY_MODE_OFF)
+			return 0;
+		return atcphy_dpphy_mux_set(atcphy, APPLE_ATCPHY_MODE_OFF);
+	case PHY_MODE_DP:
+		/* TODO: does this get called for DP-altmode? */
+		if (atcphy->mode == APPLE_ATCPHY_MODE_USB3_DP ||
+		    atcphy->mode == APPLE_ATCPHY_MODE_DP)
+			return 0;
+		return atcphy_dpphy_mux_set(atcphy, APPLE_ATCPHY_MODE_DP);
+	default:
+		break;
+	}
+
 	return -EINVAL;
 }
 
@@ -1791,6 +1835,11 @@ static int atcphy_dpphy_validate(struct phy *phy, enum phy_mode mode,
 	struct phy_configure_opts_dp *opts = &opts_->dp;
 	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
 
+	if (mode == PHY_MODE_INVALID) {
+		memset(opts, 0, sizeof(*opts));
+		return 0;
+	}
+
 	if (mode != PHY_MODE_DP)
 		return -EINVAL;
 	if (submode != 0)
@@ -1836,9 +1885,9 @@ static int atcphy_dpphy_configure(struct phy *phy,
 	if (opts->set_lanes) {
 		if (((atcphy->mode == APPLE_ATCPHY_MODE_DP && opts->lanes != 4) ||
 		     (atcphy->mode == APPLE_ATCPHY_MODE_USB3_DP && opts->lanes != 2)) &&
-	            opts->lanes != 0)
-		dev_warn(atcphy->dev, "Unexpected lane count %u for mode %u\n",
-			 opts->lanes, atcphy->mode);
+	            (atcphy->mode == APPLE_ATCPHY_MODE_OFF && opts->lanes != 0))
+			dev_warn(atcphy->dev, "Unexpected lane count %u for mode %u\n",
+				 opts->lanes, atcphy->mode);
 
 	}
 
@@ -2429,13 +2478,6 @@ static int atcphy_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	if (atcphy->dp_only) {
-		atcphy->target_mode = APPLE_ATCPHY_MODE_DP;
-		WARN_ON(!schedule_work(&atcphy->mux_set_work));
-		wait_for_completion_timeout(&atcphy->atcphy_online_event,
-					msecs_to_jiffies(1000));
-	}
-
 	return 0;
 }
 

From 2e0aeabaec6aaf94be188b42e910ffc661f05783 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 5 Mar 2024 20:17:56 +0100
Subject: [PATCH 0369/1027] phy: apple: dptx: Add debug prints for unexpected
 values

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/dptx.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)

diff --git a/drivers/phy/apple/dptx.c b/drivers/phy/apple/dptx.c
index 4de826964d3b37..f0df2d40a18023 100644
--- a/drivers/phy/apple/dptx.c
+++ b/drivers/phy/apple/dptx.c
@@ -364,7 +364,8 @@ static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate)
 
     // MMIO: R.4   0x23c541014 (dptx-phy[0], offset 0x1014) = 0x1
     sts_1014 = readl(phy->regs.dptx + 0x1014);
-    /* TODO: assert(sts_1014 == 0x1); */
+    if (sts_1014 != 0x1)
+	    dev_dbg(phy->dev, "unexpected?: dptx[0x1014]: %02x\n", sts_1014);
 
     // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf008
     // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf000
@@ -372,7 +373,8 @@ static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate)
 
     // MMIO: R.4   0x23c541008 (dptx-phy[0], offset 0x1008) = 0x1
     sts_1008 = readl(phy->regs.dptx + 0x1008);
-    /* TODO: assert(sts_1008 == 0x1); */
+    if (sts_1008 != 0x1)
+	    dev_dbg(phy->dev, "unexpected?: dptx[0x1008]: %02x\n", sts_1008);
 
     // MMIO: R.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x11090a0
     // MMIO: W.4   0x23c542220 (dptx-phy[0], offset 0x2220) = 0x1109020
@@ -469,7 +471,8 @@ static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate)
 
     // MMIO: R.4   0x23c541014 (dptx-phy[0], offset 0x1014) = 0x38f
     sts_1014 = readl(phy->regs.dptx + 0x1014);
-    /* TODO: assert(sts_1014 == 0x38f); */
+    if (sts_1014 != 0x38f)
+	    dev_dbg(phy->dev, "unexpected?: dptx[0x1014]: %02x\n", sts_1014);
 
     // MMIO: R.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf00f
     // MMIO: W.4   0x23c54100c (dptx-phy[0], offset 0x100c) = 0xf007
@@ -477,7 +480,8 @@ static int dptx_phy_set_link_rate(struct apple_dptx_phy *phy, u32 link_rate)
 
     // MMIO: R.4   0x23c541008 (dptx-phy[0], offset 0x1008) = 0x9
     sts_1008 = readl(phy->regs.dptx + 0x1008);
-    /* TODO: assert(sts_1008 == 0x9); */
+    if (sts_1008 != 0x9)
+	    dev_dbg(phy->dev, "unexpected?: dptx[0x1008]: %02x\n", sts_1008);
 
     // MMIO: R.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2000
     // MMIO: W.4   0x23c542200 (dptx-phy[0], offset 0x2200) = 0x2002

From 09436a87728480b75e38777d26fcbe5129a3e483 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 9 Jun 2024 22:39:00 +0200
Subject: [PATCH 0370/1027] fixup! phy: apple: atc: support mode switches in
 atcphy_dpphy_set_mode()

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index 57f96f7ceae295..6f66a5cd7fb92e 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -1809,6 +1809,9 @@ static int atcphy_dpphy_set_mode(struct phy *phy, enum phy_mode mode,
 {
 	struct apple_atcphy *atcphy = phy_get_drvdata(phy);
 
+	if (!atcphy->dp_only)
+		return 0;
+
 	dev_info(atcphy->dev, "%s(mode=%u, submode=%d)\n", __func__, mode, submode);
 
 	switch (mode) {

From 7b0bef813938d897023b01980790fe7ea308a39f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 26 Jul 2024 21:28:50 +0200
Subject: [PATCH 0371/1027] fixup! WIP: phy: apple: Add Apple Type-C PHY

---
 drivers/phy/apple/trace.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/phy/apple/trace.h b/drivers/phy/apple/trace.h
index c4c21c84e8917c..bcee8c52b0a1fd 100644
--- a/drivers/phy/apple/trace.h
+++ b/drivers/phy/apple/trace.h
@@ -131,7 +131,7 @@ TRACE_EVENT(atcphy_dp_configure,
 	    TP_STRUCT__entry(__string(devname, dev_name(atcphy->dev))
 				     __field(enum atcphy_dp_link_rate, lr)),
 
-	    TP_fast_assign(__assign_str(devname, dev_name(atcphy->dev));
+	    TP_fast_assign(__assign_str(devname);
 	     		  __entry->lr = lr;),
 
 	    TP_printk("%s: link rate: %s", __get_str(devname),

From e6c39d993bb77db1f0eef2654a8b9a38421ab1f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 13 Oct 2023 18:49:35 +0200
Subject: [PATCH 0372/1027] dt-bindings: dma: apple,sio: Add schema
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Describe the SIO coprocessor which serves as pretend DMA controller on
recent Apple platforms.

Reviewed-by: Rob Herring <robh@kernel.org>
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 .../devicetree/bindings/dma/apple,sio.yaml    | 111 ++++++++++++++++++
 1 file changed, 111 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/apple,sio.yaml

diff --git a/Documentation/devicetree/bindings/dma/apple,sio.yaml b/Documentation/devicetree/bindings/dma/apple,sio.yaml
new file mode 100644
index 00000000000000..0e3780ad9dd79a
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/apple,sio.yaml
@@ -0,0 +1,111 @@
+# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/dma/apple,sio.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple SIO Coprocessor
+
+description:
+  SIO is a coprocessor on Apple M1 and later chips (and maybe also on earlier
+  chips). Its role is to offload SPI, UART and DisplayPort audio transfers,
+  being a pretend DMA controller.
+
+maintainers:
+  - Martin Povišer <povik+lin@cutebit.org>
+
+allOf:
+  - $ref: dma-controller.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,t6000-sio
+          - apple,t8103-sio
+      - const: apple,sio
+
+  reg:
+    maxItems: 1
+
+  '#dma-cells':
+    const: 1
+    description:
+      DMA clients specify a single cell that corresponds to the RTKit endpoint
+      number used for arranging the transfers in question
+
+  dma-channels:
+    maximum: 128
+
+  mboxes:
+    maxItems: 1
+
+  iommus:
+    maxItems: 1
+
+  power-domains:
+    maxItems: 1
+
+  memory-region:
+    minItems: 2
+    maxItems: 8
+    description:
+      A number of references to reserved memory regions among which are the DATA/TEXT
+      sections of coprocessor executable firmware and also auxiliary firmware data
+      describing the available DMA-enabled peripherals
+
+  apple,sio-firmware-params:
+    $ref: /schemas/types.yaml#/definitions/uint32-array
+    description: |
+      Parameters in the form of opaque key/value pairs that are to be sent to the SIO
+      coprocesssor once it boots. These parameters can point into the reserved memory
+      regions (in device address space).
+
+      Note that unlike Apple's firmware, we treat the parameters, and the data they
+      refer to, as opaque. Apple embed short data blobs into their SIO devicetree node
+      that describe the DMA-enabled peripherals (presumably with defined semantics).
+      Their driver processes those blobs and sets up data structure in mapped device
+      memory, then references this memory in the parameters sent to the SIO. At the
+      level of description we are opting for in this binding, we assume the job of
+      constructing those data structures has been done in advance, leaving behind an
+      opaque list of key/value parameter pairs to be sent by a prospective driver.
+
+      This approach is chosen for two reasons:
+
+       - It means we don't need to try to understand the semantics of Apple's blobs
+         as long as we know the transformation we need to do from Apple's devicetree
+         data to SIO data (which can be shoved away into a loader). It also means the
+         semantics of Apple's blobs (or of something to replace them) need not be part
+         of the binding and be kept up with Apple's firmware changes in the future.
+
+       - It leaves less work for the driver attaching on this binding. Instead the work
+         is done upfront in the loader which can be better suited for keeping up with
+         Apple's firmware changes.
+
+required:
+  - compatible
+  - reg
+  - '#dma-cells'
+  - dma-channels
+  - mboxes
+  - iommus
+  - power-domains
+
+additionalProperties: false
+
+examples:
+  - |
+    sio: dma-controller@36400000 {
+      compatible = "apple,t8103-sio", "apple,sio";
+      reg = <0x36400000 0x8000>;
+      dma-channels = <128>;
+      #dma-cells = <1>;
+      mboxes = <&sio_mbox>;
+      iommus = <&sio_dart 0>;
+      power-domains = <&ps_sio_cpu>;
+      memory-region = <&sio_text>, <&sio_data>,
+                      <&sio_auxdata1>, <&sio_auxdata2>; /* Filled by loader */
+      apple,sio-firmware-params = <0xb 0x10>, <0xc 0x1b80>, <0xf 0x14>,
+                                  <0x10 0x1e000>, <0x30d 0x34>, <0x30e 0x4000>,
+                                  <0x1a 0x38>, <0x1b 0x50>; /* Filled by loader */
+    };

From e9e34207f4dde8505a061752fce8c54947db777b Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Tue, 18 Apr 2023 22:59:53 +0300
Subject: [PATCH 0373/1027] gpu: drm: adp: Add Apple Display Pipe driver

This display controller is present on M-series chips and is used
to drive the touchbar display.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/Kconfig       |   2 +
 drivers/gpu/drm/Makefile      |   1 +
 drivers/gpu/drm/adp/Kconfig   |  12 +
 drivers/gpu/drm/adp/Makefile  |   5 +
 drivers/gpu/drm/adp/adp_drv.c | 760 ++++++++++++++++++++++++++++++++++
 5 files changed, 780 insertions(+)
 create mode 100644 drivers/gpu/drm/adp/Kconfig
 create mode 100644 drivers/gpu/drm/adp/Makefile
 create mode 100644 drivers/gpu/drm/adp/adp_drv.c

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 6b2c6b91f96250..acdef8460d1ad2 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -428,6 +428,8 @@ source "drivers/gpu/drm/mcde/Kconfig"
 
 source "drivers/gpu/drm/tidss/Kconfig"
 
+source "drivers/gpu/drm/adp/Kconfig"
+
 source "drivers/gpu/drm/xlnx/Kconfig"
 
 source "drivers/gpu/drm/gud/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index fa432a1ac9e2b7..4defc2d3341c28 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -203,6 +203,7 @@ obj-y			+= mxsfb/
 obj-y			+= tiny/
 obj-$(CONFIG_DRM_PL111) += pl111/
 obj-$(CONFIG_DRM_TVE200) += tve200/
+obj-$(CONFIG_DRM_ADP) += adp/
 obj-$(CONFIG_DRM_XEN) += xen/
 obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/
 obj-$(CONFIG_DRM_LIMA)  += lima/
diff --git a/drivers/gpu/drm/adp/Kconfig b/drivers/gpu/drm/adp/Kconfig
new file mode 100644
index 00000000000000..739029bde31919
--- /dev/null
+++ b/drivers/gpu/drm/adp/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+config DRM_ADP
+	tristate "DRM Support for pre-DCP Apple display controllers"
+	depends on DRM && OF && ARM64
+	depends on ARCH_APPLE || COMPILE_TEST
+	select DRM_KMS_HELPER
+	select DRM_KMS_DMA_HELPER
+	select DRM_GEM_DMA_HELPER
+	select VIDEOMODE_HELPERS
+	select DRM_MIPI_DSI
+	help
+	  Say Y if you have an Apple Arm laptop with a touchbar.
diff --git a/drivers/gpu/drm/adp/Makefile b/drivers/gpu/drm/adp/Makefile
new file mode 100644
index 00000000000000..28a5d4b4a267f3
--- /dev/null
+++ b/drivers/gpu/drm/adp/Makefile
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+adpdrm-y := adp_drv.o
+obj-$(CONFIG_DRM_ADP) += adpdrm.o
+obj-$(CONFIG_DRM_ADP) += panel-summit.o
diff --git a/drivers/gpu/drm/adp/adp_drv.c b/drivers/gpu/drm/adp/adp_drv.c
new file mode 100644
index 00000000000000..23acfa9ccb00b0
--- /dev/null
+++ b/drivers/gpu/drm/adp/adp_drv.c
@@ -0,0 +1,760 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_damage_helper.h>
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_edid.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_atomic_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_mipi_dsi.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_vblank.h>
+
+#define ADP_INT_STATUS 0x34
+#define ADP_INT_STATUS_INT_MASK 0x7
+#define ADP_INT_STATUS_VBLANK 0x1
+#define ADP_CTRL 0x100
+#define ADP_CTRL_VBLANK_ON 0x12
+#define ADP_CTRL_FIFO_ON 0x601
+#define ADP_SCREEN_SIZE 0x0c
+
+#define ADBE_FIFO 0x10c0
+#define ADBE_FIFO_SYNC 0xc0000000
+
+#define ADBE_BLEND_BYPASS 0x2020
+#define ADBE_BLEND_EN1 0x2028
+#define ADBE_BLEND_EN2 0x2074
+#define ADBE_BLEND_EN3 0x202c
+#define ADBE_BLEND_EN4 0x2034
+#define ADBE_MASK_BUF 0x2200
+
+#define ADBE_SRC_START 0x4040
+#define ADBE_SRC_SIZE 0x4048
+#define ADBE_DST_START 0x4050
+#define ADBE_DST_SIZE 0x4054
+#define ADBE_STRIDE 0x4038
+#define ADBE_FB_BASE 0x4030
+
+#define ADBE_LAYER_EN1 0x4020
+#define ADBE_LAYER_EN2 0x4068
+#define ADBE_LAYER_EN3 0x40b4
+#define ADBE_LAYER_EN4 0x40f4
+#define ADBE_SCALE_CTL 0x40ac
+#define ADBE_SCALE_CTL_BYPASS 0x100000
+
+#define ADBE_LAYER_CTL 0x1038
+#define ADBE_LAYER_CTL_ENABLE 0x10000
+
+#define ADBE_PIX_FMT 0x402c
+#define ADBE_PIX_FMT_XRGB32 0x53e4001
+
+#define DSI_GEN_HDR 0x6c
+#define DSI_GEN_PLD_DATA 0x70
+
+#define DSI_CMD_PKT_STATUS 0x74
+
+#define GEN_PLD_R_EMPTY BIT(4)
+#define GEN_PLD_W_FULL BIT(3)
+#define GEN_PLD_W_EMPTY BIT(2)
+#define GEN_CMD_FULL BIT(1)
+#define GEN_CMD_EMPTY BIT(0)
+#define GEN_RD_CMD_BUSY BIT(6)
+#define CMD_PKT_STATUS_TIMEOUT_US 20000
+
+DEFINE_DRM_GEM_DMA_FOPS(adp_fops);
+
+static int adp_drm_gem_dumb_create(struct drm_file *file_priv,
+					struct drm_device *drm,
+					struct drm_mode_create_dumb *args)
+{
+	args->height = ALIGN(args->height, 64);
+	args->size = args->pitch * args->height;
+
+	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
+}
+
+static const struct drm_driver adp_driver = {
+	.driver_features = DRIVER_GEM | DRIVER_MODESET | DRIVER_ATOMIC,
+	.fops = &adp_fops,
+	DRM_GEM_DMA_DRIVER_OPS_VMAP_WITH_DUMB_CREATE(adp_drm_gem_dumb_create),
+	.name = "adp",
+	.desc = "Apple Display Pipe DRM Driver",
+	.date = "20230412",
+	.major = 0,
+	.minor = 1,
+};
+
+struct adp_drv_private {
+	struct drm_device drm;
+	struct drm_crtc crtc;
+	struct drm_encoder encoder;
+	struct drm_connector connector;
+	struct mipi_dsi_host dsi;
+	void __iomem *be;
+	void __iomem *fe;
+	void __iomem *mipi;
+	u32 *mask_buf;
+	u64 mask_buf_size;
+	dma_addr_t mask_iova;
+	int be_irq;
+	int fe_irq;
+	spinlock_t irq_lock;
+	struct drm_pending_vblank_event *event;
+};
+
+struct adp_plane {
+	struct drm_plane base_plane;
+	u8 id;
+};
+
+#define to_adp(x) container_of(x, struct adp_drv_private, drm)
+#define crtc_to_adp(x) container_of(x, struct adp_drv_private, crtc)
+#define conn_to_adp(x) container_of(x, struct adp_drv_private, connector)
+#define mipi_to_adp(x) container_of(x, struct adp_drv_private, dsi)
+
+static int adp_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state;
+	struct drm_crtc_state *crtc_state;
+
+	new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+
+	if (!new_plane_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+	return drm_atomic_helper_check_plane_state(new_plane_state,
+						   crtc_state,
+						   DRM_PLANE_NO_SCALING,
+						   DRM_PLANE_NO_SCALING,
+						   true, true);
+}
+
+static void adp_plane_atomic_update(struct drm_plane *plane,
+				    struct drm_atomic_state *state)
+{
+	struct adp_drv_private *adp;
+	struct drm_rect src_rect;
+	struct drm_gem_dma_object *obj;
+	struct drm_framebuffer *fb;
+	struct drm_plane_state *new_state = drm_atomic_get_new_plane_state(state, plane);
+	u32 src_pos, src_size, dst_pos, dst_size;
+	if (!plane || !new_state)
+		return;
+
+	fb = new_state->fb;
+	if (!fb)
+		return;
+	adp = to_adp(plane->dev);
+
+	drm_rect_fp_to_int(&src_rect, &new_state->src);
+	src_pos = src_rect.x1 << 16 | src_rect.y1;
+	dst_pos = new_state->dst.x1 << 16 | new_state->dst.y1;
+	src_size = drm_rect_width(&src_rect) << 16 | drm_rect_height(&src_rect);
+	dst_size = drm_rect_width(&new_state->dst) << 16 |
+		drm_rect_height(&new_state->dst);
+	writel(src_pos, adp->be + ADBE_SRC_START);
+	writel(src_size, adp->be + ADBE_SRC_SIZE);
+	writel(dst_pos, adp->be + ADBE_DST_START);
+	writel(dst_size, adp->be + ADBE_DST_SIZE);
+	writel(fb->pitches[0], adp->be + ADBE_STRIDE);
+	obj = drm_fb_dma_get_gem_obj(fb, 0);
+	if (obj)
+		writel(obj->dma_addr + fb->offsets[0], adp->be + ADBE_FB_BASE);
+
+	writel(0x1, adp->be + ADBE_LAYER_EN1);
+	writel(0x1, adp->be + ADBE_LAYER_EN2);
+	writel(0x1, adp->be + ADBE_LAYER_EN3);
+	writel(0x1, adp->be + ADBE_LAYER_EN4);
+	writel(ADBE_SCALE_CTL_BYPASS, adp->be + ADBE_SCALE_CTL);
+	writel(ADBE_LAYER_CTL_ENABLE | 0x1, adp->be + ADBE_LAYER_CTL);
+	writel(ADBE_PIX_FMT_XRGB32, adp->be + ADBE_PIX_FMT);
+
+}
+
+static void adp_plane_atomic_disable(struct drm_plane *plane,
+				     struct drm_atomic_state *state)
+{
+	struct adp_drv_private *adp = to_adp(plane->dev);
+	writel(0x0, adp->be + ADBE_LAYER_EN1);
+	writel(0x0, adp->be + ADBE_LAYER_EN2);
+	writel(0x0, adp->be + ADBE_LAYER_EN3);
+	writel(0x0, adp->be + ADBE_LAYER_EN4);
+	writel(ADBE_LAYER_CTL_ENABLE, adp->be + ADBE_LAYER_CTL);
+}
+
+static const struct drm_plane_helper_funcs adp_plane_helper_funcs = {
+	.atomic_check = adp_plane_atomic_check,
+	.atomic_update = adp_plane_atomic_update,
+	.atomic_disable = adp_plane_atomic_disable,
+	DRM_GEM_SHADOW_PLANE_HELPER_FUNCS
+};
+
+static const struct drm_plane_funcs adp_plane_funcs = {
+	.update_plane = drm_atomic_helper_update_plane,
+	.disable_plane = drm_atomic_helper_disable_plane,
+	DRM_GEM_SHADOW_PLANE_FUNCS
+};
+
+static const u32 plane_formats[] = {
+	DRM_FORMAT_XRGB8888,
+};
+
+#define ALL_CRTCS 1
+
+static struct adp_plane *adp_plane_new(struct adp_drv_private *adp, u8 id)
+{
+	struct drm_device *drm = &adp->drm;
+	struct adp_plane *plane;
+	enum drm_plane_type plane_type;
+
+	plane_type = (id == 0) ? DRM_PLANE_TYPE_PRIMARY :
+		DRM_PLANE_TYPE_OVERLAY;
+
+	plane = drmm_universal_plane_alloc(drm, struct adp_plane, base_plane,
+					   ALL_CRTCS, &adp_plane_funcs,
+					   plane_formats, ARRAY_SIZE(plane_formats),
+					   NULL, plane_type, "plane %d", id);
+	if (!plane) {
+		drm_err(drm, "failed to allocate plane");
+		return ERR_PTR(-ENOMEM);
+	}
+	plane->id = id;
+
+	drm_plane_helper_add(&plane->base_plane, &adp_plane_helper_funcs);
+	return plane;
+}
+
+static void adp_enable_vblank(struct adp_drv_private *adp)
+{
+	u32 cur_ctrl;
+
+	writel(ADP_INT_STATUS_INT_MASK, adp->fe + ADP_INT_STATUS);
+
+	cur_ctrl = readl(adp->fe + ADP_CTRL);
+	writel(cur_ctrl | ADP_CTRL_VBLANK_ON, adp->fe + ADP_CTRL);
+}
+
+static int adp_crtc_enable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct adp_drv_private *adp = to_adp(dev);
+	adp_enable_vblank(adp);
+
+	return 0;
+}
+
+static void adp_disable_vblank(struct adp_drv_private *adp)
+{
+	u32 cur_ctrl;
+
+	cur_ctrl = readl(adp->fe + ADP_CTRL);
+	writel(cur_ctrl & ~ADP_CTRL_VBLANK_ON, adp->fe + ADP_CTRL);
+	writel(ADP_INT_STATUS_INT_MASK, adp->fe + ADP_INT_STATUS);
+}
+
+static void adp_crtc_disable_vblank(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	struct adp_drv_private *adp = to_adp(dev);
+
+	adp_disable_vblank(adp);
+}
+
+
+static void adp_crtc_atomic_enable(struct drm_crtc *crtc,
+				   struct drm_atomic_state *state)
+{
+	struct adp_drv_private *adp = crtc_to_adp(crtc);
+	writel(0x1, adp->be + ADBE_BLEND_EN2);
+	writel(0x10, adp->be + ADBE_BLEND_EN1);
+	writel(0x1, adp->be + ADBE_BLEND_EN3);
+	writel(0x1, adp->be + ADBE_BLEND_BYPASS);
+	writel(0x1, adp->be + ADBE_BLEND_EN4);
+}
+
+static void adp_crtc_atomic_disable(struct drm_crtc *crtc,
+				    struct drm_atomic_state *state)
+{
+	struct adp_drv_private *adp = crtc_to_adp(crtc);
+	struct drm_crtc_state *old_state = drm_atomic_get_old_crtc_state(state, crtc);
+
+	drm_atomic_helper_disable_planes_on_crtc(old_state, false);
+
+	writel(0x0, adp->be + ADBE_BLEND_EN2);
+	writel(0x0, adp->be + ADBE_BLEND_EN1);
+	writel(0x0, adp->be + ADBE_BLEND_EN3);
+	writel(0x0, adp->be + ADBE_BLEND_BYPASS);
+	writel(0x0, adp->be + ADBE_BLEND_EN4);
+	drm_crtc_vblank_off(crtc);
+}
+
+static void adp_crtc_atomic_flush(struct drm_crtc *crtc,
+				  struct drm_atomic_state *state)
+{
+	u32 frame_num = 1;
+	struct adp_drv_private *adp = crtc_to_adp(crtc);
+	struct drm_crtc_state *new_state = drm_atomic_get_new_crtc_state(state, crtc);
+	u64 new_size = ALIGN(new_state->mode.hdisplay *
+			     new_state->mode.vdisplay * 4, PAGE_SIZE);
+
+	if (new_size != adp->mask_buf_size) {
+		if (adp->mask_buf)
+			dma_free_coherent(crtc->dev->dev, adp->mask_buf_size,
+					  adp->mask_buf, adp->mask_iova);
+		adp->mask_buf = NULL;
+		if (new_size != 0) {
+			adp->mask_buf = dma_alloc_coherent(crtc->dev->dev, new_size,
+							   &adp->mask_iova, GFP_KERNEL);
+			memset(adp->mask_buf, 0xFF, new_size);
+			writel(adp->mask_iova, adp->be + ADBE_MASK_BUF);
+		}
+		adp->mask_buf_size = new_size;
+	}
+	writel(ADBE_FIFO_SYNC | frame_num, adp->be + ADBE_FIFO);
+	//FIXME: use adbe flush interrupt
+	spin_lock_irq(&crtc->dev->event_lock);
+	if (crtc->state->event) {
+		drm_crtc_vblank_get(crtc);
+		adp->event = crtc->state->event;
+	}
+	crtc->state->event = NULL;
+	spin_unlock_irq(&crtc->dev->event_lock);
+}
+
+static const struct drm_crtc_funcs adp_crtc_funcs = {
+	.destroy = drm_crtc_cleanup,
+	.set_config = drm_atomic_helper_set_config,
+	.page_flip = drm_atomic_helper_page_flip,
+	.reset = drm_atomic_helper_crtc_reset,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_crtc_destroy_state,
+	.enable_vblank = adp_crtc_enable_vblank,
+	.disable_vblank = adp_crtc_disable_vblank,
+};
+
+
+static const struct drm_crtc_helper_funcs adp_crtc_helper_funcs = {
+	.atomic_enable = adp_crtc_atomic_enable,
+	.atomic_disable = adp_crtc_atomic_disable,
+	.atomic_flush = adp_crtc_atomic_flush,
+};
+
+static int adp_setup_crtc(struct adp_drv_private *adp)
+{
+	struct drm_device *drm = &adp->drm;
+	struct adp_plane *primary;
+	int ret;
+
+	primary = adp_plane_new(adp, 0);
+	if (IS_ERR(primary))
+		return PTR_ERR(primary);
+
+	ret = drm_crtc_init_with_planes(drm, &adp->crtc, &primary->base_plane,
+					NULL, &adp_crtc_funcs, NULL);
+	if (ret)
+		return ret;
+
+	drm_crtc_helper_add(&adp->crtc, &adp_crtc_helper_funcs);
+	return 0;
+}
+
+static int adp_get_modes(struct drm_connector *connector)
+{
+	struct adp_drv_private *adp = conn_to_adp(connector);
+	struct drm_display_mode *mode;
+	u32 size;
+
+	size = readl(adp->fe + ADP_SCREEN_SIZE);
+	mode = drm_mode_create(connector->dev);
+
+	mode->vdisplay = size >> 16;
+	mode->hdisplay = size & 0xFFFF;
+	mode->hsync_start = mode->hdisplay + 8;
+	mode->hsync_end = mode->hsync_start + 80;
+	mode->htotal = mode->hsync_end + 40;
+	mode->vsync_start = mode->vdisplay + 1;
+	mode->vsync_end = mode->vsync_start + 15;
+	mode->vtotal = mode->vsync_end + 6;
+	mode->clock = (mode->vtotal * mode->htotal * 60) / 1000;
+	mode->type = DRM_MODE_TYPE_DRIVER | DRM_MODE_TYPE_PREFERRED;
+	mode->flags = DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC;
+	drm_mode_set_name(mode);
+	drm_mode_probed_add(connector, mode);
+	return 1;
+}
+
+static int adp_detect_ctx(struct drm_connector *connector,
+		   struct drm_modeset_acquire_ctx *ctx,
+		   bool force) {
+	connector->display_info.non_desktop = true;
+	drm_object_property_set_value(&connector->base,
+				      connector->dev->mode_config.non_desktop_property,
+				      connector->display_info.non_desktop);
+	return connector_status_connected;
+}
+
+static const struct drm_connector_funcs adp_connector_funcs = {
+	.fill_modes = drm_helper_probe_single_connector_modes,
+	.destroy = drm_connector_cleanup,
+	.reset = drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state = drm_atomic_helper_connector_destroy_state,
+};
+
+static const struct drm_connector_helper_funcs adp_connector_helper_funcs = {
+	.get_modes = adp_get_modes,
+	.detect_ctx = adp_detect_ctx,
+};
+
+static const struct drm_mode_config_funcs adp_mode_config_funcs = {
+	.fb_create = drm_gem_fb_create_with_dirty,
+	.atomic_check = drm_atomic_helper_check,
+	.atomic_commit = drm_atomic_helper_commit,
+};
+
+static int adp_setup_mode_config(struct adp_drv_private *adp)
+{
+	struct drm_device *drm = &adp->drm;
+	int ret;
+
+	ret = drmm_mode_config_init(drm);
+	if (ret)
+		return ret;
+
+	drm->mode_config.min_width = 32;
+	drm->mode_config.min_height = 32;
+	drm->mode_config.max_width = 16384;
+	drm->mode_config.max_height = 16384;
+	drm->mode_config.preferred_depth = 24;
+	drm->mode_config.prefer_shadow = 0;
+	drm->mode_config.funcs = &adp_mode_config_funcs;
+
+	ret = adp_setup_crtc(adp);
+	if (ret) {
+		drm_err(drm, "failed to create crtc");
+		return ret;
+	}
+
+	adp->encoder.possible_crtcs = ALL_CRTCS;
+	ret = drm_simple_encoder_init(drm, &adp->encoder, DRM_MODE_ENCODER_DSI);
+	if (ret) {
+		drm_err(drm, "failed to init encoder");
+		return ret;
+	}
+	drm_connector_helper_add(&adp->connector,
+				 &adp_connector_helper_funcs);
+	ret = drm_connector_init(drm, &adp->connector, &adp_connector_funcs,
+				 DRM_MODE_CONNECTOR_DSI);
+	if (ret)
+		return ret;
+
+	drm_connector_attach_encoder(&adp->connector, &adp->encoder);
+
+	ret = drm_vblank_init(drm, drm->mode_config.num_crtc);
+	if (ret < 0) {
+		drm_err(drm, "failed to initialize vblank");
+		return ret;
+	}
+
+	drm_mode_config_reset(drm);
+
+	return 0;
+}
+
+static int adp_parse_of(struct platform_device *pdev, struct adp_drv_private *adp)
+{
+	adp->be = devm_platform_ioremap_resource_byname(pdev, "be");
+	if (IS_ERR(adp->be)) {
+		dev_err(&pdev->dev, "failed to map display backend mmio");
+		return PTR_ERR(adp->be);
+	}
+
+	adp->fe = devm_platform_ioremap_resource_byname(pdev, "fe");
+	if (IS_ERR(adp->fe)) {
+		dev_err(&pdev->dev, "failed to map display pipe mmio");
+		return PTR_ERR(adp->fe);
+	}
+
+	adp->mipi = devm_platform_ioremap_resource_byname(pdev, "mipi");
+	if (IS_ERR(adp->mipi)) {
+		dev_err(&pdev->dev, "failed to map mipi mmio");
+		return PTR_ERR(adp->mipi);
+	}
+
+	adp->be_irq = platform_get_irq_byname(pdev, "be");
+	if (adp->be_irq < 0) {
+		dev_err(&pdev->dev, "failed to find be irq");
+		return adp->be_irq;
+	}
+
+	adp->fe_irq = platform_get_irq_byname(pdev, "fe");
+	if (adp->fe_irq < 0) {
+		dev_err(&pdev->dev, "failed to find fe irq");
+		return adp->fe_irq;
+	}
+	return 0;
+}
+
+
+static int adp_dsi_gen_pkt_hdr_write(struct adp_drv_private *adp, u32 hdr_val)
+{
+	int ret;
+	u32 val, mask;
+
+	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
+				 val, !(val & GEN_CMD_FULL), 1000,
+				 CMD_PKT_STATUS_TIMEOUT_US);
+	if (ret) {
+		dev_err(adp->drm.dev, "failed to get available command FIFO\n");
+		return ret;
+	}
+
+	writel(hdr_val, adp->mipi + DSI_GEN_HDR);
+
+	mask = GEN_CMD_EMPTY | GEN_PLD_W_EMPTY;
+	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
+				 val, (val & mask) == mask,
+				 1000, CMD_PKT_STATUS_TIMEOUT_US);
+	if (ret) {
+		dev_err(adp->drm.dev, "failed to write command FIFO\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+static int adp_dsi_write(struct adp_drv_private *adp,
+			 const struct mipi_dsi_packet *packet)
+{
+	const u8 *tx_buf = packet->payload;
+	int len = packet->payload_length, pld_data_bytes = sizeof(u32), ret;
+	__le32 word;
+	u32 val;
+
+	while (len) {
+		if (len < pld_data_bytes) {
+			word = 0;
+			memcpy(&word, tx_buf, len);
+			writel(le32_to_cpu(word), adp->mipi + DSI_GEN_PLD_DATA);
+			len = 0;
+		} else {
+			memcpy(&word, tx_buf, pld_data_bytes);
+			writel(le32_to_cpu(word), adp->mipi + DSI_GEN_PLD_DATA);
+			tx_buf += pld_data_bytes;
+			len -= pld_data_bytes;
+		}
+
+		ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
+					 val, !(val & GEN_PLD_W_FULL), 1000,
+					 CMD_PKT_STATUS_TIMEOUT_US);
+		if (ret) {
+			dev_err(adp->drm.dev,
+				"failed to get available write payload FIFO\n");
+			return ret;
+		}
+	}
+
+	word = 0;
+	memcpy(&word, packet->header, sizeof(packet->header));
+	return adp_dsi_gen_pkt_hdr_write(adp, le32_to_cpu(word));
+}
+
+static int adp_dsi_read(struct adp_drv_private *adp,
+			const struct mipi_dsi_msg *msg)
+{
+	int i, j, ret, len = msg->rx_len;
+	u8 *buf = msg->rx_buf;
+	u32 val;
+
+	/* Wait end of the read operation */
+	ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
+				 val, !(val & GEN_RD_CMD_BUSY),
+				 1000, CMD_PKT_STATUS_TIMEOUT_US);
+	if (ret) {
+		dev_err(adp->drm.dev, "Timeout during read operation\n");
+		return ret;
+	}
+
+	for (i = 0; i < len; i += 4) {
+		/* Read fifo must not be empty before all bytes are read */
+		ret = readl_poll_timeout(adp->mipi + DSI_CMD_PKT_STATUS,
+					 val, !(val & GEN_PLD_R_EMPTY),
+					 1000, CMD_PKT_STATUS_TIMEOUT_US);
+		if (ret) {
+			dev_err(adp->drm.dev, "Read payload FIFO is empty\n");
+			return ret;
+		}
+
+		val = readl(adp->mipi + DSI_GEN_PLD_DATA);
+		for (j = 0; j < 4 && j + i < len; j++)
+			buf[i + j] = val >> (8 * j);
+	}
+
+	return ret;
+}
+
+static ssize_t adp_dsi_host_transfer(struct mipi_dsi_host *host,
+				     const struct mipi_dsi_msg *msg)
+{
+	struct adp_drv_private *adp = mipi_to_adp(host);
+	struct mipi_dsi_packet packet;
+	int ret, nb_bytes;
+
+	ret = mipi_dsi_create_packet(&packet, msg);
+	if (ret) {
+		dev_err(adp->drm.dev, "failed to create packet: %d\n", ret);
+		return ret;
+	}
+
+	ret = adp_dsi_write(adp, &packet);
+	if (ret)
+		return ret;
+
+	if (msg->rx_buf && msg->rx_len) {
+		ret = adp_dsi_read(adp, msg);
+		if (ret)
+			return ret;
+		nb_bytes = msg->rx_len;
+	} else {
+		nb_bytes = packet.size;
+	}
+
+	return nb_bytes;
+}
+
+static int adp_dsi_host_attach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dev)
+{
+	return 0;
+}
+
+static int adp_dsi_host_detach(struct mipi_dsi_host *host,
+			       struct mipi_dsi_device *dev)
+{
+	return 0;
+}
+
+static const struct mipi_dsi_host_ops adp_dsi_host_ops = {
+	.transfer = adp_dsi_host_transfer,
+	.attach = adp_dsi_host_attach,
+	.detach = adp_dsi_host_detach,
+};
+
+static irqreturn_t adp_fe_irq(int irq, void *arg)
+{
+	struct adp_drv_private *adp = (struct adp_drv_private *)arg;
+	u32 int_status;
+	u32 int_ctl;
+
+	spin_lock(&adp->irq_lock);
+
+	int_status = readl(adp->fe + ADP_INT_STATUS);
+	if (int_status & ADP_INT_STATUS_VBLANK) {
+		drm_crtc_handle_vblank(&adp->crtc);
+		spin_lock(&adp->crtc.dev->event_lock);
+		if (adp->event) {
+			int_ctl = readl(adp->fe + ADP_CTRL);
+			if ((int_ctl & 0xF00) == 0x600) {
+				drm_crtc_send_vblank_event(&adp->crtc, adp->event);
+				adp->event = NULL;
+				drm_crtc_vblank_put(&adp->crtc);
+			}
+		}
+		spin_unlock(&adp->crtc.dev->event_lock);
+	}
+
+	writel(int_status, adp->fe + ADP_INT_STATUS);
+
+	spin_unlock(&adp->irq_lock);
+
+	return IRQ_HANDLED;
+}
+
+static int adp_probe(struct platform_device *pdev)
+{
+	struct adp_drv_private *adp;
+	int err;
+
+	adp = devm_drm_dev_alloc(&pdev->dev, &adp_driver, struct adp_drv_private, drm);
+	if (IS_ERR(adp))
+		return PTR_ERR(adp);
+
+	spin_lock_init(&adp->irq_lock);
+
+	dev_set_drvdata(&pdev->dev, &adp->drm);
+
+	err = adp_parse_of(pdev, adp);
+	if (err < 0)
+		return err;
+
+	adp->dsi.dev = &pdev->dev;
+	adp->dsi.ops = &adp_dsi_host_ops;
+	err = mipi_dsi_host_register(&adp->dsi);
+	if (err < 0)
+		return err;
+
+	adp_disable_vblank(adp);
+	writel(ADP_CTRL_FIFO_ON | ADP_CTRL_VBLANK_ON, adp->fe + ADP_CTRL);
+
+	err = adp_setup_mode_config(adp);
+	if (err < 0)
+		return err;
+
+	err = devm_request_irq(&pdev->dev, adp->fe_irq, adp_fe_irq, 0,
+			       "adp-fe", adp);
+	if (err)
+		return err;
+
+	err = drm_dev_register(&adp->drm, 0);
+	if (err)
+		return err;
+	return 0;
+}
+
+static void adp_remove(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct drm_device *drm = dev_get_drvdata(dev);
+	struct adp_drv_private *adp = to_adp(drm);
+
+	adp_disable_vblank(adp);
+	mipi_dsi_host_unregister(&adp->dsi);
+	drm_dev_unregister(drm);
+	dev_set_drvdata(dev, NULL);
+	drm_atomic_helper_shutdown(drm);
+}
+
+static const struct of_device_id adp_of_match[] = {
+	{ .compatible = "apple,h7-display-pipe", },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, adp_of_match);
+
+static struct platform_driver adp_platform_driver = {
+	.driver = {
+		.name = "adp",
+		.of_match_table = adp_of_match,
+	},
+	.probe = adp_probe,
+	.remove = adp_remove,
+};
+
+module_platform_driver(adp_platform_driver);
+
+MODULE_DESCRIPTION("Apple Display Pipe DRM driver");
+MODULE_LICENSE("GPL v2");

From 27f8385ccf4b6b1c15e826b13d63011e88b2988c Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Tue, 18 Apr 2023 23:03:15 +0300
Subject: [PATCH 0374/1027] gpu: drm: adp: Add a backlight driver for the
 Summit LCD

This is the display panel used for the touchbar on laptops that have it.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 drivers/gpu/drm/adp/panel-summit.c | 104 +++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)
 create mode 100644 drivers/gpu/drm/adp/panel-summit.c

diff --git a/drivers/gpu/drm/adp/panel-summit.c b/drivers/gpu/drm/adp/panel-summit.c
new file mode 100644
index 00000000000000..633651fea92445
--- /dev/null
+++ b/drivers/gpu/drm/adp/panel-summit.c
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+#include <linux/backlight.h>
+#include <drm/drm_mipi_dsi.h>
+#include <video/mipi_display.h>
+
+struct summit_data {
+	struct mipi_dsi_device *dsi;
+	struct backlight_device *bl;
+};
+
+static int summit_set_brightness(struct device *dev)
+{
+	struct summit_data *panel = dev_get_drvdata(dev);
+	int level = backlight_get_brightness(panel->bl);
+	ssize_t err = mipi_dsi_dcs_write(panel->dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+					 &level, 1);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static int summit_bl_update_status(struct backlight_device *dev)
+{
+	return summit_set_brightness(&dev->dev);
+}
+
+static int summit_bl_get_brightness(struct backlight_device *dev)
+{
+	return backlight_get_brightness(dev);
+}
+
+static const struct backlight_ops summit_bl_ops = {
+	.get_brightness = summit_bl_get_brightness,
+	.update_status	= summit_bl_update_status,
+};
+
+static int summit_probe(struct mipi_dsi_device *dsi)
+{
+	struct backlight_properties props = { 0 };
+	struct device *dev = &dsi->dev;
+	struct summit_data *panel;
+	panel = devm_kzalloc(dev, sizeof(*panel), GFP_KERNEL);
+	if (!panel)
+		return -ENOMEM;
+
+	mipi_dsi_set_drvdata(dsi, panel);
+	panel->dsi = dsi;
+	props.max_brightness = 255;
+	props.type = BACKLIGHT_RAW;
+
+	panel->bl = devm_backlight_device_register(dev, dev_name(dev),
+						   dev, panel, &summit_bl_ops, &props);
+	if (IS_ERR(panel->bl)) {
+		return PTR_ERR(panel->bl);
+	}
+
+	return mipi_dsi_attach(dsi);
+}
+
+static void summit_remove(struct mipi_dsi_device *dsi)
+{
+	mipi_dsi_detach(dsi);
+}
+
+static int summit_resume(struct device *dev)
+{
+	return summit_set_brightness(dev);
+}
+
+static int summit_suspend(struct device *dev)
+{
+	int level = 0;
+	struct summit_data *panel = dev_get_drvdata(dev);
+	ssize_t err = mipi_dsi_dcs_write(panel->dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
+					 &level, 1);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+static DEFINE_SIMPLE_DEV_PM_OPS(summit_pm_ops, summit_suspend,
+				summit_resume);
+
+static const struct of_device_id summit_of_match[] = {
+	{ .compatible = "apple,summit" },
+	{},
+};
+
+MODULE_DEVICE_TABLE(of, summit_of_match);
+
+static struct mipi_dsi_driver summit_driver = {
+	.probe = summit_probe,
+	.remove = summit_remove,
+	.driver = {
+		.name = "panel-summit",
+		.of_match_table = summit_of_match,
+		.pm = pm_sleep_ptr(&summit_pm_ops),
+	},
+};
+module_mipi_dsi_driver(summit_driver);
+
+MODULE_DESCRIPTION("Summit Display Panel Driver");
+MODULE_LICENSE("GPL");

From f1c6fd05d3f258534f2eea646b8f69e39a2d43c0 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Tue, 18 Apr 2023 23:11:07 +0300
Subject: [PATCH 0375/1027] MAINTAINERS: Add entries for touchbar display
 driver

Add the MAINTAINERS entries for the driver

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 MAINTAINERS | 1 +
 1 file changed, 1 insertion(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd10..5ff228e0bde2dd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2074,6 +2074,7 @@ F:	drivers/bluetooth/hci_bcm4377.c
 F:	drivers/clk/clk-apple-nco.c
 F:	drivers/cpufreq/apple-soc-cpufreq.c
 F:	drivers/dma/apple-admac.c
+F:	drivers/gpu/drm/adp/
 F:	drivers/pmdomain/apple/
 F:	drivers/i2c/busses/i2c-pasemi-core.c
 F:	drivers/i2c/busses/i2c-pasemi-platform.c

From 0bda3794d1fcd87afb146c56a489e8976f7d1631 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 17 Jun 2023 09:24:17 +0200
Subject: [PATCH 0376/1027] gpu: drm: adp: Refuse X* as client in open()

This is hack which might or might not be removed after the Xorg's
modesetting driver is fixed to ignore non-desktop devices.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/adp/adp_drv.c | 35 ++++++++++++++++++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/adp/adp_drv.c b/drivers/gpu/drm/adp/adp_drv.c
index 23acfa9ccb00b0..9fcd233330153f 100644
--- a/drivers/gpu/drm/adp/adp_drv.c
+++ b/drivers/gpu/drm/adp/adp_drv.c
@@ -1,10 +1,16 @@
 // SPDX-License-Identifier: GPL-2.0-only
 
+#include <linux/anon_inodes.h>
 #include <linux/dma-mapping.h>
 #include <linux/io.h>
 #include <linux/iopoll.h>
+#include <linux/file.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
+#include <linux/types.h>
+
+#include <asm/current.h>
+
 #include <drm/drm_atomic.h>
 #include <drm/drm_atomic_helper.h>
 #include <drm/drm_damage_helper.h>
@@ -12,6 +18,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_edid.h>
 #include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_file.h>
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_gem_atomic_helper.h>
 #include <drm/drm_gem_dma_helper.h>
@@ -72,7 +79,33 @@
 #define GEN_RD_CMD_BUSY BIT(6)
 #define CMD_PKT_STATUS_TIMEOUT_US 20000
 
-DEFINE_DRM_GEM_DMA_FOPS(adp_fops);
+static int adp_open(struct inode *inode, struct file *filp)
+{
+	/*
+	 * The modesetting driver does not check the non-desktop connector
+	 * property and keeps the device open and locked. If the touchbar daemon
+	 * opens the device first modesetting breaks the whole X session.
+	 * Simply refuse to open the device for X11 server processes as
+	 * workaround.
+	 */
+	if (current->comm[0] == 'X')
+		return -EBUSY;
+
+	return drm_open(inode, filp);
+}
+
+static const struct file_operations adp_fops = {
+	.owner          = THIS_MODULE,
+	.open           = adp_open,
+	.release        = drm_release,
+	.unlocked_ioctl = drm_ioctl,
+	.compat_ioctl   = drm_compat_ioctl,
+	.poll           = drm_poll,
+	.read           = drm_read,
+	.llseek         = noop_llseek,
+	.mmap           = drm_gem_mmap,
+	DRM_GEM_DMA_UNMAPPED_AREA_FOPS
+};
 
 static int adp_drm_gem_dumb_create(struct drm_file *file_priv,
 					struct drm_device *drm,

From 99b57bbf3e206c3b0660dc822b9a05bda0bafed7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 17 Jun 2023 12:14:34 +0200
Subject: [PATCH 0377/1027] gpu: drm: adp: base the max framebuffer width and
 height on screen size

This is not necessary but makes a check for non-desktop devices in
Xorg's modesetting driver very simple. The intended daemon(s) for the
touchbar display have no need to allocate larger than the display
framebuffers (ignoring alignment).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/adp/adp_drv.c | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/adp/adp_drv.c b/drivers/gpu/drm/adp/adp_drv.c
index 9fcd233330153f..3333e94728b565 100644
--- a/drivers/gpu/drm/adp/adp_drv.c
+++ b/drivers/gpu/drm/adp/adp_drv.c
@@ -35,6 +35,8 @@
 #define ADP_CTRL_VBLANK_ON 0x12
 #define ADP_CTRL_FIFO_ON 0x601
 #define ADP_SCREEN_SIZE 0x0c
+#define ADP_SCREEN_HSIZE GENMASK(15, 0)
+#define ADP_SCREEN_VSIZE GENMASK(31, 16)
 
 #define ADBE_FIFO 0x10c0
 #define ADBE_FIFO_SYNC 0xc0000000
@@ -415,8 +417,8 @@ static int adp_get_modes(struct drm_connector *connector)
 	size = readl(adp->fe + ADP_SCREEN_SIZE);
 	mode = drm_mode_create(connector->dev);
 
-	mode->vdisplay = size >> 16;
-	mode->hdisplay = size & 0xFFFF;
+	mode->vdisplay = FIELD_GET(ADP_SCREEN_VSIZE, size);
+	mode->hdisplay = FIELD_GET(ADP_SCREEN_HSIZE, size);
 	mode->hsync_start = mode->hdisplay + 8;
 	mode->hsync_end = mode->hsync_start + 80;
 	mode->htotal = mode->hsync_end + 40;
@@ -464,15 +466,28 @@ static int adp_setup_mode_config(struct adp_drv_private *adp)
 {
 	struct drm_device *drm = &adp->drm;
 	int ret;
+	u32 size;
 
 	ret = drmm_mode_config_init(drm);
 	if (ret)
 		return ret;
 
+	/*
+	 * Query screen size restrict the frame buffer size to the screen size
+	 * aligned to the next multiple of 64. This is not necessary but can be
+	 * used as simple check for non-desktop devices.
+	 * Xorg's modesetting driver does not care about the connector
+	 * "non-desktop" property. The max frame buffer width or height can be
+	 * easily checked and a device can be reject if the max width/height is
+	 * smaller than 120 for example.
+	 * Any touchbar daemon is not limited by this small framebuffer size.
+	 */
+	size = readl(adp->fe + ADP_SCREEN_SIZE);
+
 	drm->mode_config.min_width = 32;
 	drm->mode_config.min_height = 32;
-	drm->mode_config.max_width = 16384;
-	drm->mode_config.max_height = 16384;
+	drm->mode_config.max_width = ALIGN(FIELD_GET(ADP_SCREEN_HSIZE, size), 64);
+	drm->mode_config.max_height = ALIGN(FIELD_GET(ADP_SCREEN_VSIZE, size), 64);
 	drm->mode_config.preferred_depth = 24;
 	drm->mode_config.prefer_shadow = 0;
 	drm->mode_config.funcs = &adp_mode_config_funcs;

From fced2d78c39009a16650a285555847f507579d7d Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 28 Jan 2023 16:42:32 +0300
Subject: [PATCH 0378/1027] dt-bindings: input: touchscreen: Add Z2 controller
 bindings.

Add bindings for touchscreen controllers attached using the Z2 protocol.
Those are present in most Apple devices.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 .../touchscreen/apple,z2-touchscreen.yaml     | 87 +++++++++++++++++++
 1 file changed, 87 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/input/touchscreen/apple,z2-touchscreen.yaml

diff --git a/Documentation/devicetree/bindings/input/touchscreen/apple,z2-touchscreen.yaml b/Documentation/devicetree/bindings/input/touchscreen/apple,z2-touchscreen.yaml
new file mode 100644
index 00000000000000..c7619e6c17d2ac
--- /dev/null
+++ b/Documentation/devicetree/bindings/input/touchscreen/apple,z2-touchscreen.yaml
@@ -0,0 +1,87 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/input/touchscreen/apple,z2-touchscreen.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple touchscreens attached using the Z2 protocol.
+
+maintainers:
+  - asahi@lists.linux.dev
+  - Sasha Finkelstein <fnkl.kernel@gmail.com>
+
+description: A series of touschscreen controllers used in Apple products.
+
+allOf:
+  - $ref: touchscreen.yaml#
+  - $ref: /schemas/spi/spi-peripheral-props.yaml#
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,j293-touchbar
+          - apple,j493-touchbar
+      - const: apple,z2-touchbar
+      - const: apple,z2-multitouch
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+  reset-gpios:
+    maxItems: 1
+
+  cs-gpios:
+    maxItems: 1
+
+  firmware-name:
+    maxItems: 1
+
+  label:
+    maxItems: 1
+
+  touchscreen-size-x: true
+  touchscreen-size-y: true
+  spi-max-frequency: true
+  spi-cs-hold-delay-ns: true
+  spi-cs-setup-delay-ns: true
+
+required:
+  - compatible
+  - interrupts
+  - reset-gpios
+  - firmware-name
+  - label
+  - touchscreen-size-x
+  - touchscreen-size-y
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/gpio/gpio.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    spi {
+        #address-cells = <1>;
+        #size-cells = <0>;
+
+        touchscreen@0 {
+            compatible = "apple,j293-touchbar", "apple,z2-touchbar",
+              "apple,z2-multitouch";
+            reg = <0>;
+            spi-max-frequency = <11500000>;
+            reset-gpios = <&pinctrl_ap 139 0>;
+            cs-gpios = <&pinctrl_ap 109 0>;
+            interrupts-extended = <&pinctrl_ap 194 IRQ_TYPE_EDGE_FALLING>;
+            firmware-name = "apple/dfrmtfw-j293.bin";
+            touchscreen-size-x = <23045>;
+            touchscreen-size-y = <640>;
+            label = "MacBookPro17,1 Touch Bar";
+        };
+    };
+
+...

From ae40dcbf7ee9325a2843e661ebbdb01ffd9d765e Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 21 Jan 2023 19:46:48 +0300
Subject: [PATCH 0379/1027] input: apple_z2: Add a driver for Apple Z2
 touchscreens

Adds a driver for Apple touchscreens using the Z2 protocol.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/input/touchscreen/Kconfig    |  13 +
 drivers/input/touchscreen/Makefile   |   1 +
 drivers/input/touchscreen/apple_z2.c | 498 +++++++++++++++++++++++++++
 3 files changed, 512 insertions(+)
 create mode 100644 drivers/input/touchscreen/apple_z2.c

diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig
index c821fe3ee794e3..9d43b082a863f1 100644
--- a/drivers/input/touchscreen/Kconfig
+++ b/drivers/input/touchscreen/Kconfig
@@ -103,6 +103,19 @@ config TOUCHSCREEN_ADC
 	  To compile this driver as a module, choose M here: the
 	  module will be called resistive-adc-touch.ko.
 
+config TOUCHSCREEN_APPLE_Z2
+	tristate "Apple Z2 touchscreens"
+	default ARCH_APPLE
+	depends on SPI && OF
+	help
+	  Say Y here if you have an Apple device with
+	  a touchscreen or a touchbar.
+
+	  If unsure, say N.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called apple_z2.
+
 config TOUCHSCREEN_AR1021_I2C
 	tristate "Microchip AR1020/1021 i2c touchscreen"
 	depends on I2C && OF
diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile
index a81cb5aa21a5b9..31dffeb6a6c13b 100644
--- a/drivers/input/touchscreen/Makefile
+++ b/drivers/input/touchscreen/Makefile
@@ -15,6 +15,7 @@ obj-$(CONFIG_TOUCHSCREEN_AD7879_I2C)	+= ad7879-i2c.o
 obj-$(CONFIG_TOUCHSCREEN_AD7879_SPI)	+= ad7879-spi.o
 obj-$(CONFIG_TOUCHSCREEN_ADC)		+= resistive-adc-touch.o
 obj-$(CONFIG_TOUCHSCREEN_ADS7846)	+= ads7846.o
+obj-$(CONFIG_TOUCHSCREEN_APPLE_Z2)	+= apple_z2.o
 obj-$(CONFIG_TOUCHSCREEN_AR1021_I2C)	+= ar1021_i2c.o
 obj-$(CONFIG_TOUCHSCREEN_ATMEL_MXT)	+= atmel_mxt_ts.o
 obj-$(CONFIG_TOUCHSCREEN_AUO_PIXCIR)	+= auo-pixcir-ts.o
diff --git a/drivers/input/touchscreen/apple_z2.c b/drivers/input/touchscreen/apple_z2.c
new file mode 100644
index 00000000000000..8576a00ef6e24b
--- /dev/null
+++ b/drivers/input/touchscreen/apple_z2.c
@@ -0,0 +1,498 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple Z2 touchscreen driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <asm/unaligned.h>
+#include <linux/delay.h>
+#include <linux/firmware.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/spi/spi.h>
+
+#define APPLE_Z2_NUM_FINGERS_OFFSET      16
+#define APPLE_Z2_FINGERS_OFFSET          24
+#define APPLE_Z2_TOUCH_STARTED           3
+#define APPLE_Z2_TOUCH_MOVED             4
+#define APPLE_Z2_CMD_READ_INTERRUPT_DATA 0xEB
+#define APPLE_Z2_HBPP_CMD_BLOB           0x3001
+#define APPLE_Z2_FW_MAGIC                0x5746325A
+#define LOAD_COMMAND_INIT_PAYLOAD        0
+#define LOAD_COMMAND_SEND_BLOB           1
+#define LOAD_COMMAND_SEND_CALIBRATION    2
+
+struct apple_z2 {
+	struct spi_device *spidev;
+	struct gpio_desc *cs_gpio;
+	struct gpio_desc *reset_gpio;
+	struct input_dev *input_dev;
+	struct completion boot_irq;
+	int booted;
+	int open;
+	int counter;
+	int y_size;
+	const char *fw_name;
+	const char *cal_blob;
+	int cal_size;
+};
+
+struct apple_z2_finger {
+	u8 finger;
+	u8 state;
+	__le16 unknown2;
+	__le16 abs_x;
+	__le16 abs_y;
+	__le16 rel_x;
+	__le16 rel_y;
+	__le16 tool_major;
+	__le16 tool_minor;
+	__le16 orientation;
+	__le16 touch_major;
+	__le16 touch_minor;
+	__le16 unused[2];
+	__le16 pressure;
+	__le16 multi;
+} __packed;
+
+struct apple_z2_hbpp_blob_hdr {
+	u16 cmd;
+	u16 len;
+	u32 addr;
+	u16 checksum;
+} __packed;
+
+struct apple_z2_fw_hdr {
+	u32 magic;
+	u32 version;
+} __packed;
+
+struct apple_z2_read_interrupt_cmd {
+	u8 cmd;
+	u8 counter;
+	u8 unused[12];
+	__le16 checksum;
+} __packed;
+
+static void apple_z2_parse_touches(struct apple_z2 *z2, char *msg, size_t msg_len)
+{
+	int i;
+	int nfingers;
+	int slot;
+	int slot_valid;
+	struct apple_z2_finger *fingers;
+
+	if (!z2->open)
+		return;
+	if (msg_len <= APPLE_Z2_NUM_FINGERS_OFFSET)
+		return;
+	nfingers = msg[APPLE_Z2_NUM_FINGERS_OFFSET];
+	fingers = (struct apple_z2_finger *)(msg + APPLE_Z2_FINGERS_OFFSET);
+	for (i = 0; i < nfingers; i++) {
+		slot = input_mt_get_slot_by_key(z2->input_dev, fingers[i].finger);
+		if (slot < 0) {
+			dev_warn(&z2->spidev->dev, "unable to get slot for finger\n");
+			continue;
+		}
+		slot_valid = fingers[i].state == APPLE_Z2_TOUCH_STARTED ||
+			     fingers[i].state == APPLE_Z2_TOUCH_MOVED;
+		input_mt_slot(z2->input_dev, slot);
+		input_mt_report_slot_state(z2->input_dev, MT_TOOL_FINGER, slot_valid);
+		if (!slot_valid)
+			continue;
+		input_report_abs(z2->input_dev, ABS_MT_POSITION_X,
+				 le16_to_cpu(fingers[i].abs_x));
+		input_report_abs(z2->input_dev, ABS_MT_POSITION_Y,
+				 z2->y_size - le16_to_cpu(fingers[i].abs_y));
+		input_report_abs(z2->input_dev, ABS_MT_WIDTH_MAJOR,
+				 le16_to_cpu(fingers[i].tool_major));
+		input_report_abs(z2->input_dev, ABS_MT_WIDTH_MINOR,
+				 le16_to_cpu(fingers[i].tool_minor));
+		input_report_abs(z2->input_dev, ABS_MT_ORIENTATION,
+				 le16_to_cpu(fingers[i].orientation));
+		input_report_abs(z2->input_dev, ABS_MT_TOUCH_MAJOR,
+				 le16_to_cpu(fingers[i].touch_major));
+		input_report_abs(z2->input_dev, ABS_MT_TOUCH_MINOR,
+				 le16_to_cpu(fingers[i].touch_minor));
+	}
+	input_mt_sync_frame(z2->input_dev);
+	input_sync(z2->input_dev);
+}
+
+static int apple_z2_spi_sync(struct apple_z2 *z2, struct spi_message *msg)
+{
+	int error;
+
+	if (z2->cs_gpio) {
+		gpiod_direction_output(z2->cs_gpio, 0);
+	}
+
+	error = spi_sync(z2->spidev, msg);
+
+	if (z2->cs_gpio) {
+		gpiod_direction_output(z2->cs_gpio, 1);
+	}
+
+	return error;
+}
+
+static int apple_z2_read_packet(struct apple_z2 *z2)
+{
+	struct spi_message msg;
+	struct spi_transfer xfer;
+	struct apple_z2_read_interrupt_cmd len_cmd;
+	char len_rx[16];
+	size_t pkt_len;
+	char *pkt_rx;
+	int error;
+
+	spi_message_init(&msg);
+	memset(&xfer, 0, sizeof(xfer));
+	memset(&len_cmd, 0, sizeof(len_cmd));
+
+	len_cmd.cmd = APPLE_Z2_CMD_READ_INTERRUPT_DATA;
+	len_cmd.counter = z2->counter + 1;
+	len_cmd.checksum = cpu_to_le16(APPLE_Z2_CMD_READ_INTERRUPT_DATA + 1 + z2->counter);
+	z2->counter = 1 - z2->counter;
+	xfer.tx_buf = &len_cmd;
+	xfer.rx_buf = len_rx;
+	xfer.len = sizeof(len_cmd);
+
+	spi_message_add_tail(&xfer, &msg);
+	error = apple_z2_spi_sync(z2, &msg);
+	if (error)
+		return error;
+
+	pkt_len = (get_unaligned_le16(len_rx + 1) + 8) & (-4);
+	pkt_rx = kzalloc(pkt_len, GFP_KERNEL);
+	if (!pkt_rx)
+		return -ENOMEM;
+
+	spi_message_init(&msg);
+	memset(&xfer, 0, sizeof(xfer));
+	xfer.rx_buf = pkt_rx;
+	xfer.len = pkt_len;
+
+	spi_message_add_tail(&xfer, &msg);
+	error = apple_z2_spi_sync(z2, &msg);
+
+	if (!error)
+		apple_z2_parse_touches(z2, pkt_rx + 5, pkt_len - 5);
+
+	kfree(pkt_rx);
+	return error;
+}
+
+static irqreturn_t apple_z2_irq(int irq, void *data)
+{
+	struct spi_device *spi = data;
+	struct apple_z2 *z2 = spi_get_drvdata(spi);
+
+	if (!z2->booted)
+		complete(&z2->boot_irq);
+	else
+		apple_z2_read_packet(z2);
+
+	return IRQ_HANDLED;
+}
+
+static void apple_z2_build_cal_blob(struct apple_z2 *z2, u32 address, char *data)
+{
+	u16 len_words = (z2->cal_size + 3) / 4;
+	u32 checksum = 0;
+	u16 checksum_hdr = 0;
+	int i;
+	struct apple_z2_hbpp_blob_hdr *hdr;
+
+	hdr = (struct apple_z2_hbpp_blob_hdr *)data;
+	hdr->cmd = APPLE_Z2_HBPP_CMD_BLOB;
+	hdr->len = len_words;
+	hdr->addr = address;
+
+	for (i = 2; i < 8; i++)
+		checksum_hdr += data[i];
+
+	hdr->checksum = checksum_hdr;
+	memcpy(data + 10, z2->cal_blob, z2->cal_size);
+
+	for (i = 0; i < z2->cal_size; i++)
+		checksum += z2->cal_blob[i];
+
+	*(u32 *)(data + z2->cal_size + 10) = checksum;
+}
+
+static int apple_z2_send_firmware_blob(struct apple_z2 *z2, const char *data, u32 size, u8 bpw)
+{
+	struct spi_message msg;
+	struct spi_transfer blob_xfer, ack_xfer;
+	char int_ack[] = {0x1a, 0xa1};
+	char ack_rsp[] = {0, 0};
+	int error;
+
+	spi_message_init(&msg);
+	memset(&blob_xfer, 0, sizeof(blob_xfer));
+	memset(&ack_xfer, 0, sizeof(ack_xfer));
+	blob_xfer.tx_buf = data;
+	blob_xfer.len = size;
+	blob_xfer.bits_per_word = bpw;
+	spi_message_add_tail(&blob_xfer, &msg);
+	ack_xfer.tx_buf = int_ack;
+	ack_xfer.rx_buf = ack_rsp;
+	ack_xfer.len = 2;
+	spi_message_add_tail(&ack_xfer, &msg);
+	reinit_completion(&z2->boot_irq);
+	error = apple_z2_spi_sync(z2, &msg);
+	if (error)
+		return error;
+	wait_for_completion_timeout(&z2->boot_irq, msecs_to_jiffies(20));
+	return 0;
+}
+
+static int apple_z2_upload_firmware(struct apple_z2 *z2)
+{
+	const struct firmware *fw;
+	struct apple_z2_fw_hdr *fw_hdr;
+	size_t fw_idx = sizeof(struct apple_z2_fw_hdr);
+	int error;
+	u32 load_cmd;
+	u32 size;
+	u32 address;
+	char *data;
+
+	error = request_firmware(&fw, z2->fw_name, &z2->spidev->dev);
+	if (error) {
+		dev_err(&z2->spidev->dev, "unable to load firmware");
+		return error;
+	}
+
+	fw_hdr = (struct apple_z2_fw_hdr *)fw->data;
+	if (fw_hdr->magic != APPLE_Z2_FW_MAGIC || fw_hdr->version != 1) {
+		dev_err(&z2->spidev->dev, "invalid firmware header");
+		return -EINVAL;
+	}
+
+	while (fw_idx < fw->size) {
+		if (fw->size - fw_idx < 8) {
+			dev_err(&z2->spidev->dev, "firmware malformed");
+			error = -EINVAL;
+			goto error;
+		}
+
+		load_cmd = *(u32 *)(fw->data + fw_idx);
+		fw_idx += 4;
+		if (load_cmd == LOAD_COMMAND_INIT_PAYLOAD || load_cmd == LOAD_COMMAND_SEND_BLOB) {
+			size = *(u32 *)(fw->data + fw_idx);
+			fw_idx += 4;
+			if (fw->size - fw_idx < size) {
+				dev_err(&z2->spidev->dev, "firmware malformed");
+				error = -EINVAL;
+				goto error;
+			}
+			error = apple_z2_send_firmware_blob(z2, fw->data + fw_idx,
+							  size, load_cmd == LOAD_COMMAND_SEND_BLOB ? 16 : 8);
+			if (error)
+				goto error;
+			fw_idx += size;
+		} else if (load_cmd == 2) {
+			address = *(u32 *)(fw->data + fw_idx);
+			fw_idx += 4;
+		        if (z2->cal_size != 0) {
+				size = z2->cal_size + sizeof(struct apple_z2_hbpp_blob_hdr) + 4;
+				data = kzalloc(size, GFP_KERNEL);
+				apple_z2_build_cal_blob(z2, address, data);
+				error = apple_z2_send_firmware_blob(z2, data, size, 16);
+				kfree(data);
+				if (error)
+					goto error;
+			}
+		} else {
+			dev_err(&z2->spidev->dev, "firmware malformed");
+			error = -EINVAL;
+			goto error;
+		}
+		if (fw_idx % 4 != 0)
+			fw_idx += 4 - (fw_idx % 4);
+	}
+
+
+	z2->booted = 1;
+	apple_z2_read_packet(z2);
+ error:
+	release_firmware(fw);
+	return error;
+}
+
+static int apple_z2_boot(struct apple_z2 *z2)
+{
+	int timeout;
+	enable_irq(z2->spidev->irq);
+	gpiod_direction_output(z2->reset_gpio, 0);
+	timeout = wait_for_completion_timeout(&z2->boot_irq, msecs_to_jiffies(20));
+	if (timeout == 0)
+		return -ETIMEDOUT;
+	return apple_z2_upload_firmware(z2);
+}
+
+static int apple_z2_open(struct input_dev *dev)
+{
+	struct apple_z2 *z2 = input_get_drvdata(dev);
+	int error;
+
+	/* Reset the device on boot */
+	gpiod_direction_output(z2->reset_gpio, 1);
+	usleep_range(5000, 10000);
+	error = apple_z2_boot(z2);
+	if (error) {
+		gpiod_direction_output(z2->reset_gpio, 1);
+		disable_irq(z2->spidev->irq);
+	} else
+		z2->open = 1;
+	return error;
+}
+
+static void apple_z2_close(struct input_dev *dev)
+{
+	struct apple_z2 *z2 = input_get_drvdata(dev);
+
+	disable_irq(z2->spidev->irq);
+	gpiod_direction_output(z2->reset_gpio, 1);
+	z2->open = 0;
+	z2->booted = 0;
+}
+
+static int apple_z2_probe(struct spi_device *spi)
+{
+	struct device *dev = &spi->dev;
+	struct apple_z2 *z2;
+	int error;
+	int x_size;
+	const char *label;
+
+	z2 = devm_kzalloc(dev, sizeof(*z2), GFP_KERNEL);
+	if (!z2)
+		return -ENOMEM;
+
+	z2->spidev = spi;
+	init_completion(&z2->boot_irq);
+	spi_set_drvdata(spi, z2);
+
+	z2->cs_gpio = devm_gpiod_get_index(dev, "cs", 0, 0);
+	if (IS_ERR(z2->cs_gpio)) {
+		if (PTR_ERR(z2->cs_gpio) != -ENOENT)
+		{
+			dev_err(dev, "unable to get cs");
+			return PTR_ERR(z2->cs_gpio);
+		} else {
+			z2->cs_gpio = NULL;
+		}
+	}
+
+	z2->reset_gpio = devm_gpiod_get_index(dev, "reset", 0, 0);
+	if (IS_ERR(z2->reset_gpio)) {
+	        dev_err(dev, "unable to get reset");
+		return PTR_ERR(z2->reset_gpio);
+	}
+
+	error = devm_request_threaded_irq(dev, z2->spidev->irq, NULL,
+					apple_z2_irq, IRQF_ONESHOT | IRQF_NO_AUTOEN,
+					"apple-z2-irq", spi);
+	if (error < 0) {
+	        dev_err(dev, "unable to request irq");
+		return z2->spidev->irq;
+	}
+
+	error = device_property_read_u32(dev, "touchscreen-size-x", &x_size);
+	if (error) {
+	        dev_err(dev, "unable to get touchscreen size");
+		return error;
+	}
+
+	error = device_property_read_u32(dev, "touchscreen-size-y", &z2->y_size);
+	if (error) {
+	        dev_err(dev, "unable to get touchscreen size");
+		return error;
+	}
+
+	error = device_property_read_string(dev, "label", &label);
+	if (error) {
+	        dev_err(dev, "unable to get device name");
+		return error;
+	}
+
+	error = device_property_read_string(dev, "firmware-name", &z2->fw_name);
+	if (error) {
+	        dev_err(dev, "unable to get firmware name");
+		return error;
+	}
+
+	z2->cal_blob = of_get_property(dev->of_node, "apple,z2-cal-blob", &z2->cal_size);
+	if (!z2->cal_blob) {
+		dev_warn(dev, "unable to get calibration, precision may be degraded");
+		z2->cal_size = 0;
+	}
+
+	z2->input_dev = devm_input_allocate_device(dev);
+	if (!z2->input_dev)
+		return -ENOMEM;
+	z2->input_dev->name = label;
+	z2->input_dev->phys = "apple_z2";
+	z2->input_dev->dev.parent = dev;
+	z2->input_dev->id.bustype = BUS_SPI;
+	z2->input_dev->open = apple_z2_open;
+	z2->input_dev->close = apple_z2_close;
+	input_set_abs_params(z2->input_dev, ABS_MT_POSITION_X, 0, x_size, 0, 0);
+	input_abs_set_res(z2->input_dev, ABS_MT_POSITION_X, 100);
+	input_set_abs_params(z2->input_dev, ABS_MT_POSITION_Y, 0, z2->y_size, 0, 0);
+	input_abs_set_res(z2->input_dev, ABS_MT_POSITION_Y, 100);
+	input_set_abs_params(z2->input_dev, ABS_MT_WIDTH_MAJOR, 0, 65535, 0, 0);
+	input_set_abs_params(z2->input_dev, ABS_MT_WIDTH_MINOR, 0, 65535, 0, 0);
+	input_set_abs_params(z2->input_dev, ABS_MT_TOUCH_MAJOR, 0, 65535, 0, 0);
+	input_set_abs_params(z2->input_dev, ABS_MT_TOUCH_MINOR, 0, 65535, 0, 0);
+	input_set_abs_params(z2->input_dev, ABS_MT_ORIENTATION, -32768, 32767, 0, 0);
+	input_set_drvdata(z2->input_dev, z2);
+	error = input_mt_init_slots(z2->input_dev, 256, INPUT_MT_DIRECT);
+	if (error < 0) {
+	        dev_err(dev, "unable to initialize multitouch slots");
+		return error;
+	}
+
+	error = input_register_device(z2->input_dev);
+	if (error < 0)
+	        dev_err(dev, "unable to register input device");
+
+	return error;
+}
+
+static const struct of_device_id apple_z2_of_match[] = {
+	{ .compatible = "apple,z2-multitouch" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, apple_z2_of_match);
+
+static struct spi_device_id apple_z2_of_id[] = {
+	{ .name = "j293-touchbar" },
+	{ .name = "j493-touchbar" },
+	{ .name = "z2-touchbar" },
+	{ .name = "z2-multitouch" },
+	{}
+};
+MODULE_DEVICE_TABLE(spi, apple_z2_of_id);
+
+static struct spi_driver apple_z2_driver = {
+	.driver = {
+		.name	= "apple-z2",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(apple_z2_of_match),
+	},
+	.id_table       = apple_z2_of_id,
+	.probe		= apple_z2_probe,
+};
+
+module_spi_driver(apple_z2_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_FIRMWARE("apple/dfrmtfw-*.bin");

From cf595f488770ed34e713ece98157509e19a75135 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sat, 28 Jan 2023 16:50:48 +0300
Subject: [PATCH 0380/1027] MAINTAINERS: Add entries for Apple Z2 touchscreen
 driver

Add the MAINTAINERS entries for the driver

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 MAINTAINERS | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index 5ff228e0bde2dd..431f32202d631b 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2057,6 +2057,7 @@ F:	Documentation/devicetree/bindings/clock/apple,nco.yaml
 F:	Documentation/devicetree/bindings/cpufreq/apple,cluster-cpufreq.yaml
 F:	Documentation/devicetree/bindings/dma/apple,admac.yaml
 F:	Documentation/devicetree/bindings/i2c/apple,i2c.yaml
+F:	Documentation/devicetree/bindings/input/touchscreen/apple,z2-touchscreen.yaml
 F:	Documentation/devicetree/bindings/interrupt-controller/apple,*
 F:	Documentation/devicetree/bindings/iommu/apple,dart.yaml
 F:	Documentation/devicetree/bindings/iommu/apple,sart.yaml
@@ -2078,6 +2079,7 @@ F:	drivers/gpu/drm/adp/
 F:	drivers/pmdomain/apple/
 F:	drivers/i2c/busses/i2c-pasemi-core.c
 F:	drivers/i2c/busses/i2c-pasemi-platform.c
+F:	drivers/input/touchscreen/apple_z2.c
 F:	drivers/iommu/apple-dart.c
 F:	drivers/iommu/io-pgtable-dart.c
 F:	drivers/irqchip/irq-apple-aic.c

From 159f78ea209f18d59ef632b15e9622e6c7e5cbef Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 31 Aug 2023 19:08:46 +0900
Subject: [PATCH 0381/1027] media: apple: Add Apple ISP driver

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/Kconfig               |   1 +
 drivers/media/platform/Makefile              |   1 +
 drivers/media/platform/apple/Kconfig         |   5 +
 drivers/media/platform/apple/Makefile        |   3 +
 drivers/media/platform/apple/isp/.gitignore  |   1 +
 drivers/media/platform/apple/isp/Kconfig     |  10 +
 drivers/media/platform/apple/isp/Makefile    |   3 +
 drivers/media/platform/apple/isp/isp-cam.c   | 540 +++++++++++++++++
 drivers/media/platform/apple/isp/isp-cam.h   |  20 +
 drivers/media/platform/apple/isp/isp-cmd.c   | 544 +++++++++++++++++
 drivers/media/platform/apple/isp/isp-cmd.h   | 532 ++++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.c   | 333 ++++++++++
 drivers/media/platform/apple/isp/isp-drv.h   | 258 ++++++++
 drivers/media/platform/apple/isp/isp-fw.c    | 606 +++++++++++++++++++
 drivers/media/platform/apple/isp/isp-fw.h    |  12 +
 drivers/media/platform/apple/isp/isp-iommu.c | 275 +++++++++
 drivers/media/platform/apple/isp/isp-iommu.h |  38 ++
 drivers/media/platform/apple/isp/isp-ipc.c   | 329 ++++++++++
 drivers/media/platform/apple/isp/isp-ipc.h   |  26 +
 drivers/media/platform/apple/isp/isp-regs.h  |  62 ++
 drivers/media/platform/apple/isp/isp-v4l2.c  | 602 ++++++++++++++++++
 drivers/media/platform/apple/isp/isp-v4l2.h  |  12 +
 22 files changed, 4213 insertions(+)
 create mode 100644 drivers/media/platform/apple/Kconfig
 create mode 100644 drivers/media/platform/apple/Makefile
 create mode 100644 drivers/media/platform/apple/isp/.gitignore
 create mode 100644 drivers/media/platform/apple/isp/Kconfig
 create mode 100644 drivers/media/platform/apple/isp/Makefile
 create mode 100644 drivers/media/platform/apple/isp/isp-cam.c
 create mode 100644 drivers/media/platform/apple/isp/isp-cam.h
 create mode 100644 drivers/media/platform/apple/isp/isp-cmd.c
 create mode 100644 drivers/media/platform/apple/isp/isp-cmd.h
 create mode 100644 drivers/media/platform/apple/isp/isp-drv.c
 create mode 100644 drivers/media/platform/apple/isp/isp-drv.h
 create mode 100644 drivers/media/platform/apple/isp/isp-fw.c
 create mode 100644 drivers/media/platform/apple/isp/isp-fw.h
 create mode 100644 drivers/media/platform/apple/isp/isp-iommu.c
 create mode 100644 drivers/media/platform/apple/isp/isp-iommu.h
 create mode 100644 drivers/media/platform/apple/isp/isp-ipc.c
 create mode 100644 drivers/media/platform/apple/isp/isp-ipc.h
 create mode 100644 drivers/media/platform/apple/isp/isp-regs.h
 create mode 100644 drivers/media/platform/apple/isp/isp-v4l2.c
 create mode 100644 drivers/media/platform/apple/isp/isp-v4l2.h

diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 85d2627776b6a4..ba75cfdb57f710 100644
--- a/drivers/media/platform/Kconfig
+++ b/drivers/media/platform/Kconfig
@@ -65,6 +65,7 @@ config VIDEO_MUX
 source "drivers/media/platform/allegro-dvt/Kconfig"
 source "drivers/media/platform/amlogic/Kconfig"
 source "drivers/media/platform/amphion/Kconfig"
+source "drivers/media/platform/apple/Kconfig"
 source "drivers/media/platform/aspeed/Kconfig"
 source "drivers/media/platform/atmel/Kconfig"
 source "drivers/media/platform/broadcom/Kconfig"
diff --git a/drivers/media/platform/Makefile b/drivers/media/platform/Makefile
index ace4e34483ddce..e59e4259064bf0 100644
--- a/drivers/media/platform/Makefile
+++ b/drivers/media/platform/Makefile
@@ -8,6 +8,7 @@
 obj-y += allegro-dvt/
 obj-y += amlogic/
 obj-y += amphion/
+obj-y += apple/
 obj-y += aspeed/
 obj-y += atmel/
 obj-y += broadcom/
diff --git a/drivers/media/platform/apple/Kconfig b/drivers/media/platform/apple/Kconfig
new file mode 100644
index 00000000000000..f16508bff5242a
--- /dev/null
+++ b/drivers/media/platform/apple/Kconfig
@@ -0,0 +1,5 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+comment "Apple media platform drivers"
+
+source "drivers/media/platform/apple/isp/Kconfig"
diff --git a/drivers/media/platform/apple/Makefile b/drivers/media/platform/apple/Makefile
new file mode 100644
index 00000000000000..d8fe985b0e6c37
--- /dev/null
+++ b/drivers/media/platform/apple/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+obj-y += isp/
diff --git a/drivers/media/platform/apple/isp/.gitignore b/drivers/media/platform/apple/isp/.gitignore
new file mode 100644
index 00000000000000..bd7fab40e0d98a
--- /dev/null
+++ b/drivers/media/platform/apple/isp/.gitignore
@@ -0,0 +1 @@
+.clang-format
diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig
new file mode 100644
index 00000000000000..f0e2173640ab73
--- /dev/null
+++ b/drivers/media/platform/apple/isp/Kconfig
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+config VIDEO_APPLE_ISP
+	tristate "Apple Silicon Image Signal Processor driver"
+	select VIDEOBUF2_CORE
+	select VIDEOBUF2_V4L2
+	select VIDEOBUF2_DMA_SG
+	depends on ARCH_APPLE || COMPILE_TEST
+	depends on V4L_PLATFORM_DRIVERS
+	depends on VIDEO_DEV
diff --git a/drivers/media/platform/apple/isp/Makefile b/drivers/media/platform/apple/isp/Makefile
new file mode 100644
index 00000000000000..4649f32987f025
--- /dev/null
+++ b/drivers/media/platform/apple/isp/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0-only
+apple-isp-y := isp-cam.o isp-cmd.o isp-drv.o isp-fw.o isp-iommu.o isp-ipc.o isp-v4l2.o
+obj-$(CONFIG_VIDEO_APPLE_ISP) += apple-isp.o
diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
new file mode 100644
index 00000000000000..6d08248ef44776
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -0,0 +1,540 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include <linux/firmware.h>
+
+#include "isp-cam.h"
+#include "isp-cmd.h"
+#include "isp-fw.h"
+#include "isp-iommu.h"
+
+struct isp_setfile {
+	u32 version;
+	u32 magic;
+	const char *path;
+	size_t size;
+};
+
+struct isp_preset {
+	u32 index;
+	u32 width;
+	u32 height;
+	u32 x1;
+	u32 y1;
+	u32 x2;
+	u32 y2;
+	u32 orig_width;
+	u32 orig_height;
+};
+
+// clang-format off
+static const struct isp_setfile isp_setfiles[] = {
+	[ISP_IMX248_1820_01] = {0x248, 0x18200103, "isp/1820_01XX.dat", 0x442c},
+	[ISP_IMX248_1822_02] = {0x248, 0x18220201, "isp/1822_02XX.dat", 0x442c},
+	[ISP_IMX343_5221_02] = {0x343, 0x52210211, "isp/5221_02XX.dat", 0x4870},
+	[ISP_IMX354_9251_02] = {0x354, 0x92510208, "isp/9251_02XX.dat", 0xa5ec},
+	[ISP_IMX356_4820_01] = {0x356, 0x48200107, "isp/4820_01XX.dat", 0x9324},
+	[ISP_IMX356_4820_02] = {0x356, 0x48200206, "isp/4820_02XX.dat", 0x9324},
+	[ISP_IMX364_8720_01] = {0x364, 0x87200103, "isp/8720_01XX.dat", 0x36ac},
+	[ISP_IMX364_8723_01] = {0x364, 0x87230101, "isp/8723_01XX.dat", 0x361c},
+	[ISP_IMX372_3820_01] = {0x372, 0x38200108, "isp/3820_01XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_02] = {0x372, 0x38200205, "isp/3820_02XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_11] = {0x372, 0x38201104, "isp/3820_11XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_12] = {0x372, 0x38201204, "isp/3820_12XX.dat", 0xfdb0},
+	[ISP_IMX405_9720_01] = {0x405, 0x97200102, "isp/9720_01XX.dat", 0x92c8},
+	[ISP_IMX405_9721_01] = {0x405, 0x97210102, "isp/9721_01XX.dat", 0x9818},
+	[ISP_IMX405_9723_01] = {0x405, 0x97230101, "isp/9723_01XX.dat", 0x92c8},
+	[ISP_IMX414_2520_01] = {0x414, 0x25200102, "isp/2520_01XX.dat", 0xa444},
+	[ISP_IMX503_7820_01] = {0x503, 0x78200109, "isp/7820_01XX.dat", 0xb268},
+	[ISP_IMX503_7820_02] = {0x503, 0x78200206, "isp/7820_02XX.dat", 0xb268},
+	[ISP_IMX505_3921_01] = {0x505, 0x39210102, "isp/3921_01XX.dat", 0x89b0},
+	[ISP_IMX514_2820_01] = {0x514, 0x28200108, "isp/2820_01XX.dat", 0xa198},
+	[ISP_IMX514_2820_02] = {0x514, 0x28200205, "isp/2820_02XX.dat", 0xa198},
+	[ISP_IMX514_2820_03] = {0x514, 0x28200305, "isp/2820_03XX.dat", 0xa198},
+	[ISP_IMX514_2820_04] = {0x514, 0x28200405, "isp/2820_04XX.dat", 0xa198},
+	[ISP_IMX558_1921_01] = {0x558, 0x19210106, "isp/1921_01XX.dat", 0xad40},
+	[ISP_IMX558_1922_02] = {0x558, 0x19220201, "isp/1922_02XX.dat", 0xad40},
+	[ISP_IMX603_7920_01] = {0x603, 0x79200109, "isp/7920_01XX.dat", 0xad2c},
+	[ISP_IMX603_7920_02] = {0x603, 0x79200205, "isp/7920_02XX.dat", 0xad2c},
+	[ISP_IMX603_7921_01] = {0x603, 0x79210104, "isp/7921_01XX.dat", 0xad90},
+	[ISP_IMX613_4920_01] = {0x613, 0x49200108, "isp/4920_01XX.dat", 0x9324},
+	[ISP_IMX613_4920_02] = {0x613, 0x49200204, "isp/4920_02XX.dat", 0x9324},
+	[ISP_IMX614_2921_01] = {0x614, 0x29210107, "isp/2921_01XX.dat", 0xed6c},
+	[ISP_IMX614_2921_02] = {0x614, 0x29210202, "isp/2921_02XX.dat", 0xed6c},
+	[ISP_IMX614_2922_02] = {0x614, 0x29220201, "isp/2922_02XX.dat", 0xed6c},
+	[ISP_IMX633_3622_01] = {0x633, 0x36220111, "isp/3622_01XX.dat", 0x100d4},
+	[ISP_IMX703_7721_01] = {0x703, 0x77210106, "isp/7721_01XX.dat", 0x936c},
+	[ISP_IMX703_7722_01] = {0x703, 0x77220106, "isp/7722_01XX.dat", 0xac20},
+	[ISP_IMX713_4721_01] = {0x713, 0x47210107, "isp/4721_01XX.dat", 0x936c},
+	[ISP_IMX713_4722_01] = {0x713, 0x47220109, "isp/4722_01XX.dat", 0x9218},
+	[ISP_IMX714_2022_01] = {0x714, 0x20220107, "isp/2022_01XX.dat", 0xa198},
+	[ISP_IMX772_3721_01] = {0x772, 0x37210106, "isp/3721_01XX.dat", 0xfdf8},
+	[ISP_IMX772_3721_11] = {0x772, 0x37211106, "isp/3721_11XX.dat", 0xfe14},
+	[ISP_IMX772_3722_01] = {0x772, 0x37220104, "isp/3722_01XX.dat", 0xfca4},
+	[ISP_IMX772_3723_01] = {0x772, 0x37230106, "isp/3723_01XX.dat", 0xfca4},
+	[ISP_IMX814_2123_01] = {0x814, 0x21230101, "isp/2123_01XX.dat", 0xed54},
+	[ISP_IMX853_7622_01] = {0x853, 0x76220112, "isp/7622_01XX.dat", 0x247f8},
+	[ISP_IMX913_7523_01] = {0x913, 0x75230107, "isp/7523_01XX.dat", 0x247f8},
+	[ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80},
+	[ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80},
+};
+// clang-format on
+
+// one day we will do this intelligently
+static const struct isp_preset isp_presets[] = {
+	[ISP_IMX248_1820_01] = { 0, 1280, 720, 8, 8, 1280, 720, 1296, 736 },
+};
+
+static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	enum isp_sensor_id id;
+	int err = 0;
+
+	/* TODO need more datapoints to figure out the sub-versions
+	 * Defaulting to 1st release for now, the calib files aren't too different.
+	 */
+	switch (fmt->version) {
+	case 0x248:
+		id = ISP_IMX248_1820_01;
+		break;
+	case 0x343:
+		id = ISP_IMX343_5221_02;
+		break;
+	case 0x354:
+		id = ISP_IMX354_9251_02;
+		break;
+	case 0x356:
+		id = ISP_IMX356_4820_01;
+		break;
+	case 0x364:
+		id = ISP_IMX364_8720_01;
+		break;
+	case 0x372:
+		id = ISP_IMX372_3820_01;
+		break;
+	case 0x405:
+		id = ISP_IMX405_9720_01;
+		break;
+	case 0x414:
+		id = ISP_IMX414_2520_01;
+		break;
+	case 0x503:
+		id = ISP_IMX503_7820_01;
+		break;
+	case 0x505:
+		id = ISP_IMX505_3921_01;
+		break;
+	case 0x514:
+		id = ISP_IMX514_2820_01;
+		break;
+	case 0x558:
+		id = ISP_IMX558_1921_01;
+		break;
+	case 0x603:
+		id = ISP_IMX603_7920_01;
+		break;
+	case 0x613:
+		id = ISP_IMX613_4920_01;
+		break;
+	case 0x614:
+		id = ISP_IMX614_2921_01;
+		break;
+	case 0x633:
+		id = ISP_IMX633_3622_01;
+		break;
+	case 0x703:
+		id = ISP_IMX703_7721_01;
+		break;
+	case 0x713:
+		id = ISP_IMX713_4721_01;
+		break;
+	case 0x714:
+		id = ISP_IMX714_2022_01;
+		break;
+	case 0x772:
+		id = ISP_IMX772_3721_01;
+		break;
+	case 0x814:
+		id = ISP_IMX814_2123_01;
+		break;
+	case 0x853:
+		id = ISP_IMX853_7622_01;
+		break;
+	case 0x913:
+		id = ISP_IMX913_7523_01;
+		break;
+	case 0xd56:
+		id = ISP_VD56G0_6221_01;
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+
+	if (err)
+		dev_err(isp->dev, "invalid sensor version: 0x%x\n",
+			fmt->version);
+	else
+		fmt->id = id;
+
+	return err;
+}
+
+static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	int err = 0;
+
+	struct cmd_ch_info *args; /* Too big to allocate on stack */
+	args = kzalloc(sizeof(*args), GFP_KERNEL);
+	if (!args)
+		return -ENOMEM;
+
+	err = isp_cmd_ch_info_get(isp, ch, args);
+	if (err)
+		goto exit;
+
+	dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version,
+		 args->module_sn, ch);
+
+	fmt->version = args->version;
+	fmt->num_presets = args->num_presets;
+
+	pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch);
+	print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4,
+		       args, sizeof(*args), false);
+
+	err = isp_ch_get_sensor_id(isp, ch);
+	if (err || (fmt->id != ISP_IMX248_1820_01)) {
+		dev_err(isp->dev,
+			"ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n",
+			ch);
+		return -ENODEV;
+	}
+
+exit:
+	kfree(args);
+
+	return err;
+}
+
+static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps)
+{
+	int err = 0;
+
+	struct cmd_ch_camera_config *args; /* Too big to allocate on stack */
+	args = kzalloc(sizeof(*args), GFP_KERNEL);
+	if (!args)
+		return -ENOMEM;
+
+	err = isp_cmd_ch_camera_config_get(isp, ch, ps, args);
+	if (err)
+		goto exit;
+
+	pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps);
+	print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4,
+		       args, sizeof(*args), false);
+
+exit:
+	kfree(args);
+
+	return err;
+}
+
+static void isp_ch_dump_camera_presets(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	for (u32 ps = 0; ps < fmt->num_presets; ps++) {
+		isp_ch_get_camera_preset(isp, ch, ps);
+	}
+}
+
+static int isp_ch_cache_camera_preset(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	const struct isp_preset *preset = &isp_presets[fmt->id];
+	size_t total_size;
+
+	isp_ch_dump_camera_presets(isp, ch);
+
+	fmt->preset = preset->index;
+
+	fmt->width = preset->width;
+	fmt->height = preset->height;
+
+	fmt->x1 = preset->x1;
+	fmt->y1 = preset->y1;
+	fmt->x2 = preset->x2;
+	fmt->y2 = preset->y2;
+
+	/* I really fucking hope they all use NV12. */
+	fmt->num_planes = 2;
+	fmt->plane_size[0] = fmt->width * fmt->height;
+	fmt->plane_size[1] = fmt->plane_size[0] / 2;
+
+	total_size = 0;
+	for (int i = 0; i < fmt->num_planes; i++)
+		total_size += fmt->plane_size[i];
+	fmt->total_size = total_size;
+
+	return 0;
+}
+
+static int isp_ch_cache_camera_info(struct apple_isp *isp, u32 ch)
+{
+	int err;
+
+	err = isp_ch_cache_sensor_info(isp, ch);
+	if (err) {
+		dev_err(isp->dev, "ch %d: failed to cache sensor info: %d\n",
+			ch, err);
+		return err;
+	}
+
+	err = isp_ch_cache_camera_preset(isp, ch);
+	if (err) {
+		dev_err(isp->dev, "ch %d: failed to cache camera preset: %d\n",
+			ch, err);
+		return err;
+	}
+
+	return 0;
+}
+
+static int isp_detect_camera(struct apple_isp *isp)
+{
+	int err;
+
+	struct cmd_config_get args;
+	memset(&args, 0, sizeof(args));
+
+	err = isp_cmd_config_get(isp, &args);
+	if (err)
+		return err;
+
+	pr_info("apple-isp: CISP_CMD_CONFIG_GET: \n");
+	print_hex_dump(KERN_INFO, "apple-isp: ", DUMP_PREFIX_NONE, 32, 4, &args,
+		       sizeof(args), false);
+
+	if (!args.num_channels) {
+		dev_err(isp->dev, "did not detect any channels\n");
+		return -ENODEV;
+	}
+
+	if (args.num_channels > ISP_MAX_CHANNELS) {
+		dev_warn(isp->dev, "found %d channels when maximum is %d\n",
+			 args.num_channels, ISP_MAX_CHANNELS);
+		args.num_channels = ISP_MAX_CHANNELS;
+	}
+
+	if (args.num_channels > 1) {
+		dev_warn(
+			isp->dev,
+			"warning: driver doesn't support multiple channels. Please file a bug report with hardware info & dmesg trace.\n");
+	}
+
+	isp->num_channels = args.num_channels;
+	isp->current_ch = 0;
+
+	return isp_ch_cache_camera_info(isp, isp->current_ch); /* I told you */
+}
+
+int apple_isp_detect_camera(struct apple_isp *isp)
+{
+	int err;
+
+	/* RPM must be enabled prior to calling this */
+	err = apple_isp_firmware_boot(isp);
+	if (err) {
+		dev_err(isp->dev,
+			"failed to boot firmware for initial sensor detection: %d\n",
+			err);
+		return -EPROBE_DEFER;
+	}
+
+	err = isp_detect_camera(isp);
+	apple_isp_firmware_shutdown(isp);
+
+	return err;
+}
+
+static int isp_ch_load_setfile(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	const struct isp_setfile *setfile = &isp_setfiles[fmt->id];
+	const struct firmware *fw;
+	u32 magic;
+	int err;
+
+	err = request_firmware(&fw, setfile->path, isp->dev);
+	if (err) {
+		dev_err(isp->dev, "failed to request setfile '%s': %d\n",
+			setfile->path, err);
+		return err;
+	}
+
+	if (fw->size < setfile->size) {
+		dev_err(isp->dev, "setfile too small (0x%lx/0x%zx)\n", fw->size,
+			setfile->size);
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	magic = be32_to_cpup((__be32 *)fw->data);
+	if (magic != setfile->magic) {
+		dev_err(isp->dev, "setfile '%s' corrupted?\n", setfile->path);
+		release_firmware(fw);
+		return -EINVAL;
+	}
+
+	isp_iowrite(isp, isp->data_surf->iova, (void *)fw->data, setfile->size);
+	release_firmware(fw);
+
+	return isp_cmd_ch_set_file_load(isp, ch, isp->data_surf->iova,
+					setfile->size);
+}
+
+static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	int err;
+
+	/* The setfile isn't requisite but then we don't get calibration */
+	err = isp_ch_load_setfile(isp, ch);
+	if (err) {
+		dev_err(isp->dev, "warning: calibration data not loaded: %d\n",
+			err);
+	}
+
+	err = isp_cmd_ch_sbs_enable(isp, ch, 1);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_buffer_recycle_mode_set(
+		isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_buffer_recycle_start(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_crop_set(isp, ch, fmt->x1, fmt->y1, fmt->x2, fmt->y2);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_output_config_set(isp, ch, fmt->width, fmt->height,
+					   CISP_COLORSPACE_REC709,
+					   CISP_OUTPUT_FORMAT_NV12);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_preview_stream_set(isp, ch, 1);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_cnr_start(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_mbnr_enable(isp, ch, 0, 1, 1);
+	if (err)
+		return err;
+
+	err = isp_cmd_apple_ch_temporal_filter_start(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_apple_ch_motion_history_start(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_ae_stability_set(isp, ch, 32);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_sif_pixel_format_set(isp, ch);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_buffer_pool_config_set(isp, ch, CISP_POOL_TYPE_META);
+	if (err)
+		return err;
+
+	err = isp_cmd_ch_buffer_pool_config_set(isp, ch,
+						CISP_POOL_TYPE_META_CAPTURE);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int isp_configure_capture(struct apple_isp *isp)
+{
+	return isp_ch_configure_capture(isp, isp->current_ch);
+}
+
+int apple_isp_start_camera(struct apple_isp *isp)
+{
+	int err;
+
+	err = apple_isp_firmware_boot(isp);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to boot firmware: %d\n", err);
+		return err;
+	}
+
+	err = isp_configure_capture(isp);
+	if (err) {
+		dev_err(isp->dev, "failed to configure capture: %d\n", err);
+		apple_isp_firmware_shutdown(isp);
+		return err;
+	}
+
+	return 0;
+}
+
+void apple_isp_stop_camera(struct apple_isp *isp)
+{
+	apple_isp_firmware_shutdown(isp);
+}
+
+int apple_isp_start_capture(struct apple_isp *isp)
+{
+	return isp_cmd_ch_start(isp, 0); // TODO channel mask
+}
+
+void apple_isp_stop_capture(struct apple_isp *isp)
+{
+	isp_cmd_ch_stop(isp, 0); // TODO channel mask
+	isp_cmd_ch_buffer_return(isp, isp->current_ch);
+}
diff --git a/drivers/media/platform/apple/isp/isp-cam.h b/drivers/media/platform/apple/isp/isp-cam.h
new file mode 100644
index 00000000000000..126e5806c8c416
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cam.h
@@ -0,0 +1,20 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_CAM_H__
+#define __ISP_CAM_H__
+
+#include "isp-drv.h"
+
+#define ISP_FRAME_RATE_NUM 256
+#define ISP_FRAME_RATE_DEN 7680
+
+int apple_isp_detect_camera(struct apple_isp *isp);
+
+int apple_isp_start_camera(struct apple_isp *isp);
+void apple_isp_stop_camera(struct apple_isp *isp);
+
+int apple_isp_start_capture(struct apple_isp *isp);
+void apple_isp_stop_capture(struct apple_isp *isp);
+
+#endif /* __ISP_CAM_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
new file mode 100644
index 00000000000000..79ffb2b1c33881
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -0,0 +1,544 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include "isp-cmd.h"
+#include "isp-iommu.h"
+#include "isp-ipc.h"
+
+#define CISP_OPCODE_SHIFT     32UL
+#define CISP_OPCODE(x)	      (((u64)(x)) << CISP_OPCODE_SHIFT)
+#define CISP_OPCODE_GET(x)    (((u64)(x)) >> CISP_OPCODE_SHIFT)
+
+#define CISP_TIMEOUT	      msecs_to_jiffies(3000)
+#define CISP_SEND_IN(x, a)    (cisp_send((x), &(a), sizeof(a), 0))
+#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a)))
+#define CISP_SEND_OUT(x, a)   (cisp_send_read((x), (a), sizeof(*a), sizeof(*a)))
+
+static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize)
+{
+	struct isp_channel *chan = isp->chan_io;
+	struct isp_message *req = &chan->req;
+	int err;
+
+	req->arg0 = isp->cmd_iova;
+	req->arg1 = insize;
+	req->arg2 = outsize;
+
+	isp_iowrite(isp, isp->cmd_iova, args, insize);
+	err = ipc_chan_send(isp, chan, CISP_TIMEOUT);
+	if (err) {
+		u64 opcode;
+		memcpy(&opcode, args, sizeof(opcode));
+		dev_err(isp->dev,
+			"%s: failed to send OPCODE 0x%04llx: [0x%llx, 0x%llx, 0x%llx]\n",
+			chan->name, CISP_OPCODE_GET(opcode), req->arg0,
+			req->arg1, req->arg2);
+	}
+
+	return err;
+}
+
+static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize,
+			  u32 outsize)
+{
+	/* TODO do I need to lock the iova space? */
+	int err = cisp_send(isp, args, insize, outsize);
+	if (err)
+		return err;
+	isp_ioread(isp, isp->cmd_iova, args, outsize);
+	return 0;
+}
+
+int isp_cmd_start(struct apple_isp *isp, u32 mode)
+{
+	struct cmd_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_START),
+		.mode = mode,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_suspend(struct apple_isp *isp)
+{
+	struct cmd_suspend args = {
+		.opcode = CISP_OPCODE(CISP_CMD_SUSPEND),
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_print_enable(struct apple_isp *isp, u32 enable)
+{
+	struct cmd_print_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_PRINT_ENABLE),
+		.enable = enable,
+	};
+	return CISP_SEND_INOUT(isp, args);
+}
+
+int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable)
+{
+	struct cmd_trace_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_TRACE_ENABLE),
+		.enable = enable,
+	};
+	return CISP_SEND_INOUT(isp, args);
+}
+
+int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args)
+{
+	args->opcode = CISP_OPCODE(CISP_CMD_CONFIG_GET);
+	return CISP_SEND_OUT(isp, args);
+}
+
+int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base)
+{
+	struct cmd_set_isp_pmu_base args = {
+		.opcode = CISP_OPCODE(CISP_CMD_SET_ISP_PMU_BASE),
+		.pmu_base = pmu_base,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0,
+				   u64 dsid_clr_base1, u64 dsid_clr_base2,
+				   u64 dsid_clr_base3, u32 dsid_clr_range0,
+				   u32 dsid_clr_range1, u32 dsid_clr_range2,
+				   u32 dsid_clr_range3)
+{
+	struct cmd_set_dsid_clr_req_base2 args = {
+		.opcode = CISP_OPCODE(CISP_CMD_SET_DSID_CLR_REG_BASE2),
+		.dsid_clr_base0 = dsid_clr_base0,
+		.dsid_clr_base1 = dsid_clr_base1,
+		.dsid_clr_base2 = dsid_clr_base2,
+		.dsid_clr_base3 = dsid_clr_base3,
+		.dsid_clr_range0 = dsid_clr_range0,
+		.dsid_clr_range1 = dsid_clr_range1,
+		.dsid_clr_range2 = dsid_clr_range2,
+		.dsid_clr_range3 = dsid_clr_range3,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch,
+			 u64 clock_base, u8 clock_bit, u8 clock_size,
+			 u64 bandwidth_scratch, u64 bandwidth_base,
+			 u8 bandwidth_bit, u8 bandwidth_size)
+{
+	struct cmd_pmp_ctrl_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_PMP_CTRL_SET),
+		.clock_scratch = clock_scratch,
+		.clock_base = clock_base,
+		.clock_bit = clock_bit,
+		.clock_size = clock_size,
+		.clock_pad = 0,
+		.bandwidth_scratch = bandwidth_scratch,
+		.bandwidth_base = bandwidth_base,
+		.bandwidth_bit = bandwidth_bit,
+		.bandwidth_size = bandwidth_size,
+		.bandwidth_pad = 0,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_fid_enter(struct apple_isp *isp)
+{
+	struct cmd_fid_enter args = {
+		.opcode = CISP_OPCODE(CISP_CMD_FID_ENTER),
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_fid_exit(struct apple_isp *isp)
+{
+	struct cmd_fid_exit args = {
+		.opcode = CISP_OPCODE(CISP_CMD_FID_EXIT),
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_start(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_START),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_stop args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_STOP),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan,
+			struct cmd_ch_info *args)
+{
+	args->opcode = CISP_OPCODE(CISP_CMD_CH_INFO_GET);
+	args->chan = chan;
+	return CISP_SEND_OUT(isp, args);
+}
+
+int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 preset,
+				 struct cmd_ch_camera_config *args)
+{
+	args->opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_GET);
+	args->preset = preset;
+	args->chan = chan;
+	return CISP_SEND_OUT(isp, args);
+}
+
+int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan,
+					 struct cmd_ch_camera_config *args)
+{
+	args->opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET);
+	args->chan = chan;
+	return CISP_SEND_OUT(isp, args);
+}
+
+int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan, u32 preset)
+{
+	struct cmd_ch_camera_config_select args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_CAMERA_CONFIG_SELECT),
+		.chan = chan,
+		.preset = preset,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_buffer_return args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RETURN),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr,
+			     u32 size)
+{
+	struct cmd_ch_set_file_load args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD),
+		.chan = chan,
+		.addr = addr,
+		.size = size,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable)
+{
+	struct cmd_ch_sbs_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_SBS_ENABLE),
+		.chan = chan,
+		.enable = enable,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
+			u32 y2)
+{
+	struct cmd_ch_crop_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_CROP_SET),
+		.chan = chan,
+		.x1 = x1,
+		.y1 = y1,
+		.x2 = x2,
+		.y2 = y2,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
+				 u32 height, u32 colorspace, u32 format)
+{
+	struct cmd_ch_output_config_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_OUTPUT_CONFIG_SET),
+		.chan = chan,
+		.width = width,
+		.height = height,
+		.colorspace = colorspace,
+		.format = format,
+		.unk_w0 = width,
+		.unk_w1 = width,
+		.unk_24 = 0,
+		.padding_rows = 0,
+		.unk_h0 = height,
+		.compress = 0,
+		.unk_w2 = width,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream)
+{
+	struct cmd_ch_preview_stream_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_PREVIEW_STREAM_SET),
+		.chan = chan,
+		.stream = stream,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_als_disable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_ALS_DISABLE),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_cnr_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_CNR_START),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case,
+			   u32 mode, u32 enable_chroma)
+{
+	struct cmd_ch_mbnr_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_MBNR_ENABLE),
+		.chan = chan,
+		.use_case = use_case,
+		.mode = mode,
+		.enable_chroma = enable_chroma,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_sif_pixel_format_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_SIF_PIXEL_FORMAT_SET),
+		.chan = chan,
+		.format = 3,
+		.type = 1,
+		.compress = 0,
+		.unk_10 = 0,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan,
+				       u32 mode)
+{
+	struct cmd_ch_buffer_recycle_mode_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET),
+		.chan = chan,
+		.mode = mode,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_buffer_recycle_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_RECYCLE_START),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type)
+{
+	struct cmd_ch_buffer_pool_config_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET),
+		.chan = chan,
+		.type = type,
+		.count = 16,
+		.meta_size0 = ISP_META_SIZE,
+		.meta_size1 = ISP_META_SIZE,
+		.data_blocks = 1,
+		.compress = 0,
+	};
+	memset(args.zero, 0, sizeof(u32) * 0x1f);
+	return CISP_SEND_INOUT(isp, args);
+}
+
+int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_ch_buffer_pool_return args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_RETURN),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_temporal_filter_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START),
+		.chan = chan,
+		.unk_c = 1,
+		.unk_10 = 0,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_temporal_filter_stop args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_motion_history_start args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_MOTION_HISTORY_START),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_motion_history_stop args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_temporal_filter_enable(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_temporal_filter_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_temporal_filter_disable(struct apple_isp *isp, u32 chan)
+{
+	struct cmd_apple_ch_temporal_filter_disable args = {
+		.opcode =
+			CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE),
+		.chan = chan,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_ae_stability_set(struct apple_isp *isp, u32 chan, u32 stability)
+{
+	struct cmd_ch_ae_stability_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_AE_STABILITY_SET),
+		.chan = chan,
+		.stability = stability,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_ae_stability_to_stable_set(struct apple_isp *isp, u32 chan,
+					  u32 stability)
+{
+	struct cmd_ch_ae_stability_to_stable_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET),
+		.chan = chan,
+		.stability = stability,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_ae_frame_rate_max_get(struct apple_isp *isp, u32 chan,
+				     struct cmd_ch_ae_frame_rate_max_get *args)
+{
+	args->opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MAX_GET);
+	args->chan = chan;
+	return CISP_SEND_OUT(isp, args);
+}
+
+int isp_cmd_ch_ae_frame_rate_max_set(struct apple_isp *isp, u32 chan,
+				     u32 framerate)
+{
+	struct cmd_ch_ae_frame_rate_max_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MAX_SET),
+		.chan = chan,
+		.framerate = framerate,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_ae_frame_rate_min_set(struct apple_isp *isp, u32 chan,
+				     u32 framerate)
+{
+	struct cmd_ch_ae_frame_rate_min_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_AE_FRAME_RATE_MIN_SET),
+		.chan = chan,
+		.framerate = framerate,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_ae_fd_scene_metering_config_set(struct apple_isp *isp,
+						     u32 chan)
+{
+	struct cmd_apple_ch_ae_fd_scene_metering_config_set args = {
+		.opcode = CISP_OPCODE(
+			CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET),
+		.chan = chan,
+		.unk_c = 0xb8,
+		.unk_10 = 0x2000200,
+		.unk_14 = 0x280800,
+		.unk_18 = 0xe10028,
+		.unk_1c = 0xa0399,
+		.unk_20 = 0x3cc02cc,
+	};
+	return CISP_SEND_INOUT(isp, args);
+}
+
+int isp_cmd_apple_ch_ae_metering_mode_set(struct apple_isp *isp, u32 chan,
+					  u32 mode)
+{
+	struct cmd_apple_ch_ae_metering_mode_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_AE_METERING_MODE_SET),
+		.chan = chan,
+		.mode = mode,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_apple_ch_ae_flicker_freq_update_current_set(struct apple_isp *isp,
+							u32 chan, u32 freq)
+{
+	struct cmd_apple_ch_ae_flicker_freq_update_current_set args = {
+		.opcode = CISP_OPCODE(
+			CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET),
+		.chan = chan,
+		.freq = freq,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_semantic_video_enable(struct apple_isp *isp, u32 chan,
+				     u32 enable)
+{
+	struct cmd_ch_semantic_video_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE),
+		.chan = chan,
+		.enable = enable,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable)
+{
+	struct cmd_ch_semantic_awb_enable args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_SEMANTIC_AWB_ENABLE),
+		.chan = chan,
+		.enable = enable,
+	};
+	return CISP_SEND_IN(isp, args);
+}
diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
new file mode 100644
index 00000000000000..dde6aad506c23e
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -0,0 +1,532 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_CMD_H__
+#define __ISP_CMD_H__
+
+#include "isp-drv.h"
+
+#define CISP_CMD_START					     0x0000
+#define CISP_CMD_STOP					     0x0001
+#define CISP_CMD_CONFIG_GET				     0x0003
+#define CISP_CMD_PRINT_ENABLE				     0x0004
+#define CISP_CMD_BUILDINFO				     0x0006
+#define CISP_CMD_GET_BES_PARAM				     0x000f
+#define CISP_CMD_SET_ISP_PMU_BASE			     0x0011
+#define CISP_CMD_PMP_CTRL_SET				     0x001c
+#define CISP_CMD_TRACE_ENABLE				     0x001d
+#define CISP_CMD_SUSPEND				     0x0021
+#define CISP_CMD_FID_ENTER				     0x0022
+#define CISP_CMD_FID_EXIT				     0x0023
+#define CISP_CMD_FLICKER_SENSOR_SET			     0x0024
+#define CISP_CMD_CH_START				     0x0100
+#define CISP_CMD_CH_STOP				     0x0101
+#define CISP_CMD_CH_BUFFER_RETURN			     0x0104
+#define CISP_CMD_CH_CAMERA_CONFIG_CURRENT_GET		     0x0105
+#define CISP_CMD_CH_CAMERA_CONFIG_GET			     0x0106
+#define CISP_CMD_CH_CAMERA_CONFIG_SELECT		     0x0107
+#define CISP_CMD_CH_INFO_GET				     0x010d
+#define CISP_CMD_CH_BUFFER_RECYCLE_MODE_SET		     0x010e
+#define CISP_CMD_CH_BUFFER_RECYCLE_START		     0x010f
+#define CISP_CMD_CH_BUFFER_RECYCLE_STOP			     0x0110
+#define CISP_CMD_CH_SET_FILE_LOAD			     0x0111
+#define CISP_CMD_CH_SIF_PIXEL_FORMAT_SET		     0x0115
+#define CISP_CMD_CH_BUFFER_POOL_CONFIG_GET		     0x0116
+#define CISP_CMD_CH_BUFFER_POOL_CONFIG_SET		     0x0117
+#define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET		     0x011a
+#define CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET		     0x011f
+#define CISP_CMD_CH_LOCAL_RAW_BUFFER_ENABLE		     0x0125
+#define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_TOTAL_GET	     0x0133
+#define CISP_CMD_CH_SBS_ENABLE				     0x013b
+#define CISP_CMD_CH_LSC_POLYNOMIAL_COEFF_GET		     0x0142
+#define CISP_CMD_CH_BUFFER_POOL_RETURN			     0x015b
+#define CISP_CMD_CH_CAMERA_AGILE_FREQ_ARRAY_CURRENT_GET	     0x015e
+#define CISP_CMD_CH_AE_START				     0x0200
+#define CISP_CMD_CH_AE_STOP				     0x0201
+#define CISP_CMD_CH_AE_FRAME_RATE_MAX_GET		     0x0207
+#define CISP_CMD_CH_AE_FRAME_RATE_MAX_SET		     0x0208
+#define CISP_CMD_CH_AE_FRAME_RATE_MIN_GET		     0x0209
+#define CISP_CMD_CH_AE_FRAME_RATE_MIN_SET		     0x020a
+#define CISP_CMD_CH_AE_STABILITY_SET			     0x021a
+#define CISP_CMD_CH_AE_STABILITY_TO_STABLE_SET		     0x0229
+#define CISP_CMD_CH_SENSOR_NVM_GET			     0x0501
+#define CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET	     0x0507
+#define CISP_CMD_CH_SENSOR_PERMODULE_LSC_GRID_GET	     0x0511
+#define CISP_CMD_CH_FOCUS_LIMITS_GET			     0x0701
+#define CISP_CMD_CH_CROP_SET				     0x0801
+#define CISP_CMD_CH_ALS_ENABLE				     0x0a1c
+#define CISP_CMD_CH_ALS_DISABLE				     0x0a1d
+#define CISP_CMD_CH_CNR_START				     0x0a2f
+#define CISP_CMD_CH_MBNR_ENABLE				     0x0a3a
+#define CISP_CMD_CH_OUTPUT_CONFIG_SET			     0x0b01
+#define CISP_CMD_CH_PREVIEW_STREAM_SET			     0x0b0d
+#define CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE		     0x0b17
+#define CISP_CMD_CH_SEMANTIC_AWB_ENABLE			     0x0b18
+#define CISP_CMD_CH_FACE_DETECTION_START		     0x0d00
+#define CISP_CMD_CH_FACE_DETECTION_CONFIG_GET		     0x0d02
+#define CISP_CMD_CH_FACE_DETECTION_CONFIG_SET		     0x0d03
+#define CISP_CMD_CH_FACE_DETECTION_ENABLE		     0x0d05
+#define CISP_CMD_CH_FID_START				     0x3000
+#define CISP_CMD_CH_FID_STOP				     0x3001
+#define CISP_CMD_IPC_ENDPOINT_SET2			     0x300c
+#define CISP_CMD_IPC_ENDPOINT_UNSET2			     0x300d
+#define CISP_CMD_SET_DSID_CLR_REG_BASE2			     0x3204
+#define CISP_CMD_APPLE_CH_AE_METERING_MODE_SET		     0x8206
+#define CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET    0x820e
+#define CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET 0x8212
+#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START		     0xc100
+#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_STOP		     0xc101
+#define CISP_CMD_APPLE_CH_MOTION_HISTORY_START		     0xc102
+#define CISP_CMD_APPLE_CH_MOTION_HISTORY_STOP		     0xc103
+#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_ENABLE	     0xc113
+#define CISP_CMD_APPLE_CH_TEMPORAL_FILTER_DISABLE	     0xc114
+
+#define CISP_POOL_TYPE_META				     0x0
+#define CISP_POOL_TYPE_RENDERED				     0x1
+#define CISP_POOL_TYPE_FD				     0x2
+#define CISP_POOL_TYPE_RAW				     0x3
+#define CISP_POOL_TYPE_STAT				     0x4
+#define CISP_POOL_TYPE_META_CAPTURE			     0x8
+
+#define CISP_COLORSPACE_REC709				     0x1
+#define CISP_OUTPUT_FORMAT_NV12				     0x0
+#define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY		     0x1
+
+struct cmd_start {
+	u64 opcode;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_start) == 0xc);
+
+struct cmd_suspend {
+	u64 opcode;
+} __packed;
+static_assert(sizeof(struct cmd_suspend) == 0x8);
+
+struct cmd_print_enable {
+	u64 opcode;
+	u32 enable;
+} __packed;
+static_assert(sizeof(struct cmd_print_enable) == 0xc);
+
+struct cmd_trace_enable {
+	u64 opcode;
+	u32 enable;
+} __packed;
+static_assert(sizeof(struct cmd_trace_enable) == 0xc);
+
+struct cmd_config_get {
+	u64 opcode;
+	u32 timestamp_freq;
+	u32 num_channels;
+	u32 unk_10;
+	u32 unk_14;
+	u32 unk_18;
+} __packed;
+static_assert(sizeof(struct cmd_config_get) == 0x1c);
+
+struct cmd_set_isp_pmu_base {
+	u64 opcode;
+	u64 pmu_base;
+} __packed;
+static_assert(sizeof(struct cmd_set_isp_pmu_base) == 0x10);
+
+struct cmd_set_dsid_clr_req_base2 {
+	u64 opcode;
+	u64 dsid_clr_base0;
+	u64 dsid_clr_base1;
+	u64 dsid_clr_base2;
+	u64 dsid_clr_base3;
+	u32 dsid_clr_range0;
+	u32 dsid_clr_range1;
+	u32 dsid_clr_range2;
+	u32 dsid_clr_range3;
+} __packed;
+static_assert(sizeof(struct cmd_set_dsid_clr_req_base2) == 0x38);
+
+struct cmd_pmp_ctrl_set {
+	u64 opcode;
+	u64 clock_scratch;
+	u64 clock_base;
+	u8 clock_bit;
+	u8 clock_size;
+	u16 clock_pad;
+	u64 bandwidth_scratch;
+	u64 bandwidth_base;
+	u8 bandwidth_bit;
+	u8 bandwidth_size;
+	u16 bandwidth_pad;
+} __packed;
+static_assert(sizeof(struct cmd_pmp_ctrl_set) == 0x30);
+
+struct cmd_fid_enter {
+	u64 opcode;
+} __packed;
+static_assert(sizeof(struct cmd_fid_enter) == 0x8);
+
+struct cmd_fid_exit {
+	u64 opcode;
+} __packed;
+static_assert(sizeof(struct cmd_fid_exit) == 0x8);
+
+int isp_cmd_start(struct apple_isp *isp, u32 mode);
+int isp_cmd_suspend(struct apple_isp *isp);
+int isp_cmd_print_enable(struct apple_isp *isp, u32 enable);
+int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable);
+int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args);
+int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base);
+int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0,
+				   u64 dsid_clr_base1, u64 dsid_clr_base2,
+				   u64 dsid_clr_base3, u32 dsid_clr_range0,
+				   u32 dsid_clr_range1, u32 dsid_clr_range2,
+				   u32 dsid_clr_range3);
+int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch,
+			 u64 clock_base, u8 clock_bit, u8 clock_size,
+			 u64 bandwidth_scratch, u64 bandwidth_base,
+			 u8 bandwidth_bit, u8 bandwidth_size);
+int isp_cmd_fid_enter(struct apple_isp *isp);
+int isp_cmd_fid_exit(struct apple_isp *isp);
+
+struct cmd_ch_start {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_start) == 0xc);
+
+struct cmd_ch_stop {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_stop) == 0xc);
+
+struct cmd_ch_info {
+	u64 opcode;
+	u32 chan;
+	u32 unk_c;
+	u32 unk_10[4];
+	u32 version;
+	u32 unk_24[3];
+	u32 unk_30[12];
+	u32 num_presets;
+	u32 unk_64[7];
+	u32 unk_80[6];
+	u32 unk_98_freq;
+	u16 pad_9c;
+	char module_sn[20];
+	u16 pad_b0;
+	u32 unk_b4[25];
+} __packed;
+static_assert(sizeof(struct cmd_ch_info) == 0x118);
+
+struct cmd_ch_camera_config {
+	u64 opcode;
+	u32 chan;
+	u32 preset;
+	u16 in_width;
+	u16 in_height;
+	u16 out_width;
+	u16 out_height;
+	u32 unk[49];
+} __packed;
+static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc);
+
+struct cmd_ch_camera_config_select {
+	u64 opcode;
+	u32 chan;
+	u32 preset;
+} __packed;
+static_assert(sizeof(struct cmd_ch_camera_config_select) == 0x10);
+
+struct cmd_ch_set_file_load {
+	u64 opcode;
+	u32 chan;
+	u32 addr;
+	u32 size;
+} __packed;
+static_assert(sizeof(struct cmd_ch_set_file_load) == 0x14);
+
+struct cmd_ch_buffer_return {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_buffer_return) == 0xc);
+
+struct cmd_ch_sbs_enable {
+	u64 opcode;
+	u32 chan;
+	u32 enable;
+} __packed;
+static_assert(sizeof(struct cmd_ch_sbs_enable) == 0x10);
+
+struct cmd_ch_crop_set {
+	u64 opcode;
+	u32 chan;
+	u32 x1;
+	u32 y1;
+	u32 x2;
+	u32 y2;
+} __packed;
+static_assert(sizeof(struct cmd_ch_crop_set) == 0x1c);
+
+struct cmd_ch_output_config_set {
+	u64 opcode;
+	u32 chan;
+	u32 width;
+	u32 height;
+	u32 colorspace;
+	u32 format;
+	u32 unk_w0;
+	u32 unk_w1;
+	u32 unk_24;
+	u32 padding_rows;
+	u32 unk_h0;
+	u32 compress;
+	u32 unk_w2;
+} __packed;
+static_assert(sizeof(struct cmd_ch_output_config_set) == 0x38);
+
+struct cmd_ch_preview_stream_set {
+	u64 opcode;
+	u32 chan;
+	u32 stream;
+} __packed;
+static_assert(sizeof(struct cmd_ch_preview_stream_set) == 0x10);
+
+struct cmd_ch_als_disable {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_als_disable) == 0xc);
+
+struct cmd_ch_cnr_start {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_cnr_start) == 0xc);
+
+struct cmd_ch_mbnr_enable {
+	u64 opcode;
+	u32 chan;
+	u32 use_case;
+	u32 mode;
+	u32 enable_chroma;
+} __packed;
+static_assert(sizeof(struct cmd_ch_mbnr_enable) == 0x18);
+
+struct cmd_ch_sif_pixel_format_set {
+	u64 opcode;
+	u32 chan;
+	u8 format;
+	u8 type;
+	u16 compress;
+	u32 unk_10;
+} __packed;
+static_assert(sizeof(struct cmd_ch_sif_pixel_format_set) == 0x14);
+
+int isp_cmd_ch_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan,
+			struct cmd_ch_info *args);
+int isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 preset,
+				 struct cmd_ch_camera_config *args);
+int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan,
+					 struct cmd_ch_camera_config *args);
+int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan,
+				    u32 preset);
+int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr,
+			     u32 size);
+int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable);
+int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
+			u32 y2);
+int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
+				 u32 height, u32 colorspace, u32 format);
+int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream);
+int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case,
+			   u32 mode, u32 enable_chroma);
+int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan);
+
+struct cmd_ch_buffer_recycle_mode_set {
+	u64 opcode;
+	u32 chan;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_ch_buffer_recycle_mode_set) == 0x10);
+
+struct cmd_ch_buffer_recycle_start {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_buffer_recycle_start) == 0xc);
+
+struct cmd_ch_buffer_pool_config_set {
+	u64 opcode;
+	u32 chan;
+	u16 type;
+	u16 count;
+	u32 meta_size0;
+	u32 meta_size1;
+	u32 zero[0x1f];
+	u32 data_blocks;
+	u32 compress;
+} __packed;
+static_assert(sizeof(struct cmd_ch_buffer_pool_config_set) == 0x9c);
+
+struct cmd_ch_buffer_pool_return {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_ch_buffer_pool_return) == 0xc);
+
+int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan,
+				       u32 mode);
+int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan,
+				      u16 type);
+int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan);
+
+struct cmd_apple_ch_temporal_filter_start {
+	u64 opcode;
+	u32 chan;
+	u32 unk_c;
+	u32 unk_10;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_temporal_filter_start) == 0x14);
+
+struct cmd_apple_ch_temporal_filter_stop {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_temporal_filter_stop) == 0xc);
+
+struct cmd_apple_ch_motion_history_start {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_motion_history_start) == 0xc);
+
+struct cmd_apple_ch_motion_history_stop {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_motion_history_stop) == 0xc);
+
+struct cmd_apple_ch_temporal_filter_enable {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_temporal_filter_enable) == 0xc);
+
+struct cmd_apple_ch_temporal_filter_disable {
+	u64 opcode;
+	u32 chan;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_temporal_filter_disable) == 0xc);
+
+int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_temporal_filter_enable(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_temporal_filter_disable(struct apple_isp *isp, u32 chan);
+
+struct cmd_ch_ae_stability_set {
+	u64 opcode;
+	u32 chan;
+	u32 stability;
+} __packed;
+static_assert(sizeof(struct cmd_ch_ae_stability_set) == 0x10);
+
+struct cmd_ch_ae_stability_to_stable_set {
+	u64 opcode;
+	u32 chan;
+	u32 stability;
+} __packed;
+static_assert(sizeof(struct cmd_ch_ae_stability_to_stable_set) == 0x10);
+
+struct cmd_ch_ae_frame_rate_max_get {
+	u64 opcode;
+	u32 chan;
+	u32 framerate;
+} __packed;
+static_assert(sizeof(struct cmd_ch_ae_frame_rate_max_get) == 0x10);
+
+struct cmd_ch_ae_frame_rate_max_set {
+	u64 opcode;
+	u32 chan;
+	u32 framerate;
+} __packed;
+static_assert(sizeof(struct cmd_ch_ae_frame_rate_max_set) == 0x10);
+
+struct cmd_ch_ae_frame_rate_min_set {
+	u64 opcode;
+	u32 chan;
+	u32 framerate;
+} __packed;
+static_assert(sizeof(struct cmd_ch_ae_frame_rate_min_set) == 0x10);
+
+struct cmd_apple_ch_ae_fd_scene_metering_config_set {
+	u64 opcode;
+	u32 chan;
+	u32 unk_c;
+	u32 unk_10;
+	u32 unk_14;
+	u32 unk_18;
+	u32 unk_1c;
+	u32 unk_20;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_ae_fd_scene_metering_config_set) ==
+	      0x24);
+
+struct cmd_apple_ch_ae_metering_mode_set {
+	u64 opcode;
+	u32 chan;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_ae_metering_mode_set) == 0x10);
+
+struct cmd_apple_ch_ae_flicker_freq_update_current_set {
+	u64 opcode;
+	u32 chan;
+	u32 freq;
+} __packed;
+static_assert(sizeof(struct cmd_apple_ch_ae_flicker_freq_update_current_set) ==
+	      0x10);
+
+int isp_cmd_ch_ae_stability_set(struct apple_isp *isp, u32 chan, u32 stability);
+int isp_cmd_ch_ae_stability_to_stable_set(struct apple_isp *isp, u32 chan,
+					  u32 stability);
+int isp_cmd_ch_ae_frame_rate_max_get(struct apple_isp *isp, u32 chan,
+				     struct cmd_ch_ae_frame_rate_max_get *args);
+int isp_cmd_ch_ae_frame_rate_max_set(struct apple_isp *isp, u32 chan,
+				     u32 framerate);
+int isp_cmd_ch_ae_frame_rate_min_set(struct apple_isp *isp, u32 chan,
+				     u32 framerate);
+int isp_cmd_apple_ch_ae_fd_scene_metering_config_set(struct apple_isp *isp,
+						     u32 chan);
+int isp_cmd_apple_ch_ae_metering_mode_set(struct apple_isp *isp, u32 chan,
+					  u32 mode);
+int isp_cmd_apple_ch_ae_flicker_freq_update_current_set(struct apple_isp *isp,
+							u32 chan, u32 freq);
+
+struct cmd_ch_semantic_video_enable {
+	u64 opcode;
+	u32 chan;
+	u32 enable;
+} __packed;
+static_assert(sizeof(struct cmd_ch_semantic_video_enable) == 0x10);
+
+struct cmd_ch_semantic_awb_enable {
+	u64 opcode;
+	u32 chan;
+	u32 enable;
+} __packed;
+static_assert(sizeof(struct cmd_ch_semantic_awb_enable) == 0x10);
+
+int isp_cmd_ch_semantic_video_enable(struct apple_isp *isp, u32 chan,
+				     u32 enable);
+int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable);
+
+#endif /* __ISP_CMD_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
new file mode 100644
index 00000000000000..e8e32ba73ad962
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Apple Image Signal Processor driver
+ *
+ * Copyright (C) 2023 The Asahi Linux Contributors
+ *
+ * Based on aspeed/aspeed-video.c
+ *  Copyright 2020 IBM Corp.
+ *  Copyright (c) 2019-2020 Intel Corporation
+ */
+
+#include <linux/iommu.h>
+#include <linux/module.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/pm_domain.h>
+#include <linux/pm_runtime.h>
+#include <linux/workqueue.h>
+
+#include "isp-cam.h"
+#include "isp-iommu.h"
+#include "isp-v4l2.h"
+
+static void apple_isp_detach_genpd(struct apple_isp *isp)
+{
+	if (isp->pd_count <= 1)
+		return;
+
+	for (int i = isp->pd_count - 1; i >= 0; i--) {
+		if (isp->pd_link[i])
+			device_link_del(isp->pd_link[i]);
+		if (!IS_ERR_OR_NULL(isp->pd_dev[i]))
+			dev_pm_domain_detach(isp->pd_dev[i], true);
+	}
+
+	return;
+}
+
+static int apple_isp_attach_genpd(struct apple_isp *isp)
+{
+	struct device *dev = isp->dev;
+
+	isp->pd_count = of_count_phandle_with_args(
+		dev->of_node, "power-domains", "#power-domain-cells");
+	if (isp->pd_count <= 1)
+		return 0;
+
+	isp->pd_dev = devm_kcalloc(dev, isp->pd_count, sizeof(*isp->pd_dev),
+				   GFP_KERNEL);
+	if (!isp->pd_dev)
+		return -ENOMEM;
+
+	isp->pd_link = devm_kcalloc(dev, isp->pd_count, sizeof(*isp->pd_link),
+				    GFP_KERNEL);
+	if (!isp->pd_link)
+		return -ENOMEM;
+
+	for (int i = 0; i < isp->pd_count; i++) {
+		isp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i);
+		if (IS_ERR(isp->pd_dev[i])) {
+			apple_isp_detach_genpd(isp);
+			return PTR_ERR(isp->pd_dev[i]);
+		}
+
+		isp->pd_link[i] =
+			device_link_add(dev, isp->pd_dev[i],
+					DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
+						DL_FLAG_RPM_ACTIVE);
+		if (!isp->pd_link[i]) {
+			apple_isp_detach_genpd(isp);
+			return -EINVAL;
+		}
+	}
+
+	return 0;
+}
+
+static int apple_isp_init_iommu(struct apple_isp *isp)
+{
+	struct device *dev = isp->dev;
+	struct isp_firmware *fw = &isp->fw;
+	u64 heap_base, heap_size, vm_size;
+	int err;
+	int i = 0;
+
+	isp->domain = iommu_get_domain_for_dev(isp->dev);
+	if (!isp->domain)
+		return -EPROBE_DEFER;
+	isp->shift = __ffs(isp->domain->pgsize_bitmap);
+
+	err = of_property_read_u64(dev->of_node, "apple,isp-heap-base",
+				   &heap_base);
+	if (err) {
+		dev_err(dev, "failed to read 'apple,isp-heap-base': %d\n", err);
+		return err;
+	}
+
+	err = of_property_read_u64(dev->of_node, "apple,isp-heap-size",
+				   &heap_size);
+	if (err) {
+		dev_err(dev, "failed to read 'apple,isp-heap-size': %d\n", err);
+		return err;
+	}
+
+	err = of_property_read_u64(dev->of_node, "apple,dart-vm-size",
+				   &vm_size);
+	if (err) {
+		dev_err(dev, "failed to read 'apple,dart-vm-size': %d\n", err);
+		return err;
+	}
+
+	drm_mm_init(&isp->iovad, heap_base, vm_size - heap_base);
+
+	/* Allocate read-only coprocessor private heap */
+	fw->heap = isp_alloc_surface(isp, heap_size);
+	if (!fw->heap) {
+		drm_mm_takedown(&isp->iovad);
+		err = -ENOMEM;
+		return err;
+	}
+
+	apple_isp_iommu_sync_ttbr(isp);
+
+	return 0;
+}
+
+static void apple_isp_free_iommu(struct apple_isp *isp)
+{
+	isp_free_surface(isp, isp->fw.heap);
+	drm_mm_takedown(&isp->iovad);
+}
+
+static int apple_isp_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct apple_isp *isp;
+	struct resource *res;
+	int err;
+
+	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
+	if (!isp)
+		return -ENOMEM;
+
+	isp->dev = dev;
+	isp->hw = of_device_get_match_data(dev);
+	platform_set_drvdata(pdev, isp);
+	dev_set_drvdata(dev, isp);
+
+	err = apple_isp_attach_genpd(isp);
+	if (err) {
+		dev_err(dev, "failed to attatch power domains\n");
+		return err;
+	}
+
+	isp->asc = devm_platform_ioremap_resource_byname(pdev, "asc");
+	if (IS_ERR(isp->asc)) {
+		err = PTR_ERR(isp->asc);
+		goto detach_genpd;
+	}
+
+	isp->core = devm_platform_ioremap_resource_byname(pdev, "core");
+	if (IS_ERR(isp->core)) {
+		err = PTR_ERR(isp->core);
+		goto detach_genpd;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dart0");
+	if (!res) {
+		err = -ENODEV;
+		goto detach_genpd;
+	}
+
+	/* Simply ioremap since it's a shared register zone */
+	isp->dart0 = devm_ioremap(dev, res->start, resource_size(res));
+	if (IS_ERR(isp->dart0)) {
+		err = PTR_ERR(isp->dart0);
+		goto detach_genpd;
+	}
+
+	isp->dart1 = devm_platform_ioremap_resource_byname(pdev, "dart1");
+	if (IS_ERR(isp->dart1)) {
+		err = PTR_ERR(isp->dart1);
+		goto detach_genpd;
+	}
+
+	isp->dart2 = devm_platform_ioremap_resource_byname(pdev, "dart2");
+	if (IS_ERR(isp->dart2)) {
+		err = PTR_ERR(isp->dart2);
+		goto detach_genpd;
+	}
+
+	isp->irq = platform_get_irq(pdev, 0);
+	if (isp->irq < 0) {
+		err = isp->irq;
+		goto detach_genpd;
+	}
+	if (!isp->irq) {
+		err = -ENODEV;
+		goto detach_genpd;
+	}
+
+	mutex_init(&isp->iovad_lock);
+	mutex_init(&isp->video_lock);
+	spin_lock_init(&isp->buf_lock);
+	init_waitqueue_head(&isp->wait);
+	INIT_LIST_HEAD(&isp->gc);
+	INIT_LIST_HEAD(&isp->buffers);
+	isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0);
+	if (!isp->wq) {
+		dev_err(dev, "failed to create workqueue\n");
+		err = -ENOMEM;
+		goto detach_genpd;
+	}
+
+	err = apple_isp_init_iommu(isp);
+	if (err) {
+		dev_err(dev, "failed to init iommu: %d\n", err);
+		goto destroy_wq;
+	}
+
+	pm_runtime_enable(dev);
+
+	err = apple_isp_detect_camera(isp);
+	if (err) {
+		dev_err(dev, "failed to detect camera: %d\n", err);
+		goto free_iommu;
+	}
+
+	err = apple_isp_setup_video(isp);
+	if (err) {
+		dev_err(dev, "failed to register video device: %d\n", err);
+		goto free_iommu;
+	}
+
+	dev_info(dev, "apple-isp probe!\n");
+
+	return 0;
+
+free_iommu:
+	pm_runtime_disable(dev);
+	apple_isp_free_iommu(isp);
+destroy_wq:
+	destroy_workqueue(isp->wq);
+detach_genpd:
+	apple_isp_detach_genpd(isp);
+	return err;
+}
+
+static void apple_isp_remove(struct platform_device *pdev)
+{
+	struct apple_isp *isp = platform_get_drvdata(pdev);
+
+	apple_isp_remove_video(isp);
+	pm_runtime_disable(isp->dev);
+	apple_isp_free_iommu(isp);
+	destroy_workqueue(isp->wq);
+	apple_isp_detach_genpd(isp);
+	return 0;
+}
+
+/* T8020/T6000 registers */
+#define DART_T8020_STREAM_COMMAND	     0x20
+#define DART_T8020_STREAM_SELECT	     0x34
+#define DART_T8020_TTBR			     0x200
+#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20)
+
+static const struct apple_isp_hw apple_isp_hw_t8103 = {
+	.pmu_base = 0x23b704000,
+
+	.dsid_clr_base0 = 0x200014000,
+	.dsid_clr_base1 = 0x200054000,
+	.dsid_clr_base2 = 0x200094000,
+	.dsid_clr_base3 = 0x2000d4000,
+	.dsid_clr_range0 = 0x1000,
+	.dsid_clr_range1 = 0x1000,
+	.dsid_clr_range2 = 0x1000,
+	.dsid_clr_range3 = 0x1000,
+
+	.clock_scratch = 0x23b738010,
+	.clock_base = 0x23bc3c000,
+	.clock_bit = 0x1,
+	.clock_size = 0x4,
+	.bandwidth_scratch = 0x23b73800c,
+	.bandwidth_base = 0x23bc3c000,
+	.bandwidth_bit = 0x0,
+	.bandwidth_size = 0x4,
+
+	.stream_command = DART_T8020_STREAM_COMMAND,
+	.stream_select = DART_T8020_STREAM_SELECT,
+	.ttbr = DART_T8020_TTBR,
+	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
+};
+
+static const struct of_device_id apple_isp_of_match[] = {
+	{ .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 },
+	{},
+};
+MODULE_DEVICE_TABLE(of, apple_isp_of_match);
+
+static __maybe_unused int apple_isp_suspend(struct device *dev)
+{
+	struct apple_isp *isp = dev_get_drvdata(dev);
+
+	apple_isp_iommu_invalidate_tlb(isp);
+
+	return 0;
+}
+
+static __maybe_unused int apple_isp_resume(struct device *dev)
+{
+	struct apple_isp *isp = dev_get_drvdata(dev);
+
+	apple_isp_iommu_sync_ttbr(isp);
+
+	return 0;
+}
+DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL);
+
+static struct platform_driver apple_isp_driver = {
+	.driver	= {
+		.name		= "apple-isp",
+		.of_match_table	= apple_isp_of_match,
+		.pm		= pm_ptr(&apple_isp_pm_ops),
+	},
+	.probe	= apple_isp_probe,
+	.remove	= apple_isp_remove,
+};
+module_platform_driver(apple_isp_driver);
+
+MODULE_AUTHOR("Eileen Yoon <eyn@gmx.com>");
+MODULE_DESCRIPTION("Apple ISP driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
new file mode 100644
index 00000000000000..5db64dcc844863
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -0,0 +1,258 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_DRV_H__
+#define __ISP_DRV_H__
+
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/spinlock.h>
+
+#include <drm/drm_mm.h>
+#include <media/v4l2-device.h>
+#include <media/videobuf2-core.h>
+#include <media/videobuf2-v4l2.h>
+
+/* #define APPLE_ISP_DEBUG */
+#define APPLE_ISP_DEVICE_NAME "apple-isp"
+
+#define ISP_MAX_CHANNELS      6
+#define ISP_IPC_MESSAGE_SIZE  64
+#define ISP_IPC_FLAG_ACK      0x1
+#define ISP_META_SIZE	      0x4640
+
+struct isp_surf {
+	struct drm_mm_node *mm;
+	struct list_head head;
+	u64 size;
+	u32 num_pages;
+	struct page **pages;
+	struct sg_table sgt;
+	dma_addr_t iova;
+	void *virt;
+	refcount_t refcount;
+	bool gc;
+};
+
+struct isp_message {
+	u64 arg0;
+	u64 arg1;
+	u64 arg2;
+	u64 arg3;
+	u64 arg4;
+	u64 arg5;
+	u64 arg6;
+	u64 arg7;
+} __packed;
+static_assert(sizeof(struct isp_message) == ISP_IPC_MESSAGE_SIZE);
+
+struct isp_channel {
+	char *name;
+	u32 type;
+	u32 src;
+	u32 num;
+	u64 size;
+	dma_addr_t iova;
+	u32 doorbell;
+	u32 cursor;
+	spinlock_t lock;
+	struct isp_message req;
+	struct isp_message rsp;
+	const struct isp_chan_ops *ops;
+};
+
+struct apple_isp_hw {
+	u64 pmu_base;
+
+	u64 dsid_clr_base0;
+	u64 dsid_clr_base1;
+	u64 dsid_clr_base2;
+	u64 dsid_clr_base3;
+	u32 dsid_clr_range0;
+	u32 dsid_clr_range1;
+	u32 dsid_clr_range2;
+	u32 dsid_clr_range3;
+
+	u64 clock_scratch;
+	u64 clock_base;
+	u8 clock_bit;
+	u8 clock_size;
+	u64 bandwidth_scratch;
+	u64 bandwidth_base;
+	u8 bandwidth_bit;
+	u8 bandwidth_size;
+
+	u32 stream_command;
+	u32 stream_select;
+	u32 ttbr;
+	u32 stream_command_invalidate;
+};
+
+struct isp_resv {
+	phys_addr_t phys;
+	dma_addr_t iova;
+	u64 size;
+};
+
+enum isp_sensor_id {
+	ISP_IMX248_1820_01,
+	ISP_IMX248_1822_02,
+	ISP_IMX343_5221_02,
+	ISP_IMX354_9251_02,
+	ISP_IMX356_4820_01,
+	ISP_IMX356_4820_02,
+	ISP_IMX364_8720_01,
+	ISP_IMX364_8723_01,
+	ISP_IMX372_3820_01,
+	ISP_IMX372_3820_02,
+	ISP_IMX372_3820_11,
+	ISP_IMX372_3820_12,
+	ISP_IMX405_9720_01,
+	ISP_IMX405_9721_01,
+	ISP_IMX405_9723_01,
+	ISP_IMX414_2520_01,
+	ISP_IMX503_7820_01,
+	ISP_IMX503_7820_02,
+	ISP_IMX505_3921_01,
+	ISP_IMX514_2820_01,
+	ISP_IMX514_2820_02,
+	ISP_IMX514_2820_03,
+	ISP_IMX514_2820_04,
+	ISP_IMX558_1921_01,
+	ISP_IMX558_1922_02,
+	ISP_IMX603_7920_01,
+	ISP_IMX603_7920_02,
+	ISP_IMX603_7921_01,
+	ISP_IMX613_4920_01,
+	ISP_IMX613_4920_02,
+	ISP_IMX614_2921_01,
+	ISP_IMX614_2921_02,
+	ISP_IMX614_2922_02,
+	ISP_IMX633_3622_01,
+	ISP_IMX703_7721_01,
+	ISP_IMX703_7722_01,
+	ISP_IMX713_4721_01,
+	ISP_IMX713_4722_01,
+	ISP_IMX714_2022_01,
+	ISP_IMX772_3721_01,
+	ISP_IMX772_3721_11,
+	ISP_IMX772_3722_01,
+	ISP_IMX772_3723_01,
+	ISP_IMX814_2123_01,
+	ISP_IMX853_7622_01,
+	ISP_IMX913_7523_01,
+	ISP_VD56G0_6221_01,
+	ISP_VD56G0_6222_01,
+};
+
+struct isp_format {
+	enum isp_sensor_id id;
+	u32 version;
+	u32 num_presets;
+	u32 preset;
+	u32 width;
+	u32 height;
+	u32 x1;
+	u32 y1;
+	u32 x2;
+	u32 y2;
+	unsigned int num_planes;
+	size_t plane_size[VB2_MAX_PLANES];
+	size_t total_size;
+};
+
+struct apple_isp {
+	struct device *dev;
+	const struct apple_isp_hw *hw;
+
+	int num_channels;
+	struct isp_format fmts[ISP_MAX_CHANNELS];
+	unsigned int current_ch;
+
+	struct video_device vdev;
+	struct media_device mdev;
+	struct v4l2_device v4l2_dev;
+	struct vb2_queue vbq;
+	struct mutex video_lock;
+	unsigned int sequence;
+	bool multiplanar;
+
+	int pd_count;
+	struct device **pd_dev;
+	struct device_link **pd_link;
+
+	int irq;
+
+	void __iomem *asc;
+	void __iomem *core;
+	void __iomem *dart0;
+	void __iomem *dart1;
+	void __iomem *dart2;
+
+	struct iommu_domain *domain;
+	unsigned long shift;
+	struct drm_mm iovad; /* TODO iova.c can't allocate bottom-up */
+	struct mutex iovad_lock;
+
+	struct isp_firmware {
+		struct isp_surf *heap;
+	} fw;
+
+	struct isp_surf *ipc_surf;
+	struct isp_surf *extra_surf;
+	struct isp_surf *data_surf;
+	struct list_head gc;
+	struct workqueue_struct *wq;
+
+	int num_ipc_chans;
+	struct isp_channel **ipc_chans;
+	struct isp_channel *chan_tm; /* TERMINAL */
+	struct isp_channel *chan_io; /* IO */
+	struct isp_channel *chan_dg; /* DEBUG */
+	struct isp_channel *chan_bh; /* BUF_H2T */
+	struct isp_channel *chan_bt; /* BUF_T2H */
+	struct isp_channel *chan_sm; /* SHAREDMALLOC */
+	struct isp_channel *chan_it; /* IO_T2H */
+
+	wait_queue_head_t wait;
+	dma_addr_t cmd_iova;
+
+	unsigned long state;
+	spinlock_t buf_lock;
+	struct list_head buffers;
+};
+
+struct isp_chan_ops {
+	int (*handle)(struct apple_isp *isp, struct isp_channel *chan);
+};
+
+struct isp_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head link;
+	struct isp_surf surfs[VB2_MAX_PLANES];
+	struct isp_surf *meta;
+};
+
+#define to_isp_buffer(x) container_of((x), struct isp_buffer, vb)
+
+enum {
+	ISP_STATE_STREAMING,
+	ISP_STATE_LOGGING,
+};
+
+#ifdef APPLE_ISP_DEBUG
+#define isp_dbg(isp, fmt, ...) \
+	dev_info((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__)
+#else
+#define isp_dbg(isp, fmt, ...) \
+	dev_dbg((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__)
+#endif
+
+#define isp_err(isp, fmt, ...) \
+	dev_err((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__)
+
+#define isp_get_format(isp, ch)	    (&(isp)->fmts[(ch)])
+#define isp_get_current_format(isp) (isp_get_format(isp, isp->current_ch))
+
+#endif /* __ISP_DRV_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
new file mode 100644
index 00000000000000..12b9c0694d68e8
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -0,0 +1,606 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include <linux/delay.h>
+#include <linux/pm_runtime.h>
+#include <linux/types.h>
+
+#include "isp-cmd.h"
+#include "isp-iommu.h"
+#include "isp-ipc.h"
+#include "isp-regs.h"
+
+#define ISP_FIRMWARE_MDELAY	   1
+#define ISP_FIRMWARE_MAX_TRIES	   1000
+
+#define ISP_FIRMWARE_BOOTARGS_SIZE 0x180
+#define ISP_FIRMWARE_IPC_SIZE	   0x1c000
+#define ISP_FIRMWARE_DATA_SIZE	   0x28000
+
+static inline u32 isp_asc_read32(struct apple_isp *isp, u32 reg)
+{
+	return readl(isp->asc + reg);
+}
+
+static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val)
+{
+	writel(val, isp->asc + reg);
+}
+
+struct isp_firmware_bootargs {
+	u32 pad_0[2];
+	u64 ipc_iova;
+	u64 unk_size;
+	u64 unk_inv;
+	u64 extra_iova;
+	u64 extra_size;
+	u32 unk4;
+	u32 pad_40[7];
+	u32 ipc_size;
+	u32 pad_60[5];
+	u32 unk5;
+	u32 pad_7c[13];
+	u32 pad_b0;
+	u32 unk7;
+	u32 pad_b8[5];
+	u32 unk_iova1;
+	u32 pad_c0[47];
+	u32 unk9;
+} __packed;
+static_assert(sizeof(struct isp_firmware_bootargs) ==
+	      ISP_FIRMWARE_BOOTARGS_SIZE);
+
+struct isp_chan_desc {
+	char name[64];
+	u32 type;
+	u32 src;
+	u32 num;
+	u32 pad;
+	u64 iova;
+	u32 padding[0x2a];
+} __packed;
+static_assert(sizeof(struct isp_chan_desc) == 0x100);
+
+static const struct isp_chan_ops tm_ops = {
+	.handle = ipc_tm_handle,
+};
+
+static const struct isp_chan_ops sm_ops = {
+	.handle = ipc_sm_handle,
+};
+
+static const struct isp_chan_ops bt_ops = {
+	.handle = ipc_bt_handle,
+};
+
+static irqreturn_t apple_isp_isr(int irq, void *dev)
+{
+	struct apple_isp *isp = dev;
+
+	isp_core_write32(isp, ISP_CORE_IRQ_ACK,
+			 isp_core_read32(isp, ISP_CORE_IRQ_INTERRUPT));
+
+	wake_up_interruptible_all(&isp->wait);
+
+	ipc_chan_handle(isp, isp->chan_sm);
+	wake_up_interruptible_all(&isp->wait); /* Some commands depend on sm */
+
+	ipc_chan_handle(isp, isp->chan_tm);
+
+	ipc_chan_handle(isp, isp->chan_bt);
+	wake_up_interruptible_all(&isp->wait);
+
+	return IRQ_HANDLED;
+}
+
+static void isp_disable_irq(struct apple_isp *isp)
+{
+	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0);
+	free_irq(isp->irq, isp);
+	isp_core_write32(isp, ISP_CORE_GPIO_1, 0xfeedbabe); /* real funny */
+}
+
+static int isp_enable_irq(struct apple_isp *isp)
+{
+	int err;
+
+	err = request_irq(isp->irq, apple_isp_isr, 0, "apple-isp", isp);
+	if (err < 0) {
+		isp_err(isp, "failed to request IRQ#%u (%d)\n", isp->irq, err);
+		return err;
+	}
+
+	isp_dbg(isp, "about to enable interrupts...\n");
+
+	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0xf);
+
+	return 0;
+}
+
+static int isp_coproc_ready(struct apple_isp *isp)
+{
+	int retries;
+	u32 status;
+
+	isp_asc_write32(isp, ISP_ASC_EDPRCR, 0x2);
+
+	isp_asc_write32(isp, ISP_ASC_PMGR_0, 0xff00ff);
+	isp_asc_write32(isp, ISP_ASC_PMGR_1, 0xff00ff);
+	isp_asc_write32(isp, ISP_ASC_PMGR_2, 0xff00ff);
+	isp_asc_write32(isp, ISP_ASC_PMGR_3, 0xff00ff);
+
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_0, 0xffffffff);
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_1, 0xffffffff);
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_2, 0xffffffff);
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_3, 0xffffffff);
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_4, 0xffffffff);
+	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_5, 0xffffffff);
+
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		status = isp_asc_read32(isp, ISP_ASC_STATUS);
+		if (!((status & 0x3) == 0)) {
+			isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n",
+				retries, status);
+			break;
+		}
+		mdelay(ISP_FIRMWARE_MDELAY);
+	}
+	if (retries >= ISP_FIRMWARE_MAX_TRIES) {
+		isp_err(isp, "coproc NOT in WFI (status: 0x%x)\n", status);
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void isp_firmware_shutdown_stage1(struct apple_isp *isp)
+{
+	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0);
+}
+
+static int isp_firmware_boot_stage1(struct apple_isp *isp)
+{
+	int err, retries;
+
+	err = isp_coproc_ready(isp);
+	if (err < 0)
+		return err;
+
+	isp_core_write32(isp, ISP_CORE_CLOCK_EN, 0x1);
+
+	isp_core_write32(isp, ISP_CORE_GPIO_0, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_2, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_3, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_4, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_5, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_6, 0x0);
+	isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0);
+
+	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0);
+
+	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0);
+	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10);
+
+	/* Wait for ISP_CORE_GPIO_7 to 0x0 -> 0x8042006 */
+	isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0);
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7);
+		if (val == 0x8042006) {
+			isp_dbg(isp,
+				"got first magic number (0x%x) from firmware\n",
+				val);
+			break;
+		}
+		mdelay(ISP_FIRMWARE_MDELAY);
+	}
+	if (retries >= ISP_FIRMWARE_MAX_TRIES) {
+		isp_err(isp,
+			"never received first magic number from firmware\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static void isp_firmware_shutdown_stage2(struct apple_isp *isp)
+{
+	isp_free_surface(isp, isp->data_surf);
+	isp_free_surface(isp, isp->extra_surf);
+	isp_free_surface(isp, isp->ipc_surf);
+}
+
+static int isp_firmware_boot_stage2(struct apple_isp *isp)
+{
+	struct isp_firmware_bootargs args;
+	dma_addr_t args_iova;
+	int err, retries;
+
+	u32 num_ipc_chans = isp_core_read32(isp, ISP_CORE_GPIO_0);
+	u32 args_offset = isp_core_read32(isp, ISP_CORE_GPIO_1);
+	u32 extra_size = isp_core_read32(isp, ISP_CORE_GPIO_3);
+	isp->num_ipc_chans = num_ipc_chans;
+
+	if (!isp->num_ipc_chans) {
+		dev_err(isp->dev, "No IPC channels found\n");
+		return -ENODEV;
+	}
+
+	if (isp->num_ipc_chans != 7)
+		dev_warn(isp->dev, "unexpected channel count (%d)\n",
+			 num_ipc_chans);
+
+	isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE);
+	if (!isp->ipc_surf) {
+		isp_err(isp, "failed to alloc surface for ipc\n");
+		return -ENOMEM;
+	}
+
+	isp->extra_surf = isp_alloc_surface_vmap(isp, extra_size);
+	if (!isp->extra_surf) {
+		isp_err(isp, "failed to alloc surface for extra heap\n");
+		goto free_ipc;
+	}
+
+	isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE);
+	if (!isp->data_surf) {
+		isp_err(isp, "failed to alloc surface for data files\n");
+		goto free_extra;
+	}
+
+	args_iova = isp->ipc_surf->iova + args_offset + 0x40;
+	isp->cmd_iova = args_iova + sizeof(args) + 0x40;
+
+	memset(&args, 0, sizeof(args));
+	args.ipc_iova = isp->ipc_surf->iova;
+	args.ipc_size = isp->ipc_surf->size;
+	args.unk_size = 0x1800000;
+	args.unk_inv = 0x10000000 - args.unk_size;
+	args.extra_iova = isp->extra_surf->iova;
+	args.extra_size = isp->extra_surf->size;
+	args.unk4 = 0x1;
+	args.unk5 = 0x40;
+	args.unk7 = 0x1;
+	args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc;
+	args.unk9 = 0x3;
+	isp_iowrite(isp, args_iova, &args, sizeof(args));
+
+	isp_core_write32(isp, ISP_CORE_GPIO_0, args_iova);
+	isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0);
+
+	/* Wait for ISP_CORE_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
+	isp_core_write32(isp, ISP_CORE_GPIO_7, 0xf7fbdff9);
+
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7);
+		if (val == 0x8042006) {
+			isp_dbg(isp,
+				"got second magic number (0x%x) from firmware\n",
+				val);
+			break;
+		}
+		mdelay(ISP_FIRMWARE_MDELAY);
+	}
+	if (retries >= ISP_FIRMWARE_MAX_TRIES) {
+		isp_err(isp,
+			"never received second magic number from firmware\n");
+		err = -ENODEV;
+		goto free_file;
+	}
+
+	return 0;
+
+free_file:
+	isp_free_surface(isp, isp->data_surf);
+free_extra:
+	isp_free_surface(isp, isp->extra_surf);
+free_ipc:
+	isp_free_surface(isp, isp->ipc_surf);
+	return err;
+}
+
+static inline struct isp_channel *isp_get_chan_index(struct apple_isp *isp,
+						     const char *name)
+{
+	for (int i = 0; i < isp->num_ipc_chans; i++) {
+		if (!strcasecmp(isp->ipc_chans[i]->name, name))
+			return isp->ipc_chans[i];
+	}
+	return NULL;
+}
+
+static void isp_free_channel_info(struct apple_isp *isp)
+{
+	for (int i = 0; i < isp->num_ipc_chans; i++) {
+		struct isp_channel *chan = isp->ipc_chans[i];
+		if (!chan)
+			continue;
+		kfree(chan->name);
+		kfree(chan);
+		isp->ipc_chans[i] = NULL;
+	}
+	kfree(isp->ipc_chans);
+	isp->ipc_chans = NULL;
+}
+
+static int isp_fill_channel_info(struct apple_isp *isp)
+{
+	u32 table_iova = isp_core_read32(isp, ISP_CORE_GPIO_0);
+
+	isp->ipc_chans = kcalloc(isp->num_ipc_chans,
+				 sizeof(struct isp_channel *), GFP_KERNEL);
+	if (!isp->ipc_chans)
+		goto out;
+
+	for (int i = 0; i < isp->num_ipc_chans; i++) {
+		struct isp_chan_desc desc;
+		dma_addr_t desc_iova = table_iova + (i * sizeof(desc));
+		struct isp_channel *chan =
+			kzalloc(sizeof(struct isp_channel), GFP_KERNEL);
+		if (!chan)
+			goto out;
+		isp->ipc_chans[i] = chan;
+
+		isp_ioread(isp, desc_iova, &desc, sizeof(desc));
+		chan->name = kstrdup(desc.name, GFP_KERNEL);
+		chan->type = desc.type;
+		chan->src = desc.src;
+		chan->doorbell = 1 << chan->src;
+		chan->num = desc.num;
+		chan->size = desc.num * ISP_IPC_MESSAGE_SIZE;
+		chan->iova = desc.iova;
+		chan->cursor = 0;
+		spin_lock_init(&chan->lock);
+
+		if ((chan->type != ISP_IPC_CHAN_TYPE_COMMAND) &&
+		    (chan->type != ISP_IPC_CHAN_TYPE_REPLY) &&
+		    (chan->type != ISP_IPC_CHAN_TYPE_REPORT)) {
+			isp_err(isp, "invalid ipc chan type (%d)\n",
+				chan->type);
+			goto out;
+		}
+
+		isp_dbg(isp, "chan: %s type: %d src: %d num: %d iova: 0x%llx\n",
+			chan->name, chan->type, chan->src, chan->num,
+			chan->iova);
+	}
+
+	isp->chan_tm = isp_get_chan_index(isp, "TERMINAL");
+	isp->chan_io = isp_get_chan_index(isp, "IO");
+	isp->chan_dg = isp_get_chan_index(isp, "DEBUG");
+	isp->chan_bh = isp_get_chan_index(isp, "BUF_H2T");
+	isp->chan_bt = isp_get_chan_index(isp, "BUF_T2H");
+	isp->chan_sm = isp_get_chan_index(isp, "SHAREDMALLOC");
+	isp->chan_it = isp_get_chan_index(isp, "IO_T2H");
+
+	if (!isp->chan_tm || !isp->chan_io || !isp->chan_dg || !isp->chan_bh ||
+	    !isp->chan_bt || !isp->chan_sm || !isp->chan_it) {
+		isp_err(isp, "did not find all of the required ipc chans\n");
+		goto out;
+	}
+
+	isp->chan_tm->ops = &tm_ops;
+	isp->chan_sm->ops = &sm_ops;
+	isp->chan_bt->ops = &bt_ops;
+
+	return 0;
+out:
+	isp_free_channel_info(isp);
+	return -ENOMEM;
+}
+
+static void isp_firmware_shutdown_stage3(struct apple_isp *isp)
+{
+	isp_free_channel_info(isp);
+}
+
+static int isp_firmware_boot_stage3(struct apple_isp *isp)
+{
+	int err, retries;
+
+	err = isp_fill_channel_info(isp);
+	if (err < 0)
+		return err;
+
+	/* Mask the command channels to prepare for submission */
+	for (int i = 0; i < isp->num_ipc_chans; i++) {
+		struct isp_channel *chan = isp->ipc_chans[i];
+		if (chan->type != ISP_IPC_CHAN_TYPE_COMMAND)
+			continue;
+		for (int j = 0; j < chan->num; j++) {
+			struct isp_message msg;
+			dma_addr_t msg_iova = chan->iova + (j * sizeof(msg));
+
+			memset(&msg, 0, sizeof(msg));
+			msg.arg0 = ISP_IPC_FLAG_ACK;
+			isp_iowrite(isp, msg_iova, &msg, sizeof(msg));
+		}
+	}
+
+	/* Wait for ISP_CORE_GPIO_3 to 0x8042006 -> 0x0 */
+	isp_core_write32(isp, ISP_CORE_GPIO_3, 0x8042006);
+
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_3);
+		if (val == 0x0) {
+			isp_dbg(isp,
+				"got third magic number (0x%x) from firmware\n",
+				val);
+			break;
+		}
+		mdelay(ISP_FIRMWARE_MDELAY);
+	}
+	if (retries >= ISP_FIRMWARE_MAX_TRIES) {
+		isp_err(isp,
+			"never received third magic number from firmware\n");
+		isp_free_channel_info(isp);
+		return -ENODEV;
+	}
+
+	isp_dbg(isp, "firmware booted!\n");
+
+	return 0;
+}
+
+static int isp_stop_command_processor(struct apple_isp *isp)
+{
+	int retries;
+
+	/* Wait for ISP_CORE_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */
+	isp_core_write32(isp, ISP_CORE_GPIO_0, 0xf7fbdff9);
+
+	/* Their CISP_CMD_STOP implementation is buggy */
+	isp_cmd_suspend(isp);
+
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_0);
+		if (val == 0x8042006) {
+			isp_dbg(isp, "got magic number (0x%x) from firmware\n",
+				val);
+			break;
+		}
+		mdelay(ISP_FIRMWARE_MDELAY);
+	}
+	if (retries >= ISP_FIRMWARE_MAX_TRIES) {
+		isp_err(isp, "never received magic number from firmware\n");
+		return -ENODEV;
+	}
+
+	return 0;
+}
+
+static int isp_start_command_processor(struct apple_isp *isp)
+{
+	int err;
+
+	err = isp_cmd_print_enable(isp, 1);
+	if (err)
+		return err;
+
+	err = isp_cmd_set_isp_pmu_base(isp, isp->hw->pmu_base);
+	if (err)
+		return err;
+
+	err = isp_cmd_set_dsid_clr_req_base2(
+		isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1,
+		isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3,
+		isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1,
+		isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3);
+	if (err)
+		return err;
+
+	err = isp_cmd_pmp_ctrl_set(
+		isp, isp->hw->clock_scratch, isp->hw->clock_base,
+		isp->hw->clock_bit, isp->hw->clock_size,
+		isp->hw->bandwidth_scratch, isp->hw->bandwidth_base,
+		isp->hw->bandwidth_bit, isp->hw->bandwidth_size);
+	if (err)
+		return err;
+
+	err = isp_cmd_start(isp, 0);
+	if (err)
+		return err;
+
+	/* Now we can access CISP_CMD_CH_* commands */
+
+	return 0;
+}
+
+static void isp_collect_gc_surface(struct apple_isp *isp)
+{
+	struct isp_surf *tmp, *surf;
+	list_for_each_entry_safe_reverse(surf, tmp, &isp->gc, head) {
+		isp_dbg(isp, "freeing iova: 0x%llx size: 0x%llx virt: %pS\n",
+			surf->iova, surf->size, (void *)surf->virt);
+		isp_free_surface(isp, surf);
+	}
+}
+
+static int isp_firmware_boot(struct apple_isp *isp)
+{
+	int err;
+
+	err = isp_firmware_boot_stage1(isp);
+	if (err < 0) {
+		isp_err(isp, "failed firmware boot stage 1: %d\n", err);
+		goto garbage_collect;
+	}
+
+	err = isp_firmware_boot_stage2(isp);
+	if (err < 0) {
+		isp_err(isp, "failed firmware boot stage 2: %d\n", err);
+		goto shutdown_stage1;
+	}
+
+	err = isp_firmware_boot_stage3(isp);
+	if (err < 0) {
+		isp_err(isp, "failed firmware boot stage 3: %d\n", err);
+		goto shutdown_stage2;
+	}
+
+	err = isp_enable_irq(isp);
+	if (err < 0) {
+		isp_err(isp, "failed to enable interrupts: %d\n", err);
+		goto shutdown_stage3;
+	}
+
+	err = isp_start_command_processor(isp);
+	if (err < 0) {
+		isp_err(isp, "failed to start command processor: %d\n", err);
+		goto disable_irqs;
+	}
+
+	flush_workqueue(isp->wq);
+
+	return 0;
+
+disable_irqs:
+	isp_disable_irq(isp);
+shutdown_stage3:
+	isp_firmware_shutdown_stage3(isp);
+shutdown_stage2:
+	isp_firmware_shutdown_stage2(isp);
+shutdown_stage1:
+	isp_firmware_shutdown_stage1(isp);
+garbage_collect:
+	isp_collect_gc_surface(isp);
+	return err;
+}
+
+static void isp_firmware_shutdown(struct apple_isp *isp)
+{
+	flush_workqueue(isp->wq);
+	isp_stop_command_processor(isp);
+	isp_disable_irq(isp);
+	isp_firmware_shutdown_stage3(isp);
+	isp_firmware_shutdown_stage2(isp);
+	isp_firmware_shutdown_stage1(isp);
+	isp_collect_gc_surface(isp);
+}
+
+int apple_isp_firmware_boot(struct apple_isp *isp)
+{
+	int err;
+
+	/* Needs to be power cycled for IOMMU to behave correctly */
+	err = pm_runtime_resume_and_get(isp->dev);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to enable power: %d\n", err);
+		return err;
+	}
+
+	err = isp_firmware_boot(isp);
+	if (err) {
+		dev_err(isp->dev, "failed to boot firmware: %d\n", err);
+		pm_runtime_put_sync(isp->dev);
+		return err;
+	}
+
+	return 0;
+}
+
+void apple_isp_firmware_shutdown(struct apple_isp *isp)
+{
+	isp_firmware_shutdown(isp);
+	pm_runtime_put_sync(isp->dev);
+}
diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h
new file mode 100644
index 00000000000000..ad9f4fdf641aaa
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-fw.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_FW_H__
+#define __ISP_FW_H__
+
+#include "isp-drv.h"
+
+int apple_isp_firmware_boot(struct apple_isp *isp);
+void apple_isp_firmware_shutdown(struct apple_isp *isp);
+
+#endif /* __ISP_FW_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c
new file mode 100644
index 00000000000000..28935d37205024
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -0,0 +1,275 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include <linux/iommu.h>
+#include <linux/vmalloc.h>
+
+#include "isp-iommu.h"
+
+void apple_isp_iommu_sync_ttbr(struct apple_isp *isp)
+{
+	writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart1 + isp->hw->ttbr);
+	writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart2 + isp->hw->ttbr);
+}
+
+void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp)
+{
+	iommu_flush_iotlb_all(isp->domain);
+	writel(0x1, isp->dart1 + isp->hw->stream_select);
+	writel(isp->hw->stream_command_invalidate,
+	       isp->dart1 + isp->hw->stream_command);
+	writel(0x1, isp->dart2 + isp->hw->stream_select);
+	writel(isp->hw->stream_command_invalidate,
+	       isp->dart2 + isp->hw->stream_command);
+}
+
+static void isp_surf_free_pages(struct isp_surf *surf)
+{
+	for (u32 i = 0; i < surf->num_pages && surf->pages[i] != NULL; i++) {
+		__free_page(surf->pages[i]);
+	}
+	kvfree(surf->pages);
+}
+
+static int isp_surf_alloc_pages(struct isp_surf *surf)
+{
+	surf->pages = kvmalloc_array(surf->num_pages, sizeof(*surf->pages),
+				     GFP_KERNEL);
+	if (!surf->pages)
+		return -ENOMEM;
+
+	for (u32 i = 0; i < surf->num_pages; i++) {
+		surf->pages[i] = alloc_page(GFP_KERNEL);
+		if (surf->pages[i] == NULL)
+			goto free_pages;
+	}
+
+	return 0;
+
+free_pages:
+	isp_surf_free_pages(surf);
+	return -ENOMEM;
+}
+
+int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf)
+{
+	surf->virt = vmap(surf->pages, surf->num_pages, VM_MAP,
+			  pgprot_writecombine(PAGE_KERNEL));
+	if (surf->virt == NULL) {
+		dev_err(isp->dev, "failed to vmap size 0x%llx\n", surf->size);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void isp_surf_vunmap(struct apple_isp *isp, struct isp_surf *surf)
+{
+	if (surf->virt)
+		vunmap(surf->virt);
+	surf->virt = NULL;
+}
+
+static void isp_surf_unreserve_iova(struct apple_isp *isp,
+				    struct isp_surf *surf)
+{
+	if (surf->mm) {
+		mutex_lock(&isp->iovad_lock);
+		drm_mm_remove_node(surf->mm);
+		mutex_unlock(&isp->iovad_lock);
+		kfree(surf->mm);
+	}
+	surf->mm = NULL;
+}
+
+static int isp_surf_reserve_iova(struct apple_isp *isp, struct isp_surf *surf)
+{
+	int err;
+
+	surf->mm = kzalloc(sizeof(*surf->mm), GFP_KERNEL);
+	if (!surf->mm)
+		return -ENOMEM;
+
+	mutex_lock(&isp->iovad_lock);
+	err = drm_mm_insert_node_generic(&isp->iovad, surf->mm,
+					 ALIGN(surf->size, 1UL << isp->shift),
+					 1UL << isp->shift, 0, 0);
+	mutex_unlock(&isp->iovad_lock);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n",
+			surf->size);
+		goto mm_free;
+	}
+
+	surf->iova = surf->mm->start;
+
+	return 0;
+mm_free:
+	kfree(surf->mm);
+	surf->mm = NULL;
+	return err;
+}
+
+static void isp_surf_iommu_unmap(struct apple_isp *isp, struct isp_surf *surf)
+{
+	iommu_unmap(isp->domain, surf->iova, surf->size);
+	apple_isp_iommu_invalidate_tlb(isp);
+	sg_free_table(&surf->sgt);
+}
+
+static int isp_surf_iommu_map(struct apple_isp *isp, struct isp_surf *surf)
+{
+	unsigned long size;
+	int err;
+
+	err = sg_alloc_table_from_pages(&surf->sgt, surf->pages,
+					surf->num_pages, 0, surf->size,
+					GFP_KERNEL);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to alloc sgt from pages\n");
+		return err;
+	}
+
+	size = iommu_map_sgtable(isp->domain, surf->iova, &surf->sgt,
+				 IOMMU_READ | IOMMU_WRITE);
+	if (size < surf->size) {
+		dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n",
+			surf->iova);
+		sg_free_table(&surf->sgt);
+		return -ENXIO;
+	}
+
+	return 0;
+}
+
+static void __isp_surf_init(struct apple_isp *isp, struct isp_surf *surf,
+			    u64 size, bool gc)
+{
+	surf->mm = NULL;
+	surf->virt = NULL;
+	surf->size = ALIGN(size, 1UL << isp->shift);
+	surf->num_pages = surf->size >> isp->shift;
+	surf->gc = gc;
+}
+
+struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc)
+{
+	int err;
+
+	struct isp_surf *surf = kzalloc(sizeof(struct isp_surf), GFP_KERNEL);
+	if (!surf)
+		return NULL;
+
+	__isp_surf_init(isp, surf, size, gc);
+
+	err = isp_surf_alloc_pages(surf);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to allocate %d pages\n",
+			surf->num_pages);
+		goto free_surf;
+	}
+
+	err = isp_surf_reserve_iova(isp, surf);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n",
+			surf->size);
+		goto free_pages;
+	}
+
+	err = isp_surf_iommu_map(isp, surf);
+	if (err < 0) {
+		dev_err(isp->dev,
+			"failed to iommu_map size 0x%llx to iova 0x%llx\n",
+			surf->size, surf->iova);
+		goto unreserve_iova;
+	}
+
+	refcount_set(&surf->refcount, 1);
+	if (surf->gc)
+		list_add_tail(&surf->head, &isp->gc);
+
+	return surf;
+
+unreserve_iova:
+	isp_surf_unreserve_iova(isp, surf);
+free_pages:
+	isp_surf_free_pages(surf);
+free_surf:
+	kfree(surf);
+	return NULL;
+}
+
+struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size)
+{
+	int err;
+
+	struct isp_surf *surf = __isp_alloc_surface(isp, size, false);
+	if (!surf)
+		return NULL;
+
+	err = isp_surf_vmap(isp, surf);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to vmap iova 0x%llx - 0x%llx\n",
+			surf->iova, surf->iova + surf->size);
+		isp_free_surface(isp, surf);
+		return NULL;
+	}
+
+	return surf;
+}
+
+void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf)
+{
+	if (refcount_dec_and_test(&surf->refcount)) {
+		isp_surf_vunmap(isp, surf);
+		isp_surf_iommu_unmap(isp, surf);
+		isp_surf_unreserve_iova(isp, surf);
+		isp_surf_free_pages(surf);
+		if (surf->gc)
+			list_del(&surf->head);
+		kfree(surf);
+	}
+}
+
+void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova)
+{
+	phys_addr_t phys = iommu_iova_to_phys(isp->domain, iova);
+	return phys_to_virt(phys);
+}
+
+int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
+			    struct sg_table *sgt, u64 size)
+{
+	int err;
+	ssize_t mapped;
+
+	// TODO userptr sends unaligned sizes
+	surf->mm = NULL;
+	surf->size = size;
+
+	err = isp_surf_reserve_iova(isp, surf);
+	if (err < 0) {
+		dev_err(isp->dev, "failed to reserve 0x%llx of iova space\n",
+			surf->size);
+		return err;
+	}
+
+	mapped = iommu_map_sgtable(isp->domain, surf->iova, sgt,
+				   IOMMU_READ | IOMMU_WRITE);
+	if (mapped < surf->size) {
+		dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n",
+			surf->iova);
+		isp_surf_unreserve_iova(isp, surf);
+		return -ENXIO;
+	}
+	surf->size = mapped;
+
+	return 0;
+}
+
+void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf)
+{
+	iommu_unmap(isp->domain, surf->iova, surf->size);
+	apple_isp_iommu_invalidate_tlb(isp);
+	isp_surf_unreserve_iova(isp, surf);
+}
diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h
new file mode 100644
index 00000000000000..f9972bd9ff93e7
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-iommu.h
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_IOMMU_H__
+#define __ISP_IOMMU_H__
+
+#include "isp-drv.h"
+
+void apple_isp_iommu_sync_ttbr(struct apple_isp *isp);
+void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp);
+
+struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc);
+#define isp_alloc_surface(isp, size)	(__isp_alloc_surface(isp, size, false))
+#define isp_alloc_surface_gc(isp, size) (__isp_alloc_surface(isp, size, true))
+struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size);
+int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf);
+void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf);
+void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova);
+
+static inline void isp_ioread(struct apple_isp *isp, dma_addr_t iova,
+			      void *data, u64 size)
+{
+	void *virt = isp_iotranslate(isp, iova);
+	memcpy(data, virt, size);
+}
+
+static inline void isp_iowrite(struct apple_isp *isp, dma_addr_t iova,
+			       void *data, u64 size)
+{
+	void *virt = isp_iotranslate(isp, iova);
+	memcpy(virt, data, size);
+}
+
+int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
+			    struct sg_table *sgt, u64 size);
+void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf);
+
+#endif /* __ISP_IOMMU_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
new file mode 100644
index 00000000000000..ef3498c4fcd191
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -0,0 +1,329 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include "isp-iommu.h"
+#include "isp-ipc.h"
+#include "isp-regs.h"
+
+#define ISP_IPC_FLAG_TERMINAL_ACK	0x3
+#define ISP_IPC_BUFEXC_STAT_META_OFFSET 0x10
+
+struct isp_sm_deferred_work {
+	struct work_struct work;
+	struct apple_isp *isp;
+	struct isp_surf *surf;
+};
+
+struct isp_bufexc_stat {
+	u64 unk_0; // 2
+	u64 unk_8; // 2
+
+	u64 meta_iova;
+	u64 pad_20[3];
+	u64 meta_size; // 0x4640
+	u64 unk_38;
+
+	u32 unk_40; // 1
+	u32 unk_44;
+	u64 unk_48;
+
+	u64 iova0;
+	u64 iova1;
+	u64 iova2;
+	u64 iova3;
+	u32 pad_70[4];
+
+	u32 unk_80; // 2
+	u32 unk_84; // 1
+	u32 unk_88; // 0x10 || 0x13
+	u32 unk_8c;
+	u32 pad_90[96];
+
+	u32 unk_210; // 0x28
+	u32 unk_214;
+	u32 index;
+	u16 bes_width; // 1296, 0x510
+	u16 bes_height; // 736, 0x2e0
+
+	u32 unk_220; // 0x0 || 0x1
+	u32 pad_224[3];
+	u32 unk_230; // 0xf7ed38
+	u32 unk_234; // 3
+	u32 pad_238[2];
+	u32 pad_240[16];
+} __packed;
+static_assert(sizeof(struct isp_bufexc_stat) == ISP_IPC_BUFEXC_STAT_SIZE);
+
+static inline dma_addr_t chan_msg_iova(struct isp_channel *chan, u32 index)
+{
+	return chan->iova + (index * ISP_IPC_MESSAGE_SIZE);
+}
+
+static inline void chan_read_msg_index(struct apple_isp *isp,
+				       struct isp_channel *chan,
+				       struct isp_message *msg, u32 index)
+{
+	isp_ioread(isp, chan_msg_iova(chan, index), msg, sizeof(*msg));
+}
+
+static inline void chan_read_msg(struct apple_isp *isp,
+				 struct isp_channel *chan,
+				 struct isp_message *msg)
+{
+	chan_read_msg_index(isp, chan, msg, chan->cursor);
+}
+
+static inline void chan_write_msg_index(struct apple_isp *isp,
+					struct isp_channel *chan,
+					struct isp_message *msg, u32 index)
+{
+	isp_iowrite(isp, chan_msg_iova(chan, index), msg, sizeof(*msg));
+}
+
+static inline void chan_write_msg(struct apple_isp *isp,
+				  struct isp_channel *chan,
+				  struct isp_message *msg)
+{
+	chan_write_msg_index(isp, chan, msg, chan->cursor);
+}
+
+static inline void chan_update_cursor(struct isp_channel *chan)
+{
+	if (chan->cursor >= (chan->num - 1)) {
+		chan->cursor = 0;
+	} else {
+		chan->cursor += 1;
+	}
+}
+
+static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan)
+{
+	int err;
+
+	lockdep_assert_held(&chan->lock);
+
+	err = chan->ops->handle(isp, chan);
+	if (err < 0) {
+		dev_err(isp->dev, "%s: handler failed: %d)\n", chan->name, err);
+		return err;
+	}
+
+	chan_write_msg(isp, chan, &chan->rsp);
+
+	isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell);
+
+	chan_update_cursor(chan);
+
+	return 0;
+}
+
+static inline bool chan_rx_done(struct apple_isp *isp, struct isp_channel *chan)
+{
+	if (((chan->req.arg0 & 0xf) == ISP_IPC_FLAG_ACK) ||
+	    ((chan->req.arg0 & 0xf) == ISP_IPC_FLAG_TERMINAL_ACK)) {
+		return true;
+	}
+	return false;
+}
+
+int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	int err = 0;
+
+	spin_lock(&chan->lock);
+	while (1) {
+		chan_read_msg(isp, chan, &chan->req);
+		if (chan_rx_done(isp, chan)) {
+			err = 0;
+			break;
+		}
+		err = chan_handle_once(isp, chan);
+		if (err < 0) {
+			break;
+		}
+	}
+	spin_unlock(&chan->lock);
+
+	return err;
+}
+
+static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan)
+{
+	chan_read_msg(isp, chan, &chan->rsp);
+	if ((chan->rsp.arg0) == (chan->req.arg0 | ISP_IPC_FLAG_ACK)) {
+		chan_update_cursor(chan);
+		return true;
+	}
+	return false;
+}
+
+int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
+		  unsigned long timeout)
+{
+	long t;
+
+	chan_write_msg(isp, chan, &chan->req);
+	wmb();
+
+	isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell);
+
+	t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan),
+					     timeout);
+	if (t == 0) {
+		dev_err(isp->dev,
+			"%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n",
+			chan->name, chan->req.arg0, chan->req.arg1,
+			chan->req.arg2);
+		return -ETIME;
+	}
+
+	isp_dbg(isp, "%s: request success (%ld)\n", chan->name, t);
+
+	return 0;
+}
+
+int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	struct isp_message *rsp = &chan->rsp;
+
+#ifdef APPLE_ISP_DEBUG
+	struct isp_message *req = &chan->req;
+	char buf[512];
+	dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK;
+	u32 size = req->arg1;
+	if (iova && size && test_bit(ISP_STATE_LOGGING, &isp->state)) {
+		size = min_t(u32, size, 512);
+		isp_ioread(isp, iova, buf, size);
+		isp_dbg(isp, "ISPASC: %.*s", size, buf);
+	}
+#endif
+
+	rsp->arg0 = ISP_IPC_FLAG_ACK;
+	rsp->arg1 = 0x0;
+	rsp->arg2 = 0x0;
+
+	return 0;
+}
+
+/* The kernel accesses exactly two dynamically allocated shared surfaces:
+ * 1) LOG: Surface for terminal logs. Optional, only enabled in debug builds.
+ * 2) STAT: Surface for BUFT2H rendered frame stat buffer. We isp_ioread() in
+ * the BUFT2H ISR below. Since the BUFT2H IRQ is triggered by the BUF_H2T
+ * doorbell, the STAT vmap must complete before the first buffer submission
+ * under VIDIOC_STREAMON(). The CISP_CMD_PRINT_ENABLE completion depends on the
+ * STAT buffer SHAREDMALLOC ISR, which is part of the firmware initialization
+ * sequence. We also call flush_workqueue(), so a fault should not occur.
+ */
+static void sm_malloc_deferred_worker(struct work_struct *work)
+{
+	struct isp_sm_deferred_work *dwork =
+		container_of(work, struct isp_sm_deferred_work, work);
+	struct apple_isp *isp = dwork->isp;
+	struct isp_surf *surf = dwork->surf;
+	int err;
+
+	err = isp_surf_vmap(isp, surf); /* Can't vmap in interrupt ctx */
+	if (err < 0) {
+		isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n",
+			surf->iova, surf->size);
+		goto out;
+	}
+
+#ifdef APPLE_ISP_DEBUG
+	/* Only enabled in debug builds so it shouldn't matter, but 
+	 * the LOG surface is always the first surface requested. 
+	 */
+	if (!test_bit(ISP_STATE_LOGGING, &isp->state))
+		set_bit(ISP_STATE_LOGGING, &isp->state);
+#endif
+
+out:
+	kfree(dwork);
+}
+
+int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	struct isp_message *req = &chan->req, *rsp = &chan->rsp;
+
+	if (req->arg0 == 0x0) {
+		struct isp_sm_deferred_work *dwork;
+		struct isp_surf *surf;
+
+		dwork = kzalloc(sizeof(*dwork), GFP_KERNEL);
+		if (!dwork)
+			return -ENOMEM;
+		dwork->isp = isp;
+
+		surf = isp_alloc_surface_gc(isp, req->arg1);
+		if (!surf) {
+			isp_err(isp, "failed to alloc requested size 0x%llx\n",
+				req->arg1);
+			kfree(dwork);
+			return -ENOMEM;
+		}
+		dwork->surf = surf;
+
+		rsp->arg0 = surf->iova | ISP_IPC_FLAG_ACK;
+		rsp->arg1 = 0x0;
+		rsp->arg2 = 0x0; /* macOS uses this to index surfaces */
+
+		INIT_WORK(&dwork->work, sm_malloc_deferred_worker);
+		if (!queue_work(isp->wq, &dwork->work)) {
+			isp_err(isp, "failed to queue deferred work\n");
+			isp_free_surface(isp, surf);
+			kfree(dwork);
+			return -ENOMEM;
+		}
+		/* To the gc it goes... */
+
+	} else {
+		/* This should be the shared surface free request, but
+		 * 1) The fw doesn't request to free all of what it requested
+		 * 2) The fw continues to access the surface after
+		 * So we link it to the gc, which runs after fw shutdown
+		 */
+#ifdef APPLE_ISP_DEBUG
+		if (test_bit(ISP_STATE_LOGGING, &isp->state))
+			clear_bit(ISP_STATE_LOGGING, &isp->state);
+#endif
+		rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
+		rsp->arg1 = 0x0;
+		rsp->arg2 = 0x0;
+	}
+
+	return 0;
+}
+
+int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	struct isp_message *req = &chan->req, *rsp = &chan->rsp;
+	struct isp_buffer *tmp, *buf;
+	int err = 0;
+
+	/* No need to read the whole struct */
+	u64 meta_iova;
+	isp_ioread(isp, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, &meta_iova,
+		   sizeof(meta_iova));
+
+	spin_lock(&isp->buf_lock);
+	list_for_each_entry_safe_reverse(buf, tmp, &isp->buffers, link) {
+		if (buf->meta->iova == meta_iova) {
+			enum vb2_buffer_state state = VB2_BUF_STATE_ERROR;
+			buf->vb.vb2_buf.timestamp = ktime_get_ns();
+			buf->vb.sequence = isp->sequence++;
+			buf->vb.field = V4L2_FIELD_NONE;
+			if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER)
+				state = VB2_BUF_STATE_DONE;
+			vb2_buffer_done(&buf->vb.vb2_buf, state);
+			list_del(&buf->link);
+			break;
+		}
+	}
+	spin_unlock(&isp->buf_lock);
+
+	rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
+	rsp->arg1 = 0x0;
+	rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK;
+
+	return err;
+}
diff --git a/drivers/media/platform/apple/isp/isp-ipc.h b/drivers/media/platform/apple/isp/isp-ipc.h
new file mode 100644
index 00000000000000..32d1e1bf321006
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-ipc.h
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_IPC_H__
+#define __ISP_IPC_H__
+
+#include "isp-drv.h"
+
+#define ISP_IPC_CHAN_TYPE_COMMAND   0
+#define ISP_IPC_CHAN_TYPE_REPLY	    1
+#define ISP_IPC_CHAN_TYPE_REPORT    2
+
+#define ISP_IPC_BUFEXC_STAT_SIZE    0x280
+#define ISP_IPC_BUFEXC_FLAG_RENDER  0x10000000
+#define ISP_IPC_BUFEXC_FLAG_COMMAND 0x30000000
+#define ISP_IPC_BUFEXC_FLAG_ACK	    0x80000000
+
+int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan);
+int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
+		  unsigned long timeout);
+
+int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan);
+int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan);
+int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan);
+
+#endif /* __ISP_IPC_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h
new file mode 100644
index 00000000000000..b9bd505844d9de
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -0,0 +1,62 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_REGS_H__
+#define __ISP_REGS_H__
+
+#include "isp-drv.h"
+
+#define ISP_ASC_PMGR_0	       0x738
+#define ISP_ASC_PMGR_1	       0x798
+#define ISP_ASC_PMGR_2	       0x7f8
+#define ISP_ASC_PMGR_3	       0x858
+
+#define ISP_ASC_RVBAR	       0x1050000
+#define ISP_ASC_EDPRCR	       0x1010310
+#define ISP_ASC_CONTROL	       0x1400044
+#define ISP_ASC_STATUS	       0x1400048
+
+#define ISP_ASC_IRQ_MASK_0     0x1400a00
+#define ISP_ASC_IRQ_MASK_1     0x1400a04
+#define ISP_ASC_IRQ_MASK_2     0x1400a08
+#define ISP_ASC_IRQ_MASK_3     0x1400a0c
+#define ISP_ASC_IRQ_MASK_4     0x1400a10
+#define ISP_ASC_IRQ_MASK_5     0x1400a14
+
+#define ISP_CORE_IRQ_INTERRUPT 0x2104000
+#define ISP_CORE_IRQ_ENABLE    0x2104004
+#define ISP_CORE_IRQ_DOORBELL  0x21043f0
+#define ISP_CORE_IRQ_ACK       0x21043fc
+
+#define ISP_CORE_GPIO_0	       0x2104170
+#define ISP_CORE_GPIO_1	       0x2104174
+#define ISP_CORE_GPIO_2	       0x2104178
+#define ISP_CORE_GPIO_3	       0x210417c
+#define ISP_CORE_GPIO_4	       0x2104180
+#define ISP_CORE_GPIO_5	       0x2104184
+#define ISP_CORE_GPIO_6	       0x2104188
+#define ISP_CORE_GPIO_7	       0x210418c
+
+#define ISP_CORE_CLOCK_EN      0x2104190
+
+#define ISP_CORE_DPE_CTRL_0    0x2504000
+#define ISP_CORE_DPE_CTRL_1    0x2508000
+
+static inline u32 isp_core_read32(struct apple_isp *isp, u32 reg)
+{
+	return readl(isp->core + reg - 0x2104000); // TODO this sucks
+}
+
+static inline void isp_core_write32(struct apple_isp *isp, u32 reg, u32 val)
+{
+	writel(val, isp->core + reg - 0x2104000);
+}
+
+static inline void isp_core_mask32(struct apple_isp *isp, u32 reg, u32 clear,
+				   u32 set)
+{
+	isp_core_write32(isp, reg, isp_core_read32(isp, reg) & ~clear);
+	isp_core_write32(isp, reg, isp_core_read32(isp, reg) | set);
+}
+
+#endif /* __ISP_REGS_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
new file mode 100644
index 00000000000000..9de6549ec9bee7
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -0,0 +1,602 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include <media/media-device.h>
+#include <media/v4l2-common.h>
+#include <media/v4l2-ioctl.h>
+#include <media/v4l2-mc.h>
+#include <media/videobuf2-dma-sg.h>
+
+#include "isp-cam.h"
+#include "isp-cmd.h"
+#include "isp-iommu.h"
+#include "isp-ipc.h"
+#include "isp-v4l2.h"
+
+#define ISP_MIN_FRAMES	     2
+#define ISP_MAX_PLANES	     4
+#define ISP_MAX_PIX_FORMATS  2
+#define ISP_BUFFER_TIMEOUT   msecs_to_jiffies(1500)
+
+struct isp_h2t_buffer {
+	u64 iovas[ISP_MAX_PLANES];
+	u32 flags[ISP_MAX_PLANES];
+	u32 num_planes;
+	u32 pool_type;
+	u32 tag;
+	u32 pad;
+} __packed;
+static_assert(sizeof(struct isp_h2t_buffer) == 0x40);
+
+struct isp_h2t_args {
+	u64 enable;
+	u64 num_buffers;
+	struct isp_h2t_buffer meta;
+	struct isp_h2t_buffer render;
+} __packed;
+
+static int isp_submit_buffers(struct apple_isp *isp)
+{
+	struct isp_format *fmt = isp_get_current_format(isp);
+	struct isp_channel *chan = isp->chan_bh;
+	struct isp_message *req = &chan->req;
+	struct isp_buffer *buf;
+	unsigned long flags;
+	size_t offset;
+	int err;
+
+	struct isp_h2t_args *args =
+		kzalloc(sizeof(struct isp_h2t_args), GFP_KERNEL);
+	if (!args)
+		return -ENOMEM;
+
+	spin_lock_irqsave(&isp->buf_lock, flags);
+	buf = list_first_entry_or_null(&isp->buffers, struct isp_buffer, link);
+	if (!buf) {
+		spin_unlock_irqrestore(&isp->buf_lock, flags);
+		kfree(args);
+		return -EPROTO;
+	}
+
+	args->meta.num_planes = 1;
+	args->meta.pool_type = CISP_POOL_TYPE_META;
+	args->meta.iovas[0] = buf->meta->iova;
+	args->meta.flags[0] = 0x40000000;
+
+	args->render.num_planes = fmt->num_planes;
+	args->render.pool_type = CISP_POOL_TYPE_RENDERED;
+	offset = 0;
+	for (int j = 0; j < fmt->num_planes; j++) {
+		args->render.iovas[j] = buf->surfs[0].iova + offset;
+		args->render.flags[j] = 0x40000000;
+		offset += fmt->plane_size[j];
+	}
+	spin_unlock_irqrestore(&isp->buf_lock, flags);
+
+	args->enable = 0x1;
+	args->num_buffers = 2;
+
+	req->arg0 = isp->cmd_iova;
+	req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE;
+	req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
+
+	isp_iowrite(isp, req->arg0, args, sizeof(*args));
+	err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
+	if (err) {
+		dev_err(isp->dev,
+			"%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n",
+			chan->name, req->arg0, req->arg1, req->arg2);
+	}
+
+	kfree(args);
+
+	return err;
+}
+
+/*
+ * Videobuf2 section
+ */
+static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
+			       unsigned int *num_planes, unsigned int sizes[],
+			       struct device *alloc_devs[])
+{
+	struct apple_isp *isp = vb2_get_drv_priv(vq);
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	if (*num_planes) {
+		if (sizes[0] < fmt->total_size)
+			return -EINVAL;
+
+		return 0;
+	}
+
+	*num_planes = 1;
+	sizes[0] = fmt->total_size;
+
+	return 0;
+}
+
+static void __isp_vb2_buf_cleanup(struct vb2_buffer *vb, unsigned int i)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+	struct isp_buffer *buf =
+		container_of(vb, struct isp_buffer, vb.vb2_buf);
+
+	while (i--)
+		apple_isp_iommu_unmap_sgt(isp, &buf->surfs[i]);
+	isp_free_surface(isp, buf->meta);
+}
+
+static void isp_vb2_buf_cleanup(struct vb2_buffer *vb)
+{
+	__isp_vb2_buf_cleanup(vb, vb->num_planes);
+}
+
+static int isp_vb2_buf_init(struct vb2_buffer *vb)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+	struct isp_buffer *buf =
+		container_of(vb, struct isp_buffer, vb.vb2_buf);
+	unsigned int i;
+	int err;
+
+	buf->meta = isp_alloc_surface(isp, ISP_META_SIZE);
+	if (!buf->meta)
+		return -ENOMEM;
+
+	for (i = 0; i < vb->num_planes; i++) {
+		struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, i);
+		err = apple_isp_iommu_map_sgt(isp, &buf->surfs[i], sgt,
+					      vb2_plane_size(vb, i));
+		if (err)
+			goto cleanup;
+	}
+
+	return 0;
+
+cleanup:
+	__isp_vb2_buf_cleanup(vb, i);
+	return err;
+}
+
+static int isp_vb2_buf_prepare(struct vb2_buffer *vb)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	if (vb2_plane_size(vb, 0) < fmt->total_size)
+		return -EINVAL;
+
+	vb2_set_plane_payload(vb, 0, fmt->total_size);
+
+	return 0;
+}
+
+static void isp_vb2_release_buffers(struct apple_isp *isp,
+				    enum vb2_buffer_state state)
+{
+	struct isp_buffer *buf;
+	unsigned long flags;
+
+	spin_lock_irqsave(&isp->buf_lock, flags);
+	list_for_each_entry(buf, &isp->buffers, link)
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	INIT_LIST_HEAD(&isp->buffers);
+	spin_unlock_irqrestore(&isp->buf_lock, flags);
+}
+
+static void isp_vb2_buf_queue(struct vb2_buffer *vb)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(vb->vb2_queue);
+	struct isp_buffer *buf =
+		container_of(vb, struct isp_buffer, vb.vb2_buf);
+	unsigned long flags;
+	bool empty;
+
+	spin_lock_irqsave(&isp->buf_lock, flags);
+	empty = list_empty(&isp->buffers);
+	list_add_tail(&buf->link, &isp->buffers);
+	spin_unlock_irqrestore(&isp->buf_lock, flags);
+
+	if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty)
+		isp_submit_buffers(isp);
+}
+
+static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(q);
+	int err;
+
+	isp->sequence = 0;
+
+	err = apple_isp_start_camera(isp);
+	if (err) {
+		dev_err(isp->dev, "failed to start camera: %d\n", err);
+		goto release_buffers;
+	}
+
+	err = isp_submit_buffers(isp);
+	if (err) {
+		dev_err(isp->dev, "failed to send initial batch: %d\n", err);
+		goto stop_camera;
+	}
+
+	err = apple_isp_start_capture(isp);
+	if (err) {
+		dev_err(isp->dev, "failed to start capture: %d\n", err);
+		goto stop_camera;
+	}
+
+	set_bit(ISP_STATE_STREAMING, &isp->state);
+
+	return 0;
+
+stop_camera:
+	apple_isp_stop_camera(isp);
+release_buffers:
+	isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED);
+	return err;
+}
+
+static void isp_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(q);
+
+	clear_bit(ISP_STATE_STREAMING, &isp->state);
+	apple_isp_stop_capture(isp);
+	apple_isp_stop_camera(isp);
+	isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR);
+}
+
+static const struct vb2_ops isp_vb2_ops = {
+	.queue_setup     = isp_vb2_queue_setup,
+	.buf_init        = isp_vb2_buf_init,
+	.buf_cleanup     = isp_vb2_buf_cleanup,
+	.buf_prepare     = isp_vb2_buf_prepare,
+	.buf_queue       = isp_vb2_buf_queue,
+	.start_streaming = isp_vb2_start_streaming,
+	.stop_streaming  = isp_vb2_stop_streaming,
+	.wait_prepare    = vb2_ops_wait_prepare,
+	.wait_finish     = vb2_ops_wait_finish,
+};
+
+/*
+ * V4L2 ioctl section
+ */
+static int isp_vidioc_querycap(struct file *file, void *priv,
+			       struct v4l2_capability *cap)
+{
+	strscpy(cap->card, APPLE_ISP_DEVICE_NAME, sizeof(cap->card));
+	strscpy(cap->driver, APPLE_ISP_DEVICE_NAME, sizeof(cap->driver));
+
+	return 0;
+}
+
+static int isp_vidioc_enum_format(struct file *file, void *fh,
+				  struct v4l2_fmtdesc *f)
+{
+	if (f->index >= ISP_MAX_PIX_FORMATS)
+		return -EINVAL;
+
+	if (!f->index)
+		f->pixelformat = V4L2_PIX_FMT_NV12;
+	else
+		f->pixelformat = V4L2_PIX_FMT_NV12M;
+
+	return 0;
+}
+
+static int isp_vidioc_enum_framesizes(struct file *file, void *fh,
+				      struct v4l2_frmsizeenum *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	if (f->index >= ISP_MAX_PIX_FORMATS)
+		return -EINVAL;
+
+	if ((!f->index && f->pixel_format != V4L2_PIX_FMT_NV12) ||
+	    (f->index && f->pixel_format != V4L2_PIX_FMT_NV12M))
+		return -EINVAL;
+
+	f->discrete.width = fmt->width;
+	f->discrete.height = fmt->height;
+	f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+
+	return 0;
+}
+
+static inline void isp_set_sp_pix_format(struct apple_isp *isp,
+					 struct v4l2_format *f)
+{
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	f->fmt.pix.width = fmt->width;
+	f->fmt.pix.height = fmt->height;
+	f->fmt.pix.sizeimage = fmt->total_size;
+
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_NV12;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_REC709;
+	f->fmt.pix.ycbcr_enc = V4L2_YCBCR_ENC_709;
+	f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709;
+}
+
+static inline void isp_set_mp_pix_format(struct apple_isp *isp,
+					 struct v4l2_format *f)
+{
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	f->fmt.pix_mp.width = fmt->width;
+	f->fmt.pix_mp.height = fmt->height;
+	f->fmt.pix_mp.num_planes = fmt->num_planes;
+	for (int i = 0; i < fmt->num_planes; i++)
+		f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i];
+
+	f->fmt.pix_mp.field = V4L2_FIELD_NONE;
+	f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M;
+	f->fmt.pix_mp.colorspace = V4L2_COLORSPACE_REC709;
+	f->fmt.pix_mp.ycbcr_enc = V4L2_YCBCR_ENC_709;
+	f->fmt.pix_mp.xfer_func = V4L2_XFER_FUNC_709;
+}
+
+static int isp_vidioc_get_format(struct file *file, void *fh,
+				 struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_sp_pix_format(isp, f);
+
+	return 0;
+}
+
+static int isp_vidioc_set_format(struct file *file, void *fh,
+				 struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_sp_pix_format(isp, f); // no
+
+	return 0;
+}
+
+static int isp_vidioc_try_format(struct file *file, void *fh,
+				 struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_sp_pix_format(isp, f); // still no
+
+	return 0;
+}
+
+static int isp_vidioc_get_format_mplane(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (!isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_mp_pix_format(isp, f);
+
+	return 0;
+}
+
+static int isp_vidioc_set_format_mplane(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (!isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_mp_pix_format(isp, f); // no
+
+	return 0;
+}
+
+static int isp_vidioc_try_format_mplane(struct file *file, void *fh,
+					struct v4l2_format *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (!isp->multiplanar)
+		return -ENOTTY;
+
+	isp_set_mp_pix_format(isp, f); // still no
+
+	return 0;
+}
+
+static int isp_vidioc_enum_input(struct file *file, void *fh,
+				 struct v4l2_input *inp)
+{
+	if (inp->index)
+		return -EINVAL;
+
+	strscpy(inp->name, APPLE_ISP_DEVICE_NAME, sizeof(inp->name));
+	inp->type = V4L2_INPUT_TYPE_CAMERA;
+
+	return 0;
+}
+
+static int isp_vidioc_get_input(struct file *file, void *fh, unsigned int *i)
+{
+	*i = 0;
+
+	return 0;
+}
+
+static int isp_vidioc_set_input(struct file *file, void *fh, unsigned int i)
+{
+	if (i)
+		return -EINVAL;
+
+	return 0;
+}
+
+static int isp_vidioc_get_param(struct file *file, void *fh,
+				struct v4l2_streamparm *a)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+					   V4L2_BUF_TYPE_VIDEO_CAPTURE))
+		return -EINVAL;
+
+	a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	a->parm.capture.readbuffers = ISP_MIN_FRAMES;
+	a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM;
+	a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN;
+
+	return 0;
+}
+
+static int isp_vidioc_set_param(struct file *file, void *fh,
+				struct v4l2_streamparm *a)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
+					   V4L2_BUF_TYPE_VIDEO_CAPTURE))
+		return -EINVAL;
+
+	/* Not supporting frame rate sets. No use. Plus floats. */
+	a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM;
+	a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = {
+	.vidioc_querycap                = isp_vidioc_querycap,
+
+	.vidioc_enum_fmt_vid_cap        = isp_vidioc_enum_format,
+	.vidioc_g_fmt_vid_cap           = isp_vidioc_get_format,
+	.vidioc_s_fmt_vid_cap           = isp_vidioc_set_format,
+	.vidioc_try_fmt_vid_cap         = isp_vidioc_try_format,
+	.vidioc_g_fmt_vid_cap_mplane    = isp_vidioc_get_format_mplane,
+	.vidioc_s_fmt_vid_cap_mplane    = isp_vidioc_set_format_mplane,
+	.vidioc_try_fmt_vid_cap_mplane  = isp_vidioc_try_format_mplane,
+
+	.vidioc_enum_framesizes         = isp_vidioc_enum_framesizes,
+	.vidioc_enum_input              = isp_vidioc_enum_input,
+	.vidioc_g_input                 = isp_vidioc_get_input,
+	.vidioc_s_input                 = isp_vidioc_set_input,
+	.vidioc_g_parm                  = isp_vidioc_get_param,
+	.vidioc_s_parm                  = isp_vidioc_set_param,
+
+	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
+	.vidioc_querybuf                = vb2_ioctl_querybuf,
+	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
+	.vidioc_qbuf                    = vb2_ioctl_qbuf,
+	.vidioc_expbuf                  = vb2_ioctl_expbuf,
+	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
+	.vidioc_streamon                = vb2_ioctl_streamon,
+	.vidioc_streamoff               = vb2_ioctl_streamoff,
+};
+
+static const struct v4l2_file_operations isp_v4l2_fops = {
+	.owner          = THIS_MODULE,
+	.open           = v4l2_fh_open,
+	.release        = vb2_fop_release,
+	.read           = vb2_fop_read,
+	.poll           = vb2_fop_poll,
+	.mmap           = vb2_fop_mmap,
+	.unlocked_ioctl = video_ioctl2,
+};
+
+static const struct media_device_ops isp_media_device_ops = {
+	.link_notify    = v4l2_pipeline_link_notify,
+};
+
+int apple_isp_setup_video(struct apple_isp *isp)
+{
+	struct video_device *vdev = &isp->vdev;
+	struct vb2_queue *vbq = &isp->vbq;
+	int err;
+
+	media_device_init(&isp->mdev);
+	isp->v4l2_dev.mdev = &isp->mdev;
+	isp->mdev.ops = &isp_media_device_ops;
+	isp->mdev.dev = isp->dev;
+	strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME, sizeof(isp->mdev.model));
+
+	err = media_device_register(&isp->mdev);
+	if (err) {
+		dev_err(isp->dev, "failed to register media device: %d\n", err);
+		goto media_cleanup;
+	}
+
+	isp->multiplanar = 0;
+
+	err = v4l2_device_register(isp->dev, &isp->v4l2_dev);
+	if (err) {
+		dev_err(isp->dev, "failed to register v4l2 device: %d\n", err);
+		goto media_unregister;
+	}
+
+	vbq->drv_priv = isp;
+	vbq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	vbq->io_modes = VB2_MMAP;
+	vbq->dev = isp->dev;
+	vbq->ops = &isp_vb2_ops;
+	vbq->mem_ops = &vb2_dma_sg_memops;
+	vbq->buf_struct_size = sizeof(struct isp_buffer);
+	vbq->timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vbq->min_queued_buffers = ISP_MIN_FRAMES;
+	vbq->lock = &isp->video_lock;
+
+	err = vb2_queue_init(vbq);
+	if (err) {
+		dev_err(isp->dev, "failed to init vb2 queue: %d\n", err);
+		goto v4l2_unregister;
+	}
+
+	vdev->queue = vbq;
+	vdev->fops = &isp_v4l2_fops;
+	vdev->ioctl_ops = &isp_v4l2_ioctl_ops;
+	vdev->device_caps = V4L2_BUF_TYPE_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	vdev->v4l2_dev = &isp->v4l2_dev;
+	vdev->vfl_type = VFL_TYPE_VIDEO;
+	vdev->vfl_dir = VFL_DIR_RX;
+	vdev->release = video_device_release_empty;
+	vdev->lock = &isp->video_lock;
+	strscpy(vdev->name, APPLE_ISP_DEVICE_NAME, sizeof(vdev->name));
+	video_set_drvdata(vdev, isp);
+
+	err = video_register_device(vdev, VFL_TYPE_VIDEO, 0);
+	if (err) {
+		dev_err(isp->dev, "failed to register video device: %d\n", err);
+		goto v4l2_unregister;
+	}
+
+	return 0;
+
+v4l2_unregister:
+	v4l2_device_unregister(&isp->v4l2_dev);
+media_unregister:
+	media_device_unregister(&isp->mdev);
+media_cleanup:
+	media_device_cleanup(&isp->mdev);
+	return err;
+}
+
+void apple_isp_remove_video(struct apple_isp *isp)
+{
+	vb2_video_unregister_device(&isp->vdev);
+	v4l2_device_unregister(&isp->v4l2_dev);
+	media_device_unregister(&isp->mdev);
+	media_device_cleanup(&isp->mdev);
+}
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h
new file mode 100644
index 00000000000000..df9b961d77bc17
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-v4l2.h
@@ -0,0 +1,12 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#ifndef __ISP_V4L2_H__
+#define __ISP_V4L2_H__
+
+#include "isp-drv.h"
+
+int apple_isp_setup_video(struct apple_isp *isp);
+void apple_isp_remove_video(struct apple_isp *isp);
+
+#endif /* __ISP_V4L2_H__ */

From 44ecdab7358f9f1297e9ece1670e88176524cf87 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Sat, 2 Sep 2023 00:47:39 +0900
Subject: [PATCH 0382/1027] media: apple: isp: IMX558 initial support

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-cam.c |  5 +-
 drivers/media/platform/apple/isp/isp-drv.c | 54 ++++++++++++++++++++++
 2 files changed, 57 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index 6d08248ef44776..bb90337cb7c19f 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -78,12 +78,13 @@ static const struct isp_setfile isp_setfiles[] = {
 	[ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80},
 	[ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80},
 };
-// clang-format on
 
 // one day we will do this intelligently
 static const struct isp_preset isp_presets[] = {
-	[ISP_IMX248_1820_01] = { 0, 1280, 720, 8, 8, 1280, 720, 1296, 736 },
+	[ISP_IMX248_1820_01] = {0, 1280,  720, 8, 8, 1280,  720, 1296,  736}, // J293AP
+	[ISP_IMX558_1921_01] = {1, 1920, 1080, 0, 0, 1920, 1080, 1920, 1080}, // J316sAP, J415AP
 };
+// clang-format on
 
 static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch)
 {
diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index e8e32ba73ad962..31aaf1e78b9e98 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -292,6 +292,60 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = {
 	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
 };
 
+static const struct apple_isp_hw apple_isp_hw_t6000 = {
+	.pmu_base = 0x28e584000,
+
+	.dsid_clr_base0 = 0x200014000,
+	.dsid_clr_base1 = 0x200054000,
+	.dsid_clr_base2 = 0x200094000,
+	.dsid_clr_base3 = 0x2000d4000,
+	.dsid_clr_range0 = 0x1000,
+	.dsid_clr_range1 = 0x1000,
+	.dsid_clr_range2 = 0x1000,
+	.dsid_clr_range3 = 0x1000,
+
+	.clock_scratch = 0x28e3d0868,
+	.clock_base = 0x0,
+	.clock_bit = 0x0,
+	.clock_size = 0x8,
+	.bandwidth_scratch = 0x28e3d0980,
+	.bandwidth_base = 0x0,
+	.bandwidth_bit = 0x0,
+	.bandwidth_size = 0x8,
+
+	.stream_command = DART_T8020_STREAM_COMMAND,
+	.stream_select = DART_T8020_STREAM_SELECT,
+	.ttbr = DART_T8020_TTBR,
+	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
+};
+
+static const struct apple_isp_hw apple_isp_hw_t8110 = {
+	.pmu_base = 0x23b704000,
+
+	.dsid_clr_base0 = 0x200014000, // TODO
+	.dsid_clr_base1 = 0x200054000,
+	.dsid_clr_base2 = 0x200094000,
+	.dsid_clr_base3 = 0x2000d4000,
+	.dsid_clr_range0 = 0x1000,
+	.dsid_clr_range1 = 0x1000,
+	.dsid_clr_range2 = 0x1000,
+	.dsid_clr_range3 = 0x1000,
+
+	.clock_scratch = 0x23b3d0560,
+	.clock_base = 0x0,
+	.clock_bit = 0x0,
+	.clock_size = 0x8,
+	.bandwidth_scratch = 0x23b3d05d0,
+	.bandwidth_base = 0x0,
+	.bandwidth_bit = 0x0,
+	.bandwidth_size = 0x8,
+
+	.stream_command = DART_T8020_STREAM_COMMAND, // TODO
+	.stream_select = DART_T8020_STREAM_SELECT,
+	.ttbr = DART_T8020_TTBR,
+	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
+};
+
 static const struct of_device_id apple_isp_of_match[] = {
 	{ .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 },
 	{},

From 6356dae6abedfedcde6b4fb034bd4383f7fef215 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Sep 2023 00:45:36 +0900
Subject: [PATCH 0383/1027] media: apple: isp: Use preallocated heap

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.c | 51 ++++++++++++----------
 drivers/media/platform/apple/isp/isp-drv.h |  2 +-
 2 files changed, 29 insertions(+), 24 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 31aaf1e78b9e98..d02a60bb34b10e 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -79,30 +79,44 @@ static int apple_isp_attach_genpd(struct apple_isp *isp)
 static int apple_isp_init_iommu(struct apple_isp *isp)
 {
 	struct device *dev = isp->dev;
-	struct isp_firmware *fw = &isp->fw;
-	u64 heap_base, heap_size, vm_size;
+	phys_addr_t heap_base;
+	size_t heap_size;
+	u64 vm_size;
 	int err;
-	int i = 0;
+	int idx;
+	int size;
+	struct device_node *mem_node;
+	const __be32 *maps, *end;
 
 	isp->domain = iommu_get_domain_for_dev(isp->dev);
 	if (!isp->domain)
 		return -EPROBE_DEFER;
 	isp->shift = __ffs(isp->domain->pgsize_bitmap);
 
-	err = of_property_read_u64(dev->of_node, "apple,isp-heap-base",
-				   &heap_base);
-	if (err) {
-		dev_err(dev, "failed to read 'apple,isp-heap-base': %d\n", err);
-		return err;
+	idx = of_property_match_string(dev->of_node, "memory-region-names", "heap");
+	mem_node = of_parse_phandle(dev->of_node, "memory-region", idx);
+	if (!mem_node) {
+		dev_err(dev, "No memory-region found for heap\n");
+		return -ENODEV;
 	}
 
-	err = of_property_read_u64(dev->of_node, "apple,isp-heap-size",
-				   &heap_size);
-	if (err) {
-		dev_err(dev, "failed to read 'apple,isp-heap-size': %d\n", err);
-		return err;
+	maps = of_get_property(mem_node, "iommu-addresses", &size);
+	if (!maps || !size) {
+		dev_err(dev, "No valid iommu-addresses found for heap\n");
+		return -ENODEV;
+	}
+
+	end = maps + size / sizeof(__be32);
+
+	while (maps < end) {
+		maps++;
+		maps = of_translate_dma_region(dev->of_node, maps, &heap_base, &heap_size);
 	}
 
+	printk("heap: 0x%llx 0x%lx\n", heap_base, heap_size);
+
+	isp->fw.heap_top = heap_base + heap_size;
+
 	err = of_property_read_u64(dev->of_node, "apple,dart-vm-size",
 				   &vm_size);
 	if (err) {
@@ -110,15 +124,7 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 		return err;
 	}
 
-	drm_mm_init(&isp->iovad, heap_base, vm_size - heap_base);
-
-	/* Allocate read-only coprocessor private heap */
-	fw->heap = isp_alloc_surface(isp, heap_size);
-	if (!fw->heap) {
-		drm_mm_takedown(&isp->iovad);
-		err = -ENOMEM;
-		return err;
-	}
+	drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base);
 
 	apple_isp_iommu_sync_ttbr(isp);
 
@@ -127,7 +133,6 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 
 static void apple_isp_free_iommu(struct apple_isp *isp)
 {
-	isp_free_surface(isp, isp->fw.heap);
 	drm_mm_takedown(&isp->iovad);
 }
 
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 5db64dcc844863..7b463eaef1c9ce 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -196,7 +196,7 @@ struct apple_isp {
 	struct mutex iovad_lock;
 
 	struct isp_firmware {
-		struct isp_surf *heap;
+		u64 heap_top;
 	} fw;
 
 	struct isp_surf *ipc_surf;

From fef461037665d31021ae4c3d883a796131fc38ca Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Sep 2023 00:45:49 +0900
Subject: [PATCH 0384/1027] media: apple: isp: Fixup shared region arg

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-fw.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 12b9c0694d68e8..4315653a0510a0 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -30,8 +30,8 @@ static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val)
 struct isp_firmware_bootargs {
 	u32 pad_0[2];
 	u64 ipc_iova;
-	u64 unk_size;
-	u64 unk_inv;
+	u64 shared_base;
+	u64 shared_size;
 	u64 extra_iova;
 	u64 extra_size;
 	u32 unk4;
@@ -254,8 +254,8 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	memset(&args, 0, sizeof(args));
 	args.ipc_iova = isp->ipc_surf->iova;
 	args.ipc_size = isp->ipc_surf->size;
-	args.unk_size = 0x1800000;
-	args.unk_inv = 0x10000000 - args.unk_size;
+	args.shared_base = isp->fw.heap_top;
+	args.shared_size = 0x10000000 - isp->fw.heap_top;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
 	args.unk4 = 0x1;

From 389b9fb634ff1e523b3e668403b20a4d142b45bd Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 10 Sep 2023 22:57:06 +0900
Subject: [PATCH 0385/1027] media: apple: isp: Enable t6000

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index d02a60bb34b10e..094af7f7c33523 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -353,6 +353,7 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = {
 
 static const struct of_device_id apple_isp_of_match[] = {
 	{ .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 },
+	{ .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 },
 	{},
 };
 MODULE_DEVICE_TABLE(of, apple_isp_of_match);

From f4d14cf2af96be6192893d2937d0121e14828fcf Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Sat, 2 Sep 2023 01:07:08 +0900
Subject: [PATCH 0386/1027] media: apple: isp: Split gpio/mbox MMIO range

Offsets differ across socs.
Makes more sense than "core" too.

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c  | 12 +++-
 drivers/media/platform/apple/isp/isp-drv.h  |  3 +-
 drivers/media/platform/apple/isp/isp-fw.c   | 76 ++++++++++++---------
 drivers/media/platform/apple/isp/isp-ipc.c  |  4 +-
 drivers/media/platform/apple/isp/isp-regs.h | 49 ++++++-------
 5 files changed, 75 insertions(+), 69 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 094af7f7c33523..eb585d37d3239f 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -164,9 +164,15 @@ static int apple_isp_probe(struct platform_device *pdev)
 		goto detach_genpd;
 	}
 
-	isp->core = devm_platform_ioremap_resource_byname(pdev, "core");
-	if (IS_ERR(isp->core)) {
-		err = PTR_ERR(isp->core);
+	isp->mbox = devm_platform_ioremap_resource_byname(pdev, "mbox");
+	if (IS_ERR(isp->mbox)) {
+		err = PTR_ERR(isp->mbox);
+		goto detach_genpd;
+	}
+
+	isp->gpio = devm_platform_ioremap_resource_byname(pdev, "gpio");
+	if (IS_ERR(isp->gpio)) {
+		err = PTR_ERR(isp->gpio);
 		goto detach_genpd;
 	}
 
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 7b463eaef1c9ce..de9b3fd2def5ee 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -185,7 +185,8 @@ struct apple_isp {
 	int irq;
 
 	void __iomem *asc;
-	void __iomem *core;
+	void __iomem *mbox;
+	void __iomem *gpio;
 	void __iomem *dart0;
 	void __iomem *dart1;
 	void __iomem *dart2;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 4315653a0510a0..1f01d175416174 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -27,6 +27,16 @@ static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val)
 	writel(val, isp->asc + reg);
 }
 
+static inline u32 isp_gpio_read32(struct apple_isp *isp, u32 reg)
+{
+	return readl(isp->gpio + reg);
+}
+
+static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val)
+{
+	writel(val, isp->gpio + reg);
+}
+
 struct isp_firmware_bootargs {
 	u32 pad_0[2];
 	u64 ipc_iova;
@@ -77,8 +87,8 @@ static irqreturn_t apple_isp_isr(int irq, void *dev)
 {
 	struct apple_isp *isp = dev;
 
-	isp_core_write32(isp, ISP_CORE_IRQ_ACK,
-			 isp_core_read32(isp, ISP_CORE_IRQ_INTERRUPT));
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK,
+			 isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT));
 
 	wake_up_interruptible_all(&isp->wait);
 
@@ -95,9 +105,9 @@ static irqreturn_t apple_isp_isr(int irq, void *dev)
 
 static void isp_disable_irq(struct apple_isp *isp)
 {
-	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0);
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0);
 	free_irq(isp->irq, isp);
-	isp_core_write32(isp, ISP_CORE_GPIO_1, 0xfeedbabe); /* real funny */
+	isp_gpio_write32(isp, ISP_GPIO_1, 0xfeedbabe); /* real funny */
 }
 
 static int isp_enable_irq(struct apple_isp *isp)
@@ -112,7 +122,7 @@ static int isp_enable_irq(struct apple_isp *isp)
 
 	isp_dbg(isp, "about to enable interrupts...\n");
 
-	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0xf);
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0xf);
 
 	return 0;
 }
@@ -166,26 +176,26 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 	if (err < 0)
 		return err;
 
-	isp_core_write32(isp, ISP_CORE_CLOCK_EN, 0x1);
+	isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1);
 
-	isp_core_write32(isp, ISP_CORE_GPIO_0, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_2, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_3, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_4, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_5, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_6, 0x0);
-	isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_0, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_2, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_3, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_4, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_5, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_6, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_7, 0x0);
 
-	isp_core_write32(isp, ISP_CORE_IRQ_ENABLE, 0x0);
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0);
 
 	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0);
 	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10);
 
-	/* Wait for ISP_CORE_GPIO_7 to 0x0 -> 0x8042006 */
-	isp_core_write32(isp, ISP_CORE_GPIO_7, 0x0);
+	/* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */
+	isp_gpio_write32(isp, ISP_GPIO_7, 0x0);
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
-		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7);
+		u32 val = isp_gpio_read32(isp, ISP_GPIO_7);
 		if (val == 0x8042006) {
 			isp_dbg(isp,
 				"got first magic number (0x%x) from firmware\n",
@@ -216,9 +226,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	dma_addr_t args_iova;
 	int err, retries;
 
-	u32 num_ipc_chans = isp_core_read32(isp, ISP_CORE_GPIO_0);
-	u32 args_offset = isp_core_read32(isp, ISP_CORE_GPIO_1);
-	u32 extra_size = isp_core_read32(isp, ISP_CORE_GPIO_3);
+	u32 num_ipc_chans = isp_gpio_read32(isp, ISP_GPIO_0);
+	u32 args_offset = isp_gpio_read32(isp, ISP_GPIO_1);
+	u32 extra_size = isp_gpio_read32(isp, ISP_GPIO_3);
 	isp->num_ipc_chans = num_ipc_chans;
 
 	if (!isp->num_ipc_chans) {
@@ -265,14 +275,14 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.unk9 = 0x3;
 	isp_iowrite(isp, args_iova, &args, sizeof(args));
 
-	isp_core_write32(isp, ISP_CORE_GPIO_0, args_iova);
-	isp_core_write32(isp, ISP_CORE_GPIO_1, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_0, args_iova);
+	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
 
-	/* Wait for ISP_CORE_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
-	isp_core_write32(isp, ISP_CORE_GPIO_7, 0xf7fbdff9);
+	/* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
+	isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9);
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
-		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_7);
+		u32 val = isp_gpio_read32(isp, ISP_GPIO_7);
 		if (val == 0x8042006) {
 			isp_dbg(isp,
 				"got second magic number (0x%x) from firmware\n",
@@ -325,7 +335,7 @@ static void isp_free_channel_info(struct apple_isp *isp)
 
 static int isp_fill_channel_info(struct apple_isp *isp)
 {
-	u32 table_iova = isp_core_read32(isp, ISP_CORE_GPIO_0);
+	u32 table_iova = isp_gpio_read32(isp, ISP_GPIO_0);
 
 	isp->ipc_chans = kcalloc(isp->num_ipc_chans,
 				 sizeof(struct isp_channel *), GFP_KERNEL);
@@ -417,11 +427,11 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp)
 		}
 	}
 
-	/* Wait for ISP_CORE_GPIO_3 to 0x8042006 -> 0x0 */
-	isp_core_write32(isp, ISP_CORE_GPIO_3, 0x8042006);
+	/* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */
+	isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006);
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
-		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_3);
+		u32 val = isp_gpio_read32(isp, ISP_GPIO_3);
 		if (val == 0x0) {
 			isp_dbg(isp,
 				"got third magic number (0x%x) from firmware\n",
@@ -446,14 +456,14 @@ static int isp_stop_command_processor(struct apple_isp *isp)
 {
 	int retries;
 
-	/* Wait for ISP_CORE_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */
-	isp_core_write32(isp, ISP_CORE_GPIO_0, 0xf7fbdff9);
+	/* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */
+	isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9);
 
 	/* Their CISP_CMD_STOP implementation is buggy */
 	isp_cmd_suspend(isp);
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
-		u32 val = isp_core_read32(isp, ISP_CORE_GPIO_0);
+		u32 val = isp_gpio_read32(isp, ISP_GPIO_0);
 		if (val == 0x8042006) {
 			isp_dbg(isp, "got magic number (0x%x) from firmware\n",
 				val);
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index ef3498c4fcd191..a9a0fdb73a4d9f 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -110,7 +110,7 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan)
 
 	chan_write_msg(isp, chan, &chan->rsp);
 
-	isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell);
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
 
 	chan_update_cursor(chan);
 
@@ -165,7 +165,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 	chan_write_msg(isp, chan, &chan->req);
 	wmb();
 
-	isp_core_write32(isp, ISP_CORE_IRQ_DOORBELL, chan->doorbell);
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
 
 	t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan),
 					     timeout);
diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h
index b9bd505844d9de..e21485ec4ce823 100644
--- a/drivers/media/platform/apple/isp/isp-regs.h
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -23,40 +23,29 @@
 #define ISP_ASC_IRQ_MASK_4     0x1400a10
 #define ISP_ASC_IRQ_MASK_5     0x1400a14
 
-#define ISP_CORE_IRQ_INTERRUPT 0x2104000
-#define ISP_CORE_IRQ_ENABLE    0x2104004
-#define ISP_CORE_IRQ_DOORBELL  0x21043f0
-#define ISP_CORE_IRQ_ACK       0x21043fc
-
-#define ISP_CORE_GPIO_0	       0x2104170
-#define ISP_CORE_GPIO_1	       0x2104174
-#define ISP_CORE_GPIO_2	       0x2104178
-#define ISP_CORE_GPIO_3	       0x210417c
-#define ISP_CORE_GPIO_4	       0x2104180
-#define ISP_CORE_GPIO_5	       0x2104184
-#define ISP_CORE_GPIO_6	       0x2104188
-#define ISP_CORE_GPIO_7	       0x210418c
-
-#define ISP_CORE_CLOCK_EN      0x2104190
-
-#define ISP_CORE_DPE_CTRL_0    0x2504000
-#define ISP_CORE_DPE_CTRL_1    0x2508000
-
-static inline u32 isp_core_read32(struct apple_isp *isp, u32 reg)
+#define ISP_MBOX_IRQ_INTERRUPT 0x000
+#define ISP_MBOX_IRQ_ENABLE    0x004
+#define ISP_MBOX_IRQ_DOORBELL  0x3f0
+#define ISP_MBOX_IRQ_ACK       0x3fc
+
+#define ISP_GPIO_0	       0x00
+#define ISP_GPIO_1	       0x04
+#define ISP_GPIO_2	       0x08
+#define ISP_GPIO_3	       0x0c
+#define ISP_GPIO_4	       0x10
+#define ISP_GPIO_5	       0x14
+#define ISP_GPIO_6	       0x18
+#define ISP_GPIO_7	       0x1c
+#define ISP_GPIO_CLOCK_EN      0x20
+
+static inline u32 isp_mbox_read32(struct apple_isp *isp, u32 reg)
 {
-	return readl(isp->core + reg - 0x2104000); // TODO this sucks
+	return readl(isp->mbox + reg);
 }
 
-static inline void isp_core_write32(struct apple_isp *isp, u32 reg, u32 val)
+static inline void isp_mbox_write32(struct apple_isp *isp, u32 reg, u32 val)
 {
-	writel(val, isp->core + reg - 0x2104000);
-}
-
-static inline void isp_core_mask32(struct apple_isp *isp, u32 reg, u32 clear,
-				   u32 set)
-{
-	isp_core_write32(isp, reg, isp_core_read32(isp, reg) & ~clear);
-	isp_core_write32(isp, reg, isp_core_read32(isp, reg) | set);
+	writel(val, isp->mbox + reg);
 }
 
 #endif /* __ISP_REGS_H__ */

From 0e59079007fe31a2b1f559d90ecb43c69917efe0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 10 Sep 2023 23:36:12 +0900
Subject: [PATCH 0387/1027] media: apple: isp: Drop the DART mirroring stuff

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.c   | 57 --------------------
 drivers/media/platform/apple/isp/isp-drv.h   |  8 ---
 drivers/media/platform/apple/isp/isp-iommu.c | 19 -------
 drivers/media/platform/apple/isp/isp-iommu.h |  3 --
 4 files changed, 87 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index eb585d37d3239f..1829f36acdd5b8 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -126,8 +126,6 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 
 	drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base);
 
-	apple_isp_iommu_sync_ttbr(isp);
-
 	return 0;
 }
 
@@ -140,7 +138,6 @@ static int apple_isp_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct apple_isp *isp;
-	struct resource *res;
 	int err;
 
 	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
@@ -176,31 +173,6 @@ static int apple_isp_probe(struct platform_device *pdev)
 		goto detach_genpd;
 	}
 
-	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dart0");
-	if (!res) {
-		err = -ENODEV;
-		goto detach_genpd;
-	}
-
-	/* Simply ioremap since it's a shared register zone */
-	isp->dart0 = devm_ioremap(dev, res->start, resource_size(res));
-	if (IS_ERR(isp->dart0)) {
-		err = PTR_ERR(isp->dart0);
-		goto detach_genpd;
-	}
-
-	isp->dart1 = devm_platform_ioremap_resource_byname(pdev, "dart1");
-	if (IS_ERR(isp->dart1)) {
-		err = PTR_ERR(isp->dart1);
-		goto detach_genpd;
-	}
-
-	isp->dart2 = devm_platform_ioremap_resource_byname(pdev, "dart2");
-	if (IS_ERR(isp->dart2)) {
-		err = PTR_ERR(isp->dart2);
-		goto detach_genpd;
-	}
-
 	isp->irq = platform_get_irq(pdev, 0);
 	if (isp->irq < 0) {
 		err = isp->irq;
@@ -270,12 +242,6 @@ static void apple_isp_remove(struct platform_device *pdev)
 	return 0;
 }
 
-/* T8020/T6000 registers */
-#define DART_T8020_STREAM_COMMAND	     0x20
-#define DART_T8020_STREAM_SELECT	     0x34
-#define DART_T8020_TTBR			     0x200
-#define DART_T8020_STREAM_COMMAND_INVALIDATE BIT(20)
-
 static const struct apple_isp_hw apple_isp_hw_t8103 = {
 	.pmu_base = 0x23b704000,
 
@@ -296,11 +262,6 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = {
 	.bandwidth_base = 0x23bc3c000,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x4,
-
-	.stream_command = DART_T8020_STREAM_COMMAND,
-	.stream_select = DART_T8020_STREAM_SELECT,
-	.ttbr = DART_T8020_TTBR,
-	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
 };
 
 static const struct apple_isp_hw apple_isp_hw_t6000 = {
@@ -323,11 +284,6 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = {
 	.bandwidth_base = 0x0,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
-
-	.stream_command = DART_T8020_STREAM_COMMAND,
-	.stream_select = DART_T8020_STREAM_SELECT,
-	.ttbr = DART_T8020_TTBR,
-	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
 };
 
 static const struct apple_isp_hw apple_isp_hw_t8110 = {
@@ -350,11 +306,6 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = {
 	.bandwidth_base = 0x0,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
-
-	.stream_command = DART_T8020_STREAM_COMMAND, // TODO
-	.stream_select = DART_T8020_STREAM_SELECT,
-	.ttbr = DART_T8020_TTBR,
-	.stream_command_invalidate = DART_T8020_STREAM_COMMAND_INVALIDATE,
 };
 
 static const struct of_device_id apple_isp_of_match[] = {
@@ -366,19 +317,11 @@ MODULE_DEVICE_TABLE(of, apple_isp_of_match);
 
 static __maybe_unused int apple_isp_suspend(struct device *dev)
 {
-	struct apple_isp *isp = dev_get_drvdata(dev);
-
-	apple_isp_iommu_invalidate_tlb(isp);
-
 	return 0;
 }
 
 static __maybe_unused int apple_isp_resume(struct device *dev)
 {
-	struct apple_isp *isp = dev_get_drvdata(dev);
-
-	apple_isp_iommu_sync_ttbr(isp);
-
 	return 0;
 }
 DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL);
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index de9b3fd2def5ee..bf3824cc0636b9 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -82,11 +82,6 @@ struct apple_isp_hw {
 	u64 bandwidth_base;
 	u8 bandwidth_bit;
 	u8 bandwidth_size;
-
-	u32 stream_command;
-	u32 stream_select;
-	u32 ttbr;
-	u32 stream_command_invalidate;
 };
 
 struct isp_resv {
@@ -187,9 +182,6 @@ struct apple_isp {
 	void __iomem *asc;
 	void __iomem *mbox;
 	void __iomem *gpio;
-	void __iomem *dart0;
-	void __iomem *dart1;
-	void __iomem *dart2;
 
 	struct iommu_domain *domain;
 	unsigned long shift;
diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c
index 28935d37205024..0a9d0d6a350c9a 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.c
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -6,23 +6,6 @@
 
 #include "isp-iommu.h"
 
-void apple_isp_iommu_sync_ttbr(struct apple_isp *isp)
-{
-	writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart1 + isp->hw->ttbr);
-	writel(readl(isp->dart0 + isp->hw->ttbr), isp->dart2 + isp->hw->ttbr);
-}
-
-void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp)
-{
-	iommu_flush_iotlb_all(isp->domain);
-	writel(0x1, isp->dart1 + isp->hw->stream_select);
-	writel(isp->hw->stream_command_invalidate,
-	       isp->dart1 + isp->hw->stream_command);
-	writel(0x1, isp->dart2 + isp->hw->stream_select);
-	writel(isp->hw->stream_command_invalidate,
-	       isp->dart2 + isp->hw->stream_command);
-}
-
 static void isp_surf_free_pages(struct isp_surf *surf)
 {
 	for (u32 i = 0; i < surf->num_pages && surf->pages[i] != NULL; i++) {
@@ -113,7 +96,6 @@ static int isp_surf_reserve_iova(struct apple_isp *isp, struct isp_surf *surf)
 static void isp_surf_iommu_unmap(struct apple_isp *isp, struct isp_surf *surf)
 {
 	iommu_unmap(isp->domain, surf->iova, surf->size);
-	apple_isp_iommu_invalidate_tlb(isp);
 	sg_free_table(&surf->sgt);
 }
 
@@ -270,6 +252,5 @@ int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
 void apple_isp_iommu_unmap_sgt(struct apple_isp *isp, struct isp_surf *surf)
 {
 	iommu_unmap(isp->domain, surf->iova, surf->size);
-	apple_isp_iommu_invalidate_tlb(isp);
 	isp_surf_unreserve_iova(isp, surf);
 }
diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h
index f9972bd9ff93e7..326cf7c12aa745 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.h
+++ b/drivers/media/platform/apple/isp/isp-iommu.h
@@ -6,9 +6,6 @@
 
 #include "isp-drv.h"
 
-void apple_isp_iommu_sync_ttbr(struct apple_isp *isp);
-void apple_isp_iommu_invalidate_tlb(struct apple_isp *isp);
-
 struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc);
 #define isp_alloc_surface(isp, size)	(__isp_alloc_surface(isp, size, false))
 #define isp_alloc_surface_gc(isp, size) (__isp_alloc_surface(isp, size, true))

From c624da7c402f42f0cd9dca0a361a87c6af6fe590 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Sep 2023 00:12:11 +0900
Subject: [PATCH 0388/1027] media: apple: isp: Do not defer on failure to
 initialize DART

This can fail for non-DEFER reasons. If this can happen due to probe
defers, we need to figure out some way to signal that specifically...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 1829f36acdd5b8..00299fd89e6038 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -90,7 +90,7 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 
 	isp->domain = iommu_get_domain_for_dev(isp->dev);
 	if (!isp->domain)
-		return -EPROBE_DEFER;
+		return -ENODEV;
 	isp->shift = __ffs(isp->domain->pgsize_bitmap);
 
 	idx = of_property_match_string(dev->of_node, "memory-region-names", "heap");

From dce376f6cef0c7a68e00a132ce72ad6c5af67b8c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Sep 2023 02:06:05 +0900
Subject: [PATCH 0389/1027] media: apple: WIP: t6000 hax

---
 drivers/media/platform/apple/isp/isp-cam.c | 2 +-
 drivers/media/platform/apple/isp/isp-fw.c  | 6 +++++-
 2 files changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index bb90337cb7c19f..74125b3c652433 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -207,7 +207,7 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
 		       args, sizeof(*args), false);
 
 	err = isp_ch_get_sensor_id(isp, ch);
-	if (err || (fmt->id != ISP_IMX248_1820_01)) {
+	if (err || (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) {
 		dev_err(isp->dev,
 			"ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n",
 			ch);
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 1f01d175416174..2fc91a9c434e0e 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -268,8 +268,12 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.shared_size = 0x10000000 - isp->fw.heap_top;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
-	args.unk4 = 0x1;
+	args.unk4 = 0x3;
+	//args.pad_40[1] = 0x3128000;
+	//args.pad_40[3] = 0x48000;
+	args.pad_40[5] = 0x90;
 	args.unk5 = 0x40;
+	//args.pad_7c[3] = 0x3b54000;
 	args.unk7 = 0x1;
 	args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc;
 	args.unk9 = 0x3;

From 98e9523186c54ce49216417d09c9c69f131b9d17 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Tue, 12 Sep 2023 17:58:26 +0900
Subject: [PATCH 0390/1027] media: apple: isp: Set platform_id in bootargs

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c | 3 +++
 drivers/media/platform/apple/isp/isp-drv.h | 1 +
 drivers/media/platform/apple/isp/isp-fw.c  | 5 ++---
 3 files changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 00299fd89e6038..8e6a846a867d00 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -243,6 +243,7 @@ static void apple_isp_remove(struct platform_device *pdev)
 }
 
 static const struct apple_isp_hw apple_isp_hw_t8103 = {
+	.platform_id = 0x1,
 	.pmu_base = 0x23b704000,
 
 	.dsid_clr_base0 = 0x200014000,
@@ -265,6 +266,7 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = {
 };
 
 static const struct apple_isp_hw apple_isp_hw_t6000 = {
+	.platform_id = 0x3,
 	.pmu_base = 0x28e584000,
 
 	.dsid_clr_base0 = 0x200014000,
@@ -287,6 +289,7 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = {
 };
 
 static const struct apple_isp_hw apple_isp_hw_t8110 = {
+	.platform_id = 0xe, // J413AP
 	.pmu_base = 0x23b704000,
 
 	.dsid_clr_base0 = 0x200014000, // TODO
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index bf3824cc0636b9..fb7a785b87c1c5 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -63,6 +63,7 @@ struct isp_channel {
 };
 
 struct apple_isp_hw {
+	u32 platform_id;
 	u64 pmu_base;
 
 	u64 dsid_clr_base0;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 2fc91a9c434e0e..06e4d64cf05e73 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -44,7 +44,7 @@ struct isp_firmware_bootargs {
 	u64 shared_size;
 	u64 extra_iova;
 	u64 extra_size;
-	u32 unk4;
+	u32 platform_id;
 	u32 pad_40[7];
 	u32 ipc_size;
 	u32 pad_60[5];
@@ -268,10 +268,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.shared_size = 0x10000000 - isp->fw.heap_top;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
-	args.unk4 = 0x3;
+	args.platform_id = isp->hw->platform_id;
 	//args.pad_40[1] = 0x3128000;
 	//args.pad_40[3] = 0x48000;
-	args.pad_40[5] = 0x90;
 	args.unk5 = 0x40;
 	//args.pad_7c[3] = 0x3b54000;
 	args.unk7 = 0x1;

From ba2e8ed2270687837f646f80c9200141101fd936 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Tue, 12 Sep 2023 18:49:25 +0900
Subject: [PATCH 0391/1027] media: apple: isp: Better document info struct
 fields

"Document". I also counted wrong multiple times.

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-cmd.h | 64 +++++++++++++++++++---
 1 file changed, 55 insertions(+), 9 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
index dde6aad506c23e..1fc484fa687853 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.h
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -202,19 +202,53 @@ static_assert(sizeof(struct cmd_ch_stop) == 0xc);
 struct cmd_ch_info {
 	u64 opcode;
 	u32 chan;
-	u32 unk_c;
-	u32 unk_10[4];
+	u32 unk_c;  // 0x7da0001, 0x7db0001
+	u32 unk_10; // 0x300ac, 0x5006d
+	u32 unk_14; // 0x40007, 0x10007
+	u32 unk_18; // 0x5, 0x2
+	u32 unk_1c; // 0x1, 0x1
 	u32 version;
-	u32 unk_24[3];
-	u32 unk_30[12];
+	u32 unk_24; // 0x7, 0x9
+	u32 unk_28; // 0x1, 0x1410
+	u32 unk_2c; // 0x7, 0x2
+	u32 pad_30[7];
+	u32 unk_4c; // 0x10000, 0x50000
+	u32 unk_50; // 0x1, 0x1
+	u32 unk_54; // 0x0, 0x0
+	u32 unk_58; // 0x4, 0x4
+	u32 unk_5c; // 0x10, 0x20
 	u32 num_presets;
-	u32 unk_64[7];
-	u32 unk_80[6];
-	u32 unk_98_freq;
+	u32 unk_64; // 0x0, 0x0
+	u32 unk_68; // 0x44c0, 0x4680
+	u32 unk_6c; // 0x40, 0x40
+	u32 unk_70; // 0x1, 0x1
+	u32 unk_74; // 0x2, 0x2
+	u32 unk_78; // 0x4000, 0x4000
+	u32 unk_7c; // 0x40, 0x40
+	u32 unk_80; // 0x1, 0x1
+	u32 pad_84[2];
+	u32 unk_8c; // 0x36, 0x36
+	u32 pad_90[2];
+	u32 timestamp_freq;
 	u16 pad_9c;
 	char module_sn[20];
 	u16 pad_b0;
-	u32 unk_b4[25];
+	u32 unk_b4; // 0x8, 0x8
+	u32 pad_b8[2];
+	u32 unk_c0; // 0x4, 0x1
+	u32 unk_c4; // 0x0, 0x0
+	u32 unk_c8; // 0x0, 0x100
+	u32 pad_cc[4];
+	u32 unk_dc; // 0xff0000, 0xff0000
+	u32 unk_e0; // 0xc00, 0xc00
+	u32 unk_e4; // 0x0, 0x0
+	u32 unk_e8; // 0x1c, 0x1c
+	u32 unk_ec; // 0x640, 0x680
+	u32 unk_f0; // 0x4, 0x4
+	u32 unk_f4; // 0x4, 0x4
+	u32 pad_f8[6];
+	u32 unk_110; // 0x0, 0x7800000
+	u32 unk_114; // 0x0, 0x780
 } __packed;
 static_assert(sizeof(struct cmd_ch_info) == 0x118);
 
@@ -226,7 +260,19 @@ struct cmd_ch_camera_config {
 	u16 in_height;
 	u16 out_width;
 	u16 out_height;
-	u32 unk[49];
+	u32 unk_28;
+	u32 unk_2c;
+	u32 unk_30[16];
+	u32 sensor_clk;
+	u32 unk_64[4];
+	u32 timestamp_freq;
+	u32 unk_78[2];
+	u32 unk_80[16];
+	u32 in_width2; // repeated in u32??
+	u32 in_height2;
+	u32 unk_c8[3];
+	u32 out_width2;
+	u32 out_height2;
 } __packed;
 static_assert(sizeof(struct cmd_ch_camera_config) == 0xdc);
 

From 9a07b894e00ca01c4f6a6f914a431f47e871e276 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Tue, 12 Sep 2023 19:44:52 +0900
Subject: [PATCH 0392/1027] media: apple: isp: Don't use define for bootargs
 size

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-fw.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 06e4d64cf05e73..1d1bbc119cd700 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -13,7 +13,6 @@
 #define ISP_FIRMWARE_MDELAY	   1
 #define ISP_FIRMWARE_MAX_TRIES	   1000
 
-#define ISP_FIRMWARE_BOOTARGS_SIZE 0x180
 #define ISP_FIRMWARE_IPC_SIZE	   0x1c000
 #define ISP_FIRMWARE_DATA_SIZE	   0x28000
 
@@ -57,8 +56,7 @@ struct isp_firmware_bootargs {
 	u32 pad_c0[47];
 	u32 unk9;
 } __packed;
-static_assert(sizeof(struct isp_firmware_bootargs) ==
-	      ISP_FIRMWARE_BOOTARGS_SIZE);
+static_assert(sizeof(struct isp_firmware_bootargs) == 0x180);
 
 struct isp_chan_desc {
 	char name[64];
@@ -274,7 +272,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.unk5 = 0x40;
 	//args.pad_7c[3] = 0x3b54000;
 	args.unk7 = 0x1;
-	args.unk_iova1 = args_iova + ISP_FIRMWARE_BOOTARGS_SIZE - 0xc;
+	args.unk_iova1 = args_iova + sizeof(args) - 0xc;
 	args.unk9 = 0x3;
 	isp_iowrite(isp, args_iova, &args, sizeof(args));
 

From ac0e4017121470de0093e0480b6fcd67c48a2409 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Tue, 12 Sep 2023 19:53:11 +0900
Subject: [PATCH 0393/1027] media: apple: isp: wmb() before GPIO write

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-fw.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 1d1bbc119cd700..9cbeb74cf96601 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -278,6 +278,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 
 	isp_gpio_write32(isp, ISP_GPIO_0, args_iova);
 	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
+	wmb();
 
 	/* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
 	isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9);

From 787cd9b9b35d68fe36c3cc16f7617e7bdd503d19 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Tue, 12 Sep 2023 20:05:34 +0900
Subject: [PATCH 0394/1027] media: apple: isp: s/asc/coproc/

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c  |  6 +--
 drivers/media/platform/apple/isp/isp-drv.h  |  2 +-
 drivers/media/platform/apple/isp/isp-fw.c   | 46 ++++++++++-----------
 drivers/media/platform/apple/isp/isp-regs.h | 32 +++++++-------
 4 files changed, 43 insertions(+), 43 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 8e6a846a867d00..7ade4b6f330371 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -155,9 +155,9 @@ static int apple_isp_probe(struct platform_device *pdev)
 		return err;
 	}
 
-	isp->asc = devm_platform_ioremap_resource_byname(pdev, "asc");
-	if (IS_ERR(isp->asc)) {
-		err = PTR_ERR(isp->asc);
+	isp->coproc = devm_platform_ioremap_resource_byname(pdev, "coproc");
+	if (IS_ERR(isp->coproc)) {
+		err = PTR_ERR(isp->coproc);
 		goto detach_genpd;
 	}
 
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index fb7a785b87c1c5..ed567c06d8dccf 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -180,7 +180,7 @@ struct apple_isp {
 
 	int irq;
 
-	void __iomem *asc;
+	void __iomem *coproc;
 	void __iomem *mbox;
 	void __iomem *gpio;
 
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 9cbeb74cf96601..064626c8ed8dec 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -10,20 +10,20 @@
 #include "isp-ipc.h"
 #include "isp-regs.h"
 
-#define ISP_FIRMWARE_MDELAY	   1
-#define ISP_FIRMWARE_MAX_TRIES	   1000
+#define ISP_FIRMWARE_MDELAY    1
+#define ISP_FIRMWARE_MAX_TRIES 1000
 
-#define ISP_FIRMWARE_IPC_SIZE	   0x1c000
-#define ISP_FIRMWARE_DATA_SIZE	   0x28000
+#define ISP_FIRMWARE_IPC_SIZE  0x1c000
+#define ISP_FIRMWARE_DATA_SIZE 0x28000
 
-static inline u32 isp_asc_read32(struct apple_isp *isp, u32 reg)
+static inline u32 isp_coproc_read32(struct apple_isp *isp, u32 reg)
 {
-	return readl(isp->asc + reg);
+	return readl(isp->coproc + reg);
 }
 
-static inline void isp_asc_write32(struct apple_isp *isp, u32 reg, u32 val)
+static inline void isp_coproc_write32(struct apple_isp *isp, u32 reg, u32 val)
 {
-	writel(val, isp->asc + reg);
+	writel(val, isp->coproc + reg);
 }
 
 static inline u32 isp_gpio_read32(struct apple_isp *isp, u32 reg)
@@ -130,22 +130,22 @@ static int isp_coproc_ready(struct apple_isp *isp)
 	int retries;
 	u32 status;
 
-	isp_asc_write32(isp, ISP_ASC_EDPRCR, 0x2);
+	isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2);
 
-	isp_asc_write32(isp, ISP_ASC_PMGR_0, 0xff00ff);
-	isp_asc_write32(isp, ISP_ASC_PMGR_1, 0xff00ff);
-	isp_asc_write32(isp, ISP_ASC_PMGR_2, 0xff00ff);
-	isp_asc_write32(isp, ISP_ASC_PMGR_3, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_PMGR_0, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_PMGR_1, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_PMGR_2, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_PMGR_3, 0xff00ff);
 
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_0, 0xffffffff);
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_1, 0xffffffff);
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_2, 0xffffffff);
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_3, 0xffffffff);
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_4, 0xffffffff);
-	isp_asc_write32(isp, ISP_ASC_IRQ_MASK_5, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_0, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_1, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_2, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_3, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_4, 0xffffffff);
+	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_5, 0xffffffff);
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
-		status = isp_asc_read32(isp, ISP_ASC_STATUS);
+		status = isp_coproc_read32(isp, ISP_COPROC_STATUS);
 		if (!((status & 0x3) == 0)) {
 			isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n",
 				retries, status);
@@ -163,7 +163,7 @@ static int isp_coproc_ready(struct apple_isp *isp)
 
 static void isp_firmware_shutdown_stage1(struct apple_isp *isp)
 {
-	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0);
+	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0);
 }
 
 static int isp_firmware_boot_stage1(struct apple_isp *isp)
@@ -187,8 +187,8 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 
 	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0);
 
-	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x0);
-	isp_asc_write32(isp, ISP_ASC_CONTROL, 0x10);
+	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0);
+	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x10);
 
 	/* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */
 	isp_gpio_write32(isp, ISP_GPIO_7, 0x0);
diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h
index e21485ec4ce823..b3032e9112c012 100644
--- a/drivers/media/platform/apple/isp/isp-regs.h
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -6,22 +6,22 @@
 
 #include "isp-drv.h"
 
-#define ISP_ASC_PMGR_0	       0x738
-#define ISP_ASC_PMGR_1	       0x798
-#define ISP_ASC_PMGR_2	       0x7f8
-#define ISP_ASC_PMGR_3	       0x858
-
-#define ISP_ASC_RVBAR	       0x1050000
-#define ISP_ASC_EDPRCR	       0x1010310
-#define ISP_ASC_CONTROL	       0x1400044
-#define ISP_ASC_STATUS	       0x1400048
-
-#define ISP_ASC_IRQ_MASK_0     0x1400a00
-#define ISP_ASC_IRQ_MASK_1     0x1400a04
-#define ISP_ASC_IRQ_MASK_2     0x1400a08
-#define ISP_ASC_IRQ_MASK_3     0x1400a0c
-#define ISP_ASC_IRQ_MASK_4     0x1400a10
-#define ISP_ASC_IRQ_MASK_5     0x1400a14
+#define ISP_COPROC_PMGR_0      0x738
+#define ISP_COPROC_PMGR_1      0x798
+#define ISP_COPROC_PMGR_2      0x7f8
+#define ISP_COPROC_PMGR_3      0x858
+
+#define ISP_COPROC_RVBAR       0x1050000
+#define ISP_COPROC_EDPRCR      0x1010310
+#define ISP_COPROC_CONTROL     0x1400044
+#define ISP_COPROC_STATUS      0x1400048
+
+#define ISP_COPROC_IRQ_MASK_0  0x1400a00
+#define ISP_COPROC_IRQ_MASK_1  0x1400a04
+#define ISP_COPROC_IRQ_MASK_2  0x1400a08
+#define ISP_COPROC_IRQ_MASK_3  0x1400a0c
+#define ISP_COPROC_IRQ_MASK_4  0x1400a10
+#define ISP_COPROC_IRQ_MASK_5  0x1400a14
 
 #define ISP_MBOX_IRQ_INTERRUPT 0x000
 #define ISP_MBOX_IRQ_ENABLE    0x004

From 401b923ee623eef6cca343ba4ea98842a47f4cc4 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 14 Sep 2023 18:26:09 +0900
Subject: [PATCH 0395/1027] media: apple: isp: rm unused bootargs members

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-fw.c | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 064626c8ed8dec..3d0d550ff52183 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -267,10 +267,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
 	args.platform_id = isp->hw->platform_id;
-	//args.pad_40[1] = 0x3128000;
-	//args.pad_40[3] = 0x48000;
 	args.unk5 = 0x40;
-	//args.pad_7c[3] = 0x3b54000;
 	args.unk7 = 0x1;
 	args.unk_iova1 = args_iova + sizeof(args) - 0xc;
 	args.unk9 = 0x3;

From 4a15a7fb7a7ead27083a85ae72770fcd7eb71854 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 14 Sep 2023 19:17:46 +0900
Subject: [PATCH 0396/1027] media: apple: isp: rm old isp_resv struct

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.h | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index ed567c06d8dccf..e672c62c0ec41c 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -85,12 +85,6 @@ struct apple_isp_hw {
 	u8 bandwidth_size;
 };
 
-struct isp_resv {
-	phys_addr_t phys;
-	dma_addr_t iova;
-	u64 size;
-};
-
 enum isp_sensor_id {
 	ISP_IMX248_1820_01,
 	ISP_IMX248_1822_02,

From fb3e96633d7e8a97f86d0c3386fea3d9f71104cb Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 14 Sep 2023 19:32:24 +0900
Subject: [PATCH 0397/1027] media: apple: isp: misc isp-fw.c improvements

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-fw.c   | 19 +++++++++++--------
 drivers/media/platform/apple/isp/isp-regs.h |  8 ++++----
 2 files changed, 15 insertions(+), 12 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 3d0d550ff52183..57c1db6aee3dbc 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -16,6 +16,8 @@
 #define ISP_FIRMWARE_IPC_SIZE  0x1c000
 #define ISP_FIRMWARE_DATA_SIZE 0x28000
 
+#define ISP_COPROC_IN_WFI      0x3
+
 static inline u32 isp_coproc_read32(struct apple_isp *isp, u32 reg)
 {
 	return readl(isp->coproc + reg);
@@ -125,17 +127,17 @@ static int isp_enable_irq(struct apple_isp *isp)
 	return 0;
 }
 
-static int isp_coproc_ready(struct apple_isp *isp)
+static int isp_reset_coproc(struct apple_isp *isp)
 {
 	int retries;
 	u32 status;
 
 	isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2);
 
-	isp_coproc_write32(isp, ISP_COPROC_PMGR_0, 0xff00ff);
-	isp_coproc_write32(isp, ISP_COPROC_PMGR_1, 0xff00ff);
-	isp_coproc_write32(isp, ISP_COPROC_PMGR_2, 0xff00ff);
-	isp_coproc_write32(isp, ISP_COPROC_PMGR_3, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_FABRIC_0, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_FABRIC_1, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_FABRIC_2, 0xff00ff);
+	isp_coproc_write32(isp, ISP_COPROC_FABRIC_3, 0xff00ff);
 
 	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_0, 0xffffffff);
 	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_1, 0xffffffff);
@@ -146,7 +148,7 @@ static int isp_coproc_ready(struct apple_isp *isp)
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
 		status = isp_coproc_read32(isp, ISP_COPROC_STATUS);
-		if (!((status & 0x3) == 0)) {
+		if (status & ISP_COPROC_IN_WFI) {
 			isp_dbg(isp, "%d: coproc in WFI (status: 0x%x)\n",
 				retries, status);
 			break;
@@ -170,7 +172,7 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 {
 	int err, retries;
 
-	err = isp_coproc_ready(isp);
+	err = isp_reset_coproc(isp);
 	if (err < 0)
 		return err;
 
@@ -263,7 +265,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.ipc_iova = isp->ipc_surf->iova;
 	args.ipc_size = isp->ipc_surf->size;
 	args.shared_base = isp->fw.heap_top;
-	args.shared_size = 0x10000000 - isp->fw.heap_top;
+	args.shared_size = 0x10000000UL - isp->fw.heap_top;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
 	args.platform_id = isp->hw->platform_id;
@@ -425,6 +427,7 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp)
 			isp_iowrite(isp, msg_iova, &msg, sizeof(msg));
 		}
 	}
+	wmb();
 
 	/* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */
 	isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006);
diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h
index b3032e9112c012..3a99229f6d4c8f 100644
--- a/drivers/media/platform/apple/isp/isp-regs.h
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -6,10 +6,10 @@
 
 #include "isp-drv.h"
 
-#define ISP_COPROC_PMGR_0      0x738
-#define ISP_COPROC_PMGR_1      0x798
-#define ISP_COPROC_PMGR_2      0x7f8
-#define ISP_COPROC_PMGR_3      0x858
+#define ISP_COPROC_FABRIC_0    0x738
+#define ISP_COPROC_FABRIC_1    0x798
+#define ISP_COPROC_FABRIC_2    0x7f8
+#define ISP_COPROC_FABRIC_3    0x858
 
 #define ISP_COPROC_RVBAR       0x1050000
 #define ISP_COPROC_EDPRCR      0x1010310

From 0de1b44be56748c3d9e62b47b55145b4f8357026 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 14 Sep 2023 20:06:55 +0900
Subject: [PATCH 0398/1027] media: apple: isp: alloc static surfaces only once

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c | 16 ++++++--
 drivers/media/platform/apple/isp/isp-fw.c  | 47 +++++++++++++---------
 drivers/media/platform/apple/isp/isp-fw.h  |  3 ++
 3 files changed, 43 insertions(+), 23 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 7ade4b6f330371..c188724b4d773b 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -19,6 +19,7 @@
 #include <linux/workqueue.h>
 
 #include "isp-cam.h"
+#include "isp-fw.h"
 #include "isp-iommu.h"
 #include "isp-v4l2.h"
 
@@ -202,26 +203,34 @@ static int apple_isp_probe(struct platform_device *pdev)
 		goto destroy_wq;
 	}
 
+	err = apple_isp_alloc_firmware_surface(isp);
+	if (err) {
+		dev_err(dev, "failed to alloc firmware surface: %d\n", err);
+		goto free_iommu;
+	}
+
 	pm_runtime_enable(dev);
 
 	err = apple_isp_detect_camera(isp);
 	if (err) {
 		dev_err(dev, "failed to detect camera: %d\n", err);
-		goto free_iommu;
+		goto free_surface;
 	}
 
 	err = apple_isp_setup_video(isp);
 	if (err) {
 		dev_err(dev, "failed to register video device: %d\n", err);
-		goto free_iommu;
+		goto free_surface;
 	}
 
 	dev_info(dev, "apple-isp probe!\n");
 
 	return 0;
 
-free_iommu:
+free_surface:
 	pm_runtime_disable(dev);
+	apple_isp_free_firmware_surface(isp);
+free_iommu:
 	apple_isp_free_iommu(isp);
 destroy_wq:
 	destroy_workqueue(isp->wq);
@@ -236,6 +245,7 @@ static void apple_isp_remove(struct platform_device *pdev)
 
 	apple_isp_remove_video(isp);
 	pm_runtime_disable(isp->dev);
+	apple_isp_free_firmware_surface(isp);
 	apple_isp_free_iommu(isp);
 	destroy_workqueue(isp->wq);
 	apple_isp_detach_genpd(isp);
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 57c1db6aee3dbc..93e18df1cf41c1 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -213,13 +213,36 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 	return 0;
 }
 
-static void isp_firmware_shutdown_stage2(struct apple_isp *isp)
+int apple_isp_alloc_firmware_surface(struct apple_isp *isp)
+{
+	/* These are static, so let's do it once and for all */
+	isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE);
+	if (!isp->ipc_surf) {
+		isp_err(isp, "failed to alloc shared surface for ipc\n");
+		return -ENOMEM;
+	}
+
+	isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE);
+	if (!isp->data_surf) {
+		isp_err(isp, "failed to alloc shared surface for data files\n");
+		isp_free_surface(isp, isp->ipc_surf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void apple_isp_free_firmware_surface(struct apple_isp *isp)
 {
 	isp_free_surface(isp, isp->data_surf);
-	isp_free_surface(isp, isp->extra_surf);
 	isp_free_surface(isp, isp->ipc_surf);
 }
 
+static void isp_firmware_shutdown_stage2(struct apple_isp *isp)
+{
+	isp_free_surface(isp, isp->extra_surf);
+}
+
 static int isp_firmware_boot_stage2(struct apple_isp *isp)
 {
 	struct isp_firmware_bootargs args;
@@ -240,22 +263,10 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 		dev_warn(isp->dev, "unexpected channel count (%d)\n",
 			 num_ipc_chans);
 
-	isp->ipc_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_IPC_SIZE);
-	if (!isp->ipc_surf) {
-		isp_err(isp, "failed to alloc surface for ipc\n");
-		return -ENOMEM;
-	}
-
 	isp->extra_surf = isp_alloc_surface_vmap(isp, extra_size);
 	if (!isp->extra_surf) {
 		isp_err(isp, "failed to alloc surface for extra heap\n");
-		goto free_ipc;
-	}
-
-	isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE);
-	if (!isp->data_surf) {
-		isp_err(isp, "failed to alloc surface for data files\n");
-		goto free_extra;
+		return -ENOMEM;
 	}
 
 	args_iova = isp->ipc_surf->iova + args_offset + 0x40;
@@ -296,17 +307,13 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 		isp_err(isp,
 			"never received second magic number from firmware\n");
 		err = -ENODEV;
-		goto free_file;
+		goto free_extra;
 	}
 
 	return 0;
 
-free_file:
-	isp_free_surface(isp, isp->data_surf);
 free_extra:
 	isp_free_surface(isp, isp->extra_surf);
-free_ipc:
-	isp_free_surface(isp, isp->ipc_surf);
 	return err;
 }
 
diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h
index ad9f4fdf641aaa..264717793cea02 100644
--- a/drivers/media/platform/apple/isp/isp-fw.h
+++ b/drivers/media/platform/apple/isp/isp-fw.h
@@ -6,6 +6,9 @@
 
 #include "isp-drv.h"
 
+int apple_isp_alloc_firmware_surface(struct apple_isp *isp);
+void apple_isp_free_firmware_surface(struct apple_isp *isp);
+
 int apple_isp_firmware_boot(struct apple_isp *isp);
 void apple_isp_firmware_shutdown(struct apple_isp *isp);
 

From 1bcc0ecea3f8119d1d9dbc4fa2d327e7a7501048 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Thu, 14 Sep 2023 20:32:02 +0900
Subject: [PATCH 0399/1027] media: apple: isp: fix copyright

Not really anymore.

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index c188724b4d773b..936543681cc588 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -3,10 +3,6 @@
  * Apple Image Signal Processor driver
  *
  * Copyright (C) 2023 The Asahi Linux Contributors
- *
- * Based on aspeed/aspeed-video.c
- *  Copyright 2020 IBM Corp.
- *  Copyright (c) 2019-2020 Intel Corporation
  */
 
 #include <linux/iommu.h>

From 41596f4ab574605090374a08b568df1500de9607 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 24 Sep 2023 01:01:59 +0900
Subject: [PATCH 0400/1027] media: apple: isp: Support >32bit VAs for t602x

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 7 ++++++-
 drivers/media/platform/apple/isp/isp-fw.c  | 9 +++++----
 2 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 936543681cc588..109a40a18219bd 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -121,7 +121,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 		return err;
 	}
 
-	drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - heap_base);
+	// FIXME: refactor this, maybe use regular iova stuff?
+	drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - (heap_base & 0xffffffff));
 
 	return 0;
 }
@@ -137,6 +138,10 @@ static int apple_isp_probe(struct platform_device *pdev)
 	struct apple_isp *isp;
 	int err;
 
+	err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42));
+	if (err)
+		return err;
+
 	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
 	if (!isp)
 		return -ENOMEM;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 93e18df1cf41c1..70ffaa97cd260a 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -275,8 +275,8 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	memset(&args, 0, sizeof(args));
 	args.ipc_iova = isp->ipc_surf->iova;
 	args.ipc_size = isp->ipc_surf->size;
-	args.shared_base = isp->fw.heap_top;
-	args.shared_size = 0x10000000UL - isp->fw.heap_top;
+	args.shared_base = isp->fw.heap_top & 0xffffffff;
+	args.shared_size = 0x10000000UL - args.shared_base;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
 	args.platform_id = isp->hw->platform_id;
@@ -287,7 +287,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	isp_iowrite(isp, args_iova, &args, sizeof(args));
 
 	isp_gpio_write32(isp, ISP_GPIO_0, args_iova);
-	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
+	isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32);
 	wmb();
 
 	/* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
@@ -343,7 +343,8 @@ static void isp_free_channel_info(struct apple_isp *isp)
 
 static int isp_fill_channel_info(struct apple_isp *isp)
 {
-	u32 table_iova = isp_gpio_read32(isp, ISP_GPIO_0);
+	u64 table_iova = isp_gpio_read32(isp, ISP_GPIO_0) |
+		((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32;
 
 	isp->ipc_chans = kcalloc(isp->num_ipc_chans,
 				 sizeof(struct isp_channel *), GFP_KERNEL);

From 003f1841935d80685e72407648d7b9a4d5a4f42e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 24 Sep 2023 01:02:41 +0900
Subject: [PATCH 0401/1027] media: apple: isp: t602x hw config

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 24 ++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 109a40a18219bd..91dd1cb607076b 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -322,9 +322,33 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = {
 	.bandwidth_size = 0x8,
 };
 
+static const struct apple_isp_hw apple_isp_hw_t6020 = {
+	.platform_id = 0x7, // J416cAP
+	.pmu_base = 0x290284000,
+
+	.dsid_clr_base0 = 0x200014000, // TODO
+	.dsid_clr_base1 = 0x200054000,
+	.dsid_clr_base2 = 0x200094000,
+	.dsid_clr_base3 = 0x2000d4000,
+	.dsid_clr_range0 = 0x1000,
+	.dsid_clr_range1 = 0x1000,
+	.dsid_clr_range2 = 0x1000,
+	.dsid_clr_range3 = 0x1000,
+
+	.clock_scratch = 0x28e3d0868, // CHECK
+	.clock_base = 0x0,
+	.clock_bit = 0x0,
+	.clock_size = 0x8,
+	.bandwidth_scratch = 0x28e3d0980, // CHECK
+	.bandwidth_base = 0x0,
+	.bandwidth_bit = 0x0,
+	.bandwidth_size = 0x8,
+};
+
 static const struct of_device_id apple_isp_of_match[] = {
 	{ .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 },
 	{ .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 },
+	{ .compatible = "apple,t6020-isp", .data = &apple_isp_hw_t6020 },
 	{},
 };
 MODULE_DEVICE_TABLE(of, apple_isp_of_match);

From b4d2b4922ce8a77666206a64314192e08c1aca0a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 24 Sep 2023 01:03:11 +0900
Subject: [PATCH 0402/1027] media: apple: isp: Working t602x and multiple
 formats and more fixes

Sorry for the horrible big commit...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cam.c  | 256 ++++++--------
 drivers/media/platform/apple/isp/isp-cmd.c  |  94 +++++-
 drivers/media/platform/apple/isp/isp-cmd.h  | 106 +++++-
 drivers/media/platform/apple/isp/isp-drv.c  | 154 +++++++--
 drivers/media/platform/apple/isp/isp-drv.h  |  43 ++-
 drivers/media/platform/apple/isp/isp-fw.c   |  30 +-
 drivers/media/platform/apple/isp/isp-ipc.c  |   9 +-
 drivers/media/platform/apple/isp/isp-v4l2.c | 349 ++++++++++++++------
 8 files changed, 706 insertions(+), 335 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index 74125b3c652433..593b780ab73b15 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -8,6 +8,8 @@
 #include "isp-fw.h"
 #include "isp-iommu.h"
 
+#define ISP_MAX_PRESETS 32
+
 struct isp_setfile {
 	u32 version;
 	u32 magic;
@@ -15,74 +17,56 @@ struct isp_setfile {
 	size_t size;
 };
 
-struct isp_preset {
-	u32 index;
-	u32 width;
-	u32 height;
-	u32 x1;
-	u32 y1;
-	u32 x2;
-	u32 y2;
-	u32 orig_width;
-	u32 orig_height;
-};
-
 // clang-format off
 static const struct isp_setfile isp_setfiles[] = {
-	[ISP_IMX248_1820_01] = {0x248, 0x18200103, "isp/1820_01XX.dat", 0x442c},
-	[ISP_IMX248_1822_02] = {0x248, 0x18220201, "isp/1822_02XX.dat", 0x442c},
-	[ISP_IMX343_5221_02] = {0x343, 0x52210211, "isp/5221_02XX.dat", 0x4870},
-	[ISP_IMX354_9251_02] = {0x354, 0x92510208, "isp/9251_02XX.dat", 0xa5ec},
-	[ISP_IMX356_4820_01] = {0x356, 0x48200107, "isp/4820_01XX.dat", 0x9324},
-	[ISP_IMX356_4820_02] = {0x356, 0x48200206, "isp/4820_02XX.dat", 0x9324},
-	[ISP_IMX364_8720_01] = {0x364, 0x87200103, "isp/8720_01XX.dat", 0x36ac},
-	[ISP_IMX364_8723_01] = {0x364, 0x87230101, "isp/8723_01XX.dat", 0x361c},
-	[ISP_IMX372_3820_01] = {0x372, 0x38200108, "isp/3820_01XX.dat", 0xfdb0},
-	[ISP_IMX372_3820_02] = {0x372, 0x38200205, "isp/3820_02XX.dat", 0xfdb0},
-	[ISP_IMX372_3820_11] = {0x372, 0x38201104, "isp/3820_11XX.dat", 0xfdb0},
-	[ISP_IMX372_3820_12] = {0x372, 0x38201204, "isp/3820_12XX.dat", 0xfdb0},
-	[ISP_IMX405_9720_01] = {0x405, 0x97200102, "isp/9720_01XX.dat", 0x92c8},
-	[ISP_IMX405_9721_01] = {0x405, 0x97210102, "isp/9721_01XX.dat", 0x9818},
-	[ISP_IMX405_9723_01] = {0x405, 0x97230101, "isp/9723_01XX.dat", 0x92c8},
-	[ISP_IMX414_2520_01] = {0x414, 0x25200102, "isp/2520_01XX.dat", 0xa444},
-	[ISP_IMX503_7820_01] = {0x503, 0x78200109, "isp/7820_01XX.dat", 0xb268},
-	[ISP_IMX503_7820_02] = {0x503, 0x78200206, "isp/7820_02XX.dat", 0xb268},
-	[ISP_IMX505_3921_01] = {0x505, 0x39210102, "isp/3921_01XX.dat", 0x89b0},
-	[ISP_IMX514_2820_01] = {0x514, 0x28200108, "isp/2820_01XX.dat", 0xa198},
-	[ISP_IMX514_2820_02] = {0x514, 0x28200205, "isp/2820_02XX.dat", 0xa198},
-	[ISP_IMX514_2820_03] = {0x514, 0x28200305, "isp/2820_03XX.dat", 0xa198},
-	[ISP_IMX514_2820_04] = {0x514, 0x28200405, "isp/2820_04XX.dat", 0xa198},
-	[ISP_IMX558_1921_01] = {0x558, 0x19210106, "isp/1921_01XX.dat", 0xad40},
-	[ISP_IMX558_1922_02] = {0x558, 0x19220201, "isp/1922_02XX.dat", 0xad40},
-	[ISP_IMX603_7920_01] = {0x603, 0x79200109, "isp/7920_01XX.dat", 0xad2c},
-	[ISP_IMX603_7920_02] = {0x603, 0x79200205, "isp/7920_02XX.dat", 0xad2c},
-	[ISP_IMX603_7921_01] = {0x603, 0x79210104, "isp/7921_01XX.dat", 0xad90},
-	[ISP_IMX613_4920_01] = {0x613, 0x49200108, "isp/4920_01XX.dat", 0x9324},
-	[ISP_IMX613_4920_02] = {0x613, 0x49200204, "isp/4920_02XX.dat", 0x9324},
-	[ISP_IMX614_2921_01] = {0x614, 0x29210107, "isp/2921_01XX.dat", 0xed6c},
-	[ISP_IMX614_2921_02] = {0x614, 0x29210202, "isp/2921_02XX.dat", 0xed6c},
-	[ISP_IMX614_2922_02] = {0x614, 0x29220201, "isp/2922_02XX.dat", 0xed6c},
-	[ISP_IMX633_3622_01] = {0x633, 0x36220111, "isp/3622_01XX.dat", 0x100d4},
-	[ISP_IMX703_7721_01] = {0x703, 0x77210106, "isp/7721_01XX.dat", 0x936c},
-	[ISP_IMX703_7722_01] = {0x703, 0x77220106, "isp/7722_01XX.dat", 0xac20},
-	[ISP_IMX713_4721_01] = {0x713, 0x47210107, "isp/4721_01XX.dat", 0x936c},
-	[ISP_IMX713_4722_01] = {0x713, 0x47220109, "isp/4722_01XX.dat", 0x9218},
-	[ISP_IMX714_2022_01] = {0x714, 0x20220107, "isp/2022_01XX.dat", 0xa198},
-	[ISP_IMX772_3721_01] = {0x772, 0x37210106, "isp/3721_01XX.dat", 0xfdf8},
-	[ISP_IMX772_3721_11] = {0x772, 0x37211106, "isp/3721_11XX.dat", 0xfe14},
-	[ISP_IMX772_3722_01] = {0x772, 0x37220104, "isp/3722_01XX.dat", 0xfca4},
-	[ISP_IMX772_3723_01] = {0x772, 0x37230106, "isp/3723_01XX.dat", 0xfca4},
-	[ISP_IMX814_2123_01] = {0x814, 0x21230101, "isp/2123_01XX.dat", 0xed54},
-	[ISP_IMX853_7622_01] = {0x853, 0x76220112, "isp/7622_01XX.dat", 0x247f8},
-	[ISP_IMX913_7523_01] = {0x913, 0x75230107, "isp/7523_01XX.dat", 0x247f8},
-	[ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "isp/6221_01XX.dat", 0x1b80},
-	[ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "isp/6222_01XX.dat", 0x1b80},
-};
-
-// one day we will do this intelligently
-static const struct isp_preset isp_presets[] = {
-	[ISP_IMX248_1820_01] = {0, 1280,  720, 8, 8, 1280,  720, 1296,  736}, // J293AP
-	[ISP_IMX558_1921_01] = {1, 1920, 1080, 0, 0, 1920, 1080, 1920, 1080}, // J316sAP, J415AP
+	[ISP_IMX248_1820_01] = {0x248, 0x18200103, "apple/isp_1820_01XX.dat", 0x442c},
+	[ISP_IMX248_1822_02] = {0x248, 0x18220201, "apple/isp_1822_02XX.dat", 0x442c},
+	[ISP_IMX343_5221_02] = {0x343, 0x52210211, "apple/isp_5221_02XX.dat", 0x4870},
+	[ISP_IMX354_9251_02] = {0x354, 0x92510208, "apple/isp_9251_02XX.dat", 0xa5ec},
+	[ISP_IMX356_4820_01] = {0x356, 0x48200107, "apple/isp_4820_01XX.dat", 0x9324},
+	[ISP_IMX356_4820_02] = {0x356, 0x48200206, "apple/isp_4820_02XX.dat", 0x9324},
+	[ISP_IMX364_8720_01] = {0x364, 0x87200103, "apple/isp_8720_01XX.dat", 0x36ac},
+	[ISP_IMX364_8723_01] = {0x364, 0x87230101, "apple/isp_8723_01XX.dat", 0x361c},
+	[ISP_IMX372_3820_01] = {0x372, 0x38200108, "apple/isp_3820_01XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_02] = {0x372, 0x38200205, "apple/isp_3820_02XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_11] = {0x372, 0x38201104, "apple/isp_3820_11XX.dat", 0xfdb0},
+	[ISP_IMX372_3820_12] = {0x372, 0x38201204, "apple/isp_3820_12XX.dat", 0xfdb0},
+	[ISP_IMX405_9720_01] = {0x405, 0x97200102, "apple/isp_9720_01XX.dat", 0x92c8},
+	[ISP_IMX405_9721_01] = {0x405, 0x97210102, "apple/isp_9721_01XX.dat", 0x9818},
+	[ISP_IMX405_9723_01] = {0x405, 0x97230101, "apple/isp_9723_01XX.dat", 0x92c8},
+	[ISP_IMX414_2520_01] = {0x414, 0x25200102, "apple/isp_2520_01XX.dat", 0xa444},
+	[ISP_IMX503_7820_01] = {0x503, 0x78200109, "apple/isp_7820_01XX.dat", 0xb268},
+	[ISP_IMX503_7820_02] = {0x503, 0x78200206, "apple/isp_7820_02XX.dat", 0xb268},
+	[ISP_IMX505_3921_01] = {0x505, 0x39210102, "apple/isp_3921_01XX.dat", 0x89b0},
+	[ISP_IMX514_2820_01] = {0x514, 0x28200108, "apple/isp_2820_01XX.dat", 0xa198},
+	[ISP_IMX514_2820_02] = {0x514, 0x28200205, "apple/isp_2820_02XX.dat", 0xa198},
+	[ISP_IMX514_2820_03] = {0x514, 0x28200305, "apple/isp_2820_03XX.dat", 0xa198},
+	[ISP_IMX514_2820_04] = {0x514, 0x28200405, "apple/isp_2820_04XX.dat", 0xa198},
+	[ISP_IMX558_1921_01] = {0x558, 0x19210106, "apple/isp_1921_01XX.dat", 0xad40},
+	[ISP_IMX558_1922_02] = {0x558, 0x19220201, "apple/isp_1922_02XX.dat", 0xad40},
+	[ISP_IMX603_7920_01] = {0x603, 0x79200109, "apple/isp_7920_01XX.dat", 0xad2c},
+	[ISP_IMX603_7920_02] = {0x603, 0x79200205, "apple/isp_7920_02XX.dat", 0xad2c},
+	[ISP_IMX603_7921_01] = {0x603, 0x79210104, "apple/isp_7921_01XX.dat", 0xad90},
+	[ISP_IMX613_4920_01] = {0x613, 0x49200108, "apple/isp_4920_01XX.dat", 0x9324},
+	[ISP_IMX613_4920_02] = {0x613, 0x49200204, "apple/isp_4920_02XX.dat", 0x9324},
+	[ISP_IMX614_2921_01] = {0x614, 0x29210107, "apple/isp_2921_01XX.dat", 0xed6c},
+	[ISP_IMX614_2921_02] = {0x614, 0x29210202, "apple/isp_2921_02XX.dat", 0xed6c},
+	[ISP_IMX614_2922_02] = {0x614, 0x29220201, "apple/isp_2922_02XX.dat", 0xed6c},
+	[ISP_IMX633_3622_01] = {0x633, 0x36220111, "apple/isp_3622_01XX.dat", 0x100d4},
+	[ISP_IMX703_7721_01] = {0x703, 0x77210106, "apple/isp_7721_01XX.dat", 0x936c},
+	[ISP_IMX703_7722_01] = {0x703, 0x77220106, "apple/isp_7722_01XX.dat", 0xac20},
+	[ISP_IMX713_4721_01] = {0x713, 0x47210107, "apple/isp_4721_01XX.dat", 0x936c},
+	[ISP_IMX713_4722_01] = {0x713, 0x47220109, "apple/isp_4722_01XX.dat", 0x9218},
+	[ISP_IMX714_2022_01] = {0x714, 0x20220107, "apple/isp_2022_01XX.dat", 0xa198},
+	[ISP_IMX772_3721_01] = {0x772, 0x37210106, "apple/isp_3721_01XX.dat", 0xfdf8},
+	[ISP_IMX772_3721_11] = {0x772, 0x37211106, "apple/isp_3721_11XX.dat", 0xfe14},
+	[ISP_IMX772_3722_01] = {0x772, 0x37220104, "apple/isp_3722_01XX.dat", 0xfca4},
+	[ISP_IMX772_3723_01] = {0x772, 0x37230106, "apple/isp_3723_01XX.dat", 0xfca4},
+	[ISP_IMX814_2123_01] = {0x814, 0x21230101, "apple/isp_2123_01XX.dat", 0xed54},
+	[ISP_IMX853_7622_01] = {0x853, 0x76220112, "apple/isp_7622_01XX.dat", 0x247f8},
+	[ISP_IMX913_7523_01] = {0x913, 0x75230107, "apple/isp_7523_01XX.dat", 0x247f8},
+	[ISP_VD56G0_6221_01] = {0xd56, 0x62210102, "apple/isp_6221_01XX.dat", 0x1b80},
+	[ISP_VD56G0_6222_01] = {0xd56, 0x62220102, "apple/isp_6222_01XX.dat", 0x1b80},
 };
 // clang-format on
 
@@ -182,125 +166,69 @@ static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch)
 	return err;
 }
 
-static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
+static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps)
 {
-	struct isp_format *fmt = isp_get_format(isp, ch);
 	int err = 0;
 
-	struct cmd_ch_info *args; /* Too big to allocate on stack */
+	struct cmd_ch_camera_config *args; /* Too big to allocate on stack */
 	args = kzalloc(sizeof(*args), GFP_KERNEL);
 	if (!args)
 		return -ENOMEM;
 
-	err = isp_cmd_ch_info_get(isp, ch, args);
+	err = isp_cmd_ch_camera_config_get(isp, ch, ps, args);
 	if (err)
 		goto exit;
 
-	dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version,
-		 args->module_sn, ch);
-
-	fmt->version = args->version;
-	fmt->num_presets = args->num_presets;
-
-	pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch);
-	print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4,
+	pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps);
+	print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4,
 		       args, sizeof(*args), false);
 
-	err = isp_ch_get_sensor_id(isp, ch);
-	if (err || (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) {
-		dev_err(isp->dev,
-			"ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n",
-			ch);
-		return -ENODEV;
-	}
-
 exit:
 	kfree(args);
 
 	return err;
 }
 
-static int isp_ch_get_camera_preset(struct apple_isp *isp, u32 ch, u32 ps)
+static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
 {
+	struct isp_format *fmt = isp_get_format(isp, ch);
 	int err = 0;
 
-	struct cmd_ch_camera_config *args; /* Too big to allocate on stack */
+	struct cmd_ch_info *args; /* Too big to allocate on stack */
 	args = kzalloc(sizeof(*args), GFP_KERNEL);
 	if (!args)
 		return -ENOMEM;
 
-	err = isp_cmd_ch_camera_config_get(isp, ch, ps, args);
+	err = isp_cmd_ch_info_get(isp, ch, args);
 	if (err)
 		goto exit;
 
-	pr_info("apple-isp: ps: CISP_CMD_CH_CAMERA_CONFIG_GET: %d\n", ps);
-	print_hex_dump(KERN_INFO, "apple-isp: ps: ", DUMP_PREFIX_NONE, 32, 4,
-		       args, sizeof(*args), false);
+	dev_info(isp->dev, "found sensor %x %s on ch %d\n", args->version,
+		 args->module_sn, ch);
 
-exit:
-	kfree(args);
+	fmt->version = args->version;
 
-	return err;
-}
+	pr_info("apple-isp: ch: CISP_CMD_CH_INFO_GET: %d\n", ch);
+	print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4,
+		       args, sizeof(*args), false);
 
-static void isp_ch_dump_camera_presets(struct apple_isp *isp, u32 ch)
-{
-	struct isp_format *fmt = isp_get_format(isp, ch);
-	for (u32 ps = 0; ps < fmt->num_presets; ps++) {
-		isp_ch_get_camera_preset(isp, ch, ps);
+	err = isp_ch_get_sensor_id(isp, ch);
+	if (err ||
+	    (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) {
+		dev_err(isp->dev,
+			"ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n",
+			ch);
+		return -ENODEV;
 	}
-}
-
-static int isp_ch_cache_camera_preset(struct apple_isp *isp, u32 ch)
-{
-	struct isp_format *fmt = isp_get_format(isp, ch);
-	const struct isp_preset *preset = &isp_presets[fmt->id];
-	size_t total_size;
-
-	isp_ch_dump_camera_presets(isp, ch);
-
-	fmt->preset = preset->index;
-
-	fmt->width = preset->width;
-	fmt->height = preset->height;
-
-	fmt->x1 = preset->x1;
-	fmt->y1 = preset->y1;
-	fmt->x2 = preset->x2;
-	fmt->y2 = preset->y2;
-
-	/* I really fucking hope they all use NV12. */
-	fmt->num_planes = 2;
-	fmt->plane_size[0] = fmt->width * fmt->height;
-	fmt->plane_size[1] = fmt->plane_size[0] / 2;
-
-	total_size = 0;
-	for (int i = 0; i < fmt->num_planes; i++)
-		total_size += fmt->plane_size[i];
-	fmt->total_size = total_size;
-
-	return 0;
-}
-
-static int isp_ch_cache_camera_info(struct apple_isp *isp, u32 ch)
-{
-	int err;
 
-	err = isp_ch_cache_sensor_info(isp, ch);
-	if (err) {
-		dev_err(isp->dev, "ch %d: failed to cache sensor info: %d\n",
-			ch, err);
-		return err;
+	for (u32 ps = 0; ps < args->num_presets; ps++) {
+		isp_ch_get_camera_preset(isp, ch, ps);
 	}
 
-	err = isp_ch_cache_camera_preset(isp, ch);
-	if (err) {
-		dev_err(isp->dev, "ch %d: failed to cache camera preset: %d\n",
-			ch, err);
-		return err;
-	}
+exit:
+	kfree(args);
 
-	return 0;
+	return err;
 }
 
 static int isp_detect_camera(struct apple_isp *isp)
@@ -338,7 +266,13 @@ static int isp_detect_camera(struct apple_isp *isp)
 	isp->num_channels = args.num_channels;
 	isp->current_ch = 0;
 
-	return isp_ch_cache_camera_info(isp, isp->current_ch); /* I told you */
+	err = isp_ch_cache_sensor_info(isp, isp->current_ch);
+	if (err) {
+		dev_err(isp->dev, "failed to cache sensor info\n");
+		return err;
+	}
+
+	return 0;
 }
 
 int apple_isp_detect_camera(struct apple_isp *isp)
@@ -408,6 +342,12 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 			err);
 	}
 
+	if (isp->hw->gen >= ISP_GEN_T8112) {
+		err = isp_cmd_ch_lpdp_hs_receiver_tuning_set(isp, ch, 1, 15);
+		if (err)
+			return err;
+	}
+
 	err = isp_cmd_ch_sbs_enable(isp, ch, 1);
 	if (err)
 		return err;
@@ -421,17 +361,21 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset);
+	err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_crop_set(isp, ch, fmt->x1, fmt->y1, fmt->x2, fmt->y2);
+	err = isp_cmd_ch_crop_set(isp, ch, fmt->preset->crop_offset.x,
+				  fmt->preset->crop_offset.y,
+				  fmt->preset->crop_size.x,
+				  fmt->preset->crop_size.y);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_output_config_set(isp, ch, fmt->width, fmt->height,
-					   CISP_COLORSPACE_REC709,
-					   CISP_OUTPUT_FORMAT_NV12);
+	err = isp_cmd_ch_output_config_set(isp, ch, fmt->preset->output_dim.x,
+					   fmt->preset->output_dim.y,
+					   fmt->strides, CISP_COLORSPACE_REC709,
+					   CISP_OUTPUT_FORMAT_YUV_2PLANE);
 	if (err)
 		return err;
 
@@ -443,7 +387,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_mbnr_enable(isp, ch, 0, 1, 1);
+	err = isp_cmd_ch_mbnr_enable(isp, ch, 0, ISP_MBNR_MODE_ENABLE, 1);
 	if (err)
 		return err;
 
diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 79ffb2b1c33881..1e812400e52f7d 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -119,6 +119,17 @@ int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0,
 	return CISP_SEND_IN(isp, args);
 }
 
+int isp_cmd_set_dsid_clr_req_base(struct apple_isp *isp, u64 dsid_clr_base,
+				  u32 dsid_clr_range)
+{
+	struct cmd_set_dsid_clr_req_base args = {
+		.opcode = CISP_OPCODE(CISP_CMD_SET_DSID_CLR_REG_BASE),
+		.dsid_clr_base = dsid_clr_base,
+		.dsid_clr_range = dsid_clr_range,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
 int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch,
 			 u64 clock_base, u8 clock_bit, u8 clock_size,
 			 u64 bandwidth_scratch, u64 bandwidth_base,
@@ -218,16 +229,26 @@ int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan)
 	return CISP_SEND_IN(isp, args);
 }
 
-int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr,
+int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr,
 			     u32 size)
 {
-	struct cmd_ch_set_file_load args = {
-		.opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD),
-		.chan = chan,
-		.addr = addr,
-		.size = size,
-	};
-	return CISP_SEND_IN(isp, args);
+	if (isp->hw->gen >= ISP_GEN_T8112) {
+		struct cmd_ch_set_file_load64 args = {
+			.opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD),
+			.chan = chan,
+			.addr = addr,
+			.size = size,
+		};
+		return CISP_SEND_IN(isp, args);
+	} else {
+		struct cmd_ch_set_file_load args = {
+			.opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD),
+			.chan = chan,
+			.addr = addr,
+			.size = size,
+		};
+		return CISP_SEND_IN(isp, args);
+	}
 }
 
 int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable)
@@ -244,7 +265,8 @@ int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
 			u32 y2)
 {
 	struct cmd_ch_crop_set args = {
-		.opcode = CISP_OPCODE(CISP_CMD_CH_CROP_SET),
+		.opcode = CISP_OPCODE(isp->hw->scl1 ? CISP_CMD_CH_CROP_SCL1_SET
+				      : CISP_CMD_CH_CROP_SET),
 		.chan = chan,
 		.x1 = x1,
 		.y1 = y1,
@@ -255,23 +277,22 @@ int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
 }
 
 int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
-				 u32 height, u32 colorspace, u32 format)
+				 u32 height, u32 strides[3], u32 colorspace, u32 format)
 {
 	struct cmd_ch_output_config_set args = {
-		.opcode = CISP_OPCODE(CISP_CMD_CH_OUTPUT_CONFIG_SET),
+		.opcode = CISP_OPCODE(isp->hw->scl1 ? CISP_CMD_CH_OUTPUT_CONFIG_SCL1_SET
+				      : CISP_CMD_CH_OUTPUT_CONFIG_SET),
 		.chan = chan,
 		.width = width,
 		.height = height,
 		.colorspace = colorspace,
 		.format = format,
-		.unk_w0 = width,
-		.unk_w1 = width,
-		.unk_24 = 0,
 		.padding_rows = 0,
 		.unk_h0 = height,
 		.compress = 0,
 		.unk_w2 = width,
 	};
+	memcpy(args.strides, strides, sizeof(args.strides));
 	return CISP_SEND_IN(isp, args);
 }
 
@@ -356,12 +377,14 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type)
 		.chan = chan,
 		.type = type,
 		.count = 16,
-		.meta_size0 = ISP_META_SIZE,
-		.meta_size1 = ISP_META_SIZE,
+		.meta_size0 = isp->hw->meta_size,
+		.meta_size1 = isp->hw->meta_size,
+		.unk0 = 0,
+		.unk1 = 0,
+		.unk2 = 0,
 		.data_blocks = 1,
 		.compress = 0,
 	};
-	memset(args.zero, 0, sizeof(u32) * 0x1f);
 	return CISP_SEND_INOUT(isp, args);
 }
 
@@ -542,3 +565,40 @@ int isp_cmd_ch_semantic_awb_enable(struct apple_isp *isp, u32 chan, u32 enable)
 	};
 	return CISP_SEND_IN(isp, args);
 }
+
+int isp_cmd_ch_lpdp_hs_receiver_tuning_set(struct apple_isp *isp, u32 chan, u32 unk1, u32 unk2)
+{
+	struct cmd_ch_lpdp_hs_receiver_tuning_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_LPDP_HS_RECEIVER_TUNING_SET),
+		.chan = chan,
+		.unk1 = unk1,
+		.unk2 = unk2,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_property_write(struct apple_isp *isp, u32 chan, u32 prop, u32 val)
+{
+	struct cmd_ch_property_write args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_PROPERTY_WRITE),
+		.chan = chan,
+		.prop = prop,
+		.val = val,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_ch_property_read(struct apple_isp *isp, u32 chan, u32 prop, u32 *val)
+{
+	struct cmd_ch_property_write args = {
+		.opcode = CISP_OPCODE(CISP_CMD_CH_PROPERTY_READ),
+		.chan = chan,
+		.prop = prop,
+		.val = 0xdeadbeef,
+	};
+	int ret = CISP_SEND_OUT(isp, &args);
+
+	*val = args.val;
+
+	return ret;
+}
diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
index 1fc484fa687853..1586df89f1cdab 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.h
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -35,10 +35,14 @@
 #define CISP_CMD_CH_BUFFER_POOL_CONFIG_SET		     0x0117
 #define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_GET		     0x011a
 #define CISP_CMD_CH_CAMERA_PIX_FREQUENCY_GET		     0x011f
+#define CISP_CMD_CH_PROPERTY_WRITE			     0x0122
+#define CISP_CMD_CH_PROPERTY_READ			     0x0123
 #define CISP_CMD_CH_LOCAL_RAW_BUFFER_ENABLE		     0x0125
+#define CISP_CMD_CH_META_DATA_ENABLE			     0x0126
 #define CISP_CMD_CH_CAMERA_MIPI_FREQUENCY_TOTAL_GET	     0x0133
 #define CISP_CMD_CH_SBS_ENABLE				     0x013b
 #define CISP_CMD_CH_LSC_POLYNOMIAL_COEFF_GET		     0x0142
+#define CISP_CMD_CH_SET_META_DATA_REQUIRED		     0x014f
 #define CISP_CMD_CH_BUFFER_POOL_RETURN			     0x015b
 #define CISP_CMD_CH_CAMERA_AGILE_FREQ_ARRAY_CURRENT_GET	     0x015e
 #define CISP_CMD_CH_AE_START				     0x0200
@@ -52,25 +56,35 @@
 #define CISP_CMD_CH_SENSOR_NVM_GET			     0x0501
 #define CISP_CMD_CH_SENSOR_PERMODULE_LSC_INFO_GET	     0x0507
 #define CISP_CMD_CH_SENSOR_PERMODULE_LSC_GRID_GET	     0x0511
+#define CISP_CMD_CH_LPDP_HS_RECEIVER_TUNING_SET		     0x051b
 #define CISP_CMD_CH_FOCUS_LIMITS_GET			     0x0701
+#define CISP_CMD_CH_CROP_GET				     0x0800
 #define CISP_CMD_CH_CROP_SET				     0x0801
+#define CISP_CMD_CH_SCALER_CROP_SET			     0x080a
+#define CISP_CMD_CH_CROP_SCL1_GET			     0x080b
+#define CISP_CMD_CH_CROP_SCL1_SET			     0x080c
+#define CISP_CMD_CH_SCALER_CROP_SCL1_SET		     0x080d
 #define CISP_CMD_CH_ALS_ENABLE				     0x0a1c
 #define CISP_CMD_CH_ALS_DISABLE				     0x0a1d
 #define CISP_CMD_CH_CNR_START				     0x0a2f
 #define CISP_CMD_CH_MBNR_ENABLE				     0x0a3a
 #define CISP_CMD_CH_OUTPUT_CONFIG_SET			     0x0b01
+#define CISP_CMD_CH_OUTPUT_CONFIG_SCL1_SET		     0x0b09
 #define CISP_CMD_CH_PREVIEW_STREAM_SET			     0x0b0d
 #define CISP_CMD_CH_SEMANTIC_VIDEO_ENABLE		     0x0b17
 #define CISP_CMD_CH_SEMANTIC_AWB_ENABLE			     0x0b18
 #define CISP_CMD_CH_FACE_DETECTION_START		     0x0d00
+#define CISP_CMD_CH_FACE_DETECTION_STOP			     0x0d01
 #define CISP_CMD_CH_FACE_DETECTION_CONFIG_GET		     0x0d02
 #define CISP_CMD_CH_FACE_DETECTION_CONFIG_SET		     0x0d03
+#define CISP_CMD_CH_FACE_DETECTION_DISABLE		     0x0d04
 #define CISP_CMD_CH_FACE_DETECTION_ENABLE		     0x0d05
 #define CISP_CMD_CH_FID_START				     0x3000
 #define CISP_CMD_CH_FID_STOP				     0x3001
 #define CISP_CMD_IPC_ENDPOINT_SET2			     0x300c
 #define CISP_CMD_IPC_ENDPOINT_UNSET2			     0x300d
 #define CISP_CMD_SET_DSID_CLR_REG_BASE2			     0x3204
+#define CISP_CMD_SET_DSID_CLR_REG_BASE			     0x3205
 #define CISP_CMD_APPLE_CH_AE_METERING_MODE_SET		     0x8206
 #define CISP_CMD_APPLE_CH_AE_FD_SCENE_METERING_CONFIG_SET    0x820e
 #define CISP_CMD_APPLE_CH_AE_FLICKER_FREQ_UPDATE_CURRENT_SET 0x8212
@@ -86,10 +100,28 @@
 #define CISP_POOL_TYPE_FD				     0x2
 #define CISP_POOL_TYPE_RAW				     0x3
 #define CISP_POOL_TYPE_STAT				     0x4
+#define CISP_POOL_TYPE_RAW_AUX				     0x5
+#define CISP_POOL_TYPE_YCC				     0x6
+#define CISP_POOL_TYPE_CAPTURE_FULL_RES			     0x7
 #define CISP_POOL_TYPE_META_CAPTURE			     0x8
+#define CISP_POOL_TYPE_RENDERED_SCL1			     0x9
+#define CISP_POOL_TYPE_STAT_PIXELOUTPUT			     0x11
+#define CISP_POOL_TYPE_FSCL				     0x12
+#define CISP_POOL_TYPE_CAPTURE_FULL_RES_YCC		     0x13
+#define CISP_POOL_TYPE_RENDERED_RAW			     0x14
+#define CISP_POOL_TYPE_CAPTURE_PDC_RAW			     0x16
+#define CISP_POOL_TYPE_FPC_DATA				     0x17
+#define CISP_POOL_TYPE_AICAM_SEG			     0x19
+#define CISP_POOL_TYPE_SPD				     0x1a
+#define CISP_POOL_TYPE_META_DEPTH			     0x1c
+#define CISP_POOL_TYPE_JASPER_DEPTH			     0x1d
+#define CISP_POOL_TYPE_RAW_SIFR				     0x1f
+#define CISP_POOL_TYPE_FEP_THUMBNAIL_DYNAMIC_POOL_RAW	     0x21
 
 #define CISP_COLORSPACE_REC709				     0x1
-#define CISP_OUTPUT_FORMAT_NV12				     0x0
+#define CISP_OUTPUT_FORMAT_YUV_2PLANE			     0x0
+#define CISP_OUTPUT_FORMAT_YUV_1PLANE			     0x1
+#define CISP_OUTPUT_FORMAT_RGB				     0x2
 #define CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY		     0x1
 
 struct cmd_start {
@@ -144,6 +176,13 @@ struct cmd_set_dsid_clr_req_base2 {
 } __packed;
 static_assert(sizeof(struct cmd_set_dsid_clr_req_base2) == 0x38);
 
+struct cmd_set_dsid_clr_req_base {
+	u64 opcode;
+	u64 dsid_clr_base;
+	u32 dsid_clr_range;
+} __packed;
+static_assert(sizeof(struct cmd_set_dsid_clr_req_base) == 0x14);
+
 struct cmd_pmp_ctrl_set {
 	u64 opcode;
 	u64 clock_scratch;
@@ -169,12 +208,26 @@ struct cmd_fid_exit {
 } __packed;
 static_assert(sizeof(struct cmd_fid_exit) == 0x8);
 
+struct cmd_ipc_endpoint_set2 {
+	u64 opcode;
+	u32 unk;
+	u64 addr1;
+	u32 size1;
+	u64 addr2;
+	u32 size2;
+	u64 regs;
+	u32 unk2;
+} __packed;
+static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30);
+
 int isp_cmd_start(struct apple_isp *isp, u32 mode);
 int isp_cmd_suspend(struct apple_isp *isp);
 int isp_cmd_print_enable(struct apple_isp *isp, u32 enable);
 int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable);
 int isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args);
 int isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base);
+int isp_cmd_set_dsid_clr_req_base(struct apple_isp *isp, u64 dsid_clr_base,
+				  u32 dsid_clr_range);
 int isp_cmd_set_dsid_clr_req_base2(struct apple_isp *isp, u64 dsid_clr_base0,
 				   u64 dsid_clr_base1, u64 dsid_clr_base2,
 				   u64 dsid_clr_base3, u32 dsid_clr_range0,
@@ -291,6 +344,14 @@ struct cmd_ch_set_file_load {
 } __packed;
 static_assert(sizeof(struct cmd_ch_set_file_load) == 0x14);
 
+struct cmd_ch_set_file_load64 {
+	u64 opcode;
+	u32 chan;
+	u64 addr;
+	u32 size;
+} __packed;
+static_assert(sizeof(struct cmd_ch_set_file_load64) == 0x18);
+
 struct cmd_ch_buffer_return {
 	u64 opcode;
 	u32 chan;
@@ -321,9 +382,7 @@ struct cmd_ch_output_config_set {
 	u32 height;
 	u32 colorspace;
 	u32 format;
-	u32 unk_w0;
-	u32 unk_w1;
-	u32 unk_24;
+	u32 strides[3];
 	u32 padding_rows;
 	u32 unk_h0;
 	u32 compress;
@@ -369,6 +428,24 @@ struct cmd_ch_sif_pixel_format_set {
 } __packed;
 static_assert(sizeof(struct cmd_ch_sif_pixel_format_set) == 0x14);
 
+struct cmd_ch_lpdp_hs_receiver_tuning_set {
+	u64 opcode;
+	u32 chan;
+	u32 unk1;
+	u32 unk2;
+} __packed;
+static_assert(sizeof(struct cmd_ch_lpdp_hs_receiver_tuning_set) == 0x14);
+
+struct cmd_ch_property_write {
+	u64 opcode;
+	u32 chan;
+	u32 prop;
+	u32 val;
+	u32 unk1;
+	u32 unk2;
+} __packed;
+static_assert(sizeof(struct cmd_ch_property_write) == 0x1c);
+
 int isp_cmd_ch_start(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan,
@@ -379,20 +456,30 @@ int isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan,
 					 struct cmd_ch_camera_config *args);
 int isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan,
 				    u32 preset);
-int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u32 addr,
+int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr,
 			     u32 size);
 int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable);
 int isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
 			u32 y2);
 int isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
-				 u32 height, u32 colorspace, u32 format);
+				 u32 height, u32 strides[3], u32 colorspace, u32 format);
 int isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream);
 int isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case,
 			   u32 mode, u32 enable_chroma);
 int isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan);
+int isp_cmd_ch_lpdp_hs_receiver_tuning_set(struct apple_isp *isp, u32 chan, u32 unk1, u32 unk2);
+
+int isp_cmd_ch_property_read(struct apple_isp *isp, u32 chan, u32 prop, u32 *val);
+int isp_cmd_ch_property_write(struct apple_isp *isp, u32 chan, u32 prop, u32 val);
+
+enum isp_mbnr_mode {
+	ISP_MBNR_MODE_DISABLE = 0,
+	ISP_MBNR_MODE_ENABLE = 1,
+	ISP_MBNR_MODE_BYPASS = 2,
+};
 
 struct cmd_ch_buffer_recycle_mode_set {
 	u64 opcode;
@@ -414,7 +501,10 @@ struct cmd_ch_buffer_pool_config_set {
 	u16 count;
 	u32 meta_size0;
 	u32 meta_size1;
-	u32 zero[0x1f];
+	u64 unk0;
+	u64 unk1;
+	u64 unk2;
+	u32 zero[0x19];
 	u32 data_blocks;
 	u32 compress;
 } __packed;
@@ -431,6 +521,8 @@ int isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan,
 int isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan);
 int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan,
 				      u16 type);
+int isp_cmd_ch_buffer_pool_config_get(struct apple_isp *isp, u32 chan,
+				      u16 type);
 int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan);
 
 struct cmd_apple_ch_temporal_filter_start {
diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 91dd1cb607076b..3dad32f2b58dbf 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -90,7 +90,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 		return -ENODEV;
 	isp->shift = __ffs(isp->domain->pgsize_bitmap);
 
-	idx = of_property_match_string(dev->of_node, "memory-region-names", "heap");
+	idx = of_property_match_string(dev->of_node, "memory-region-names",
+				       "heap");
 	mem_node = of_parse_phandle(dev->of_node, "memory-region", idx);
 	if (!mem_node) {
 		dev_err(dev, "No memory-region found for heap\n");
@@ -107,11 +108,10 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 
 	while (maps < end) {
 		maps++;
-		maps = of_translate_dma_region(dev->of_node, maps, &heap_base, &heap_size);
+		maps = of_translate_dma_region(dev->of_node, maps, &heap_base,
+					       &heap_size);
 	}
 
-	printk("heap: 0x%llx 0x%lx\n", heap_base, heap_size);
-
 	isp->fw.heap_top = heap_base + heap_size;
 
 	err = of_property_read_u64(dev->of_node, "apple,dart-vm-size",
@@ -122,7 +122,8 @@ static int apple_isp_init_iommu(struct apple_isp *isp)
 	}
 
 	// FIXME: refactor this, maybe use regular iova stuff?
-	drm_mm_init(&isp->iovad, isp->fw.heap_top, vm_size - (heap_base & 0xffffffff));
+	drm_mm_init(&isp->iovad, isp->fw.heap_top,
+		    vm_size - (heap_base & 0xffffffff));
 
 	return 0;
 }
@@ -132,6 +133,92 @@ static void apple_isp_free_iommu(struct apple_isp *isp)
 	drm_mm_takedown(&isp->iovad);
 }
 
+/* NOTE: of_node_put()s the OF node on failure. */
+static int isp_of_read_coord(struct device *dev, struct device_node *np,
+			     const char *prop, struct coord *val)
+{
+	u32 xy[2];
+	int ret;
+
+	ret = of_property_read_u32_array(np, prop, xy, 2);
+	if (ret) {
+		dev_err(dev, "failed to read '%s' property\n", prop);
+		of_node_put(np);
+		return ret;
+	}
+
+	val->x = xy[0];
+	val->y = xy[1];
+	return 0;
+}
+
+static int apple_isp_init_presets(struct apple_isp *isp)
+{
+	struct device *dev = isp->dev;
+	struct device_node *np, *child;
+	struct isp_preset *preset;
+	int err = 0;
+
+	np = of_get_child_by_name(dev->of_node, "sensor-presets");
+	if (!np) {
+		dev_err(dev, "failed to get DT node 'presets'\n");
+		return -EINVAL;
+	}
+
+	isp->num_presets = of_get_child_count(np);
+	if (!isp->num_presets) {
+		dev_err(dev, "no sensor presets found\n");
+		err = -EINVAL;
+		goto err;
+	}
+
+	isp->presets = devm_kzalloc(
+		dev, sizeof(*isp->presets) * isp->num_presets, GFP_KERNEL);
+	if (!isp->presets) {
+		err = -ENOMEM;
+		goto err;
+	}
+
+	preset = isp->presets;
+	for_each_child_of_node(np, child) {
+		u32 xywh[4];
+
+		err = of_property_read_u32(child, "apple,config-index",
+					   &preset->index);
+		if (err) {
+			dev_err(dev, "no apple,config-index property\n");
+			of_node_put(child);
+			goto err;
+		}
+
+		err = isp_of_read_coord(dev, child, "apple,input-size",
+					&preset->input_dim);
+		if (err)
+			goto err;
+		err = isp_of_read_coord(dev, child, "apple,output-size",
+					&preset->output_dim);
+		if (err)
+			goto err;
+
+		err = of_property_read_u32_array(child, "apple,crop", xywh, 4);
+		if (err) {
+			dev_err(dev, "failed to read 'apple,crop' property\n");
+			of_node_put(child);
+			goto err;
+		}
+		preset->crop_offset.x = xywh[0];
+		preset->crop_offset.y = xywh[1];
+		preset->crop_size.x = xywh[2];
+		preset->crop_size.y = xywh[3];
+
+		preset++;
+	}
+
+err:
+	of_node_put(np);
+	return err;
+}
+
 static int apple_isp_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -151,6 +238,20 @@ static int apple_isp_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, isp);
 	dev_set_drvdata(dev, isp);
 
+	err = of_property_read_u32(dev->of_node, "apple,platform-id",
+				   &isp->platform_id);
+	if (err) {
+		dev_err(dev, "failed to get 'apple,platform-id' property: %d\n",
+			err);
+		return err;
+	}
+
+	err = apple_isp_init_presets(isp);
+	if (err) {
+		dev_err(dev, "failed to initialize presets\n");
+		return err;
+	}
+
 	err = apple_isp_attach_genpd(isp);
 	if (err) {
 		dev_err(dev, "failed to attatch power domains\n");
@@ -190,7 +291,8 @@ static int apple_isp_probe(struct platform_device *pdev)
 	spin_lock_init(&isp->buf_lock);
 	init_waitqueue_head(&isp->wait);
 	INIT_LIST_HEAD(&isp->gc);
-	INIT_LIST_HEAD(&isp->buffers);
+	INIT_LIST_HEAD(&isp->bufs_pending);
+	INIT_LIST_HEAD(&isp->bufs_submitted);
 	isp->wq = alloc_workqueue("apple-isp-wq", WQ_UNBOUND, 0);
 	if (!isp->wq) {
 		dev_err(dev, "failed to create workqueue\n");
@@ -250,13 +352,13 @@ static void apple_isp_remove(struct platform_device *pdev)
 	apple_isp_free_iommu(isp);
 	destroy_workqueue(isp->wq);
 	apple_isp_detach_genpd(isp);
-	return 0;
 }
 
 static const struct apple_isp_hw apple_isp_hw_t8103 = {
-	.platform_id = 0x1,
+	.gen = ISP_GEN_T8103,
 	.pmu_base = 0x23b704000,
 
+	.dsid_count = 4,
 	.dsid_clr_base0 = 0x200014000,
 	.dsid_clr_base1 = 0x200054000,
 	.dsid_clr_base2 = 0x200094000,
@@ -274,12 +376,16 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = {
 	.bandwidth_base = 0x23bc3c000,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x4,
+
+	.scl1 = false,
+	.meta_size = ISP_META_SIZE_T8103,
 };
 
 static const struct apple_isp_hw apple_isp_hw_t6000 = {
-	.platform_id = 0x3,
+	.gen = ISP_GEN_T8103,
 	.pmu_base = 0x28e584000,
 
+	.dsid_count = 1,
 	.dsid_clr_base0 = 0x200014000,
 	.dsid_clr_base1 = 0x200054000,
 	.dsid_clr_base2 = 0x200094000,
@@ -297,12 +403,16 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = {
 	.bandwidth_base = 0x0,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
+
+	.scl1 = false,
+	.meta_size = ISP_META_SIZE_T8103,
 };
 
 static const struct apple_isp_hw apple_isp_hw_t8110 = {
-	.platform_id = 0xe, // J413AP
+	.gen = ISP_GEN_T8112,
 	.pmu_base = 0x23b704000,
 
+	.dsid_count = 4,
 	.dsid_clr_base0 = 0x200014000, // TODO
 	.dsid_clr_base1 = 0x200054000,
 	.dsid_clr_base2 = 0x200094000,
@@ -320,29 +430,30 @@ static const struct apple_isp_hw apple_isp_hw_t8110 = {
 	.bandwidth_base = 0x0,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
+
+	.scl1 = true,
+	.meta_size = ISP_META_SIZE_T8112,
 };
 
 static const struct apple_isp_hw apple_isp_hw_t6020 = {
-	.platform_id = 0x7, // J416cAP
+	.gen = ISP_GEN_T8112,
 	.pmu_base = 0x290284000,
 
-	.dsid_clr_base0 = 0x200014000, // TODO
-	.dsid_clr_base1 = 0x200054000,
-	.dsid_clr_base2 = 0x200094000,
-	.dsid_clr_base3 = 0x2000d4000,
+	.dsid_count = 1,
+	.dsid_clr_base0 = 0x200f14000,
 	.dsid_clr_range0 = 0x1000,
-	.dsid_clr_range1 = 0x1000,
-	.dsid_clr_range2 = 0x1000,
-	.dsid_clr_range3 = 0x1000,
 
-	.clock_scratch = 0x28e3d0868, // CHECK
+	.clock_scratch = 0x28e3d10a8,
 	.clock_base = 0x0,
 	.clock_bit = 0x0,
 	.clock_size = 0x8,
-	.bandwidth_scratch = 0x28e3d0980, // CHECK
+	.bandwidth_scratch = 0x28e3d1200,
 	.bandwidth_base = 0x0,
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
+
+	.scl1 = true,
+	.meta_size = ISP_META_SIZE_T8112,
 };
 
 static const struct of_device_id apple_isp_of_match[] = {
@@ -362,7 +473,8 @@ static __maybe_unused int apple_isp_resume(struct device *dev)
 {
 	return 0;
 }
-DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume, NULL);
+DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume,
+			  NULL);
 
 static struct platform_driver apple_isp_driver = {
 	.driver	= {
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index e672c62c0ec41c..926c921849544a 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -20,7 +20,13 @@
 #define ISP_MAX_CHANNELS      6
 #define ISP_IPC_MESSAGE_SIZE  64
 #define ISP_IPC_FLAG_ACK      0x1
-#define ISP_META_SIZE	      0x4640
+#define ISP_META_SIZE_T8103      0x4640
+#define ISP_META_SIZE_T8112      0x4840
+
+enum isp_generation {
+	ISP_GEN_T8103,
+	ISP_GEN_T8112,
+};
 
 struct isp_surf {
 	struct drm_mm_node *mm;
@@ -62,10 +68,24 @@ struct isp_channel {
 	const struct isp_chan_ops *ops;
 };
 
+struct coord {
+	u32 x;
+	u32 y;
+};
+
+struct isp_preset {
+	u32 index;
+	struct coord input_dim;
+	struct coord output_dim;
+	struct coord crop_offset;
+	struct coord crop_size;
+};
+
 struct apple_isp_hw {
-	u32 platform_id;
+	enum isp_generation gen;
 	u64 pmu_base;
 
+	int dsid_count;
 	u64 dsid_clr_base0;
 	u64 dsid_clr_base1;
 	u64 dsid_clr_base2;
@@ -83,6 +103,9 @@ struct apple_isp_hw {
 	u64 bandwidth_base;
 	u8 bandwidth_bit;
 	u8 bandwidth_size;
+
+	u32 meta_size;
+	bool scl1;
 };
 
 enum isp_sensor_id {
@@ -139,15 +162,9 @@ enum isp_sensor_id {
 struct isp_format {
 	enum isp_sensor_id id;
 	u32 version;
-	u32 num_presets;
-	u32 preset;
-	u32 width;
-	u32 height;
-	u32 x1;
-	u32 y1;
-	u32 x2;
-	u32 y2;
+	struct isp_preset *preset;
 	unsigned int num_planes;
+	u32 strides[VB2_MAX_PLANES];
 	size_t plane_size[VB2_MAX_PLANES];
 	size_t total_size;
 };
@@ -155,6 +172,9 @@ struct isp_format {
 struct apple_isp {
 	struct device *dev;
 	const struct apple_isp_hw *hw;
+	u32 platform_id;
+	struct isp_preset *presets;
+	int num_presets;
 
 	int num_channels;
 	struct isp_format fmts[ISP_MAX_CHANNELS];
@@ -208,7 +228,8 @@ struct apple_isp {
 
 	unsigned long state;
 	spinlock_t buf_lock;
-	struct list_head buffers;
+	struct list_head bufs_pending;
+	struct list_head bufs_submitted;
 };
 
 struct isp_chan_ops {
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 70ffaa97cd260a..972867a93a0193 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -46,7 +46,10 @@ struct isp_firmware_bootargs {
 	u64 extra_iova;
 	u64 extra_size;
 	u32 platform_id;
-	u32 pad_40[7];
+	u32 pad_40;
+	u64 logbuf_addr;
+	u64 logbuf_size;
+	u64 logbuf_entsize;
 	u32 ipc_size;
 	u32 pad_60[5];
 	u32 unk5;
@@ -279,9 +282,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.shared_size = 0x10000000UL - args.shared_base;
 	args.extra_iova = isp->extra_surf->iova;
 	args.extra_size = isp->extra_surf->size;
-	args.platform_id = isp->hw->platform_id;
+	args.platform_id = isp->platform_id;
 	args.unk5 = 0x40;
-	args.unk7 = 0x1;
+	args.unk7 = 0x1; // 0?
 	args.unk_iova1 = args_iova + sizeof(args) - 0xc;
 	args.unk9 = 0x3;
 	isp_iowrite(isp, args_iova, &args, sizeof(args));
@@ -501,13 +504,20 @@ static int isp_start_command_processor(struct apple_isp *isp)
 	if (err)
 		return err;
 
-	err = isp_cmd_set_dsid_clr_req_base2(
-		isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1,
-		isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3,
-		isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1,
-		isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3);
-	if (err)
-		return err;
+	if (isp->hw->dsid_count == 1) {
+		err = isp_cmd_set_dsid_clr_req_base(
+			isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_range0);
+		if (err)
+			return err;
+	} else {
+		err = isp_cmd_set_dsid_clr_req_base2(
+			isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_base1,
+			isp->hw->dsid_clr_base2, isp->hw->dsid_clr_base3,
+			isp->hw->dsid_clr_range0, isp->hw->dsid_clr_range1,
+			isp->hw->dsid_clr_range2, isp->hw->dsid_clr_range3);
+		if (err)
+			return err;
+	}
 
 	err = isp_cmd_pmp_ctrl_set(
 		isp, isp->hw->clock_scratch, isp->hw->clock_base,
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index a9a0fdb73a4d9f..14249a44798ba5 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -230,8 +230,8 @@ static void sm_malloc_deferred_worker(struct work_struct *work)
 	}
 
 #ifdef APPLE_ISP_DEBUG
-	/* Only enabled in debug builds so it shouldn't matter, but 
-	 * the LOG surface is always the first surface requested. 
+	/* Only enabled in debug builds so it shouldn't matter, but
+	 * the LOG surface is always the first surface requested.
 	 */
 	if (!test_bit(ISP_STATE_LOGGING, &isp->state))
 		set_bit(ISP_STATE_LOGGING, &isp->state);
@@ -306,9 +306,10 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan)
 		   sizeof(meta_iova));
 
 	spin_lock(&isp->buf_lock);
-	list_for_each_entry_safe_reverse(buf, tmp, &isp->buffers, link) {
-		if (buf->meta->iova == meta_iova) {
+	list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) {
+		if ((u32)buf->meta->iova == (u32)meta_iova) {
 			enum vb2_buffer_state state = VB2_BUF_STATE_ERROR;
+
 			buf->vb.vb2_buf.timestamp = ktime_get_ns();
 			buf->vb.sequence = isp->sequence++;
 			buf->vb.field = V4L2_FIELD_NONE;
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index 9de6549ec9bee7..a35b9cbf20fef9 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -13,10 +13,11 @@
 #include "isp-ipc.h"
 #include "isp-v4l2.h"
 
-#define ISP_MIN_FRAMES	     2
-#define ISP_MAX_PLANES	     4
-#define ISP_MAX_PIX_FORMATS  2
-#define ISP_BUFFER_TIMEOUT   msecs_to_jiffies(1500)
+#define ISP_MIN_FRAMES 2
+#define ISP_MAX_PLANES 4
+#define ISP_MAX_PIX_FORMATS 2
+#define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500)
+#define ISP_STRIDE_ALIGNMENT 64
 
 struct isp_h2t_buffer {
 	u64 iovas[ISP_MAX_PLANES];
@@ -40,7 +41,7 @@ static int isp_submit_buffers(struct apple_isp *isp)
 	struct isp_format *fmt = isp_get_current_format(isp);
 	struct isp_channel *chan = isp->chan_bh;
 	struct isp_message *req = &chan->req;
-	struct isp_buffer *buf;
+	struct isp_buffer *buf, *buf2, *tmp;
 	unsigned long flags;
 	size_t offset;
 	int err;
@@ -51,43 +52,76 @@ static int isp_submit_buffers(struct apple_isp *isp)
 		return -ENOMEM;
 
 	spin_lock_irqsave(&isp->buf_lock, flags);
-	buf = list_first_entry_or_null(&isp->buffers, struct isp_buffer, link);
-	if (!buf) {
+	while ((buf = list_first_entry_or_null(&isp->bufs_pending,
+					       struct isp_buffer, link))) {
+		args->meta.num_planes = 1;
+		args->meta.pool_type = 0;
+		args->meta.iovas[0] = buf->meta->iova;
+		args->meta.flags[0] = 0x40000000;
+
+		args->render.num_planes = fmt->num_planes;
+		args->render.pool_type = isp->hw->scl1 ?
+						 CISP_POOL_TYPE_RENDERED_SCL1 :
+						 CISP_POOL_TYPE_RENDERED;
+		offset = 0;
+		for (int j = 0; j < fmt->num_planes; j++) {
+			args->render.iovas[j] = buf->surfs[0].iova + offset;
+			args->render.flags[j] = 0x40000000;
+			offset += fmt->plane_size[j];
+		}
+
+		/*
+		 * Queue the buffer as submitted and release the lock for now.
+		 * We need to do this before actually submitting to avoid a
+		 * race with the buffer return codepath.
+		 */
+		list_move_tail(&buf->link, &isp->bufs_submitted);
 		spin_unlock_irqrestore(&isp->buf_lock, flags);
-		kfree(args);
-		return -EPROTO;
-	}
 
-	args->meta.num_planes = 1;
-	args->meta.pool_type = CISP_POOL_TYPE_META;
-	args->meta.iovas[0] = buf->meta->iova;
-	args->meta.flags[0] = 0x40000000;
-
-	args->render.num_planes = fmt->num_planes;
-	args->render.pool_type = CISP_POOL_TYPE_RENDERED;
-	offset = 0;
-	for (int j = 0; j < fmt->num_planes; j++) {
-		args->render.iovas[j] = buf->surfs[0].iova + offset;
-		args->render.flags[j] = 0x40000000;
-		offset += fmt->plane_size[j];
+		args->enable = 0x1;
+		args->num_buffers = 2;
+
+		req->arg0 = isp->cmd_iova;
+		req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE;
+		req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
+
+		isp_iowrite(isp, req->arg0, args, sizeof(*args));
+		err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
+		if (err) {
+			/* If we fail, consider the buffer not submitted. */
+			dev_err(isp->dev,
+				"%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n",
+				chan->name, req->arg0, req->arg1, req->arg2);
+
+			/*
+			 * Try to find the buffer in the list, and if it's
+			 * still there, move it back to the pending list.
+			 */
+			spin_lock_irqsave(&isp->buf_lock, flags);
+			list_for_each_entry_safe_reverse(
+				buf2, tmp, &isp->bufs_submitted, link) {
+				if (buf2 == buf) {
+					list_move_tail(&buf->link,
+						       &isp->bufs_pending);
+					spin_unlock_irqrestore(&isp->buf_lock,
+							       flags);
+					return err;
+				}
+			}
+			/*
+			 * We didn't find the buffer, which means it somehow was returned
+			 * by the firmware even though submission failed?
+			 */
+			dev_err(isp->dev,
+				"buffer submission failed but buffer was returned?\n");
+			spin_unlock_irqrestore(&isp->buf_lock, flags);
+			return err;
+		}
+
+		spin_lock_irqsave(&isp->buf_lock, flags);
 	}
 	spin_unlock_irqrestore(&isp->buf_lock, flags);
 
-	args->enable = 0x1;
-	args->num_buffers = 2;
-
-	req->arg0 = isp->cmd_iova;
-	req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE;
-	req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
-
-	isp_iowrite(isp, req->arg0, args, sizeof(*args));
-	err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
-	if (err) {
-		dev_err(isp->dev,
-			"%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n",
-			chan->name, req->arg0, req->arg1, req->arg2);
-	}
-
 	kfree(args);
 
 	return err;
@@ -140,7 +174,7 @@ static int isp_vb2_buf_init(struct vb2_buffer *vb)
 	unsigned int i;
 	int err;
 
-	buf->meta = isp_alloc_surface(isp, ISP_META_SIZE);
+	buf->meta = isp_alloc_surface(isp, isp->hw->meta_size);
 	if (!buf->meta)
 		return -ENOMEM;
 
@@ -179,9 +213,12 @@ static void isp_vb2_release_buffers(struct apple_isp *isp,
 	unsigned long flags;
 
 	spin_lock_irqsave(&isp->buf_lock, flags);
-	list_for_each_entry(buf, &isp->buffers, link)
+	list_for_each_entry(buf, &isp->bufs_submitted, link)
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+	INIT_LIST_HEAD(&isp->bufs_submitted);
+	list_for_each_entry(buf, &isp->bufs_pending, link)
 		vb2_buffer_done(&buf->vb.vb2_buf, state);
-	INIT_LIST_HEAD(&isp->buffers);
+	INIT_LIST_HEAD(&isp->bufs_pending);
 	spin_unlock_irqrestore(&isp->buf_lock, flags);
 }
 
@@ -194,8 +231,9 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb)
 	bool empty;
 
 	spin_lock_irqsave(&isp->buf_lock, flags);
-	empty = list_empty(&isp->buffers);
-	list_add_tail(&buf->link, &isp->buffers);
+	empty = list_empty(&isp->bufs_pending) &&
+		list_empty(&isp->bufs_submitted);
+	list_add_tail(&buf->link, &isp->bufs_pending);
 	spin_unlock_irqrestore(&isp->buf_lock, flags);
 
 	if (test_bit(ISP_STATE_STREAMING, &isp->state) && !empty)
@@ -249,17 +287,64 @@ static void isp_vb2_stop_streaming(struct vb2_queue *q)
 }
 
 static const struct vb2_ops isp_vb2_ops = {
-	.queue_setup     = isp_vb2_queue_setup,
-	.buf_init        = isp_vb2_buf_init,
-	.buf_cleanup     = isp_vb2_buf_cleanup,
-	.buf_prepare     = isp_vb2_buf_prepare,
-	.buf_queue       = isp_vb2_buf_queue,
+	.queue_setup = isp_vb2_queue_setup,
+	.buf_init = isp_vb2_buf_init,
+	.buf_cleanup = isp_vb2_buf_cleanup,
+	.buf_prepare = isp_vb2_buf_prepare,
+	.buf_queue = isp_vb2_buf_queue,
 	.start_streaming = isp_vb2_start_streaming,
-	.stop_streaming  = isp_vb2_stop_streaming,
-	.wait_prepare    = vb2_ops_wait_prepare,
-	.wait_finish     = vb2_ops_wait_finish,
+	.stop_streaming = isp_vb2_stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish,
 };
 
+static int isp_set_preset(struct apple_isp *isp, struct isp_format *fmt,
+			  struct isp_preset *preset)
+{
+	int i;
+	size_t total_size;
+
+	fmt->preset = preset;
+
+	/* I really fucking hope they all use NV12. */
+	fmt->num_planes = 2;
+	fmt->strides[0] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT);
+	/* UV subsampled interleaved */
+	fmt->strides[1] = ALIGN(preset->output_dim.x, ISP_STRIDE_ALIGNMENT);
+	fmt->plane_size[0] = fmt->strides[0] * preset->output_dim.y;
+	fmt->plane_size[1] = fmt->strides[1] * preset->output_dim.y / 2;
+
+	total_size = 0;
+	for (i = 0; i < fmt->num_planes; i++)
+		total_size += fmt->plane_size[i];
+	fmt->total_size = total_size;
+
+	return 0;
+}
+
+static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width,
+				     u32 height)
+{
+	struct isp_preset *preset, *best = &isp->presets[0];
+	int i, score, best_score = INT_MAX;
+
+	/* Default if no dimensions */
+	if (width == 0 || height == 0)
+		return &isp->presets[0];
+
+	for (i = 0; i < isp->num_presets; i++) {
+		preset = &isp->presets[i];
+		score = abs((int)preset->output_dim.x - (int)width) +
+		abs((int)preset->output_dim.y - (int)height);
+		if (score < best_score) {
+			best = preset;
+			best_score = score;
+		}
+	}
+
+	return best;
+}
+
 /*
  * V4L2 ioctl section
  */
@@ -290,29 +375,28 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh,
 				      struct v4l2_frmsizeenum *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
-	struct isp_format *fmt = isp_get_current_format(isp);
 
-	if (f->index >= ISP_MAX_PIX_FORMATS)
+	if (f->index >= isp->num_presets)
 		return -EINVAL;
 
-	if ((!f->index && f->pixel_format != V4L2_PIX_FMT_NV12) ||
-	    (f->index && f->pixel_format != V4L2_PIX_FMT_NV12M))
+	if ((f->pixel_format != V4L2_PIX_FMT_NV12) ||
+	    (f->pixel_format != V4L2_PIX_FMT_NV12M))
 		return -EINVAL;
 
-	f->discrete.width = fmt->width;
-	f->discrete.height = fmt->height;
+	f->discrete.width = isp->presets[f->index].output_dim.x;
+	f->discrete.height = isp->presets[f->index].output_dim.y;
 	f->type = V4L2_FRMSIZE_TYPE_DISCRETE;
 
 	return 0;
 }
 
-static inline void isp_set_sp_pix_format(struct apple_isp *isp,
-					 struct v4l2_format *f)
+static inline void isp_get_sp_pix_format(struct apple_isp *isp,
+					 struct v4l2_format *f,
+					 struct isp_format *fmt)
 {
-	struct isp_format *fmt = isp_get_current_format(isp);
-
-	f->fmt.pix.width = fmt->width;
-	f->fmt.pix.height = fmt->height;
+	f->fmt.pix.width = fmt->preset->output_dim.x;
+	f->fmt.pix.height = fmt->preset->output_dim.y;
+	f->fmt.pix.bytesperline = fmt->strides[0];
 	f->fmt.pix.sizeimage = fmt->total_size;
 
 	f->fmt.pix.field = V4L2_FIELD_NONE;
@@ -322,16 +406,17 @@ static inline void isp_set_sp_pix_format(struct apple_isp *isp,
 	f->fmt.pix.xfer_func = V4L2_XFER_FUNC_709;
 }
 
-static inline void isp_set_mp_pix_format(struct apple_isp *isp,
-					 struct v4l2_format *f)
+static inline void isp_get_mp_pix_format(struct apple_isp *isp,
+					 struct v4l2_format *f,
+					 struct isp_format *fmt)
 {
-	struct isp_format *fmt = isp_get_current_format(isp);
-
-	f->fmt.pix_mp.width = fmt->width;
-	f->fmt.pix_mp.height = fmt->height;
+	f->fmt.pix_mp.width = fmt->preset->output_dim.x;
+	f->fmt.pix_mp.height = fmt->preset->output_dim.y;
 	f->fmt.pix_mp.num_planes = fmt->num_planes;
-	for (int i = 0; i < fmt->num_planes; i++)
+	for (int i = 0; i < fmt->num_planes; i++) {
 		f->fmt.pix_mp.plane_fmt[i].sizeimage = fmt->plane_size[i];
+		f->fmt.pix_mp.plane_fmt[i].bytesperline = fmt->strides[i];
+	}
 
 	f->fmt.pix_mp.field = V4L2_FIELD_NONE;
 	f->fmt.pix_mp.pixelformat = V4L2_PIX_FMT_NV12M;
@@ -344,11 +429,12 @@ static int isp_vidioc_get_format(struct file *file, void *fh,
 				 struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format *fmt = isp_get_current_format(isp);
 
 	if (isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_sp_pix_format(isp, f);
+	isp_get_sp_pix_format(isp, f, fmt);
 
 	return 0;
 }
@@ -357,11 +443,19 @@ static int isp_vidioc_set_format(struct file *file, void *fh,
 				 struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format *fmt = isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
 
 	if (isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_sp_pix_format(isp, f); // no
+	preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height);
+	err = isp_set_preset(isp, fmt, preset);
+	if (err)
+		return err;
+
+	isp_get_sp_pix_format(isp, f, fmt);
 
 	return 0;
 }
@@ -370,11 +464,19 @@ static int isp_vidioc_try_format(struct file *file, void *fh,
 				 struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format fmt = *isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
 
 	if (isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_sp_pix_format(isp, f); // still no
+	preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height);
+	err = isp_set_preset(isp, &fmt, preset);
+	if (err)
+		return err;
+
+	isp_get_sp_pix_format(isp, f, &fmt);
 
 	return 0;
 }
@@ -383,11 +485,12 @@ static int isp_vidioc_get_format_mplane(struct file *file, void *fh,
 					struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format *fmt = isp_get_current_format(isp);
 
 	if (!isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_mp_pix_format(isp, f);
+	isp_get_mp_pix_format(isp, f, fmt);
 
 	return 0;
 }
@@ -396,11 +499,20 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh,
 					struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format *fmt = isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
 
 	if (!isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_mp_pix_format(isp, f); // no
+	preset = isp_select_preset(isp, f->fmt.pix_mp.width,
+				   f->fmt.pix_mp.height);
+	err = isp_set_preset(isp, fmt, preset);
+	if (err)
+		return err;
+
+	isp_get_mp_pix_format(isp, f, fmt);
 
 	return 0;
 }
@@ -409,11 +521,20 @@ static int isp_vidioc_try_format_mplane(struct file *file, void *fh,
 					struct v4l2_format *f)
 {
 	struct apple_isp *isp = video_drvdata(file);
+	struct isp_format fmt = *isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
 
 	if (!isp->multiplanar)
 		return -ENOTTY;
 
-	isp_set_mp_pix_format(isp, f); // still no
+	preset = isp_select_preset(isp, f->fmt.pix_mp.width,
+				   f->fmt.pix_mp.height);
+	err = isp_set_preset(isp, &fmt, preset);
+	if (err)
+		return err;
+
+	isp_get_mp_pix_format(isp, f, &fmt);
 
 	return 0;
 }
@@ -472,6 +593,8 @@ static int isp_vidioc_set_param(struct file *file, void *fh,
 		return -EINVAL;
 
 	/* Not supporting frame rate sets. No use. Plus floats. */
+	a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	a->parm.capture.readbuffers = ISP_MIN_FRAMES;
 	a->parm.capture.timeperframe.numerator = ISP_FRAME_RATE_NUM;
 	a->parm.capture.timeperframe.denominator = ISP_FRAME_RATE_DEN;
 
@@ -479,59 +602,67 @@ static int isp_vidioc_set_param(struct file *file, void *fh,
 }
 
 static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = {
-	.vidioc_querycap                = isp_vidioc_querycap,
-
-	.vidioc_enum_fmt_vid_cap        = isp_vidioc_enum_format,
-	.vidioc_g_fmt_vid_cap           = isp_vidioc_get_format,
-	.vidioc_s_fmt_vid_cap           = isp_vidioc_set_format,
-	.vidioc_try_fmt_vid_cap         = isp_vidioc_try_format,
-	.vidioc_g_fmt_vid_cap_mplane    = isp_vidioc_get_format_mplane,
-	.vidioc_s_fmt_vid_cap_mplane    = isp_vidioc_set_format_mplane,
-	.vidioc_try_fmt_vid_cap_mplane  = isp_vidioc_try_format_mplane,
-
-	.vidioc_enum_framesizes         = isp_vidioc_enum_framesizes,
-	.vidioc_enum_input              = isp_vidioc_enum_input,
-	.vidioc_g_input                 = isp_vidioc_get_input,
-	.vidioc_s_input                 = isp_vidioc_set_input,
-	.vidioc_g_parm                  = isp_vidioc_get_param,
-	.vidioc_s_parm                  = isp_vidioc_set_param,
-
-	.vidioc_reqbufs                 = vb2_ioctl_reqbufs,
-	.vidioc_querybuf                = vb2_ioctl_querybuf,
-	.vidioc_create_bufs             = vb2_ioctl_create_bufs,
-	.vidioc_qbuf                    = vb2_ioctl_qbuf,
-	.vidioc_expbuf                  = vb2_ioctl_expbuf,
-	.vidioc_dqbuf                   = vb2_ioctl_dqbuf,
-	.vidioc_prepare_buf             = vb2_ioctl_prepare_buf,
-	.vidioc_streamon                = vb2_ioctl_streamon,
-	.vidioc_streamoff               = vb2_ioctl_streamoff,
+	.vidioc_querycap = isp_vidioc_querycap,
+
+	.vidioc_enum_fmt_vid_cap = isp_vidioc_enum_format,
+	.vidioc_g_fmt_vid_cap = isp_vidioc_get_format,
+	.vidioc_s_fmt_vid_cap = isp_vidioc_set_format,
+	.vidioc_try_fmt_vid_cap = isp_vidioc_try_format,
+	.vidioc_g_fmt_vid_cap_mplane = isp_vidioc_get_format_mplane,
+	.vidioc_s_fmt_vid_cap_mplane = isp_vidioc_set_format_mplane,
+	.vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane,
+
+	.vidioc_enum_framesizes = isp_vidioc_enum_framesizes,
+	.vidioc_enum_input = isp_vidioc_enum_input,
+	.vidioc_g_input = isp_vidioc_get_input,
+	.vidioc_s_input = isp_vidioc_set_input,
+	.vidioc_g_parm = isp_vidioc_get_param,
+	.vidioc_s_parm = isp_vidioc_set_param,
+
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
 };
 
 static const struct v4l2_file_operations isp_v4l2_fops = {
-	.owner          = THIS_MODULE,
-	.open           = v4l2_fh_open,
-	.release        = vb2_fop_release,
-	.read           = vb2_fop_read,
-	.poll           = vb2_fop_poll,
-	.mmap           = vb2_fop_mmap,
+	.owner = THIS_MODULE,
+	.open = v4l2_fh_open,
+	.release = vb2_fop_release,
+	.read = vb2_fop_read,
+	.poll = vb2_fop_poll,
+	.mmap = vb2_fop_mmap,
 	.unlocked_ioctl = video_ioctl2,
 };
 
 static const struct media_device_ops isp_media_device_ops = {
-	.link_notify    = v4l2_pipeline_link_notify,
+	.link_notify = v4l2_pipeline_link_notify,
 };
 
 int apple_isp_setup_video(struct apple_isp *isp)
 {
 	struct video_device *vdev = &isp->vdev;
 	struct vb2_queue *vbq = &isp->vbq;
+	struct isp_format *fmt = isp_get_current_format(isp);
 	int err;
 
+	err = isp_set_preset(isp, fmt, &isp->presets[0]);
+	if (err) {
+		dev_err(isp->dev, "failed to set default preset: %d\n", err);
+		return err;
+	}
+
 	media_device_init(&isp->mdev);
 	isp->v4l2_dev.mdev = &isp->mdev;
 	isp->mdev.ops = &isp_media_device_ops;
 	isp->mdev.dev = isp->dev;
-	strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME, sizeof(isp->mdev.model));
+	strscpy(isp->mdev.model, APPLE_ISP_DEVICE_NAME,
+		sizeof(isp->mdev.model));
 
 	err = media_device_register(&isp->mdev);
 	if (err) {

From 10f642f8ff081190667c4b5ba6f72c6ee2b4d5e7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 16:06:31 +0900
Subject: [PATCH 0403/1027] media: apple: isp: Always enable singleplane API,
 make multiple a module param

This requires modifying the vbq type when set_format is called,
depending on the style... this is ugly, but it should work?

Multiplane is still quite broken, but this enables testing it with
gstreamer. Still lots of things to fix to make this actually work.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-v4l2.c | 49 ++++++++++++++-------
 1 file changed, 32 insertions(+), 17 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index a35b9cbf20fef9..1d1e8a8bd6c81e 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
 
+#include <linux/module.h>
+
 #include <media/media-device.h>
 #include <media/v4l2-common.h>
 #include <media/v4l2-ioctl.h>
@@ -19,6 +21,10 @@
 #define ISP_BUFFER_TIMEOUT msecs_to_jiffies(1500)
 #define ISP_STRIDE_ALIGNMENT 64
 
+static bool multiplanar = false;
+module_param(multiplanar, bool, 0644);
+MODULE_PARM_DESC(multiplanar, "Enable multiplanar API");
+
 struct isp_h2t_buffer {
 	u64 iovas[ISP_MAX_PLANES];
 	u32 flags[ISP_MAX_PLANES];
@@ -360,13 +366,23 @@ static int isp_vidioc_querycap(struct file *file, void *priv,
 static int isp_vidioc_enum_format(struct file *file, void *fh,
 				  struct v4l2_fmtdesc *f)
 {
+	struct apple_isp *isp = video_drvdata(file);
+
 	if (f->index >= ISP_MAX_PIX_FORMATS)
 		return -EINVAL;
 
-	if (!f->index)
+	switch (f->index) {
+	case 0:
 		f->pixelformat = V4L2_PIX_FMT_NV12;
-	else
+		break;
+	case 1:
+		if (!isp->multiplanar)
+			return -EINVAL;
 		f->pixelformat = V4L2_PIX_FMT_NV12M;
+		break;
+	default:
+		return -EINVAL;
+	}
 
 	return 0;
 }
@@ -379,7 +395,7 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh,
 	if (f->index >= isp->num_presets)
 		return -EINVAL;
 
-	if ((f->pixel_format != V4L2_PIX_FMT_NV12) ||
+	if ((f->pixel_format != V4L2_PIX_FMT_NV12) &&
 	    (f->pixel_format != V4L2_PIX_FMT_NV12M))
 		return -EINVAL;
 
@@ -431,9 +447,6 @@ static int isp_vidioc_get_format(struct file *file, void *fh,
 	struct apple_isp *isp = video_drvdata(file);
 	struct isp_format *fmt = isp_get_current_format(isp);
 
-	if (isp->multiplanar)
-		return -ENOTTY;
-
 	isp_get_sp_pix_format(isp, f, fmt);
 
 	return 0;
@@ -447,9 +460,6 @@ static int isp_vidioc_set_format(struct file *file, void *fh,
 	struct isp_preset *preset;
 	int err;
 
-	if (isp->multiplanar)
-		return -ENOTTY;
-
 	preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height);
 	err = isp_set_preset(isp, fmt, preset);
 	if (err)
@@ -457,6 +467,8 @@ static int isp_vidioc_set_format(struct file *file, void *fh,
 
 	isp_get_sp_pix_format(isp, f, fmt);
 
+	isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
 	return 0;
 }
 
@@ -468,9 +480,6 @@ static int isp_vidioc_try_format(struct file *file, void *fh,
 	struct isp_preset *preset;
 	int err;
 
-	if (isp->multiplanar)
-		return -ENOTTY;
-
 	preset = isp_select_preset(isp, f->fmt.pix.width, f->fmt.pix.height);
 	err = isp_set_preset(isp, &fmt, preset);
 	if (err)
@@ -514,6 +523,8 @@ static int isp_vidioc_set_format_mplane(struct file *file, void *fh,
 
 	isp_get_mp_pix_format(isp, f, fmt);
 
+	isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+
 	return 0;
 }
 
@@ -571,8 +582,9 @@ static int isp_vidioc_get_param(struct file *file, void *fh,
 {
 	struct apple_isp *isp = video_drvdata(file);
 
-	if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
-					   V4L2_BUF_TYPE_VIDEO_CAPTURE))
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    (!isp->multiplanar ||
+	     a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE))
 		return -EINVAL;
 
 	a->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
@@ -588,8 +600,9 @@ static int isp_vidioc_set_param(struct file *file, void *fh,
 {
 	struct apple_isp *isp = video_drvdata(file);
 
-	if (a->type != (isp->multiplanar ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE :
-					   V4L2_BUF_TYPE_VIDEO_CAPTURE))
+	if (a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    (!isp->multiplanar ||
+	     a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE))
 		return -EINVAL;
 
 	/* Not supporting frame rate sets. No use. Plus floats. */
@@ -670,7 +683,7 @@ int apple_isp_setup_video(struct apple_isp *isp)
 		goto media_cleanup;
 	}
 
-	isp->multiplanar = 0;
+	isp->multiplanar = multiplanar;
 
 	err = v4l2_device_register(isp->dev, &isp->v4l2_dev);
 	if (err) {
@@ -699,6 +712,8 @@ int apple_isp_setup_video(struct apple_isp *isp)
 	vdev->fops = &isp_v4l2_fops;
 	vdev->ioctl_ops = &isp_v4l2_ioctl_ops;
 	vdev->device_caps = V4L2_BUF_TYPE_VIDEO_CAPTURE | V4L2_CAP_STREAMING;
+	if (isp->multiplanar)
+		vdev->device_caps |= V4L2_CAP_VIDEO_CAPTURE_MPLANE;
 	vdev->v4l2_dev = &isp->v4l2_dev;
 	vdev->vfl_type = VFL_TYPE_VIDEO;
 	vdev->vfl_dir = VFL_DIR_RX;

From 1fb3768e04a9a23e9bd564d22379cfddfffc97c8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 19:10:58 +0900
Subject: [PATCH 0404/1027] media: apple: isp: Switch to threaded IRQs

There's no reason to run all the command handling in hard IRQ context.
Let's switch to threaded IRQs, which should simplify some things.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 972867a93a0193..01b81714547206 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -93,6 +93,13 @@ static irqreturn_t apple_isp_isr(int irq, void *dev)
 	isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK,
 			 isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT));
 
+	return IRQ_WAKE_THREAD;
+}
+
+static irqreturn_t apple_isp_isr_thread(int irq, void *dev)
+{
+	struct apple_isp *isp = dev;
+
 	wake_up_interruptible_all(&isp->wait);
 
 	ipc_chan_handle(isp, isp->chan_sm);
@@ -117,7 +124,8 @@ static int isp_enable_irq(struct apple_isp *isp)
 {
 	int err;
 
-	err = request_irq(isp->irq, apple_isp_isr, 0, "apple-isp", isp);
+	err = request_threaded_irq(isp->irq, apple_isp_isr,
+				   apple_isp_isr_thread, 0, "apple-isp", isp);
 	if (err < 0) {
 		isp_err(isp, "failed to request IRQ#%u (%d)\n", isp->irq, err);
 		return err;

From 3da7b4ad795738c62c13c0b95e5bad84985831fe Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 18:38:22 +0900
Subject: [PATCH 0405/1027] media: apple: isp: Remove ioread/iowrite and stop
 doing raw address translation

Translating IOVAs via the DART and then trying to access physical memory
directly is slow and error-prone. We know what surfaces IOVAs are
supposed to be part of, so we can use the surface vmap to access the
contents. Where we get an IOVA from the firmware, assert that it is
within the expected range before accessing it.

Since we're using threaded IRQs now, this also lets us get rid of the
deferred vmap.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cam.c   |   2 +-
 drivers/media/platform/apple/isp/isp-cmd.c   |   5 +-
 drivers/media/platform/apple/isp/isp-drv.h   |   5 +
 drivers/media/platform/apple/isp/isp-fw.c    |  70 +++++++++++--
 drivers/media/platform/apple/isp/isp-fw.h    |   9 ++
 drivers/media/platform/apple/isp/isp-iommu.c |   6 --
 drivers/media/platform/apple/isp/isp-iommu.h |  15 ---
 drivers/media/platform/apple/isp/isp-ipc.c   | 105 +++++++++----------
 drivers/media/platform/apple/isp/isp-v4l2.c  |   2 +-
 9 files changed, 130 insertions(+), 89 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index 593b780ab73b15..abdc9e345933d8 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -323,7 +323,7 @@ static int isp_ch_load_setfile(struct apple_isp *isp, u32 ch)
 		return -EINVAL;
 	}
 
-	isp_iowrite(isp, isp->data_surf->iova, (void *)fw->data, setfile->size);
+	memcpy(isp->data_surf->virt, (void *)fw->data, setfile->size);
 	release_firmware(fw);
 
 	return isp_cmd_ch_set_file_load(isp, ch, isp->data_surf->iova,
diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 1e812400e52f7d..1166f0990830ed 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -24,7 +24,7 @@ static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize)
 	req->arg1 = insize;
 	req->arg2 = outsize;
 
-	isp_iowrite(isp, isp->cmd_iova, args, insize);
+	memcpy(isp->cmd_virt, args, insize);
 	err = ipc_chan_send(isp, chan, CISP_TIMEOUT);
 	if (err) {
 		u64 opcode;
@@ -45,7 +45,8 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize,
 	int err = cisp_send(isp, args, insize, outsize);
 	if (err)
 		return err;
-	isp_ioread(isp, isp->cmd_iova, args, outsize);
+
+	memcpy(args, isp->cmd_virt, outsize);
 	return 0;
 }
 
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 926c921849544a..26b9ee0e4d709f 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -32,6 +32,7 @@ struct isp_surf {
 	struct drm_mm_node *mm;
 	struct list_head head;
 	u64 size;
+	u64 type;
 	u32 num_pages;
 	struct page **pages;
 	struct sg_table sgt;
@@ -60,6 +61,7 @@ struct isp_channel {
 	u32 num;
 	u64 size;
 	dma_addr_t iova;
+	void *virt;
 	u32 doorbell;
 	u32 cursor;
 	spinlock_t lock;
@@ -210,6 +212,8 @@ struct apple_isp {
 	struct isp_surf *ipc_surf;
 	struct isp_surf *extra_surf;
 	struct isp_surf *data_surf;
+	struct isp_surf *log_surf;
+	struct isp_surf *bt_surf;
 	struct list_head gc;
 	struct workqueue_struct *wq;
 
@@ -225,6 +229,7 @@ struct apple_isp {
 
 	wait_queue_head_t wait;
 	dma_addr_t cmd_iova;
+	void *cmd_virt;
 
 	unsigned long state;
 	spinlock_t buf_lock;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 01b81714547206..70e201ea1ebd6f 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -1,6 +1,9 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
 
+#include "isp-fw.h"
+
+#include <asm/io.h>
 #include <linux/delay.h>
 #include <linux/pm_runtime.h>
 #include <linux/types.h>
@@ -38,6 +41,35 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val)
 	writel(val, isp->gpio + reg);
 }
 
+void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf,
+			  dma_addr_t iova, size_t size)
+{
+	dma_addr_t end = iova + size;
+	if (!surf) {
+		dev_err(isp->dev,
+			"Failed to translate IPC iova 0x%llx (0x%zx): No surface\n",
+			(long long)iova, size);
+		return NULL;
+	}
+
+	if (end < iova || iova < surf->iova ||
+	    end > (surf->iova + surf->size)) {
+		dev_err(isp->dev,
+			"Failed to translate IPC iova 0x%llx (0x%zx): Out of bounds\n",
+			(long long)iova, size);
+		return NULL;
+	}
+
+	if (!surf->virt) {
+		dev_err(isp->dev,
+			"Failed to translate IPC iova 0x%llx (0x%zx): No VMap\n",
+			(long long)iova, size);
+		return NULL;
+	}
+
+	return surf->virt + (iova - surf->iova);
+}
+
 struct isp_firmware_bootargs {
 	u32 pad_0[2];
 	u64 ipc_iova;
@@ -232,6 +264,8 @@ int apple_isp_alloc_firmware_surface(struct apple_isp *isp)
 		isp_err(isp, "failed to alloc shared surface for ipc\n");
 		return -ENOMEM;
 	}
+	dev_info(isp->dev, "IPC surface iova: 0x%llx\n",
+		 (long long)isp->ipc_surf->iova);
 
 	isp->data_surf = isp_alloc_surface_vmap(isp, ISP_FIRMWARE_DATA_SIZE);
 	if (!isp->data_surf) {
@@ -239,6 +273,8 @@ int apple_isp_alloc_firmware_surface(struct apple_isp *isp)
 		isp_free_surface(isp, isp->ipc_surf);
 		return -ENOMEM;
 	}
+	dev_info(isp->dev, "Data surface iova: 0x%llx\n",
+		 (long long)isp->data_surf->iova);
 
 	return 0;
 }
@@ -258,6 +294,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 {
 	struct isp_firmware_bootargs args;
 	dma_addr_t args_iova;
+	void *args_virt;
 	int err, retries;
 
 	u32 num_ipc_chans = isp_gpio_read32(isp, ISP_GPIO_0);
@@ -281,7 +318,9 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	}
 
 	args_iova = isp->ipc_surf->iova + args_offset + 0x40;
+	args_virt = isp->ipc_surf->virt + args_offset + 0x40;
 	isp->cmd_iova = args_iova + sizeof(args) + 0x40;
+	isp->cmd_virt = args_virt + sizeof(args) + 0x40;
 
 	memset(&args, 0, sizeof(args));
 	args.ipc_iova = isp->ipc_surf->iova;
@@ -295,7 +334,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 	args.unk7 = 0x1; // 0?
 	args.unk_iova1 = args_iova + sizeof(args) - 0xc;
 	args.unk9 = 0x3;
-	isp_iowrite(isp, args_iova, &args, sizeof(args));
+	memcpy(args_virt, &args, sizeof(args));
 
 	isp_gpio_write32(isp, ISP_GPIO_0, args_iova);
 	isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32);
@@ -355,7 +394,15 @@ static void isp_free_channel_info(struct apple_isp *isp)
 static int isp_fill_channel_info(struct apple_isp *isp)
 {
 	u64 table_iova = isp_gpio_read32(isp, ISP_GPIO_0) |
-		((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32;
+			 ((u64)isp_gpio_read32(isp, ISP_GPIO_1)) << 32;
+	void *table_virt = apple_isp_ipc_translate(
+		isp, table_iova,
+		sizeof(struct isp_chan_desc) * isp->num_ipc_chans);
+
+	if (!table_virt) {
+		dev_err(isp->dev, "Failed to find channel table\n");
+		return -EIO;
+	}
 
 	isp->ipc_chans = kcalloc(isp->num_ipc_chans,
 				 sizeof(struct isp_channel *), GFP_KERNEL);
@@ -364,14 +411,14 @@ static int isp_fill_channel_info(struct apple_isp *isp)
 
 	for (int i = 0; i < isp->num_ipc_chans; i++) {
 		struct isp_chan_desc desc;
-		dma_addr_t desc_iova = table_iova + (i * sizeof(desc));
+		void *desc_virt = table_virt + (i * sizeof(desc));
 		struct isp_channel *chan =
 			kzalloc(sizeof(struct isp_channel), GFP_KERNEL);
 		if (!chan)
 			goto out;
 		isp->ipc_chans[i] = chan;
 
-		isp_ioread(isp, desc_iova, &desc, sizeof(desc));
+		memcpy(&desc, desc_virt, sizeof(desc));
 		chan->name = kstrdup(desc.name, GFP_KERNEL);
 		chan->type = desc.type;
 		chan->src = desc.src;
@@ -379,9 +426,16 @@ static int isp_fill_channel_info(struct apple_isp *isp)
 		chan->num = desc.num;
 		chan->size = desc.num * ISP_IPC_MESSAGE_SIZE;
 		chan->iova = desc.iova;
+		chan->virt =
+			apple_isp_ipc_translate(isp, desc.iova, chan->size);
 		chan->cursor = 0;
 		spin_lock_init(&chan->lock);
 
+		if (!chan->virt) {
+			dev_err(isp->dev, "Failed to find channel buffer\n");
+			goto out;
+		}
+
 		if ((chan->type != ISP_IPC_CHAN_TYPE_COMMAND) &&
 		    (chan->type != ISP_IPC_CHAN_TYPE_REPLY) &&
 		    (chan->type != ISP_IPC_CHAN_TYPE_REPORT)) {
@@ -439,11 +493,11 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp)
 			continue;
 		for (int j = 0; j < chan->num; j++) {
 			struct isp_message msg;
-			dma_addr_t msg_iova = chan->iova + (j * sizeof(msg));
+			void *msg_virt = chan->virt + (j * sizeof(msg));
 
 			memset(&msg, 0, sizeof(msg));
 			msg.arg0 = ISP_IPC_FLAG_ACK;
-			isp_iowrite(isp, msg_iova, &msg, sizeof(msg));
+			memcpy(msg_virt, &msg, sizeof(msg));
 		}
 	}
 	wmb();
@@ -547,6 +601,10 @@ static int isp_start_command_processor(struct apple_isp *isp)
 static void isp_collect_gc_surface(struct apple_isp *isp)
 {
 	struct isp_surf *tmp, *surf;
+
+	isp->log_surf = NULL;
+	isp->bt_surf = NULL;
+
 	list_for_each_entry_safe_reverse(surf, tmp, &isp->gc, head) {
 		isp_dbg(isp, "freeing iova: 0x%llx size: 0x%llx virt: %pS\n",
 			surf->iova, surf->size, (void *)surf->virt);
diff --git a/drivers/media/platform/apple/isp/isp-fw.h b/drivers/media/platform/apple/isp/isp-fw.h
index 264717793cea02..974216f0989f91 100644
--- a/drivers/media/platform/apple/isp/isp-fw.h
+++ b/drivers/media/platform/apple/isp/isp-fw.h
@@ -12,4 +12,13 @@ void apple_isp_free_firmware_surface(struct apple_isp *isp);
 int apple_isp_firmware_boot(struct apple_isp *isp);
 void apple_isp_firmware_shutdown(struct apple_isp *isp);
 
+void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf,
+			  dma_addr_t iova, size_t size);
+
+static inline void *apple_isp_ipc_translate(struct apple_isp *isp,
+					    dma_addr_t iova, size_t size)
+{
+	return apple_isp_translate(isp, isp->ipc_surf, iova, size);
+}
+
 #endif /* __ISP_FW_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c
index 0a9d0d6a350c9a..845c35da0253ae 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.c
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -213,12 +213,6 @@ void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf)
 	}
 }
 
-void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova)
-{
-	phys_addr_t phys = iommu_iova_to_phys(isp->domain, iova);
-	return phys_to_virt(phys);
-}
-
 int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
 			    struct sg_table *sgt, u64 size)
 {
diff --git a/drivers/media/platform/apple/isp/isp-iommu.h b/drivers/media/platform/apple/isp/isp-iommu.h
index 326cf7c12aa745..b99a182e284b72 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.h
+++ b/drivers/media/platform/apple/isp/isp-iommu.h
@@ -12,21 +12,6 @@ struct isp_surf *__isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc);
 struct isp_surf *isp_alloc_surface_vmap(struct apple_isp *isp, u64 size);
 int isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf);
 void isp_free_surface(struct apple_isp *isp, struct isp_surf *surf);
-void *isp_iotranslate(struct apple_isp *isp, dma_addr_t iova);
-
-static inline void isp_ioread(struct apple_isp *isp, dma_addr_t iova,
-			      void *data, u64 size)
-{
-	void *virt = isp_iotranslate(isp, iova);
-	memcpy(data, virt, size);
-}
-
-static inline void isp_iowrite(struct apple_isp *isp, dma_addr_t iova,
-			       void *data, u64 size)
-{
-	void *virt = isp_iotranslate(isp, iova);
-	memcpy(virt, data, size);
-}
 
 int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
 			    struct sg_table *sgt, u64 size);
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 14249a44798ba5..00bd7642177a59 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -4,6 +4,7 @@
 #include "isp-iommu.h"
 #include "isp-ipc.h"
 #include "isp-regs.h"
+#include "isp-fw.h"
 
 #define ISP_IPC_FLAG_TERMINAL_ACK	0x3
 #define ISP_IPC_BUFEXC_STAT_META_OFFSET 0x10
@@ -54,16 +55,16 @@ struct isp_bufexc_stat {
 } __packed;
 static_assert(sizeof(struct isp_bufexc_stat) == ISP_IPC_BUFEXC_STAT_SIZE);
 
-static inline dma_addr_t chan_msg_iova(struct isp_channel *chan, u32 index)
+static inline void *chan_msg_virt(struct isp_channel *chan, u32 index)
 {
-	return chan->iova + (index * ISP_IPC_MESSAGE_SIZE);
+	return chan->virt + (index * ISP_IPC_MESSAGE_SIZE);
 }
 
 static inline void chan_read_msg_index(struct apple_isp *isp,
 				       struct isp_channel *chan,
 				       struct isp_message *msg, u32 index)
 {
-	isp_ioread(isp, chan_msg_iova(chan, index), msg, sizeof(*msg));
+	memcpy(msg, chan_msg_virt(chan, index), sizeof(*msg));
 }
 
 static inline void chan_read_msg(struct apple_isp *isp,
@@ -77,7 +78,7 @@ static inline void chan_write_msg_index(struct apple_isp *isp,
 					struct isp_channel *chan,
 					struct isp_message *msg, u32 index)
 {
-	isp_iowrite(isp, chan_msg_iova(chan, index), msg, sizeof(*msg));
+	memcpy(chan_msg_virt(chan, index), msg, sizeof(*msg));
 }
 
 static inline void chan_write_msg(struct apple_isp *isp,
@@ -191,10 +192,14 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan)
 	char buf[512];
 	dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK;
 	u32 size = req->arg1;
-	if (iova && size && test_bit(ISP_STATE_LOGGING, &isp->state)) {
-		size = min_t(u32, size, 512);
-		isp_ioread(isp, iova, buf, size);
-		isp_dbg(isp, "ISPASC: %.*s", size, buf);
+	if (iova && size && size < sizeof(buf) &&
+	    test_bit(ISP_STATE_LOGGING, &isp->state)) {
+		void *p = apple_isp_translate(isp, isp->log_surf, iova, size);
+		if (p) {
+			size = min_t(u32, size, 512);
+			memcpy(buf, p, size);
+			isp_dbg(isp, "ISPASC: %.*s", size, buf);
+		}
 	}
 #endif
 
@@ -205,55 +210,15 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan)
 	return 0;
 }
 
-/* The kernel accesses exactly two dynamically allocated shared surfaces:
- * 1) LOG: Surface for terminal logs. Optional, only enabled in debug builds.
- * 2) STAT: Surface for BUFT2H rendered frame stat buffer. We isp_ioread() in
- * the BUFT2H ISR below. Since the BUFT2H IRQ is triggered by the BUF_H2T
- * doorbell, the STAT vmap must complete before the first buffer submission
- * under VIDIOC_STREAMON(). The CISP_CMD_PRINT_ENABLE completion depends on the
- * STAT buffer SHAREDMALLOC ISR, which is part of the firmware initialization
- * sequence. We also call flush_workqueue(), so a fault should not occur.
- */
-static void sm_malloc_deferred_worker(struct work_struct *work)
-{
-	struct isp_sm_deferred_work *dwork =
-		container_of(work, struct isp_sm_deferred_work, work);
-	struct apple_isp *isp = dwork->isp;
-	struct isp_surf *surf = dwork->surf;
-	int err;
-
-	err = isp_surf_vmap(isp, surf); /* Can't vmap in interrupt ctx */
-	if (err < 0) {
-		isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n",
-			surf->iova, surf->size);
-		goto out;
-	}
-
-#ifdef APPLE_ISP_DEBUG
-	/* Only enabled in debug builds so it shouldn't matter, but
-	 * the LOG surface is always the first surface requested.
-	 */
-	if (!test_bit(ISP_STATE_LOGGING, &isp->state))
-		set_bit(ISP_STATE_LOGGING, &isp->state);
-#endif
-
-out:
-	kfree(dwork);
-}
-
 int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan)
 {
 	struct isp_message *req = &chan->req, *rsp = &chan->rsp;
+	int err;
 
 	if (req->arg0 == 0x0) {
 		struct isp_sm_deferred_work *dwork;
 		struct isp_surf *surf;
 
-		dwork = kzalloc(sizeof(*dwork), GFP_KERNEL);
-		if (!dwork)
-			return -ENOMEM;
-		dwork->isp = isp;
-
 		surf = isp_alloc_surface_gc(isp, req->arg1);
 		if (!surf) {
 			isp_err(isp, "failed to alloc requested size 0x%llx\n",
@@ -261,19 +226,36 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan)
 			kfree(dwork);
 			return -ENOMEM;
 		}
-		dwork->surf = surf;
+		surf->type = req->arg2;
 
 		rsp->arg0 = surf->iova | ISP_IPC_FLAG_ACK;
 		rsp->arg1 = 0x0;
 		rsp->arg2 = 0x0; /* macOS uses this to index surfaces */
 
-		INIT_WORK(&dwork->work, sm_malloc_deferred_worker);
-		if (!queue_work(isp->wq, &dwork->work)) {
-			isp_err(isp, "failed to queue deferred work\n");
-			isp_free_surface(isp, surf);
-			kfree(dwork);
-			return -ENOMEM;
+		err = isp_surf_vmap(isp, surf);
+		if (err < 0) {
+			isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n",
+				surf->iova, surf->size);
+		} else {
+			switch (surf->type) {
+			case 0x4c4f47: /* "LOG" */
+				isp->log_surf = surf;
+				break;
+			case 0x4d495343: /* "MISC" */
+				/* Hacky... maybe there's a better way to identify this surface? */
+				if (surf->size == 0xc000)
+					isp->bt_surf = surf;
+				break;
+			}
 		}
+
+#ifdef APPLE_ISP_DEBUG
+		/* Only enabled in debug builds so it shouldn't matter, but
+		* the LOG surface is always the first surface requested.
+		*/
+		if (!test_bit(ISP_STATE_LOGGING, &isp->state))
+			set_bit(ISP_STATE_LOGGING, &isp->state);
+#endif
 		/* To the gc it goes... */
 
 	} else {
@@ -302,8 +284,15 @@ int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan)
 
 	/* No need to read the whole struct */
 	u64 meta_iova;
-	isp_ioread(isp, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET, &meta_iova,
-		   sizeof(meta_iova));
+	u64 *p_meta_iova = apple_isp_translate(
+		isp, isp->bt_surf, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET,
+		sizeof(u64));
+
+	if (!p_meta_iova) {
+		dev_err(isp->dev, "Failed to find bufexc stat meta\n");
+		return -EIO;
+	}
+	meta_iova = *p_meta_iova;
 
 	spin_lock(&isp->buf_lock);
 	list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) {
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index 1d1e8a8bd6c81e..daa49f8e3214dc 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -91,7 +91,7 @@ static int isp_submit_buffers(struct apple_isp *isp)
 		req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE;
 		req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
 
-		isp_iowrite(isp, req->arg0, args, sizeof(*args));
+		memcpy(isp->cmd_virt, args, sizeof(*args));
 		err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
 		if (err) {
 			/* If we fail, consider the buffer not submitted. */

From 18a171a82d02537aace32640332e0e6cfe9f79bd Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 19:18:40 +0900
Subject: [PATCH 0406/1027] media: apple: isp: Propagate EINTR from firmware
 loads

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cam.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index abdc9e345933d8..9ccdc2a1304bed 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -340,6 +340,10 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err) {
 		dev_err(isp->dev, "warning: calibration data not loaded: %d\n",
 			err);
+
+		/* If this failed due to a signal, propagate */
+		if (err == -EINTR)
+			return err;
 	}
 
 	if (isp->hw->gen >= ISP_GEN_T8112) {

From de3d4a7d5b4e4942a5bf17bd09e34d31820c7719 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 19:19:12 +0900
Subject: [PATCH 0407/1027] media: apple: isp: Implement posted commands

Useful for shutdown type commands which may not be acked...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cmd.c | 11 ++++++-----
 drivers/media/platform/apple/isp/isp-ipc.c |  3 +++
 2 files changed, 9 insertions(+), 5 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 1166f0990830ed..26ae639b3a63d9 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -10,11 +10,12 @@
 #define CISP_OPCODE_GET(x)    (((u64)(x)) >> CISP_OPCODE_SHIFT)
 
 #define CISP_TIMEOUT	      msecs_to_jiffies(3000)
-#define CISP_SEND_IN(x, a)    (cisp_send((x), &(a), sizeof(a), 0))
-#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a)))
+#define CISP_SEND_IN(x, a)    (cisp_send((x), &(a), sizeof(a), 0, CISP_TIMEOUT))
+#define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT))
 #define CISP_SEND_OUT(x, a)   (cisp_send_read((x), (a), sizeof(*a), sizeof(*a)))
+#define CISP_POST_IN(x, a)    (cisp_send((x), &(a), sizeof(a), 0, 0))
 
-static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize)
+static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout)
 {
 	struct isp_channel *chan = isp->chan_io;
 	struct isp_message *req = &chan->req;
@@ -25,7 +26,7 @@ static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize)
 	req->arg2 = outsize;
 
 	memcpy(isp->cmd_virt, args, insize);
-	err = ipc_chan_send(isp, chan, CISP_TIMEOUT);
+	err = ipc_chan_send(isp, chan, timeout);
 	if (err) {
 		u64 opcode;
 		memcpy(&opcode, args, sizeof(opcode));
@@ -42,7 +43,7 @@ static int cisp_send_read(struct apple_isp *isp, void *args, u32 insize,
 			  u32 outsize)
 {
 	/* TODO do I need to lock the iova space? */
-	int err = cisp_send(isp, args, insize, outsize);
+	int err = cisp_send(isp, args, insize, outsize, CISP_TIMEOUT);
 	if (err)
 		return err;
 
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 00bd7642177a59..56a482c17424cb 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -168,6 +168,9 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 
 	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
 
+	if (!timeout)
+		return 0;
+
 	t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan),
 					     timeout);
 	if (t == 0) {

From 8b3798a2895df28b86213edb5c4097bc10d78d8f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 29 Sep 2023 19:21:38 +0900
Subject: [PATCH 0408/1027] media: apple: isp: Add STOP and POWER_DOWN commands

Not sure if these work properly yet, but worth having them to
experiment.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cmd.c | 17 +++++++++++++++++
 drivers/media/platform/apple/isp/isp-cmd.h | 14 ++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 26ae639b3a63d9..bd82d266522dc0 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -60,6 +60,23 @@ int isp_cmd_start(struct apple_isp *isp, u32 mode)
 	return CISP_SEND_IN(isp, args);
 }
 
+int isp_cmd_stop(struct apple_isp *isp, u32 mode)
+{
+	struct cmd_stop args = {
+		.opcode = CISP_OPCODE(CISP_CMD_STOP),
+		.mode = mode,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int isp_cmd_power_down(struct apple_isp *isp)
+{
+	struct cmd_power_down args = {
+		.opcode = CISP_OPCODE(CISP_CMD_POWER_DOWN),
+	};
+	return CISP_POST_INOUT(isp, args);
+}
+
 int isp_cmd_suspend(struct apple_isp *isp)
 {
 	struct cmd_suspend args = {
diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
index 1586df89f1cdab..2de2a49f2cd398 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.h
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -12,6 +12,7 @@
 #define CISP_CMD_PRINT_ENABLE				     0x0004
 #define CISP_CMD_BUILDINFO				     0x0006
 #define CISP_CMD_GET_BES_PARAM				     0x000f
+#define CISP_CMD_POWER_DOWN				     0x0010
 #define CISP_CMD_SET_ISP_PMU_BASE			     0x0011
 #define CISP_CMD_PMP_CTRL_SET				     0x001c
 #define CISP_CMD_TRACE_ENABLE				     0x001d
@@ -130,6 +131,17 @@ struct cmd_start {
 } __packed;
 static_assert(sizeof(struct cmd_start) == 0xc);
 
+struct cmd_stop {
+	u64 opcode;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_stop) == 0xc);
+
+struct cmd_power_down {
+	u64 opcode;
+} __packed;
+static_assert(sizeof(struct cmd_power_down) == 0x8);
+
 struct cmd_suspend {
 	u64 opcode;
 } __packed;
@@ -221,6 +233,8 @@ struct cmd_ipc_endpoint_set2 {
 static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30);
 
 int isp_cmd_start(struct apple_isp *isp, u32 mode);
+int isp_cmd_stop(struct apple_isp *isp, u32 mode);
+int isp_cmd_power_down(struct apple_isp *isp);
 int isp_cmd_suspend(struct apple_isp *isp);
 int isp_cmd_print_enable(struct apple_isp *isp, u32 enable);
 int isp_cmd_trace_enable(struct apple_isp *isp, u32 enable);

From d78da4f3878cace4ab1b73f53e6250eabbef36e4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 30 Sep 2023 00:15:27 +0900
Subject: [PATCH 0409/1027] media: apple: isp: Maybe fix some DMA ordering
 issues

Maybe.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c  |  4 ++--
 drivers/media/platform/apple/isp/isp-ipc.c | 11 +++++++++--
 2 files changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 70e201ea1ebd6f..2ee815fcc0c72c 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -338,7 +338,7 @@ static int isp_firmware_boot_stage2(struct apple_isp *isp)
 
 	isp_gpio_write32(isp, ISP_GPIO_0, args_iova);
 	isp_gpio_write32(isp, ISP_GPIO_1, args_iova >> 32);
-	wmb();
+	dma_wmb();
 
 	/* Wait for ISP_GPIO_7 to 0xf7fbdff9 -> 0x8042006 */
 	isp_gpio_write32(isp, ISP_GPIO_7, 0xf7fbdff9);
@@ -500,7 +500,7 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp)
 			memcpy(msg_virt, &msg, sizeof(msg));
 		}
 	}
-	wmb();
+	dma_wmb();
 
 	/* Wait for ISP_GPIO_3 to 0x8042006 -> 0x0 */
 	isp_gpio_write32(isp, ISP_GPIO_3, 0x8042006);
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 56a482c17424cb..21c494f49ebc5f 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -78,7 +78,14 @@ static inline void chan_write_msg_index(struct apple_isp *isp,
 					struct isp_channel *chan,
 					struct isp_message *msg, u32 index)
 {
-	memcpy(chan_msg_virt(chan, index), msg, sizeof(*msg));
+	u64 *p0 = chan_msg_virt(chan, index);
+	memcpy(p0 + 1, &msg->arg1, sizeof(*msg) - 8);
+
+	/* Make sure we write arg0 last, since that indicates message validity. */
+
+	dma_wmb();
+	*p0 = msg->arg0;
+	dma_wmb();
 }
 
 static inline void chan_write_msg(struct apple_isp *isp,
@@ -164,7 +171,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 	long t;
 
 	chan_write_msg(isp, chan, &chan->req);
-	wmb();
+	dma_wmb();
 
 	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
 

From 18ce508566c6d886069617831f3beb32402ffdb5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 30 Sep 2023 00:15:41 +0900
Subject: [PATCH 0410/1027] media: apple: isp: Make channel sends not
 interruptible

Otherwise processes receiving a signal will break our command flows.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c  | 6 +++---
 drivers/media/platform/apple/isp/isp-ipc.c | 3 +--
 2 files changed, 4 insertions(+), 5 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 2ee815fcc0c72c..dd88ddf8a2a8c6 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -132,15 +132,15 @@ static irqreturn_t apple_isp_isr_thread(int irq, void *dev)
 {
 	struct apple_isp *isp = dev;
 
-	wake_up_interruptible_all(&isp->wait);
+	wake_up_all(&isp->wait);
 
 	ipc_chan_handle(isp, isp->chan_sm);
-	wake_up_interruptible_all(&isp->wait); /* Some commands depend on sm */
+	wake_up_all(&isp->wait); /* Some commands depend on sm */
 
 	ipc_chan_handle(isp, isp->chan_tm);
 
 	ipc_chan_handle(isp, isp->chan_bt);
-	wake_up_interruptible_all(&isp->wait);
+	wake_up_all(&isp->wait);
 
 	return IRQ_HANDLED;
 }
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 21c494f49ebc5f..c63babfb9951b6 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -178,8 +178,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 	if (!timeout)
 		return 0;
 
-	t = wait_event_interruptible_timeout(isp->wait, chan_tx_done(isp, chan),
-					     timeout);
+	t = wait_event_timeout(isp->wait, chan_tx_done(isp, chan), timeout);
 	if (t == 0) {
 		dev_err(isp->dev,
 			"%s: timed out on request [0x%llx, 0x%llx, 0x%llx]\n",

From ae24da7d21ed0d9e8079b64ca4f4d03c042cd0d5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 28 Sep 2023 08:11:20 +0200
Subject: [PATCH 0411/1027] media: apple: isp: Use a second region for
 MBOX_IRQ_{DOORBELL,ACK}

t8112 uses a different register layout.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-drv.c  |  6 ++++++
 drivers/media/platform/apple/isp/isp-drv.h  |  1 +
 drivers/media/platform/apple/isp/isp-fw.c   |  2 +-
 drivers/media/platform/apple/isp/isp-ipc.c  |  4 ++--
 drivers/media/platform/apple/isp/isp-regs.h | 13 +++++++++----
 5 files changed, 19 insertions(+), 7 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 3dad32f2b58dbf..d6e7a294822b25 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -276,6 +276,12 @@ static int apple_isp_probe(struct platform_device *pdev)
 		goto detach_genpd;
 	}
 
+	isp->mbox2 = devm_platform_ioremap_resource_byname(pdev, "mbox2");
+	if (IS_ERR(isp->mbox2)) {
+		err = PTR_ERR(isp->mbox2);
+		goto detach_genpd;
+	}
+
 	isp->irq = platform_get_irq(pdev, 0);
 	if (isp->irq < 0) {
 		err = isp->irq;
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 26b9ee0e4d709f..4d3b1bd7603aea 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -199,6 +199,7 @@ struct apple_isp {
 	void __iomem *coproc;
 	void __iomem *mbox;
 	void __iomem *gpio;
+	void __iomem *mbox2;
 
 	struct iommu_domain *domain;
 	unsigned long shift;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index dd88ddf8a2a8c6..5c1739e58ab001 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -122,7 +122,7 @@ static irqreturn_t apple_isp_isr(int irq, void *dev)
 {
 	struct apple_isp *isp = dev;
 
-	isp_mbox_write32(isp, ISP_MBOX_IRQ_ACK,
+	isp_mbox2_write32(isp, ISP_MBOX2_IRQ_ACK,
 			 isp_mbox_read32(isp, ISP_MBOX_IRQ_INTERRUPT));
 
 	return IRQ_WAKE_THREAD;
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index c63babfb9951b6..0475b3cf2699ee 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -118,7 +118,7 @@ static int chan_handle_once(struct apple_isp *isp, struct isp_channel *chan)
 
 	chan_write_msg(isp, chan, &chan->rsp);
 
-	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
+	isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell);
 
 	chan_update_cursor(chan);
 
@@ -173,7 +173,7 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 	chan_write_msg(isp, chan, &chan->req);
 	dma_wmb();
 
-	isp_mbox_write32(isp, ISP_MBOX_IRQ_DOORBELL, chan->doorbell);
+	isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell);
 
 	if (!timeout)
 		return 0;
diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h
index 3a99229f6d4c8f..7357fa10fa5483 100644
--- a/drivers/media/platform/apple/isp/isp-regs.h
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -23,10 +23,10 @@
 #define ISP_COPROC_IRQ_MASK_4  0x1400a10
 #define ISP_COPROC_IRQ_MASK_5  0x1400a14
 
-#define ISP_MBOX_IRQ_INTERRUPT 0x000
-#define ISP_MBOX_IRQ_ENABLE    0x004
-#define ISP_MBOX_IRQ_DOORBELL  0x3f0
-#define ISP_MBOX_IRQ_ACK       0x3fc
+#define ISP_MBOX_IRQ_INTERRUPT 0x00
+#define ISP_MBOX_IRQ_ENABLE    0x04
+#define ISP_MBOX2_IRQ_DOORBELL 0x00
+#define ISP_MBOX2_IRQ_ACK      0x0c
 
 #define ISP_GPIO_0	       0x00
 #define ISP_GPIO_1	       0x04
@@ -48,4 +48,9 @@ static inline void isp_mbox_write32(struct apple_isp *isp, u32 reg, u32 val)
 	writel(val, isp->mbox + reg);
 }
 
+static inline void isp_mbox2_write32(struct apple_isp *isp, u32 reg, u32 val)
+{
+	writel(val, isp->mbox2 + reg);
+}
+
 #endif /* __ISP_REGS_H__ */

From 50d5b7e58c3cc23d5a63d46dbfc05ece0b91fe36 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 28 Sep 2023 08:27:10 +0200
Subject: [PATCH 0412/1027] media: apple: isp: t8112 HW config

Not yet working.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index d6e7a294822b25..5613ed801b97a8 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -414,19 +414,14 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = {
 	.meta_size = ISP_META_SIZE_T8103,
 };
 
-static const struct apple_isp_hw apple_isp_hw_t8110 = {
+static const struct apple_isp_hw apple_isp_hw_t8112 = {
 	.gen = ISP_GEN_T8112,
 	.pmu_base = 0x23b704000,
 
-	.dsid_count = 4,
-	.dsid_clr_base0 = 0x200014000, // TODO
-	.dsid_clr_base1 = 0x200054000,
-	.dsid_clr_base2 = 0x200094000,
-	.dsid_clr_base3 = 0x2000d4000,
+	// TODO: verify
+	.dsid_count = 1,
+	.dsid_clr_base0 = 0x200f14000,
 	.dsid_clr_range0 = 0x1000,
-	.dsid_clr_range1 = 0x1000,
-	.dsid_clr_range2 = 0x1000,
-	.dsid_clr_range3 = 0x1000,
 
 	.clock_scratch = 0x23b3d0560,
 	.clock_base = 0x0,
@@ -464,6 +459,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = {
 
 static const struct of_device_id apple_isp_of_match[] = {
 	{ .compatible = "apple,t8103-isp", .data = &apple_isp_hw_t8103 },
+	{ .compatible = "apple,t8112-isp", .data = &apple_isp_hw_t8112 },
 	{ .compatible = "apple,t6000-isp", .data = &apple_isp_hw_t6000 },
 	{ .compatible = "apple,t6020-isp", .data = &apple_isp_hw_t6020 },
 	{},

From 273e6810a5c522d0955242588230e3cf1bc17be4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 28 Sep 2023 20:45:18 +0200
Subject: [PATCH 0413/1027] media: apple: isp: Limit maximal number of buffers

ISP (FW 12.3) on t6001 times out if more buffers than count in the
buffer pool config are submitted before streaming is started.
To avoid keeping track of the number of submitted buffers limit the
number. 16 buffers / frames should be more than enough.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-cmd.c  | 2 +-
 drivers/media/platform/apple/isp/isp-drv.h  | 3 +++
 drivers/media/platform/apple/isp/isp-v4l2.c | 8 ++++++++
 3 files changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index bd82d266522dc0..cbd9348f592dc2 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -395,7 +395,7 @@ int isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan, u16 type)
 		.opcode = CISP_OPCODE(CISP_CMD_CH_BUFFER_POOL_CONFIG_SET),
 		.chan = chan,
 		.type = type,
-		.count = 16,
+		.count = ISP_MAX_BUFFERS,
 		.meta_size0 = isp->hw->meta_size,
 		.meta_size1 = isp->hw->meta_size,
 		.unk0 = 0,
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 4d3b1bd7603aea..8269b772bbd1bd 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -23,6 +23,9 @@
 #define ISP_META_SIZE_T8103      0x4640
 #define ISP_META_SIZE_T8112      0x4840
 
+/* used to limit the user space buffers to the buffer_pool_config */
+#define ISP_MAX_BUFFERS 16
+
 enum isp_generation {
 	ISP_GEN_T8103,
 	ISP_GEN_T8112,
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index daa49f8e3214dc..ae15aee4513f00 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -11,6 +11,7 @@
 
 #include "isp-cam.h"
 #include "isp-cmd.h"
+#include "isp-drv.h"
 #include "isp-iommu.h"
 #include "isp-ipc.h"
 #include "isp-v4l2.h"
@@ -143,6 +144,13 @@ static int isp_vb2_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers,
 	struct apple_isp *isp = vb2_get_drv_priv(vq);
 	struct isp_format *fmt = isp_get_current_format(isp);
 
+	/* This is not strictly neccessary but makes it easy to enforce that
+	 * at most 16 buffers are submitted at once. ISP on t6001 (FW 12.3)
+	 * times out if more buffers are submitted than set in the buffer pool
+	 * config before streaming is started.
+	 */
+	*nbuffers = min_t(unsigned int, *nbuffers, ISP_MAX_BUFFERS);
+
 	if (*num_planes) {
 		if (sizes[0] < fmt->total_size)
 			return -EINVAL;

From 0a72056979a24c0d15af22bf70a8f20f10bde2d0 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 30 Sep 2023 18:53:26 +0900
Subject: [PATCH 0414/1027] media: apple: isp: t8112 fixes...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cam.c |  4 ++--
 drivers/media/platform/apple/isp/isp-cmd.c |  4 ++--
 drivers/media/platform/apple/isp/isp-cmd.h |  2 +-
 drivers/media/platform/apple/isp/isp-drv.c | 12 ++++++++++--
 drivers/media/platform/apple/isp/isp-drv.h |  2 ++
 5 files changed, 17 insertions(+), 7 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index 9ccdc2a1304bed..4966fe64aac299 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -346,7 +346,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 			return err;
 	}
 
-	if (isp->hw->gen >= ISP_GEN_T8112) {
+	if (isp->hw->lpdp) {
 		err = isp_cmd_ch_lpdp_hs_receiver_tuning_set(isp, ch, 1, 15);
 		if (err)
 			return err;
@@ -395,7 +395,7 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_temporal_filter_start(isp, ch);
+	err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter);
 	if (err)
 		return err;
 
diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index cbd9348f592dc2..15a5ec22778ced 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -416,13 +416,13 @@ int isp_cmd_ch_buffer_pool_return(struct apple_isp *isp, u32 chan)
 	return CISP_SEND_IN(isp, args);
 }
 
-int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan)
+int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan, u32 arg)
 {
 	struct cmd_apple_ch_temporal_filter_start args = {
 		.opcode = CISP_OPCODE(CISP_CMD_APPLE_CH_TEMPORAL_FILTER_START),
 		.chan = chan,
 		.unk_c = 1,
-		.unk_10 = 0,
+		.unk_10 = arg,
 	};
 	return CISP_SEND_IN(isp, args);
 }
diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
index 2de2a49f2cd398..718ae88045ac25 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.h
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -577,7 +577,7 @@ struct cmd_apple_ch_temporal_filter_disable {
 } __packed;
 static_assert(sizeof(struct cmd_apple_ch_temporal_filter_disable) == 0xc);
 
-int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan);
+int isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan, u32 arg);
 int isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan);
 int isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan);
 int isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan);
diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 5613ed801b97a8..a2d32ede9150ca 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -246,6 +246,11 @@ static int apple_isp_probe(struct platform_device *pdev)
 		return err;
 	}
 
+	err = of_property_read_u32(dev->of_node, "apple,temporal-filter",
+				   &isp->temporal_filter);
+	if (err)
+		isp->temporal_filter = 0;
+
 	err = apple_isp_init_presets(isp);
 	if (err) {
 		dev_err(dev, "failed to initialize presets\n");
@@ -384,6 +389,7 @@ static const struct apple_isp_hw apple_isp_hw_t8103 = {
 	.bandwidth_size = 0x4,
 
 	.scl1 = false,
+	.lpdp = false,
 	.meta_size = ISP_META_SIZE_T8103,
 };
 
@@ -411,6 +417,7 @@ static const struct apple_isp_hw apple_isp_hw_t6000 = {
 	.bandwidth_size = 0x8,
 
 	.scl1 = false,
+	.lpdp = false,
 	.meta_size = ISP_META_SIZE_T8103,
 };
 
@@ -418,7 +425,6 @@ static const struct apple_isp_hw apple_isp_hw_t8112 = {
 	.gen = ISP_GEN_T8112,
 	.pmu_base = 0x23b704000,
 
-	// TODO: verify
 	.dsid_count = 1,
 	.dsid_clr_base0 = 0x200f14000,
 	.dsid_clr_range0 = 0x1000,
@@ -432,7 +438,8 @@ static const struct apple_isp_hw apple_isp_hw_t8112 = {
 	.bandwidth_bit = 0x0,
 	.bandwidth_size = 0x8,
 
-	.scl1 = true,
+	.scl1 = false,
+	.lpdp = false,
 	.meta_size = ISP_META_SIZE_T8112,
 };
 
@@ -454,6 +461,7 @@ static const struct apple_isp_hw apple_isp_hw_t6020 = {
 	.bandwidth_size = 0x8,
 
 	.scl1 = true,
+	.lpdp = true,
 	.meta_size = ISP_META_SIZE_T8112,
 };
 
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 8269b772bbd1bd..b62d389442e810 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -111,6 +111,7 @@ struct apple_isp_hw {
 
 	u32 meta_size;
 	bool scl1;
+	bool lpdp;
 };
 
 enum isp_sensor_id {
@@ -178,6 +179,7 @@ struct apple_isp {
 	struct device *dev;
 	const struct apple_isp_hw *hw;
 	u32 platform_id;
+	u32 temporal_filter;
 	struct isp_preset *presets;
 	int num_presets;
 

From 2d2c1c5d071d40bc1ad42334c2bf78d3c831faa0 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:18:25 +0900
Subject: [PATCH 0415/1027] media: apple: isp: Add flicker_sensor_set cmd

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/iommu/io-pgtable-dart.c            |  2 +-
 drivers/media/platform/apple/isp/isp-cmd.c | 10 ++++++++++
 drivers/media/platform/apple/isp/isp-cmd.h |  7 +++++++
 3 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/iommu/io-pgtable-dart.c b/drivers/iommu/io-pgtable-dart.c
index c004640640ee50..06aca9ab52f9a8 100644
--- a/drivers/iommu/io-pgtable-dart.c
+++ b/drivers/iommu/io-pgtable-dart.c
@@ -135,7 +135,6 @@ static int dart_init_pte(struct dart_io_pgtable *data,
 	pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_START, 0);
 	pte |= FIELD_PREP(APPLE_DART_PTE_SUBPAGE_END, 0xfff);
 
-	pte |= APPLE_DART1_PTE_PROT_SP_DIS;
 	pte |= APPLE_DART_PTE_VALID;
 
 	for (i = 0; i < num_entries; i++)
@@ -211,6 +210,7 @@ static dart_iopte dart_prot_to_pte(struct dart_io_pgtable *data,
 	dart_iopte pte = 0;
 
 	if (data->iop.fmt == APPLE_DART) {
+		pte |= APPLE_DART1_PTE_PROT_SP_DIS;
 		if (!(prot & IOMMU_WRITE))
 			pte |= APPLE_DART1_PTE_PROT_NO_WRITE;
 		if (!(prot & IOMMU_READ))
diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 15a5ec22778ced..9c5808b4e831be 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -14,6 +14,7 @@
 #define CISP_SEND_INOUT(x, a) (cisp_send((x), &(a), sizeof(a), sizeof(a), CISP_TIMEOUT))
 #define CISP_SEND_OUT(x, a)   (cisp_send_read((x), (a), sizeof(*a), sizeof(*a)))
 #define CISP_POST_IN(x, a)    (cisp_send((x), &(a), sizeof(a), 0, 0))
+#define CISP_POST_INOUT(x, a)    (cisp_send((x), &(a), sizeof(a), sizeof(a), 0))
 
 static int cisp_send(struct apple_isp *isp, void *args, u32 insize, u32 outsize, int timeout)
 {
@@ -204,6 +205,15 @@ int isp_cmd_ch_stop(struct apple_isp *isp, u32 chan)
 	return CISP_SEND_IN(isp, args);
 }
 
+int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode)
+{
+	struct cmd_flicker_sensor_set args = {
+		.opcode = CISP_OPCODE(CISP_CMD_FLICKER_SENSOR_SET),
+		.mode = mode,
+	};
+	return CISP_SEND_INOUT(isp, args);
+}
+
 int isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan,
 			struct cmd_ch_info *args)
 {
diff --git a/drivers/media/platform/apple/isp/isp-cmd.h b/drivers/media/platform/apple/isp/isp-cmd.h
index 718ae88045ac25..5a3c8cd9177e48 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.h
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -232,6 +232,12 @@ struct cmd_ipc_endpoint_set2 {
 } __packed;
 static_assert(sizeof(struct cmd_ipc_endpoint_set2) == 0x30);
 
+struct cmd_flicker_sensor_set {
+	u64 opcode;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_flicker_sensor_set) == 0xc);
+
 int isp_cmd_start(struct apple_isp *isp, u32 mode);
 int isp_cmd_stop(struct apple_isp *isp, u32 mode);
 int isp_cmd_power_down(struct apple_isp *isp);
@@ -253,6 +259,7 @@ int isp_cmd_pmp_ctrl_set(struct apple_isp *isp, u64 clock_scratch,
 			 u8 bandwidth_bit, u8 bandwidth_size);
 int isp_cmd_fid_enter(struct apple_isp *isp);
 int isp_cmd_fid_exit(struct apple_isp *isp);
+int isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode);
 
 struct cmd_ch_start {
 	u64 opcode;

From 130cc14bc80a8ec1a9eaf101770b4643470272b1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:18:46 +0900
Subject: [PATCH 0416/1027] media: apple: isp: Minor changes to cam flow

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-cam.c | 36 +++++++++++++---------
 drivers/media/platform/apple/isp/isp-cam.h |  1 +
 2 files changed, 23 insertions(+), 14 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index 4966fe64aac299..cc0c24c3cfb715 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -289,6 +289,12 @@ int apple_isp_detect_camera(struct apple_isp *isp)
 	}
 
 	err = isp_detect_camera(isp);
+
+	isp_cmd_flicker_sensor_set(isp, 0);
+
+	isp_cmd_ch_stop(isp, 0);
+	isp_cmd_ch_buffer_return(isp, isp->current_ch);
+
 	apple_isp_firmware_shutdown(isp);
 
 	return err;
@@ -335,6 +341,8 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	struct isp_format *fmt = isp_get_format(isp, ch);
 	int err;
 
+	isp_cmd_flicker_sensor_set(isp, 0);
+
 	/* The setfile isn't requisite but then we don't get calibration */
 	err = isp_ch_load_setfile(isp, ch);
 	if (err) {
@@ -356,16 +364,16 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_buffer_recycle_mode_set(
-		isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY);
+	err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_buffer_recycle_start(isp, ch);
+	err = isp_cmd_ch_buffer_recycle_mode_set(
+		isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index);
+	err = isp_cmd_ch_buffer_recycle_start(isp, ch);
 	if (err)
 		return err;
 
@@ -395,43 +403,43 @@ static int isp_ch_configure_capture(struct apple_isp *isp, u32 ch)
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter);
+	err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch);
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_motion_history_start(isp, ch);
+	err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3);
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch);
+	err = isp_cmd_ch_ae_stability_set(isp, ch, 32);
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch);
+	err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20);
 	if (err)
 		return err;
 
-	err = isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3);
+	err = isp_cmd_ch_sif_pixel_format_set(isp, ch);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_ae_stability_set(isp, ch, 32);
+	err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20);
+	err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN2);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_sif_pixel_format_set(isp, ch);
+	err = isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN);
+	err = isp_cmd_apple_ch_motion_history_start(isp, ch);
 	if (err)
 		return err;
 
-	err = isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN);
+	err = isp_cmd_apple_ch_temporal_filter_enable(isp, ch);
 	if (err)
 		return err;
 
diff --git a/drivers/media/platform/apple/isp/isp-cam.h b/drivers/media/platform/apple/isp/isp-cam.h
index 126e5806c8c416..f4fa4224c7a934 100644
--- a/drivers/media/platform/apple/isp/isp-cam.h
+++ b/drivers/media/platform/apple/isp/isp-cam.h
@@ -8,6 +8,7 @@
 
 #define ISP_FRAME_RATE_NUM 256
 #define ISP_FRAME_RATE_DEN 7680
+#define ISP_FRAME_RATE_DEN2 3840
 
 int apple_isp_detect_camera(struct apple_isp *isp);
 

From e4dd81b57ea9c17ff58f07a01f78b5a1875cdaa7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:21:54 +0900
Subject: [PATCH 0417/1027] media: apple: isp: Make sub-pmdomain handling
 explicit

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 11 ++++--
 drivers/media/platform/apple/isp/isp-drv.h |  1 +
 drivers/media/platform/apple/isp/isp-fw.c  | 45 ++++++++++++++++++++++
 3 files changed, 54 insertions(+), 3 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index a2d32ede9150ca..1365e5e29ca099 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -54,6 +54,12 @@ static int apple_isp_attach_genpd(struct apple_isp *isp)
 		return -ENOMEM;
 
 	for (int i = 0; i < isp->pd_count; i++) {
+		int flags = DL_FLAG_STATELESS;
+
+		/* Primary power domain uses RPM integration */
+		if (i == 0)
+			flags |= DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE;
+
 		isp->pd_dev[i] = dev_pm_domain_attach_by_id(dev, i);
 		if (IS_ERR(isp->pd_dev[i])) {
 			apple_isp_detach_genpd(isp);
@@ -61,9 +67,8 @@ static int apple_isp_attach_genpd(struct apple_isp *isp)
 		}
 
 		isp->pd_link[i] =
-			device_link_add(dev, isp->pd_dev[i],
-					DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME |
-						DL_FLAG_RPM_ACTIVE);
+			device_link_add(dev, isp->pd_dev[i], flags);
+
 		if (!isp->pd_link[i]) {
 			apple_isp_detach_genpd(isp);
 			return -EINVAL;
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index b62d389442e810..775a435c4a06ad 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -198,6 +198,7 @@ struct apple_isp {
 	int pd_count;
 	struct device **pd_dev;
 	struct device_link **pd_link;
+	bool pds_active;
 
 	int irq;
 
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 5c1739e58ab001..75405c2258239e 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -41,6 +41,46 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val)
 	writel(val, isp->gpio + reg);
 }
 
+int apple_isp_power_up_domains(struct apple_isp *isp)
+static int apple_isp_power_up_domains(struct apple_isp *isp)
+	int ret;
+
+	if (isp->pds_active)
+		return 0;
+
+	for (int i = 1; i < isp->pd_count; i++) {
+		ret = pm_runtime_get_sync(isp->pd_dev[i]);
+		if (ret < 0) {
+			dev_err(isp->dev,
+				"Failed to power up power domain %d: %d\n", i, ret);
+			while (--i != 1)
+				pm_runtime_put_sync(isp->pd_dev[i]);
+			return ret;
+		}
+	}
+
+	isp->pds_active = true;
+
+	return 0;
+}
+
+void apple_isp_power_down_domains(struct apple_isp *isp)
+static void apple_isp_power_down_domains(struct apple_isp *isp)
+	int ret;
+
+	if (!isp->pds_active)
+		return;
+
+	for (int i = isp->pd_count - 1; i >= 1; i--) {
+		ret = pm_runtime_put_sync(isp->pd_dev[i]);
+		if (ret < 0)
+			dev_err(isp->dev,
+				"Failed to power up power domain %d: %d\n", i, ret);
+	}
+
+	isp->pds_active = false;
+}
+
 void *apple_isp_translate(struct apple_isp *isp, struct isp_surf *surf,
 			  dma_addr_t iova, size_t size)
 {
@@ -209,11 +249,16 @@ static int isp_reset_coproc(struct apple_isp *isp)
 static void isp_firmware_shutdown_stage1(struct apple_isp *isp)
 {
 	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0);
+
+	apple_isp_power_down_domains(isp);
 }
 
 static int isp_firmware_boot_stage1(struct apple_isp *isp)
 {
 	int err, retries;
+	err = apple_isp_power_up_domains(isp);
+	if (err < 0)
+		return err;
 
 	err = isp_reset_coproc(isp);
 	if (err < 0)

From 3d4761ba75d6e4b1d810bb593ece169db13e8008 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:22:49 +0900
Subject: [PATCH 0418/1027] media: apple: isp: Zero out pages allocated to ISP

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-iommu.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c
index 845c35da0253ae..19d3c3bfa62ee9 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.c
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -22,7 +22,7 @@ static int isp_surf_alloc_pages(struct isp_surf *surf)
 		return -ENOMEM;
 
 	for (u32 i = 0; i < surf->num_pages; i++) {
-		surf->pages[i] = alloc_page(GFP_KERNEL);
+		surf->pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
 		if (surf->pages[i] == NULL)
 			goto free_pages;
 	}

From b269969b9514fc08cd834ed88eb812add50a003c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:22:58 +0900
Subject: [PATCH 0419/1027] media: apple: isp: Use cached IOMMU mappings

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-iommu.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-iommu.c b/drivers/media/platform/apple/isp/isp-iommu.c
index 19d3c3bfa62ee9..1ddd089d77355a 100644
--- a/drivers/media/platform/apple/isp/isp-iommu.c
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -113,7 +113,7 @@ static int isp_surf_iommu_map(struct apple_isp *isp, struct isp_surf *surf)
 	}
 
 	size = iommu_map_sgtable(isp->domain, surf->iova, &surf->sgt,
-				 IOMMU_READ | IOMMU_WRITE);
+				 IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE);
 	if (size < surf->size) {
 		dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n",
 			surf->iova);
@@ -231,7 +231,7 @@ int apple_isp_iommu_map_sgt(struct apple_isp *isp, struct isp_surf *surf,
 	}
 
 	mapped = iommu_map_sgtable(isp->domain, surf->iova, sgt,
-				   IOMMU_READ | IOMMU_WRITE);
+				   IOMMU_READ | IOMMU_WRITE | IOMMU_CACHE);
 	if (mapped < surf->size) {
 		dev_err(isp->dev, "failed to iommu_map sgt to iova 0x%llx\n",
 			surf->iova);

From 8e88a2b88343f2d739d0d6034e57e280629e0ccc Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:23:42 +0900
Subject: [PATCH 0420/1027] media: apple: isp: Rework meta surface handling &
 buffer return

Now we keep track of meta surfaces independently, and always allocate 16
of them, plus handle buffer return messages more correctly.

Fixes t8112 asserts (for some reason).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-drv.h  |   3 +-
 drivers/media/platform/apple/isp/isp-fw.c   |   1 +
 drivers/media/platform/apple/isp/isp-ipc.c  |  42 ----
 drivers/media/platform/apple/isp/isp-ipc.h  |   1 -
 drivers/media/platform/apple/isp/isp-v4l2.c | 239 ++++++++++++++------
 drivers/media/platform/apple/isp/isp-v4l2.h |   1 +
 6 files changed, 176 insertions(+), 111 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 775a435c4a06ad..31c527532aebac 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -43,6 +43,7 @@ struct isp_surf {
 	void *virt;
 	refcount_t refcount;
 	bool gc;
+	bool submitted;
 };
 
 struct isp_message {
@@ -221,6 +222,7 @@ struct apple_isp {
 	struct isp_surf *data_surf;
 	struct isp_surf *log_surf;
 	struct isp_surf *bt_surf;
+	struct isp_surf *meta_surfs[ISP_MAX_BUFFERS];
 	struct list_head gc;
 	struct workqueue_struct *wq;
 
@@ -252,7 +254,6 @@ struct isp_buffer {
 	struct vb2_v4l2_buffer vb;
 	struct list_head link;
 	struct isp_surf surfs[VB2_MAX_PLANES];
-	struct isp_surf *meta;
 };
 
 #define to_isp_buffer(x) container_of((x), struct isp_buffer, vb)
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 75405c2258239e..1db1294f843a7a 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -12,6 +12,7 @@
 #include "isp-iommu.h"
 #include "isp-ipc.h"
 #include "isp-regs.h"
+#include "isp-v4l2.h"
 
 #define ISP_FIRMWARE_MDELAY    1
 #define ISP_FIRMWARE_MAX_TRIES 1000
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 0475b3cf2699ee..7df434513b6c64 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -284,45 +284,3 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan)
 
 	return 0;
 }
-
-int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan)
-{
-	struct isp_message *req = &chan->req, *rsp = &chan->rsp;
-	struct isp_buffer *tmp, *buf;
-	int err = 0;
-
-	/* No need to read the whole struct */
-	u64 meta_iova;
-	u64 *p_meta_iova = apple_isp_translate(
-		isp, isp->bt_surf, req->arg0 + ISP_IPC_BUFEXC_STAT_META_OFFSET,
-		sizeof(u64));
-
-	if (!p_meta_iova) {
-		dev_err(isp->dev, "Failed to find bufexc stat meta\n");
-		return -EIO;
-	}
-	meta_iova = *p_meta_iova;
-
-	spin_lock(&isp->buf_lock);
-	list_for_each_entry_safe_reverse(buf, tmp, &isp->bufs_submitted, link) {
-		if ((u32)buf->meta->iova == (u32)meta_iova) {
-			enum vb2_buffer_state state = VB2_BUF_STATE_ERROR;
-
-			buf->vb.vb2_buf.timestamp = ktime_get_ns();
-			buf->vb.sequence = isp->sequence++;
-			buf->vb.field = V4L2_FIELD_NONE;
-			if (req->arg2 == ISP_IPC_BUFEXC_FLAG_RENDER)
-				state = VB2_BUF_STATE_DONE;
-			vb2_buffer_done(&buf->vb.vb2_buf, state);
-			list_del(&buf->link);
-			break;
-		}
-	}
-	spin_unlock(&isp->buf_lock);
-
-	rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
-	rsp->arg1 = 0x0;
-	rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK;
-
-	return err;
-}
diff --git a/drivers/media/platform/apple/isp/isp-ipc.h b/drivers/media/platform/apple/isp/isp-ipc.h
index 32d1e1bf321006..0c1d681835c72f 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.h
+++ b/drivers/media/platform/apple/isp/isp-ipc.h
@@ -21,6 +21,5 @@ int ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
 
 int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan);
 int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan);
-int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan);
 
 #endif /* __ISP_IPC_H__ */
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index ae15aee4513f00..ff2ea72f57ee9d 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -11,9 +11,9 @@
 
 #include "isp-cam.h"
 #include "isp-cmd.h"
-#include "isp-drv.h"
 #include "isp-iommu.h"
 #include "isp-ipc.h"
+#include "isp-fw.h"
 #include "isp-v4l2.h"
 
 #define ISP_MIN_FRAMES 2
@@ -26,7 +26,7 @@ static bool multiplanar = false;
 module_param(multiplanar, bool, 0644);
 MODULE_PARM_DESC(multiplanar, "Enable multiplanar API");
 
-struct isp_h2t_buffer {
+struct isp_buflist_buffer {
 	u64 iovas[ISP_MAX_PLANES];
 	u32 flags[ISP_MAX_PLANES];
 	u32 num_planes;
@@ -34,102 +34,190 @@ struct isp_h2t_buffer {
 	u32 tag;
 	u32 pad;
 } __packed;
-static_assert(sizeof(struct isp_h2t_buffer) == 0x40);
+static_assert(sizeof(struct isp_buflist_buffer) == 0x40);
 
-struct isp_h2t_args {
-	u64 enable;
+struct isp_buflist {
+	u64 type;
 	u64 num_buffers;
-	struct isp_h2t_buffer meta;
-	struct isp_h2t_buffer render;
-} __packed;
+	struct isp_buflist_buffer buffers[];
+};
+
+int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	struct isp_message *req = &chan->req, *rsp = &chan->rsp;
+	struct isp_buffer *tmp, *buf;
+	struct isp_buflist *bl;
+	u32 count;
+	int err = 0;
+
+	/* printk("H2T: 0x%llx 0x%llx 0x%llx\n", (long long)req->arg0,
+	       (long long)req->arg1, (long long)req->arg2); */
+
+	if (req->arg1 < sizeof(struct isp_buflist)) {
+		dev_err(isp->dev, "%s: Bad length 0x%llx\n", chan->name,
+			req->arg1);
+		return -EIO;
+	}
+
+	bl = apple_isp_translate(isp, isp->bt_surf, req->arg0, req->arg1);
+
+	count = bl->num_buffers;
+	if (count > (req->arg1 - sizeof(struct isp_buffer)) /
+			    sizeof(struct isp_buflist_buffer)) {
+		dev_err(isp->dev, "%s: Bad length 0x%llx\n", chan->name,
+			req->arg1);
+		return -EIO;
+	}
+
+	spin_lock(&isp->buf_lock);
+	for (int i = 0; i < count; i++) {
+		struct isp_buflist_buffer *bufd = &bl->buffers[i];
+
+		/* printk("Return: 0x%llx (%d)\n", bufd->iovas[0],
+		       bufd->pool_type); */
+
+		if (bufd->pool_type == 0) {
+			for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) {
+				struct isp_surf *meta = isp->meta_surfs[j];
+				if ((u32)bufd->iovas[0] == (u32)meta->iova) {
+					WARN_ON(!meta->submitted);
+					meta->submitted = false;
+				}
+			}
+		} else {
+			list_for_each_entry_safe_reverse(
+				buf, tmp, &isp->bufs_submitted, link) {
+				if ((u32)buf->surfs[0].iova ==
+				    (u32)bufd->iovas[0]) {
+					enum vb2_buffer_state state =
+						VB2_BUF_STATE_ERROR;
+
+					buf->vb.vb2_buf.timestamp =
+						ktime_get_ns();
+					buf->vb.sequence = isp->sequence++;
+					buf->vb.field = V4L2_FIELD_NONE;
+					if (req->arg2 ==
+					    ISP_IPC_BUFEXC_FLAG_RENDER)
+						state = VB2_BUF_STATE_DONE;
+					vb2_buffer_done(&buf->vb.vb2_buf,
+							state);
+					list_del(&buf->link);
+				}
+			}
+		}
+	}
+	spin_unlock(&isp->buf_lock);
+
+	rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
+	rsp->arg1 = 0x0;
+	rsp->arg2 = ISP_IPC_BUFEXC_FLAG_ACK;
+
+	return err;
+}
 
 static int isp_submit_buffers(struct apple_isp *isp)
 {
 	struct isp_format *fmt = isp_get_current_format(isp);
 	struct isp_channel *chan = isp->chan_bh;
 	struct isp_message *req = &chan->req;
-	struct isp_buffer *buf, *buf2, *tmp;
+	struct isp_buffer *buf, *tmp;
 	unsigned long flags;
 	size_t offset;
 	int err;
 
-	struct isp_h2t_args *args =
-		kzalloc(sizeof(struct isp_h2t_args), GFP_KERNEL);
-	if (!args)
-		return -ENOMEM;
+	struct isp_buflist *bl = isp->cmd_virt;
+	struct isp_buflist_buffer *bufd = &bl->buffers[0];
+
+	bl->type = 1;
+	bl->num_buffers = 0;
 
 	spin_lock_irqsave(&isp->buf_lock, flags);
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		struct isp_surf *meta = isp->meta_surfs[i];
+
+		if (meta->submitted)
+			continue;
+
+		/* printk("Submit: 0x%llx .. 0x%llx (meta)\n", meta->iova,
+		       meta->iova + meta->size); */
+
+		bufd->num_planes = 1;
+		bufd->pool_type = 0;
+		bufd->iovas[0] = meta->iova;
+		bufd->flags[0] = 0x40000000;
+		bufd++;
+		bl->num_buffers++;
+
+		meta->submitted = true;
+	}
+
 	while ((buf = list_first_entry_or_null(&isp->bufs_pending,
 					       struct isp_buffer, link))) {
-		args->meta.num_planes = 1;
-		args->meta.pool_type = 0;
-		args->meta.iovas[0] = buf->meta->iova;
-		args->meta.flags[0] = 0x40000000;
-
-		args->render.num_planes = fmt->num_planes;
-		args->render.pool_type = isp->hw->scl1 ?
-						 CISP_POOL_TYPE_RENDERED_SCL1 :
-						 CISP_POOL_TYPE_RENDERED;
+		memset(bufd, 0, sizeof(*bufd));
+
+		bufd->num_planes = fmt->num_planes;
+		bufd->pool_type = isp->hw->scl1 ? CISP_POOL_TYPE_RENDERED_SCL1 :
+						  CISP_POOL_TYPE_RENDERED;
 		offset = 0;
 		for (int j = 0; j < fmt->num_planes; j++) {
-			args->render.iovas[j] = buf->surfs[0].iova + offset;
-			args->render.flags[j] = 0x40000000;
+			bufd->iovas[j] = buf->surfs[0].iova + offset;
+			bufd->flags[j] = 0x40000000;
 			offset += fmt->plane_size[j];
 		}
 
+		/* printk("Submit: 0x%llx .. 0x%llx (render)\n",
+		       buf->surfs[0].iova,
+		       buf->surfs[0].iova + buf->surfs[0].size); */
+		bufd++;
+		bl->num_buffers++;
+
 		/*
 		 * Queue the buffer as submitted and release the lock for now.
 		 * We need to do this before actually submitting to avoid a
 		 * race with the buffer return codepath.
 		 */
 		list_move_tail(&buf->link, &isp->bufs_submitted);
-		spin_unlock_irqrestore(&isp->buf_lock, flags);
+	}
+
+	spin_unlock_irqrestore(&isp->buf_lock, flags);
+
+	req->arg0 = isp->cmd_iova;
+	req->arg1 = max_t(u64, ISP_IPC_BUFEXC_STAT_SIZE,
+			  ((uintptr_t)bufd - (uintptr_t)bl));
+	req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
+
+	err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
+	if (err) {
+		/* If we fail, consider the buffer not submitted. */
+		dev_err(isp->dev,
+			"%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n",
+			chan->name, req->arg0, req->arg1, req->arg2);
+
+		/*
+		 * Try to find the buffer in the list, and if it's
+		 * still there, move it back to the pending list.
+		 */
+		spin_lock_irqsave(&isp->buf_lock, flags);
 
-		args->enable = 0x1;
-		args->num_buffers = 2;
-
-		req->arg0 = isp->cmd_iova;
-		req->arg1 = ISP_IPC_BUFEXC_STAT_SIZE;
-		req->arg2 = ISP_IPC_BUFEXC_FLAG_COMMAND;
-
-		memcpy(isp->cmd_virt, args, sizeof(*args));
-		err = ipc_chan_send(isp, chan, ISP_BUFFER_TIMEOUT);
-		if (err) {
-			/* If we fail, consider the buffer not submitted. */
-			dev_err(isp->dev,
-				"%s: failed to send bufs: [0x%llx, 0x%llx, 0x%llx]\n",
-				chan->name, req->arg0, req->arg1, req->arg2);
-
-			/*
-			 * Try to find the buffer in the list, and if it's
-			 * still there, move it back to the pending list.
-			 */
-			spin_lock_irqsave(&isp->buf_lock, flags);
+		bufd = &bl->buffers[0];
+		for (int i = 0; i < bl->num_buffers; i++, bufd++) {
 			list_for_each_entry_safe_reverse(
-				buf2, tmp, &isp->bufs_submitted, link) {
-				if (buf2 == buf) {
+				buf, tmp, &isp->bufs_submitted, link) {
+				if (bufd->iovas[0] == buf->surfs[0].iova) {
 					list_move_tail(&buf->link,
 						       &isp->bufs_pending);
-					spin_unlock_irqrestore(&isp->buf_lock,
-							       flags);
-					return err;
 				}
 			}
-			/*
-			 * We didn't find the buffer, which means it somehow was returned
-			 * by the firmware even though submission failed?
-			 */
-			dev_err(isp->dev,
-				"buffer submission failed but buffer was returned?\n");
-			spin_unlock_irqrestore(&isp->buf_lock, flags);
-			return err;
+			for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) {
+				struct isp_surf *meta = isp->meta_surfs[j];
+				if (bufd->iovas[0] == meta->iova) {
+					meta->submitted = false;
+				}
+			}
 		}
 
-		spin_lock_irqsave(&isp->buf_lock, flags);
+		spin_unlock_irqrestore(&isp->buf_lock, flags);
 	}
-	spin_unlock_irqrestore(&isp->buf_lock, flags);
-
-	kfree(args);
 
 	return err;
 }
@@ -172,7 +260,6 @@ static void __isp_vb2_buf_cleanup(struct vb2_buffer *vb, unsigned int i)
 
 	while (i--)
 		apple_isp_iommu_unmap_sgt(isp, &buf->surfs[i]);
-	isp_free_surface(isp, buf->meta);
 }
 
 static void isp_vb2_buf_cleanup(struct vb2_buffer *vb)
@@ -188,10 +275,6 @@ static int isp_vb2_buf_init(struct vb2_buffer *vb)
 	unsigned int i;
 	int err;
 
-	buf->meta = isp_alloc_surface(isp, isp->hw->meta_size);
-	if (!buf->meta)
-		return -ENOMEM;
-
 	for (i = 0; i < vb->num_planes; i++) {
 		struct sg_table *sgt = vb2_dma_sg_plane_desc(vb, i);
 		err = apple_isp_iommu_map_sgt(isp, &buf->surfs[i], sgt,
@@ -678,6 +761,16 @@ int apple_isp_setup_video(struct apple_isp *isp)
 		return err;
 	}
 
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		isp->meta_surfs[i] =
+			isp_alloc_surface_vmap(isp, isp->hw->meta_size);
+		if (!isp->meta_surfs[i]) {
+			isp_err(isp, "failed to alloc meta surface\n");
+			err = -ENOMEM;
+			goto surf_cleanup;
+		}
+	}
+
 	media_device_init(&isp->mdev);
 	isp->v4l2_dev.mdev = &isp->mdev;
 	isp->mdev.ops = &isp_media_device_ops;
@@ -744,6 +837,13 @@ int apple_isp_setup_video(struct apple_isp *isp)
 	media_device_unregister(&isp->mdev);
 media_cleanup:
 	media_device_cleanup(&isp->mdev);
+surf_cleanup:
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		if (isp->meta_surfs[i])
+			isp_free_surface(isp, isp->meta_surfs[i]);
+		isp->meta_surfs[i] = NULL;
+	}
+
 	return err;
 }
 
@@ -753,4 +853,9 @@ void apple_isp_remove_video(struct apple_isp *isp)
 	v4l2_device_unregister(&isp->v4l2_dev);
 	media_device_unregister(&isp->mdev);
 	media_device_cleanup(&isp->mdev);
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		if (isp->meta_surfs[i])
+			isp_free_surface(isp, isp->meta_surfs[i]);
+		isp->meta_surfs[i] = NULL;
+	}
 }
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h
index df9b961d77bc17..e81e4de6ca641f 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.h
+++ b/drivers/media/platform/apple/isp/isp-v4l2.h
@@ -8,5 +8,6 @@
 
 int apple_isp_setup_video(struct apple_isp *isp);
 void apple_isp_remove_video(struct apple_isp *isp);
+int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan);
 
 #endif /* __ISP_V4L2_H__ */

From a8694a2910974e64f8c704559901df856894f3b5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:25:07 +0900
Subject: [PATCH 0421/1027] media: apple: isp: Clear IRQs when resetting coproc

XXX this might be wrong on some chips?

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 1db1294f843a7a..addf6ba6b37525 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -215,6 +215,7 @@ static int isp_reset_coproc(struct apple_isp *isp)
 {
 	int retries;
 	u32 status;
+	u32 val;
 
 	isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2);
 
@@ -230,6 +231,18 @@ static int isp_reset_coproc(struct apple_isp *isp)
 	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_4, 0xffffffff);
 	isp_coproc_write32(isp, ISP_COPROC_IRQ_MASK_5, 0xffffffff);
 
+	for (retries = 0; retries < 128; retries++) {
+		val = isp_coproc_read32(isp, 0x818);
+		if (val == 0)
+			break;
+	}
+
+	for (retries = 0; retries < 128; retries++) {
+		val = isp_coproc_read32(isp, 0x81c);
+		if (val == 0)
+			break;
+	}
+
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
 		status = isp_coproc_read32(isp, ISP_COPROC_STATUS);
 		if (status & ISP_COPROC_IN_WFI) {

From 37f240ee7b16afbe22cabf4893bff7a5eae9b0a2 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:25:50 +0900
Subject: [PATCH 0422/1027] media: apple: isp: Add a missing read barrier
 (possibly?)

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-ipc.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 7df434513b6c64..9d965ef7756b9b 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -157,6 +157,8 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan)
 
 static inline bool chan_tx_done(struct apple_isp *isp, struct isp_channel *chan)
 {
+	dma_rmb();
+
 	chan_read_msg(isp, chan, &chan->rsp);
 	if ((chan->rsp.arg0) == (chan->req.arg0 | ISP_IPC_FLAG_ACK)) {
 		chan_update_cursor(chan);

From 84bcce2f2eb50888dbc8fcea9f073d488b88a6cc Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:26:47 +0900
Subject: [PATCH 0423/1027] media: apple: isp: VMap only what is necessary,
 remove redundant logging state bit

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-ipc.c | 41 ++++++++--------------
 1 file changed, 15 insertions(+), 26 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 9d965ef7756b9b..54a88ed876c2b7 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -204,7 +204,7 @@ int ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan)
 	dma_addr_t iova = req->arg0 & ~ISP_IPC_FLAG_TERMINAL_ACK;
 	u32 size = req->arg1;
 	if (iova && size && size < sizeof(buf) &&
-	    test_bit(ISP_STATE_LOGGING, &isp->state)) {
+	    isp->log_surf) {
 		void *p = apple_isp_translate(isp, isp->log_surf, iova, size);
 		if (p) {
 			size = min_t(u32, size, 512);
@@ -243,42 +243,31 @@ int ipc_sm_handle(struct apple_isp *isp, struct isp_channel *chan)
 		rsp->arg1 = 0x0;
 		rsp->arg2 = 0x0; /* macOS uses this to index surfaces */
 
+		switch (surf->type) {
+		case 0x4c4f47: /* "LOG" */
+			isp->log_surf = surf;
+			break;
+		case 0x4d495343: /* "MISC" */
+			/* Hacky... maybe there's a better way to identify this surface? */
+			if (surf->size == 0xc000)
+				isp->bt_surf = surf;
+			break;
+		default:
+			// skip vmap
+			return 0;
+		}
+
 		err = isp_surf_vmap(isp, surf);
 		if (err < 0) {
 			isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n",
 				surf->iova, surf->size);
-		} else {
-			switch (surf->type) {
-			case 0x4c4f47: /* "LOG" */
-				isp->log_surf = surf;
-				break;
-			case 0x4d495343: /* "MISC" */
-				/* Hacky... maybe there's a better way to identify this surface? */
-				if (surf->size == 0xc000)
-					isp->bt_surf = surf;
-				break;
-			}
 		}
-
-#ifdef APPLE_ISP_DEBUG
-		/* Only enabled in debug builds so it shouldn't matter, but
-		* the LOG surface is always the first surface requested.
-		*/
-		if (!test_bit(ISP_STATE_LOGGING, &isp->state))
-			set_bit(ISP_STATE_LOGGING, &isp->state);
-#endif
-		/* To the gc it goes... */
-
 	} else {
 		/* This should be the shared surface free request, but
 		 * 1) The fw doesn't request to free all of what it requested
 		 * 2) The fw continues to access the surface after
 		 * So we link it to the gc, which runs after fw shutdown
 		 */
-#ifdef APPLE_ISP_DEBUG
-		if (test_bit(ISP_STATE_LOGGING, &isp->state))
-			clear_bit(ISP_STATE_LOGGING, &isp->state);
-#endif
 		rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
 		rsp->arg1 = 0x0;
 		rsp->arg2 = 0x0;

From 020d08587a3d68f4d2709af6afd745c68a4d787a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:28:03 +0900
Subject: [PATCH 0424/1027] media: apple: isp: Only reset coproc when
 necessary, fix minor race

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c | 13 +++++++++----
 1 file changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index addf6ba6b37525..a61c14453479d9 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -270,16 +270,22 @@ static void isp_firmware_shutdown_stage1(struct apple_isp *isp)
 static int isp_firmware_boot_stage1(struct apple_isp *isp)
 {
 	int err, retries;
+	u32 val;
+
 	err = apple_isp_power_up_domains(isp);
 	if (err < 0)
 		return err;
 
-	err = isp_reset_coproc(isp);
-	if (err < 0)
-		return err;
 
 	isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1);
 
+	val = isp_gpio_read32(isp, ISP_GPIO_1);
+	if (val == 0xfeedbabe) {
+		err = isp_reset_coproc(isp);
+		if (err < 0)
+			return err;
+	}
+
 	isp_gpio_write32(isp, ISP_GPIO_0, 0x0);
 	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
 	isp_gpio_write32(isp, ISP_GPIO_2, 0x0);
@@ -295,7 +301,6 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x10);
 
 	/* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */
-	isp_gpio_write32(isp, ISP_GPIO_7, 0x0);
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
 		u32 val = isp_gpio_read32(isp, ISP_GPIO_7);
 		if (val == 0x8042006) {

From f58c6441d884558171c82170a67dae51ad3ca520 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 4 Oct 2023 22:28:55 +0900
Subject: [PATCH 0425/1027] media: apple: isp: Option to use CMD_STOP (ifdeffed
 out)

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/media/platform/apple/isp/isp-fw.c | 19 +++++++++++++++++--
 1 file changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index a61c14453479d9..90895616c7c5ad 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -595,11 +595,26 @@ static int isp_stop_command_processor(struct apple_isp *isp)
 {
 	int retries;
 
+#if 0
+	int res = isp_cmd_stop(isp, 0);
+	if (res) {
+		isp_err(isp, "isp_cmd_stop() failed\n");
+		return res;
+	}
+
 	/* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */
 	isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9);
 
-	/* Their CISP_CMD_STOP implementation is buggy */
-	isp_cmd_suspend(isp);
+	isp_cmd_power_down(isp);
+#else
+	isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9);
+
+	int res = isp_cmd_suspend(isp);
+	if (res) {
+		isp_err(isp, "isp_cmd_suspend() failed\n");
+		return res;
+	}
+#endif
 
 	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
 		u32 val = isp_gpio_read32(isp, ISP_GPIO_0);

From 89183f22f25d5bb57b3001e3e5f1642049a5b3c6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 23:24:32 +0900
Subject: [PATCH 0426/1027] media: apple: isp: Use a more user-friendly device
 name

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.h  | 1 +
 drivers/media/platform/apple/isp/isp-v4l2.c | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 31c527532aebac..847e0a90975fb5 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -16,6 +16,7 @@
 
 /* #define APPLE_ISP_DEBUG */
 #define APPLE_ISP_DEVICE_NAME "apple-isp"
+#define APPLE_ISP_CARD_NAME "FaceTime HD Camera"
 
 #define ISP_MAX_CHANNELS      6
 #define ISP_IPC_MESSAGE_SIZE  64
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index ff2ea72f57ee9d..44d79cc8e0f444 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -448,7 +448,7 @@ static struct isp_preset *isp_select_preset(struct apple_isp *isp, u32 width,
 static int isp_vidioc_querycap(struct file *file, void *priv,
 			       struct v4l2_capability *cap)
 {
-	strscpy(cap->card, APPLE_ISP_DEVICE_NAME, sizeof(cap->card));
+	strscpy(cap->card, APPLE_ISP_CARD_NAME, sizeof(cap->card));
 	strscpy(cap->driver, APPLE_ISP_DEVICE_NAME, sizeof(cap->driver));
 
 	return 0;

From c315f1f3a3ea1194889bdb7268d16b4fda3af659 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 6 Oct 2023 21:34:11 +0200
Subject: [PATCH 0427/1027] media: apple: isp: Parse firmware version from
 device tree

Required since t8112-isp uses a 32-bit address in the
CISP_CMD_CH_SET_FILE_LOAD command with the macOS 12.4 firmware.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-cmd.c |  3 +-
 drivers/media/platform/apple/isp/isp-drv.c | 71 ++++++++++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.h |  8 +++
 3 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-cmd.c b/drivers/media/platform/apple/isp/isp-cmd.c
index 9c5808b4e831be..ee491d2cb42c5b 100644
--- a/drivers/media/platform/apple/isp/isp-cmd.c
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -2,6 +2,7 @@
 /* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
 
 #include "isp-cmd.h"
+#include "isp-drv.h"
 #include "isp-iommu.h"
 #include "isp-ipc.h"
 
@@ -261,7 +262,7 @@ int isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan)
 int isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr,
 			     u32 size)
 {
-	if (isp->hw->gen >= ISP_GEN_T8112) {
+	if (isp->fw_compat >= ISP_FIRMWARE_V_13_5) {
 		struct cmd_ch_set_file_load64 args = {
 			.opcode = CISP_OPCODE(CISP_CMD_CH_SET_FILE_LOAD),
 			.chan = chan,
diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 1365e5e29ca099..808b4618abb402 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -224,6 +224,72 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 	return err;
 }
 
+static const char * isp_fw2str(enum isp_firmware_version version)
+{
+	switch (version) {
+	case ISP_FIRMWARE_V_12_3:
+		return "12.3";
+	case ISP_FIRMWARE_V_12_4:
+		return "12.4";
+	case ISP_FIRMWARE_V_13_5:
+		return "13.5";
+	default:
+		return "unknown";
+	}
+}
+
+#define ISP_FW_VERSION_MIN_LEN	3
+#define ISP_FW_VERSION_MAX_LEN	5
+
+static enum isp_firmware_version isp_read_fw_version(struct device *dev,
+						     const char *name)
+{
+	u32 ver[ISP_FW_VERSION_MAX_LEN];
+	int len = of_property_read_variable_u32_array(dev->of_node, name, ver,
+						      ISP_FW_VERSION_MIN_LEN,
+						      ISP_FW_VERSION_MAX_LEN);
+
+	switch (len) {
+	case 3:
+		if (ver[0] == 12 && ver[1] == 3 && ver[2] <= 1)
+			return ISP_FIRMWARE_V_12_3;
+		else if (ver[0] == 12 && ver[1] == 4 && ver[2] == 0)
+			return ISP_FIRMWARE_V_12_4;
+		else if (ver[0] == 13 && ver[1] == 5 && ver[2] == 0)
+			return ISP_FIRMWARE_V_13_5;
+
+		dev_warn(dev, "unknown %s: %d.%d.%d\n", name, ver[0], ver[1], ver[2]);
+		break;
+	case 4:
+		dev_warn(dev, "unknown %s: %d.%d.%d.%d\n", name, ver[0], ver[1],
+			 ver[2], ver[3]);
+		break;
+	case 5:
+		dev_warn(dev, "unknown %s: %d.%d.%d.%d.%d\n", name, ver[0],
+			 ver[1], ver[2], ver[3], ver[4]);
+		break;
+	default:
+		dev_warn(dev, "could not parse %s: %d\n", name, len);
+		break;
+	}
+
+	return ISP_FIRMWARE_V_UNKNOWN;
+}
+
+static enum isp_firmware_version isp_check_firmware_version(struct device *dev)
+{
+	enum isp_firmware_version version, compat;
+
+	/* firmware version is just informative */
+	version = isp_read_fw_version(dev, "apple,firmware-version");
+	compat = isp_read_fw_version(dev, "apple,firmware-compat");
+
+	dev_info(dev, "ISP firmware-compat: %s (FW: %s)\n", isp_fw2str(compat),
+		 isp_fw2str(version));
+
+	return compat;
+}
+
 static int apple_isp_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -243,6 +309,11 @@ static int apple_isp_probe(struct platform_device *pdev)
 	platform_set_drvdata(pdev, isp);
 	dev_set_drvdata(dev, isp);
 
+	/* Differences between firmware versions are rather minor so try to work
+	 * with unknown firmware.
+	 */
+	isp->fw_compat = isp_check_firmware_version(dev);
+
 	err = of_property_read_u32(dev->of_node, "apple,platform-id",
 				   &isp->platform_id);
 	if (err) {
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 847e0a90975fb5..2ccd3524be65b8 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -32,6 +32,13 @@ enum isp_generation {
 	ISP_GEN_T8112,
 };
 
+enum isp_firmware_version {
+	ISP_FIRMWARE_V_UNKNOWN,
+	ISP_FIRMWARE_V_12_3,
+	ISP_FIRMWARE_V_12_4,
+	ISP_FIRMWARE_V_13_5,
+};
+
 struct isp_surf {
 	struct drm_mm_node *mm;
 	struct list_head head;
@@ -180,6 +187,7 @@ struct isp_format {
 struct apple_isp {
 	struct device *dev;
 	const struct apple_isp_hw *hw;
+	enum isp_firmware_version fw_compat;
 	u32 platform_id;
 	u32 temporal_filter;
 	struct isp_preset *presets;

From 7de8d18d22362fcda3a797986a356f3e8de721f8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 8 Oct 2023 18:02:12 +0900
Subject: [PATCH 0428/1027] media: apple: isp: Show camera presets even for
 unsupported sensors

This makes adding support easier.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-cam.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index cc0c24c3cfb715..fac81fef11bc7e 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -212,6 +212,10 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
 	print_hex_dump(KERN_INFO, "apple-isp: ch: ", DUMP_PREFIX_NONE, 32, 4,
 		       args, sizeof(*args), false);
 
+	for (u32 ps = 0; ps < args->num_presets; ps++) {
+		isp_ch_get_camera_preset(isp, ch, ps);
+	}
+
 	err = isp_ch_get_sensor_id(isp, ch);
 	if (err ||
 	    (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) {
@@ -221,10 +225,6 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
 		return -ENODEV;
 	}
 
-	for (u32 ps = 0; ps < args->num_presets; ps++) {
-		isp_ch_get_camera_preset(isp, ch, ps);
-	}
-
 exit:
 	kfree(args);
 

From ca4a8a3c21c5576a45f7cd76a0b3845c9d8696ce Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 8 Oct 2023 18:03:20 +0900
Subject: [PATCH 0429/1027] media: apple: isp: Enable IMX364 sensor

This is used on j45[67].

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-cam.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c
index fac81fef11bc7e..c889173bd348f3 100644
--- a/drivers/media/platform/apple/isp/isp-cam.c
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -218,7 +218,8 @@ static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
 
 	err = isp_ch_get_sensor_id(isp, ch);
 	if (err ||
-	    (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01)) {
+	    (fmt->id != ISP_IMX248_1820_01 && fmt->id != ISP_IMX558_1921_01 &&
+	     fmt->id != ISP_IMX364_8720_01)) {
 		dev_err(isp->dev,
 			"ch %d: unsupported sensor. Please file a bug report with hardware info & dmesg trace.\n",
 			ch);

From f03fb7e0dbeed6547ce38c2dc88a9947d711281f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 02:41:08 +0900
Subject: [PATCH 0430/1027] media: apple: isp: implement ENUM_FRAMEINTERVALS
 trivially

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-v4l2.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index 44d79cc8e0f444..34a0d6d91c1274 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -497,6 +497,18 @@ static int isp_vidioc_enum_framesizes(struct file *file, void *fh,
 	return 0;
 }
 
+static int isp_vidioc_enum_frameintervals(struct file *filp, void *priv,
+					  struct v4l2_frmivalenum *interval)
+{
+	if (interval->index != 0)
+		return -EINVAL;
+
+	interval->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	interval->discrete.numerator = 1;
+	interval->discrete.denominator = 30;
+	return 0;
+}
+
 static inline void isp_get_sp_pix_format(struct apple_isp *isp,
 					 struct v4l2_format *f,
 					 struct isp_format *fmt)
@@ -717,6 +729,7 @@ static const struct v4l2_ioctl_ops isp_v4l2_ioctl_ops = {
 	.vidioc_try_fmt_vid_cap_mplane = isp_vidioc_try_format_mplane,
 
 	.vidioc_enum_framesizes = isp_vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = isp_vidioc_enum_frameintervals,
 	.vidioc_enum_input = isp_vidioc_enum_input,
 	.vidioc_g_input = isp_vidioc_get_input,
 	.vidioc_s_input = isp_vidioc_set_input,

From 4a0b2ff83da9d0bdccf6292e2972f98775d42a37 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 3 Nov 2023 20:49:38 +0900
Subject: [PATCH 0431/1027] media: apple: isp: Use a mutex instead of a
 spinlock for channels

Fixes lockdep splats because we do surface stuff with this held, which
takes a mutex.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/media/platform/apple/isp/isp-drv.h | 2 +-
 drivers/media/platform/apple/isp/isp-fw.c  | 2 +-
 drivers/media/platform/apple/isp/isp-ipc.c | 4 ++--
 3 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 2ccd3524be65b8..4bdf7616e0efe4 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -76,7 +76,7 @@ struct isp_channel {
 	void *virt;
 	u32 doorbell;
 	u32 cursor;
-	spinlock_t lock;
+	struct mutex lock;
 	struct isp_message req;
 	struct isp_message rsp;
 	const struct isp_chan_ops *ops;
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 90895616c7c5ad..3e322d40fb881f 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -493,7 +493,7 @@ static int isp_fill_channel_info(struct apple_isp *isp)
 		chan->virt =
 			apple_isp_ipc_translate(isp, desc.iova, chan->size);
 		chan->cursor = 0;
-		spin_lock_init(&chan->lock);
+		mutex_init(&chan->lock);
 
 		if (!chan->virt) {
 			dev_err(isp->dev, "Failed to find channel buffer\n");
diff --git a/drivers/media/platform/apple/isp/isp-ipc.c b/drivers/media/platform/apple/isp/isp-ipc.c
index 54a88ed876c2b7..7300eb60892116 100644
--- a/drivers/media/platform/apple/isp/isp-ipc.c
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -138,7 +138,7 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan)
 {
 	int err = 0;
 
-	spin_lock(&chan->lock);
+	mutex_lock(&chan->lock);
 	while (1) {
 		chan_read_msg(isp, chan, &chan->req);
 		if (chan_rx_done(isp, chan)) {
@@ -150,7 +150,7 @@ int ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan)
 			break;
 		}
 	}
-	spin_unlock(&chan->lock);
+	mutex_unlock(&chan->lock);
 
 	return err;
 }

From 2eb812826ec58e3b803055a8aa0fd857f1675f14 Mon Sep 17 00:00:00 2001
From: Eileen Yoon <eyn@gmx.com>
Date: Fri, 13 Oct 2023 21:09:43 +0900
Subject: [PATCH 0432/1027] media: apple: isp: Support system sleep

Signed-off-by: Eileen Yoon <eyn@gmx.com>
---
 drivers/media/platform/apple/isp/isp-drv.c  | 29 +++++++++++-
 drivers/media/platform/apple/isp/isp-drv.h  |  1 +
 drivers/media/platform/apple/isp/isp-fw.c   | 13 ++++--
 drivers/media/platform/apple/isp/isp-v4l2.c | 50 ++++++++++++++++++---
 drivers/media/platform/apple/isp/isp-v4l2.h |  3 ++
 5 files changed, 84 insertions(+), 12 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index 808b4618abb402..fdbe93ca14b6d6 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -550,17 +550,42 @@ static const struct of_device_id apple_isp_of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, apple_isp_of_match);
 
+static __maybe_unused int apple_isp_runtime_suspend(struct device *dev)
+{
+	/* RPM sleep is called when the V4L2 file handle is closed */
+	return 0;
+}
+
+static __maybe_unused int apple_isp_runtime_resume(struct device *dev)
+{
+	return 0;
+}
+
 static __maybe_unused int apple_isp_suspend(struct device *dev)
 {
+	struct apple_isp *isp = dev_get_drvdata(dev);
+
+	/* We must restore V4L2 context on system resume. If we were streaming
+	 * before, we (essentially) stop streaming and start streaming again.
+	 */
+	apple_isp_video_suspend(isp);
+
 	return 0;
 }
 
 static __maybe_unused int apple_isp_resume(struct device *dev)
 {
+	struct apple_isp *isp = dev_get_drvdata(dev);
+
+	apple_isp_video_resume(isp);
+
 	return 0;
 }
-DEFINE_RUNTIME_DEV_PM_OPS(apple_isp_pm_ops, apple_isp_suspend, apple_isp_resume,
-			  NULL);
+
+static const struct dev_pm_ops apple_isp_pm_ops = {
+	SYSTEM_SLEEP_PM_OPS(apple_isp_suspend, apple_isp_resume)
+	RUNTIME_PM_OPS(apple_isp_runtime_suspend, apple_isp_runtime_resume, NULL)
+};
 
 static struct platform_driver apple_isp_driver = {
 	.driver	= {
diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h
index 4bdf7616e0efe4..96a1d0b39f860d 100644
--- a/drivers/media/platform/apple/isp/isp-drv.h
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -270,6 +270,7 @@ struct isp_buffer {
 enum {
 	ISP_STATE_STREAMING,
 	ISP_STATE_LOGGING,
+	ISP_STATE_SLEEPING,
 };
 
 #ifdef APPLE_ISP_DEBUG
diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c
index 3e322d40fb881f..a39f5fb4445fa7 100644
--- a/drivers/media/platform/apple/isp/isp-fw.c
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -42,8 +42,8 @@ static inline void isp_gpio_write32(struct apple_isp *isp, u32 reg, u32 val)
 	writel(val, isp->gpio + reg);
 }
 
-int apple_isp_power_up_domains(struct apple_isp *isp)
 static int apple_isp_power_up_domains(struct apple_isp *isp)
+{
 	int ret;
 
 	if (isp->pds_active)
@@ -65,8 +65,8 @@ static int apple_isp_power_up_domains(struct apple_isp *isp)
 	return 0;
 }
 
-void apple_isp_power_down_domains(struct apple_isp *isp)
 static void apple_isp_power_down_domains(struct apple_isp *isp)
+{
 	int ret;
 
 	if (!isp->pds_active)
@@ -270,7 +270,7 @@ static void isp_firmware_shutdown_stage1(struct apple_isp *isp)
 static int isp_firmware_boot_stage1(struct apple_isp *isp)
 {
 	int err, retries;
-	u32 val;
+	// u32 val;
 
 	err = apple_isp_power_up_domains(isp);
 	if (err < 0)
@@ -279,12 +279,19 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp)
 
 	isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1);
 
+#if 0
+	/* This doesn't work well with system sleep */
 	val = isp_gpio_read32(isp, ISP_GPIO_1);
 	if (val == 0xfeedbabe) {
 		err = isp_reset_coproc(isp);
 		if (err < 0)
 			return err;
 	}
+#endif
+
+	err = isp_reset_coproc(isp);
+	if (err < 0)
+		return err;
 
 	isp_gpio_write32(isp, ISP_GPIO_0, 0x0);
 	isp_gpio_write32(isp, ISP_GPIO_1, 0x0);
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c
index 34a0d6d91c1274..0561653ea7becd 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.c
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -337,13 +337,10 @@ static void isp_vb2_buf_queue(struct vb2_buffer *vb)
 		isp_submit_buffers(isp);
 }
 
-static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+static int apple_isp_start_streaming(struct apple_isp *isp)
 {
-	struct apple_isp *isp = vb2_get_drv_priv(q);
 	int err;
 
-	isp->sequence = 0;
-
 	err = apple_isp_start_camera(isp);
 	if (err) {
 		dev_err(isp->dev, "failed to start camera: %d\n", err);
@@ -373,16 +370,55 @@ static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
 	return err;
 }
 
-static void isp_vb2_stop_streaming(struct vb2_queue *q)
+static void apple_isp_stop_streaming(struct apple_isp *isp)
 {
-	struct apple_isp *isp = vb2_get_drv_priv(q);
-
 	clear_bit(ISP_STATE_STREAMING, &isp->state);
 	apple_isp_stop_capture(isp);
 	apple_isp_stop_camera(isp);
+}
+
+static int isp_vb2_start_streaming(struct vb2_queue *q, unsigned int count)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(q);
+
+	isp->sequence = 0;
+
+	return apple_isp_start_streaming(isp);
+}
+
+static void isp_vb2_stop_streaming(struct vb2_queue *q)
+{
+	struct apple_isp *isp = vb2_get_drv_priv(q);
+
+	apple_isp_stop_streaming(isp);
 	isp_vb2_release_buffers(isp, VB2_BUF_STATE_ERROR);
 }
 
+int apple_isp_video_suspend(struct apple_isp *isp)
+{
+	/* Swap into STATE_SLEEPING as isp_vb2_buf_queue() submits on
+	 * STATE_STREAMING.
+	 */
+	if (test_bit(ISP_STATE_STREAMING, &isp->state)) {
+		/* Signal buffers to be recycled for clean shutdown */
+		isp_vb2_release_buffers(isp, VB2_BUF_STATE_QUEUED);
+		apple_isp_stop_streaming(isp);
+		set_bit(ISP_STATE_SLEEPING, &isp->state);
+	}
+
+	return 0;
+}
+
+int apple_isp_video_resume(struct apple_isp *isp)
+{
+	if (test_bit(ISP_STATE_SLEEPING, &isp->state)) {
+		clear_bit(ISP_STATE_SLEEPING, &isp->state);
+		apple_isp_start_streaming(isp);
+	}
+
+	return 0;
+}
+
 static const struct vb2_ops isp_vb2_ops = {
 	.queue_setup = isp_vb2_queue_setup,
 	.buf_init = isp_vb2_buf_init,
diff --git a/drivers/media/platform/apple/isp/isp-v4l2.h b/drivers/media/platform/apple/isp/isp-v4l2.h
index e81e4de6ca641f..4d47deeb83b055 100644
--- a/drivers/media/platform/apple/isp/isp-v4l2.h
+++ b/drivers/media/platform/apple/isp/isp-v4l2.h
@@ -10,4 +10,7 @@ int apple_isp_setup_video(struct apple_isp *isp);
 void apple_isp_remove_video(struct apple_isp *isp);
 int ipc_bt_handle(struct apple_isp *isp, struct isp_channel *chan);
 
+int apple_isp_video_suspend(struct apple_isp *isp);
+int apple_isp_video_resume(struct apple_isp *isp);
+
 #endif /* __ISP_V4L2_H__ */

From 082a51636fe6428bdf9d364274b302956bca002e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:09:24 +0900
Subject: [PATCH 0433/1027] soc: apple: Add DockChannel driver

DockChannel is a simple FIFO interface used to communicate between SoC
blocks. Add a driver that represents the shared interrupt controller for
the DockChannel block, and then exposes probe and data transfer
functions that child device drivers can use to instantiate individual
FIFOs.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/soc/apple/Kconfig             |  10 +
 drivers/soc/apple/Makefile            |   3 +
 drivers/soc/apple/dockchannel.c       | 406 ++++++++++++++++++++++++++
 include/linux/soc/apple/dockchannel.h |  26 ++
 4 files changed, 445 insertions(+)
 create mode 100644 drivers/soc/apple/dockchannel.c
 create mode 100644 include/linux/soc/apple/dockchannel.h

diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index 6388cbe1e56b5a..cceb514b05bbfa 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -41,6 +41,16 @@ config APPLE_SART
 
 	  Say 'y' here if you have an Apple SoC.
 
+config APPLE_DOCKCHANNEL
+	tristate "Apple DockChannel FIFO"
+	depends on ARCH_APPLE || COMPILE_TEST
+	default ARCH_APPLE
+	help
+	  DockChannel is a simple FIFO used on Apple SoCs for debug and inter-processor
+	  communications.
+
+	  Say 'y' here if you have an Apple SoC.
+
 endmenu
 
 endif
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 4d9ab8f3037b71..07d43360426110 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -8,3 +8,6 @@ apple-rtkit-y = rtkit.o rtkit-crashlog.o
 
 obj-$(CONFIG_APPLE_SART) += apple-sart.o
 apple-sart-y = sart.o
+
+obj-$(CONFIG_APPLE_DOCKCHANNEL) += apple-dockchannel.o
+apple-dockchannel-y = dockchannel.o
diff --git a/drivers/soc/apple/dockchannel.c b/drivers/soc/apple/dockchannel.c
new file mode 100644
index 00000000000000..a3fe4db5158134
--- /dev/null
+++ b/drivers/soc/apple/dockchannel.c
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple DockChannel FIFO driver
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <asm/unaligned.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/irq.h>
+#include <linux/irqchip/chained_irq.h>
+#include <linux/irqdomain.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+
+#define DOCKCHANNEL_MAX_IRQ	32
+
+#define DOCKCHANNEL_TX_TIMEOUT_MS 1000
+#define DOCKCHANNEL_RX_TIMEOUT_MS 1000
+
+#define IRQ_MASK		0x0
+#define IRQ_FLAG		0x4
+
+#define IRQ_TX			BIT(0)
+#define IRQ_RX			BIT(1)
+
+#define CONFIG_TX_THRESH	0x0
+#define CONFIG_RX_THRESH	0x4
+
+#define DATA_TX8		0x4
+#define DATA_TX16		0x8
+#define DATA_TX24		0xc
+#define DATA_TX32		0x10
+#define DATA_TX_FREE		0x14
+#define DATA_RX8		0x1c
+#define DATA_RX16		0x20
+#define DATA_RX24		0x24
+#define DATA_RX32		0x28
+#define DATA_RX_COUNT		0x2c
+
+struct dockchannel {
+	struct device *dev;
+	int tx_irq;
+	int rx_irq;
+
+	void __iomem *config_base;
+	void __iomem *data_base;
+
+	u32 fifo_size;
+	bool awaiting;
+	struct completion tx_comp;
+	struct completion rx_comp;
+
+	void *cookie;
+	void (*data_available)(void *cookie, size_t avail);
+};
+
+struct dockchannel_common {
+	struct device *dev;
+	struct irq_domain *domain;
+	int irq;
+
+	void __iomem *irq_base;
+};
+
+/* Dockchannel FIFO functions */
+
+static irqreturn_t dockchannel_tx_irq(int irq, void *data)
+{
+	struct dockchannel *dockchannel = data;
+
+	disable_irq_nosync(irq);
+	complete(&dockchannel->tx_comp);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t dockchannel_rx_irq(int irq, void *data)
+{
+	struct dockchannel *dockchannel = data;
+
+	disable_irq_nosync(irq);
+
+	if (dockchannel->awaiting) {
+		return IRQ_WAKE_THREAD;
+	} else {
+		complete(&dockchannel->rx_comp);
+		return IRQ_HANDLED;
+	}
+}
+
+static irqreturn_t dockchannel_rx_irq_thread(int irq, void *data)
+{
+	struct dockchannel *dockchannel = data;
+	size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+
+	dockchannel->awaiting = false;
+	dockchannel->data_available(dockchannel->cookie, avail);
+
+	return IRQ_HANDLED;
+}
+
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count)
+{
+	size_t left = count;
+	const u8 *p = buf;
+
+	while (left > 0) {
+		size_t avail = readl_relaxed(dockchannel->data_base + DATA_TX_FREE);
+		size_t block = min(left, avail);
+
+		if (avail == 0) {
+			size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+			writel_relaxed(threshold, dockchannel->config_base + CONFIG_TX_THRESH);
+			reinit_completion(&dockchannel->tx_comp);
+			enable_irq(dockchannel->tx_irq);
+
+			if (!wait_for_completion_timeout(&dockchannel->tx_comp,
+                                                 msecs_to_jiffies(DOCKCHANNEL_TX_TIMEOUT_MS))) {
+				disable_irq(dockchannel->tx_irq);
+				return -ETIMEDOUT;
+			}
+
+			continue;
+		}
+
+		while (block >= 4) {
+			writel_relaxed(get_unaligned_le32(p), dockchannel->data_base + DATA_TX32);
+			p += 4;
+			left -= 4;
+			block -= 4;
+		}
+		while (block > 0) {
+			writeb_relaxed(*p++, dockchannel->data_base + DATA_TX8);
+			left--;
+			block--;
+		}
+	}
+
+	return count;
+}
+EXPORT_SYMBOL(dockchannel_send);
+
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count)
+{
+	size_t left = count;
+	u8 *p = buf;
+
+	while (left > 0) {
+		size_t avail = readl_relaxed(dockchannel->data_base + DATA_RX_COUNT);
+		size_t block = min(left, avail);
+
+		if (avail == 0) {
+			size_t threshold = min((size_t)(dockchannel->fifo_size / 2), left);
+
+			writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+			reinit_completion(&dockchannel->rx_comp);
+			enable_irq(dockchannel->rx_irq);
+
+			if (!wait_for_completion_timeout(&dockchannel->rx_comp,
+                                                 msecs_to_jiffies(DOCKCHANNEL_RX_TIMEOUT_MS))) {
+				disable_irq(dockchannel->rx_irq);
+				return -ETIMEDOUT;
+			}
+
+			continue;
+		}
+
+		while (block >= 4) {
+			put_unaligned_le32(readl_relaxed(dockchannel->data_base + DATA_RX32), p);
+			p += 4;
+			left -= 4;
+			block -= 4;
+		}
+		while (block > 0) {
+			*p++ = readl_relaxed(dockchannel->data_base + DATA_RX8) >> 8;
+			left--;
+			block--;
+		}
+	}
+
+	return count;
+}
+EXPORT_SYMBOL(dockchannel_recv);
+
+int dockchannel_await(struct dockchannel *dockchannel,
+			    void (*callback)(void *cookie, size_t avail),
+			    void *cookie, size_t count)
+{
+	size_t threshold = min((size_t)dockchannel->fifo_size, count);
+
+	if (!count) {
+		dockchannel->awaiting = false;
+		disable_irq(dockchannel->rx_irq);
+		return 0;
+	}
+
+	dockchannel->data_available = callback;
+	dockchannel->cookie = cookie;
+	dockchannel->awaiting = true;
+	writel_relaxed(threshold, dockchannel->config_base + CONFIG_RX_THRESH);
+	enable_irq(dockchannel->rx_irq);
+
+	return threshold;
+}
+EXPORT_SYMBOL(dockchannel_await);
+
+struct dockchannel *dockchannel_init(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dockchannel *dockchannel;
+	int ret;
+
+	dockchannel = devm_kzalloc(dev, sizeof(*dockchannel), GFP_KERNEL);
+	if (!dockchannel)
+		return ERR_PTR(-ENOMEM);
+
+	dockchannel->dev = dev;
+	dockchannel->config_base = devm_platform_ioremap_resource_byname(pdev, "config");
+	if (IS_ERR(dockchannel->config_base))
+		return (__force void *)dockchannel->config_base;
+
+	dockchannel->data_base = devm_platform_ioremap_resource_byname(pdev, "data");
+	if (IS_ERR(dockchannel->data_base))
+		return (__force void *)dockchannel->data_base;
+
+	ret = of_property_read_u32(dev->of_node, "apple,fifo-size", &dockchannel->fifo_size);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Missing apple,fifo-size property"));
+
+	init_completion(&dockchannel->tx_comp);
+	init_completion(&dockchannel->rx_comp);
+
+	dockchannel->tx_irq = platform_get_irq_byname(pdev, "tx");
+	if (dockchannel->tx_irq <= 0) {
+		return ERR_PTR(dev_err_probe(dev, dockchannel->tx_irq,
+				     "Failed to get TX IRQ"));
+	}
+
+	dockchannel->rx_irq = platform_get_irq_byname(pdev, "rx");
+	if (dockchannel->rx_irq <= 0) {
+		return ERR_PTR(dev_err_probe(dev, dockchannel->rx_irq,
+				     "Failed to get RX IRQ"));
+	}
+
+	ret = devm_request_irq(dev, dockchannel->tx_irq, dockchannel_tx_irq, IRQF_NO_AUTOEN,
+			       "apple-dockchannel-tx", dockchannel);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Failed to request TX IRQ"));
+
+	ret = devm_request_threaded_irq(dev, dockchannel->rx_irq, dockchannel_rx_irq,
+					dockchannel_rx_irq_thread, IRQF_NO_AUTOEN,
+					"apple-dockchannel-rx", dockchannel);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Failed to request RX IRQ"));
+
+	return dockchannel;
+}
+EXPORT_SYMBOL(dockchannel_init);
+
+
+/* Dockchannel IRQchip */
+
+static void dockchannel_irq(struct irq_desc *desc)
+{
+	unsigned int irq = irq_desc_get_irq(desc);
+	struct irq_chip *chip = irq_desc_get_chip(desc);
+	struct dockchannel_common *dcc = irq_get_handler_data(irq);
+	unsigned long flags = readl_relaxed(dcc->irq_base + IRQ_FLAG);
+	int bit;
+
+	chained_irq_enter(chip, desc);
+
+	for_each_set_bit(bit, &flags, DOCKCHANNEL_MAX_IRQ)
+		generic_handle_domain_irq(dcc->domain, bit);
+
+	chained_irq_exit(chip, desc);
+}
+
+static void dockchannel_irq_ack(struct irq_data *data)
+{
+	struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+	unsigned int hwirq = data->hwirq;
+
+	writel_relaxed(BIT(hwirq), dcc->irq_base + IRQ_FLAG);
+}
+
+static void dockchannel_irq_mask(struct irq_data *data)
+{
+	struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+	unsigned int hwirq = data->hwirq;
+	u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+	writel_relaxed(val & ~BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static void dockchannel_irq_unmask(struct irq_data *data)
+{
+	struct dockchannel_common *dcc = irq_data_get_irq_chip_data(data);
+	unsigned int hwirq = data->hwirq;
+	u32 val = readl_relaxed(dcc->irq_base + IRQ_MASK);
+
+	writel_relaxed(val | BIT(hwirq), dcc->irq_base + IRQ_MASK);
+}
+
+static const struct irq_chip dockchannel_irqchip = {
+	.name = "dockchannel-irqc",
+	.irq_ack = dockchannel_irq_ack,
+	.irq_mask = dockchannel_irq_mask,
+	.irq_unmask = dockchannel_irq_unmask,
+};
+
+static int dockchannel_irq_domain_map(struct irq_domain *d, unsigned int virq,
+				      irq_hw_number_t hw)
+{
+	irq_set_chip_data(virq, d->host_data);
+	irq_set_chip_and_handler(virq, &dockchannel_irqchip, handle_level_irq);
+
+	return 0;
+}
+
+static const struct irq_domain_ops dockchannel_irq_domain_ops = {
+	.xlate	= irq_domain_xlate_twocell,
+	.map	= dockchannel_irq_domain_map,
+};
+
+static int dockchannel_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dockchannel_common *dcc;
+	struct device_node *child;
+
+	dcc = devm_kzalloc(dev, sizeof(*dcc), GFP_KERNEL);
+	if (!dcc)
+		return -ENOMEM;
+
+	dcc->dev = dev;
+	platform_set_drvdata(pdev, dcc);
+
+	dcc->irq_base = devm_platform_ioremap_resource_byname(pdev, "irq");
+	if (IS_ERR(dcc->irq_base))
+		return PTR_ERR(dcc->irq_base);
+
+	writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+	writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+
+	dcc->domain = irq_domain_add_linear(dev->of_node, DOCKCHANNEL_MAX_IRQ,
+					    &dockchannel_irq_domain_ops, dcc);
+	if (!dcc->domain)
+		return -ENOMEM;
+
+	dcc->irq = platform_get_irq(pdev, 0);
+	if (dcc->irq <= 0)
+		return dev_err_probe(dev, dcc->irq, "Failed to get IRQ");
+
+	irq_set_handler_data(dcc->irq, dcc);
+	irq_set_chained_handler(dcc->irq, dockchannel_irq);
+
+	for_each_child_of_node(dev->of_node, child)
+		of_platform_device_create(child, NULL, dev);
+
+	return 0;
+}
+
+static void dockchannel_remove(struct platform_device *pdev)
+{
+	struct dockchannel_common *dcc = platform_get_drvdata(pdev);
+	int hwirq;
+
+	device_for_each_child(&pdev->dev, NULL, of_platform_device_destroy);
+
+	irq_set_chained_handler_and_data(dcc->irq, NULL, NULL);
+
+	for (hwirq = 0; hwirq < DOCKCHANNEL_MAX_IRQ; hwirq++)
+		irq_dispose_mapping(irq_find_mapping(dcc->domain, hwirq));
+
+	irq_domain_remove(dcc->domain);
+
+	writel_relaxed(0, dcc->irq_base + IRQ_MASK);
+	writel_relaxed(~0, dcc->irq_base + IRQ_FLAG);
+}
+
+static const struct of_device_id dockchannel_of_match[] = {
+	{ .compatible = "apple,dockchannel" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_of_match);
+
+static struct platform_driver dockchannel_driver = {
+	.driver = {
+		.name = "dockchannel",
+		.of_match_table = dockchannel_of_match,
+	},
+	.probe = dockchannel_probe,
+	.remove = dockchannel_remove,
+};
+module_platform_driver(dockchannel_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple DockChannel driver");
diff --git a/include/linux/soc/apple/dockchannel.h b/include/linux/soc/apple/dockchannel.h
new file mode 100644
index 00000000000000..0b7093935ddf47
--- /dev/null
+++ b/include/linux/soc/apple/dockchannel.h
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0-only OR MIT */
+/*
+ * Apple Dockchannel devices
+ * Copyright (C) The Asahi Linux Contributors
+ */
+#ifndef _LINUX_APPLE_DOCKCHANNEL_H_
+#define _LINUX_APPLE_DOCKCHANNEL_H_
+
+#include <linux/device.h>
+#include <linux/types.h>
+#include <linux/of_platform.h>
+
+#if IS_ENABLED(CONFIG_APPLE_DOCKCHANNEL)
+
+struct dockchannel;
+
+struct dockchannel *dockchannel_init(struct platform_device *pdev);
+
+int dockchannel_send(struct dockchannel *dockchannel, const void *buf, size_t count);
+int dockchannel_recv(struct dockchannel *dockchannel, void *buf, size_t count);
+int dockchannel_await(struct dockchannel *dockchannel,
+		      void (*callback)(void *cookie, size_t avail),
+		      void *cookie, size_t count);
+
+#endif
+#endif

From d83b2c3ce57b99c5a6db29647c57a2d4734f21ac Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 8 Jul 2022 02:11:21 +0900
Subject: [PATCH 0434/1027] hid: Add Apple DockChannel HID transport driver

Apple M2 devices have an MTP coprocessor embedded in the SoC that
handles HID for the integrated touchpad/keyboard, and communicates
over the DockChannel interface. This driver implements this new
interface.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/Kconfig                           |    2 +
 drivers/hid/Makefile                          |    2 +
 drivers/hid/dockchannel-hid/Kconfig           |   14 +
 drivers/hid/dockchannel-hid/Makefile          |    6 +
 drivers/hid/dockchannel-hid/dockchannel-hid.c | 1213 +++++++++++++++++
 5 files changed, 1237 insertions(+)
 create mode 100644 drivers/hid/dockchannel-hid/Kconfig
 create mode 100644 drivers/hid/dockchannel-hid/Makefile
 create mode 100644 drivers/hid/dockchannel-hid/dockchannel-hid.c

diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig
index 8a356b24bb6705..c057443138afac 100644
--- a/drivers/hid/Kconfig
+++ b/drivers/hid/Kconfig
@@ -1371,4 +1371,6 @@ source "drivers/hid/surface-hid/Kconfig"
 
 source "drivers/hid/spi-hid/Kconfig"
 
+source "drivers/hid/dockchannel-hid/Kconfig"
+
 endif # HID_SUPPORT
diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile
index 019c845cd24610..aa00868803cf56 100644
--- a/drivers/hid/Makefile
+++ b/drivers/hid/Makefile
@@ -171,3 +171,5 @@ obj-$(CONFIG_AMD_SFH_HID)       += amd-sfh-hid/
 obj-$(CONFIG_SURFACE_HID_CORE)  += surface-hid/
 
 obj-$(CONFIG_SPI_HID_APPLE_CORE)	+= spi-hid/
+
+obj-$(CONFIG_HID_DOCKCHANNEL)	+= dockchannel-hid/
diff --git a/drivers/hid/dockchannel-hid/Kconfig b/drivers/hid/dockchannel-hid/Kconfig
new file mode 100644
index 00000000000000..8a81d551a83d51
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/Kconfig
@@ -0,0 +1,14 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+menu "DockChannel HID support"
+	depends on APPLE_DOCKCHANNEL
+
+config HID_DOCKCHANNEL
+	tristate "HID over DockChannel transport layer for Apple Silicon SoCs"
+	default ARCH_APPLE
+	depends on APPLE_DOCKCHANNEL && INPUT && OF && HID
+	help
+	  Say Y here if you use an M2 or later Apple Silicon based laptop.
+	  The keyboard and touchpad are HID based devices connected via the
+	  proprietary DockChannel interface.
+
+endmenu
diff --git a/drivers/hid/dockchannel-hid/Makefile b/drivers/hid/dockchannel-hid/Makefile
new file mode 100644
index 00000000000000..7dba766b047fcc
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
+#
+# Makefile for DockChannel HID transport drivers
+#
+
+obj-$(CONFIG_HID_DOCKCHANNEL)	+= dockchannel-hid.o
diff --git a/drivers/hid/dockchannel-hid/dockchannel-hid.c b/drivers/hid/dockchannel-hid/dockchannel-hid.c
new file mode 100644
index 00000000000000..5d43e9e33805e9
--- /dev/null
+++ b/drivers/hid/dockchannel-hid/dockchannel-hid.c
@@ -0,0 +1,1213 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0 OR MIT
+ *
+ * Apple DockChannel HID transport driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/firmware.h>
+#include <linux/gpio/consumer.h>
+#include <linux/hid.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/soc/apple/dockchannel.h>
+#include <linux/string.h>
+#include <linux/of.h>
+#include "../hid-ids.h"
+
+#define COMMAND_TIMEOUT_MS 1000
+#define START_TIMEOUT_MS 2000
+
+#define MAX_INTERFACES 16
+
+/* Data + checksum */
+#define MAX_PKT_SIZE (0xffff + 4)
+
+#define DCHID_CHANNEL_CMD 0x11
+#define DCHID_CHANNEL_REPORT 0x12
+
+struct dchid_hdr {
+	u8 hdr_len;
+	u8 channel;
+	u16 length;
+	u8 seq;
+	u8 iface;
+	u16 pad;
+} __packed;
+
+#define IFACE_COMM 0
+
+#define FLAGS_GROUP GENMASK(7, 6)
+#define FLAGS_REQ GENMASK(5, 0)
+
+#define REQ_SET_REPORT 0
+#define REQ_GET_REPORT 1
+
+struct dchid_subhdr {
+	u8 flags;
+	u8 unk;
+	u16 length;
+	u32 retcode;
+} __packed;
+
+#define EVENT_GPIO_CMD	0xa0
+#define EVENT_INIT	0xf0
+#define EVENT_READY	0xf1
+
+struct dchid_init_hdr {
+	u8 type;
+	u8 unk1;
+	u8 unk2;
+	u8 iface;
+	char name[16];
+	u8 more_packets;
+	u8 unkpad;
+} __packed;
+
+#define INIT_HID_DESCRIPTOR	0
+#define INIT_GPIO_REQUEST	1
+#define INIT_TERMINATOR		2
+#define INIT_PRODUCT_NAME	7
+
+#define CMD_RESET_INTERFACE 0x40
+#define CMD_SEND_FIRMWARE 0x95
+#define CMD_ENABLE_INTERFACE 0xb4
+#define CMD_ACK_GPIO_CMD 0xa1
+
+struct dchid_init_block_hdr {
+	u16 type;
+	u16 length;
+} __packed;
+
+#define MAX_GPIO_NAME 32
+
+struct dchid_gpio_request {
+	u16 unk;
+	u16 id;
+	char name[MAX_GPIO_NAME];
+} __packed;
+
+struct dchid_gpio_cmd {
+	u8 type;
+	u8 iface;
+	u8 gpio;
+	u8 unk;
+	u8 cmd;
+} __packed;
+
+struct dchid_gpio_ack {
+	u8 type;
+	u32 retcode;
+	u8 cmd[];
+} __packed;
+
+#define STM_REPORT_ID		0x10
+#define STM_REPORT_SERIAL	0x11
+#define STM_REPORT_KEYBTYPE	0x14
+
+struct dchid_stm_id {
+	u8 unk;
+	u16 vendor_id;
+	u16 product_id;
+	u16 version_number;
+	u8 unk2;
+	u8 unk3;
+	u8 keyboard_type;
+	u8 serial_length;
+	/* Serial follows, but we grab it with a different report. */
+} __packed;
+
+#define FW_MAGIC 0x46444948
+#define FW_VER 1
+
+struct fw_header {
+	u32 magic;
+	u32 version;
+	u32 hdr_length;
+	u32 data_length;
+	u32 iface_offset;
+} __packed;
+
+struct dchid_work {
+	struct work_struct work;
+	struct dchid_iface *iface;
+
+	struct dchid_hdr hdr;
+	u8 data[];
+};
+
+struct dchid_iface {
+	struct dockchannel_hid *dchid;
+	struct hid_device *hid;
+	struct workqueue_struct *wq;
+
+	bool creating;
+	struct work_struct create_work;
+
+	int index;
+	const char *name;
+	const struct device_node *of_node;
+
+	uint8_t tx_seq;
+	bool deferred;
+	bool starting;
+	bool open;
+	struct completion ready;
+
+	void *hid_desc;
+	size_t hid_desc_len;
+
+	struct gpio_desc *gpio;
+	char gpio_name[MAX_GPIO_NAME];
+	int gpio_id;
+
+	struct mutex out_mutex;
+	u32 out_flags;
+	int out_report;
+	u32 retcode;
+	void *resp_buf;
+	size_t resp_size;
+	struct completion out_complete;
+
+	u32 keyboard_layout_id;
+};
+
+struct dockchannel_hid {
+	struct device *dev;
+	struct dockchannel *dc;
+	struct device_link *helper_link;
+
+	bool id_ready;
+	struct dchid_stm_id device_id;
+	char serial[64];
+
+	struct dchid_iface *comm;
+	struct dchid_iface *ifaces[MAX_INTERFACES];
+
+	u8 pkt_buf[MAX_PKT_SIZE];
+
+	/* Workqueue to asynchronously create HID devices */
+	struct workqueue_struct *new_iface_wq;
+};
+
+static ssize_t apple_layout_id_show(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct hid_device *hdev = to_hid_device(dev);
+	struct dchid_iface *iface = hdev->driver_data;
+
+	return scnprintf(buf, PAGE_SIZE, "%d\n", iface->keyboard_layout_id);
+}
+
+static DEVICE_ATTR_RO(apple_layout_id);
+
+static struct dchid_iface *
+dchid_get_interface(struct dockchannel_hid *dchid, int index, const char *name)
+{
+	struct dchid_iface *iface;
+
+	if (index >= MAX_INTERFACES) {
+		dev_err(dchid->dev, "Interface index %d out of range\n", index);
+		return NULL;
+	}
+
+	if (dchid->ifaces[index])
+		return dchid->ifaces[index];
+
+	iface = devm_kzalloc(dchid->dev, sizeof(struct dchid_iface), GFP_KERNEL);
+	if (!iface)
+		return NULL;
+
+	iface->index = index;
+	iface->name = devm_kstrdup(dchid->dev, name, GFP_KERNEL);
+	iface->dchid = dchid;
+	iface->out_report= -1;
+	init_completion(&iface->out_complete);
+	init_completion(&iface->ready);
+	mutex_init(&iface->out_mutex);
+	iface->wq = alloc_ordered_workqueue("dchid-%s", WQ_MEM_RECLAIM, iface->name);
+	if (!iface->wq)
+		return NULL;
+
+	/* Comm is not a HID subdevice */
+	if (!strcmp(name, "comm")) {
+		dchid->ifaces[index] = iface;
+		return iface;
+	}
+
+	iface->of_node = of_get_child_by_name(dchid->dev->of_node, name);
+	if (!iface->of_node) {
+		dev_warn(dchid->dev, "No OF node for subdevice %s, ignoring.", name);
+		return NULL;
+	}
+
+	dchid->ifaces[index] = iface;
+	return iface;
+}
+
+static u32 dchid_checksum(void *p, size_t length)
+{
+	u32 sum = 0;
+
+	while (length >= 4) {
+		sum += get_unaligned_le32(p);
+		p += 4;
+		length -= 4;
+	}
+
+	WARN_ON_ONCE(length);
+	return sum;
+}
+
+static int dchid_send(struct dchid_iface *iface, u32 flags, void *msg, size_t size)
+{
+	u32 checksum = 0xffffffff;
+	size_t wsize = round_down(size, 4);
+	size_t tsize = size - wsize;
+	int ret;
+	struct {
+		struct dchid_hdr hdr;
+		struct dchid_subhdr sub;
+	} __packed h;
+
+	memset(&h, 0, sizeof(h));
+	h.hdr.hdr_len = sizeof(h.hdr);
+	h.hdr.channel = DCHID_CHANNEL_CMD;
+	h.hdr.length = round_up(size, 4) + sizeof(h.sub);
+	h.hdr.seq = iface->tx_seq;
+	h.hdr.iface = iface->index;
+	h.sub.flags = flags;
+	h.sub.length = size;
+
+	ret = dockchannel_send(iface->dchid->dc, &h, sizeof(h));
+	if (ret < 0)
+		return ret;
+	checksum -= dchid_checksum(&h, sizeof(h));
+
+	ret = dockchannel_send(iface->dchid->dc, msg, wsize);
+	if (ret < 0)
+		return ret;
+	checksum -= dchid_checksum(msg, wsize);
+
+	if (tsize) {
+		u8 tail[4] = {0, 0, 0, 0};
+
+		memcpy(tail, msg + wsize, tsize);
+		ret = dockchannel_send(iface->dchid->dc, tail, sizeof(tail));
+		if (ret < 0)
+			return ret;
+		checksum -= dchid_checksum(tail, sizeof(tail));
+	}
+
+	ret = dockchannel_send(iface->dchid->dc, &checksum, sizeof(checksum));
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dchid_cmd(struct dchid_iface *iface, u32 type, u32 req,
+		     void *data, size_t size, void *resp_buf, size_t resp_size)
+{
+	int ret;
+	int report_id = *(u8*)data;
+
+	mutex_lock(&iface->out_mutex);
+
+	WARN_ON(iface->out_report != -1);
+	iface->out_report = report_id;
+	iface->out_flags = FIELD_PREP(FLAGS_GROUP, type) | FIELD_PREP(FLAGS_REQ, req);
+	iface->resp_buf = resp_buf;
+	iface->resp_size = resp_size;
+	reinit_completion(&iface->out_complete);
+
+	ret = dchid_send(iface, iface->out_flags, data, size);
+	if (ret < 0)
+		goto done;
+
+	if (!wait_for_completion_timeout(&iface->out_complete, msecs_to_jiffies(COMMAND_TIMEOUT_MS))) {
+		dev_err(iface->dchid->dev, "output report 0x%x to iface  %d (%s) timed out\n",
+			report_id, iface->index, iface->name);
+		ret = -ETIMEDOUT;
+		goto done;
+	}
+
+	ret = iface->resp_size;
+	if (iface->retcode) {
+		dev_err(iface->dchid->dev,
+			"output report 0x%x to iface %d (%s) failed with err 0x%x\n",
+			report_id, iface->index, iface->name, iface->retcode);
+		ret = -EIO;
+	}
+
+done:
+	iface->tx_seq++;
+	iface->out_report = -1;
+	iface->out_flags = 0;
+	iface->resp_buf = NULL;
+	iface->resp_size = 0;
+	mutex_unlock(&iface->out_mutex);
+	return ret;
+}
+
+static int dchid_comm_cmd(struct dockchannel_hid *dchid, void *cmd, size_t size)
+{
+	return dchid_cmd(dchid->comm, HID_FEATURE_REPORT, REQ_SET_REPORT, cmd, size, NULL, 0);
+}
+
+static int dchid_enable_interface(struct dchid_iface *iface)
+{
+	u8 msg[] = { CMD_ENABLE_INTERFACE, iface->index };
+
+	return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_reset_interface(struct dchid_iface *iface, int state)
+{
+	u8 msg[] = { CMD_RESET_INTERFACE, 1, iface->index, state };
+
+	return dchid_comm_cmd(iface->dchid, msg, sizeof(msg));
+}
+
+static int dchid_send_firmware(struct dchid_iface *iface, void *firmware, size_t size)
+{
+	struct {
+		u8 cmd;
+		u8 unk1;
+		u8 unk2;
+		u8 iface;
+		u64 addr;
+		u32 size;
+	} __packed msg = {
+		.cmd = CMD_SEND_FIRMWARE,
+		.unk1 = 2,
+		.unk2 = 0,
+		.iface = iface->index,
+		.size = size,
+	};
+	dma_addr_t addr;
+	void *buf = dmam_alloc_coherent(iface->dchid->dev, size, &addr, GFP_KERNEL);
+
+	if (IS_ERR_OR_NULL(buf))
+		return buf ? PTR_ERR(buf) : -ENOMEM;
+
+	msg.addr = addr;
+	memcpy(buf, firmware, size);
+	wmb();
+
+	return dchid_comm_cmd(iface->dchid, &msg, sizeof(msg));
+}
+
+static int dchid_get_firmware(struct dchid_iface *iface, void **firmware, size_t *size)
+{
+	int ret;
+	const char *fw_name;
+	const struct firmware *fw;
+	struct fw_header *hdr;
+	u8 *fw_data;
+
+	ret = of_property_read_string(iface->of_node, "firmware-name", &fw_name);
+	if (ret) {
+		/* Firmware is only for some devices */
+		*firmware = NULL;
+		*size = 0;
+		return 0;
+	}
+
+	ret = request_firmware(&fw, fw_name, iface->dchid->dev);
+	if (ret)
+		return ret;
+
+	hdr = (struct fw_header *)fw->data;
+
+	if (hdr->magic != FW_MAGIC || hdr->version != FW_VER ||
+		hdr->hdr_length < sizeof(*hdr) || hdr->hdr_length > fw->size ||
+		(hdr->hdr_length + (size_t)hdr->data_length) > fw->size ||
+		hdr->iface_offset >= hdr->data_length) {
+		dev_warn(iface->dchid->dev, "%s: invalid firmware header\n",
+			 fw_name);
+		ret = -EINVAL;
+		goto done;
+	}
+
+	fw_data = devm_kmemdup(iface->dchid->dev, fw->data + hdr->hdr_length,
+			       hdr->data_length, GFP_KERNEL);
+	if (!fw_data) {
+		ret = -ENOMEM;
+		goto done;
+	}
+
+	if (hdr->iface_offset)
+		fw_data[hdr->iface_offset] = iface->index;
+
+	*firmware = fw_data;
+	*size = hdr->data_length;
+
+done:
+	release_firmware(fw);
+	return ret;
+}
+
+static int dchid_request_gpio(struct dchid_iface *iface)
+{
+	char prop_name[MAX_GPIO_NAME + 16];
+
+	if (iface->gpio)
+		return 0;
+
+	dev_info(iface->dchid->dev, "Requesting GPIO %s#%d: %s\n",
+		 iface->name, iface->gpio_id, iface->gpio_name);
+
+	snprintf(prop_name, sizeof(prop_name), "apple,%s", iface->gpio_name);
+
+	iface->gpio = devm_gpiod_get_index(iface->dchid->dev, prop_name, 0, GPIOD_OUT_LOW);
+
+	if (IS_ERR_OR_NULL(iface->gpio)) {
+		dev_err(iface->dchid->dev, "Failed to request GPIO %s-gpios\n", prop_name);
+		iface->gpio = NULL;
+		return -1;
+	}
+
+	return 0;
+}
+
+static int dchid_start_interface(struct dchid_iface *iface)
+{
+	void *fw;
+	size_t size;
+	int ret;
+
+	if (iface->starting) {
+		dev_warn(iface->dchid->dev, "Interface %s is already starting", iface->name);
+		return -EINPROGRESS;
+	}
+
+	dev_info(iface->dchid->dev, "Starting interface %s\n", iface->name);
+
+	iface->starting = true;
+
+	/* Look to see if we need firmware */
+	ret = dchid_get_firmware(iface, &fw, &size);
+	if (ret < 0)
+		goto err;
+
+	/* If we need a GPIO, make sure we have it. */
+	if (iface->gpio_id) {
+		ret = dchid_request_gpio(iface);
+		if (ret < 0)
+			goto err;
+	}
+
+	/* Only multi-touch has firmware */
+	if (fw && size) {
+
+		/* Send firmware to the device */
+		dev_info(iface->dchid->dev, "Sending firmware for %s\n", iface->name);
+		ret = dchid_send_firmware(iface, fw, size);
+		if (ret < 0) {
+			dev_err(iface->dchid->dev, "Failed to send %s firmwareS", iface->name);
+			goto err;
+		}
+
+		/* After loading firmware, multi-touch needs a reset */
+		dev_info(iface->dchid->dev, "Resetting %s\n", iface->name);
+		dchid_reset_interface(iface, 0);
+		dchid_reset_interface(iface, 2);
+	}
+
+	return 0;
+
+err:
+	iface->starting = false;
+	return ret;
+}
+
+static int dchid_start(struct hid_device *hdev)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+
+	if (iface->keyboard_layout_id) {
+		int ret = device_create_file(&hdev->dev, &dev_attr_apple_layout_id);
+		if (ret) {
+			dev_warn(iface->dchid->dev, "Failed to create apple_layout_id: %d", ret);
+			iface->keyboard_layout_id = 0;
+		}
+	}
+
+	return 0;
+};
+
+static void dchid_stop(struct hid_device *hdev)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+
+	if (iface->keyboard_layout_id)
+		device_remove_file(&hdev->dev, &dev_attr_apple_layout_id);
+}
+
+static int dchid_open(struct hid_device *hdev)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+	int ret;
+
+	if (!completion_done(&iface->ready)) {
+		ret = dchid_start_interface(iface);
+		if (ret < 0)
+			return ret;
+
+		if (!wait_for_completion_timeout(&iface->ready, msecs_to_jiffies(START_TIMEOUT_MS))) {
+			dev_err(iface->dchid->dev, "iface %s start timed out\n", iface->name);
+			return -ETIMEDOUT;
+		}
+	}
+
+	iface->open = true;
+	return 0;
+}
+
+static void dchid_close(struct hid_device *hdev)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+
+	iface->open = false;
+}
+
+static int dchid_parse(struct hid_device *hdev)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+
+	return hid_parse_report(hdev, iface->hid_desc, iface->hid_desc_len);
+}
+
+/* Note: buf excludes report number! For ease of fetching strings/etc. */
+static int dchid_get_report_cmd(struct dchid_iface *iface, u8 reportnum, void *buf, size_t len)
+{
+	int ret = dchid_cmd(iface, HID_FEATURE_REPORT, REQ_GET_REPORT, &reportnum, 1, buf, len);
+
+	return ret <= 0 ? ret : ret - 1;
+}
+
+/* Note: buf includes report number! */
+static int dchid_set_report(struct dchid_iface *iface, void *buf, size_t len)
+{
+	return dchid_cmd(iface, HID_OUTPUT_REPORT, REQ_SET_REPORT, buf, len, NULL, 0);
+}
+
+static int dchid_raw_request(struct hid_device *hdev,
+				unsigned char reportnum, __u8 *buf, size_t len,
+				unsigned char rtype, int reqtype)
+{
+	struct dchid_iface *iface = hdev->driver_data;
+
+	switch (reqtype) {
+	case HID_REQ_GET_REPORT:
+		buf[0] = reportnum;
+		return dchid_cmd(iface, rtype, REQ_GET_REPORT, &reportnum, 1, buf + 1, len - 1);
+	case HID_REQ_SET_REPORT:
+		return dchid_set_report(iface, buf, len);
+	default:
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static struct hid_ll_driver dchid_ll = {
+	.start = &dchid_start,
+	.stop = &dchid_stop,
+	.open = &dchid_open,
+	.close = &dchid_close,
+	.parse = &dchid_parse,
+	.raw_request = &dchid_raw_request,
+};
+
+static void dchid_create_interface_work(struct work_struct *ws)
+{
+	struct dchid_iface *iface = container_of(ws, struct dchid_iface, create_work);
+	struct dockchannel_hid *dchid = iface->dchid;
+	struct hid_device *hid;
+	int ret;
+
+	if (iface->hid) {
+		dev_warn(dchid->dev, "Interface %s already created!\n",
+			 iface->name);
+		return;
+	}
+
+	dev_info(dchid->dev, "New interface %s\n", iface->name);
+
+	/* Start the interface. This is not the entire init process, as firmware is loaded later on device open. */
+	ret = dchid_enable_interface(iface);
+	if (ret < 0) {
+		dev_warn(dchid->dev, "Failed to enable %s: %d\n", iface->name, ret);
+		return;
+	}
+
+	iface->deferred = false;
+
+	hid = hid_allocate_device();
+	if (IS_ERR(hid))
+		return;
+
+	snprintf(hid->name, sizeof(hid->name), "Apple MTP %s", iface->name);
+	snprintf(hid->phys, sizeof(hid->phys), "%s.%d (%s)",
+		 dev_name(dchid->dev), iface->index, iface->name);
+	strscpy(hid->uniq, dchid->serial, sizeof(hid->uniq));
+
+	hid->ll_driver = &dchid_ll;
+	hid->bus = BUS_HOST;
+	hid->vendor = dchid->device_id.vendor_id;
+	hid->product = dchid->device_id.product_id;
+	hid->version = dchid->device_id.version_number;
+	hid->type = HID_TYPE_OTHER;
+	if (!strcmp(iface->name, "multi-touch")) {
+		hid->type = HID_TYPE_SPI_MOUSE;
+	} else if (!strcmp(iface->name, "keyboard")) {
+		u32 country_code = 0;
+
+		hid->type = HID_TYPE_SPI_KEYBOARD;
+
+		/*
+		 * We have to get the country code from the device tree, since the
+		 * device provides no reliable way to get this info.
+		 */
+		if (!of_property_read_u32(iface->of_node, "hid-country-code", &country_code))
+			hid->country = country_code;
+
+		of_property_read_u32(iface->of_node, "apple,keyboard-layout-id",
+			&iface->keyboard_layout_id);
+	}
+
+	hid->dev.parent = iface->dchid->dev;
+	hid->driver_data = iface;
+
+	iface->hid = hid;
+
+	ret = hid_add_device(hid);
+	if (ret < 0) {
+		iface->hid = NULL;
+		hid_destroy_device(hid);
+		dev_warn(iface->dchid->dev, "Failed to register hid device %s", iface->name);
+	}
+}
+
+static int dchid_create_interface(struct dchid_iface *iface)
+{
+	if (iface->creating)
+		return -EBUSY;
+
+	iface->creating = true;
+	INIT_WORK(&iface->create_work, dchid_create_interface_work);
+	return queue_work(iface->dchid->new_iface_wq, &iface->create_work);
+}
+
+static void dchid_handle_descriptor(struct dchid_iface *iface, void *hid_desc, size_t desc_len)
+{
+	if (iface->hid) {
+		dev_warn(iface->dchid->dev, "Tried to initialize already started interface %s!\n",
+			 iface->name);
+		return;
+	}
+
+	iface->hid_desc = devm_kmemdup(iface->dchid->dev, hid_desc, desc_len, GFP_KERNEL);
+	if (!iface->hid_desc)
+		return;
+
+	iface->hid_desc_len = desc_len;
+}
+
+static void dchid_handle_ready(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+	struct dchid_iface *iface;
+	u8 *pkt = data;
+	u8 index;
+	int i, ret;
+
+	if (length < 2) {
+		dev_err(dchid->dev, "Bad length for ready message: %zu\n", length);
+		return;
+	}
+
+	index = pkt[1];
+
+	if (index >= MAX_INTERFACES) {
+		dev_err(dchid->dev, "Got ready notification for bad iface %d\n", index);
+		return;
+	}
+
+	iface = dchid->ifaces[index];
+	if (!iface) {
+		dev_err(dchid->dev, "Got ready notification for unknown iface %d\n", index);
+		return;
+	}
+
+	dev_info(dchid->dev, "Interface %s is now ready\n", iface->name);
+	complete_all(&iface->ready);
+
+	/* When STM is ready, grab global device info */
+	if (!strcmp(iface->name, "stm")) {
+		ret = dchid_get_report_cmd(iface, STM_REPORT_ID, &dchid->device_id,
+					   sizeof(dchid->device_id));
+		if (ret < sizeof(dchid->device_id)) {
+			dev_warn(iface->dchid->dev, "Failed to get device ID from STM!\n");
+			/* Fake it and keep going. Things might still work... */
+			memset(&dchid->device_id, 0, sizeof(dchid->device_id));
+			dchid->device_id.vendor_id = HOST_VENDOR_ID_APPLE;
+		}
+		ret = dchid_get_report_cmd(iface, STM_REPORT_SERIAL, dchid->serial,
+					   sizeof(dchid->serial) - 1);
+		if (ret < 0) {
+			dev_warn(iface->dchid->dev, "Failed to get serial from STM!\n");
+			dchid->serial[0] = 0;
+		}
+
+		dchid->id_ready = true;
+		for (i = 0; i < MAX_INTERFACES; i++) {
+			if (!dchid->ifaces[i] || !dchid->ifaces[i]->deferred)
+				continue;
+			dchid_create_interface(dchid->ifaces[i]);
+		}
+	}
+}
+
+static void dchid_handle_init(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+	struct dchid_init_hdr *hdr = data;
+	struct dchid_iface *iface;
+	struct dchid_init_block_hdr *blk;
+
+	if (length < sizeof(*hdr))
+		return;
+
+	iface = dchid_get_interface(dchid, hdr->iface, hdr->name);
+	if (!iface)
+		return;
+
+	data += sizeof(*hdr);
+	length -= sizeof(*hdr);
+
+	while (length >= sizeof(*blk)) {
+		blk = data;
+		data += sizeof(*blk);
+		length -= sizeof(*blk);
+
+		if (blk->length > length)
+			break;
+
+		switch (blk->type) {
+		case INIT_HID_DESCRIPTOR:
+			dchid_handle_descriptor(iface, data, blk->length);
+			break;
+
+		case INIT_GPIO_REQUEST: {
+			struct dchid_gpio_request *req = data;
+
+			if (sizeof(*req) > length)
+				break;
+
+			if (iface->gpio_id) {
+				dev_err(dchid->dev,
+					"Cannot request more than one GPIO per interface!\n");
+				break;
+			}
+
+			strscpy(iface->gpio_name, req->name, MAX_GPIO_NAME);
+			iface->gpio_id = req->id;
+			break;
+		}
+
+		case INIT_TERMINATOR:
+			break;
+
+		case INIT_PRODUCT_NAME: {
+			char *product = data;
+
+			if (product[blk->length - 1] != 0) {
+				dev_warn(dchid->dev, "Unterminated product name for %s\n",
+					 iface->name);
+			} else {
+				dev_info(dchid->dev, "Product name for %s: %s\n",
+					 iface->name, product);
+			}
+			break;
+		}
+
+		default:
+			dev_warn(dchid->dev, "Unknown init packet %d for %s\n",
+				 blk->type, iface->name);
+			break;
+		}
+
+		data += blk->length;
+		length -= blk->length;
+
+		if (blk->type == INIT_TERMINATOR)
+			break;
+	}
+
+	if (hdr->more_packets)
+		return;
+
+	/* We need to enable STM first, since it'll give us the device IDs */
+	if (iface->dchid->id_ready || !strcmp(iface->name, "stm")) {
+		dchid_create_interface(iface);
+	} else {
+		iface->deferred = true;
+	}
+}
+
+static void dchid_handle_gpio(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+	struct dchid_gpio_cmd *cmd = data;
+	struct dchid_iface *iface;
+	u32 retcode = 0xe000f00d; /* Give it a random Apple-style error code */
+	struct dchid_gpio_ack *ack;
+
+	if (length < sizeof(*cmd))
+		return;
+
+	if (cmd->iface >= MAX_INTERFACES || !(iface = dchid->ifaces[cmd->iface])) {
+		dev_err(dchid->dev, "Got GPIO command for bad inteface %d\n", cmd->iface);
+		goto err;
+	}
+
+	if (dchid_request_gpio(iface) < 0)
+		goto err;
+
+	if (!iface->gpio || cmd->gpio != iface->gpio_id) {
+		dev_err(dchid->dev, "Got GPIO command for bad GPIO %s#%d\n",
+			iface->name, cmd->gpio);
+		goto err;
+	}
+
+	dev_info(dchid->dev, "GPIO command: %s#%d: %d\n", iface->name, cmd->gpio, cmd->cmd);
+
+	switch (cmd->cmd) {
+	case 3:
+		/* Pulse.  */
+		gpiod_set_value_cansleep(iface->gpio, 1);
+		msleep(10); /* Random guess... */
+		gpiod_set_value_cansleep(iface->gpio, 0);
+		retcode = 0;
+		break;
+	default:
+		dev_err(dchid->dev, "Unknown GPIO command %d\n", cmd->cmd	);
+		break;
+	}
+
+err:
+	/* Ack it */
+	ack = kzalloc(sizeof(*ack) + length, GFP_KERNEL);
+	if (!ack)
+		return;
+
+	ack->type = CMD_ACK_GPIO_CMD;
+	ack->retcode = retcode;
+	memcpy(ack->cmd, data, length);
+
+	if (dchid_comm_cmd(dchid, ack, sizeof(*ack) + length) < 0)
+		dev_err(dchid->dev, "Failed to ACK GPIO command\n");
+
+	kfree(ack);
+}
+
+static void dchid_handle_event(struct dockchannel_hid *dchid, void *data, size_t length)
+{
+	u8 *p = data;
+	switch (*p) {
+	case EVENT_INIT:
+		dchid_handle_init(dchid, data, length);
+		break;
+	case EVENT_READY:
+		dchid_handle_ready(dchid, data, length);
+		break;
+	case EVENT_GPIO_CMD:
+		dchid_handle_gpio(dchid, data, length);
+		break;
+	}
+}
+
+static void dchid_handle_report(struct dchid_iface *iface, void *data, size_t length)
+{
+	struct dockchannel_hid *dchid = iface->dchid;
+
+	if (!iface->hid) {
+		dev_warn(dchid->dev, "Report received but %s is not initialized!\n", iface->name);
+		return;
+	}
+
+	if (!iface->open)
+		return;
+
+	hid_input_report(iface->hid, HID_INPUT_REPORT, data, length, 1);
+}
+
+static void dchid_packet_work(struct work_struct *ws)
+{
+	struct dchid_work *work = container_of(ws, struct dchid_work, work);
+	struct dchid_subhdr *shdr = (void *)work->data;
+	struct dockchannel_hid *dchid = work->iface->dchid;
+	int type = FIELD_GET(FLAGS_GROUP, shdr->flags);
+	u8 *payload = work->data + sizeof(*shdr);
+
+	if (shdr->length + sizeof(*shdr) > work->hdr.length) {
+		dev_err(dchid->dev, "Bad sub header length (%d > %zu)\n",
+			shdr->length, work->hdr.length - sizeof(*shdr));
+		return;
+	}
+
+	switch (type) {
+	case HID_INPUT_REPORT:
+		if (work->hdr.iface == IFACE_COMM)
+			dchid_handle_event(dchid, payload, shdr->length);
+		else
+			dchid_handle_report(work->iface, payload, shdr->length);
+		break;
+	default:
+		dev_err(dchid->dev, "Received unknown packet type %d\n", type);
+		break;
+	}
+
+	kfree(work);
+}
+
+static void dchid_handle_ack(struct dchid_iface *iface, struct dchid_hdr *hdr, void *data)
+{
+	struct dchid_subhdr *shdr = (void *)data;
+	u8 *payload = data + sizeof(*shdr);
+
+	if (shdr->length + sizeof(*shdr) > hdr->length) {
+		dev_err(iface->dchid->dev, "Bad sub header length (%d > %ld)\n",
+			shdr->length, hdr->length - sizeof(*shdr));
+		return;
+	}
+	if (shdr->flags != iface->out_flags) {
+		dev_err(iface->dchid->dev,
+			"Received unexpected flags 0x%x on ACK channel (expFected 0x%x)\n",
+			shdr->flags, iface->out_flags);
+		return;
+	}
+
+	if (shdr->length < 1) {
+		dev_err(iface->dchid->dev, "Received length 0 output report ack\n");
+		return;
+	}
+	if (iface->tx_seq != hdr->seq) {
+		dev_err(iface->dchid->dev, "Received ACK with bad seq (expected %d, got %d)\n",
+			iface->tx_seq, hdr->seq);
+		return;
+	}
+	if (iface->out_report != payload[0]) {
+		dev_err(iface->dchid->dev, "Received ACK with bad report (expected %d, got %d\n",
+			iface->out_report, payload[0]);
+		return;
+	}
+
+	if (iface->resp_buf && iface->resp_size)
+		memcpy(iface->resp_buf, payload + 1, min((size_t)shdr->length - 1, iface->resp_size));
+
+	iface->resp_size = shdr->length;
+	iface->out_report = -1;
+	iface->retcode = shdr->retcode;
+	complete(&iface->out_complete);
+}
+
+static void dchid_handle_packet(void *cookie, size_t avail)
+{
+	struct dockchannel_hid *dchid = cookie;
+	struct dchid_hdr hdr;
+	struct dchid_work *work;
+	struct dchid_iface *iface;
+	u32 checksum;
+
+	if (dockchannel_recv(dchid->dc, &hdr, sizeof(hdr)) != sizeof(hdr)) {
+		dev_err(dchid->dev, "Read failed (header)\n");
+		return;
+	}
+
+	if (hdr.hdr_len != sizeof(hdr)) {
+		dev_err(dchid->dev, "Bad header length %d\n", hdr.hdr_len);
+		goto done;
+	}
+
+	if (dockchannel_recv(dchid->dc, dchid->pkt_buf, hdr.length + 4) != (hdr.length + 4)) {
+		dev_err(dchid->dev, "Read failed (body)\n");
+		goto done;
+	}
+
+	checksum = dchid_checksum(&hdr, sizeof(hdr));
+	checksum += dchid_checksum(dchid->pkt_buf, hdr.length + 4);
+
+	if (checksum != 0xffffffff) {
+		dev_err(dchid->dev, "Checksum mismatch (iface %d): 0x%08x != 0xffffffff\n",
+			hdr.iface, checksum);
+		goto done;
+	}
+
+
+	if (hdr.iface >= MAX_INTERFACES) {
+		dev_err(dchid->dev, "Bad iface %d\n", hdr.iface);
+	}
+
+	iface = dchid->ifaces[hdr.iface];
+
+	if (!iface) {
+		dev_err(dchid->dev, "Received packet for uninitialized iface %d\n", hdr.iface);
+		goto done;
+	}
+
+	switch (hdr.channel) {
+		case DCHID_CHANNEL_CMD:
+			dchid_handle_ack(iface, &hdr, dchid->pkt_buf);
+			goto done;
+		case DCHID_CHANNEL_REPORT:
+			break;
+		default:
+			dev_warn(dchid->dev, "Unknown channel 0x%x, treating as report...\n",
+				 hdr.channel);
+			break;
+	}
+
+	work = kzalloc(sizeof(*work) + hdr.length, GFP_KERNEL);
+	if (!work)
+		return;
+
+	work->hdr = hdr;
+	work->iface = iface;
+	memcpy(work->data, dchid->pkt_buf, hdr.length);
+	INIT_WORK(&work->work, dchid_packet_work);
+
+	queue_work(iface->wq, &work->work);
+
+done:
+	dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+}
+
+static int dockchannel_hid_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dockchannel_hid *dchid;
+	struct device_node *child, *helper;
+	struct platform_device *helper_pdev;
+	struct property *prop;
+	int ret;
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (ret)
+		return ret;
+
+	dchid = devm_kzalloc(dev, sizeof(*dchid), GFP_KERNEL);
+	if (!dchid) {
+		return -ENOMEM;
+	}
+
+	dchid->dev = dev;
+
+	/*
+	 * First make sure all the GPIOs are available, in cased we need to defer.
+	 * This is necessary because MTP will request them by name later, and by then
+	 * it's too late to defer the probe.
+	 */
+
+	for_each_child_of_node(dev->of_node, child) {
+		for_each_property_of_node(child, prop) {
+			size_t len = strlen(prop->name);
+			struct gpio_desc *gpio;
+
+			if (len < 12 || strncmp("apple,", prop->name, 6) ||
+			    strcmp("-gpios", prop->name + len - 6))
+				continue;
+
+			gpio = fwnode_gpiod_get_index(&child->fwnode, prop->name, 0, GPIOD_ASIS,
+						      prop->name);
+			if (IS_ERR_OR_NULL(gpio)) {
+				if (PTR_ERR(gpio) == -EPROBE_DEFER) {
+					of_node_put(child);
+					return -EPROBE_DEFER;
+				}
+			} else {
+				gpiod_put(gpio);
+			}
+		}
+	}
+
+	/*
+	 * Make sure we also have the MTP coprocessor available, and
+	 * defer probe if the helper hasn't probed yet.
+	 */
+	helper = of_parse_phandle(dev->of_node, "apple,helper-cpu", 0);
+	if (!helper) {
+		dev_err(dev, "Missing apple,helper-cpu property");
+		return -EINVAL;
+	}
+
+	helper_pdev = of_find_device_by_node(helper);
+	of_node_put(helper);
+	if (!helper_pdev) {
+		dev_err(dev, "Failed to find helper device");
+		return -EINVAL;
+	}
+
+	dchid->helper_link = device_link_add(dev, &helper_pdev->dev,
+					     DL_FLAG_AUTOREMOVE_CONSUMER);
+	put_device(&helper_pdev->dev);
+	if (!dchid->helper_link) {
+		dev_err(dev, "Failed to link to helper device");
+		return -EINVAL;
+	}
+
+	if (dchid->helper_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
+		return -EPROBE_DEFER;
+
+	/* Now it is safe to begin initializing */
+	dchid->dc = dockchannel_init(pdev);
+	if (IS_ERR_OR_NULL(dchid->dc)) {
+		return PTR_ERR(dchid->dc);
+	}
+	dchid->new_iface_wq = alloc_workqueue("dchid-new", WQ_MEM_RECLAIM, 0);
+	if (!dchid->new_iface_wq)
+		return -ENOMEM;
+
+	dchid->comm = dchid_get_interface(dchid, IFACE_COMM, "comm");
+	if (!dchid->comm) {
+		dev_err(dchid->dev, "Failed to initialize comm interface");
+		return -EIO;
+	}
+
+	dev_info(dchid->dev, "Initialized, awaiting packets\n");
+	dockchannel_await(dchid->dc, dchid_handle_packet, dchid, sizeof(struct dchid_hdr));
+
+	return 0;
+}
+
+static void dockchannel_hid_remove(struct platform_device *pdev)
+{
+	BUG_ON(1);
+}
+
+static const struct of_device_id dockchannel_hid_of_match[] = {
+	{ .compatible = "apple,dockchannel-hid" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, dockchannel_hid_of_match);
+MODULE_FIRMWARE("apple/tpmtfw-*.bin");
+
+static struct platform_driver dockchannel_hid_driver = {
+	.driver = {
+		.name = "dockchannel-hid",
+		.of_match_table = dockchannel_hid_of_match,
+	},
+	.probe = dockchannel_hid_probe,
+	.remove = dockchannel_hid_remove,
+};
+module_platform_driver(dockchannel_hid_driver);
+
+MODULE_DESCRIPTION("Apple DockChannel HID transport driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");

From b53ed79019f45831b06962ba44190a3d354aa7c2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 3 Jul 2022 23:33:37 +0900
Subject: [PATCH 0435/1027] soc: apple: Add RTKit helper driver

This driver can be used for coprocessors that do some background task or
communicate out-of-band, and do not do any mailbox I/O beyond the
standard RTKit initialization.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/soc/apple/Kconfig        |  14 +++
 drivers/soc/apple/Makefile       |   3 +
 drivers/soc/apple/rtkit-helper.c | 151 +++++++++++++++++++++++++++++++
 3 files changed, 168 insertions(+)
 create mode 100644 drivers/soc/apple/rtkit-helper.c

diff --git a/drivers/soc/apple/Kconfig b/drivers/soc/apple/Kconfig
index cceb514b05bbfa..4aeb027a533d0b 100644
--- a/drivers/soc/apple/Kconfig
+++ b/drivers/soc/apple/Kconfig
@@ -30,6 +30,20 @@ config APPLE_RTKIT
 
 	  Say 'y' here if you have an Apple SoC.
 
+config APPLE_RTKIT_HELPER
+	tristate "Apple Generic RTKit helper co-processor"
+	depends on APPLE_RTKIT
+	depends on ARCH_APPLE || COMPILE_TEST
+	default ARCH_APPLE
+	help
+	  Apple SoCs such as the M1 come with various co-processors running
+	  their proprietary RTKit operating system. This option enables support
+	  for a generic co-processor that does not implement any additional
+	  in-band communications. It can be used for testing purposes, or for
+	  coprocessors such as MTP that communicate over a different interface.
+
+	  Say 'y' here if you have an Apple SoC.
+
 config APPLE_SART
 	tristate "Apple SART DMA address filter"
 	depends on ARCH_APPLE || COMPILE_TEST
diff --git a/drivers/soc/apple/Makefile b/drivers/soc/apple/Makefile
index 07d43360426110..8ae2890bc40b45 100644
--- a/drivers/soc/apple/Makefile
+++ b/drivers/soc/apple/Makefile
@@ -6,6 +6,9 @@ apple-mailbox-y = mailbox.o
 obj-$(CONFIG_APPLE_RTKIT) += apple-rtkit.o
 apple-rtkit-y = rtkit.o rtkit-crashlog.o
 
+obj-$(CONFIG_APPLE_RTKIT_HELPER) += apple-rtkit-helper.o
+apple-rtkit-helper-y = rtkit-helper.o
+
 obj-$(CONFIG_APPLE_SART) += apple-sart.o
 apple-sart-y = sart.o
 
diff --git a/drivers/soc/apple/rtkit-helper.c b/drivers/soc/apple/rtkit-helper.c
new file mode 100644
index 00000000000000..080d083ed9bd2f
--- /dev/null
+++ b/drivers/soc/apple/rtkit-helper.c
@@ -0,0 +1,151 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple Generic RTKit helper coprocessor
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/rtkit.h>
+
+#define APPLE_ASC_CPU_CONTROL		0x44
+#define APPLE_ASC_CPU_CONTROL_RUN	BIT(4)
+
+struct apple_rtkit_helper {
+	struct device *dev;
+	struct apple_rtkit *rtk;
+
+	void __iomem *asc_base;
+
+	struct resource *sram;
+	void __iomem *sram_base;
+};
+
+static int apple_rtkit_helper_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	struct apple_rtkit_helper *helper = cookie;
+	struct resource res = {
+		.start = bfr->iova,
+		.end = bfr->iova + bfr->size - 1,
+		.name = "rtkit_map",
+	};
+
+	if (!bfr->iova) {
+		bfr->buffer = dma_alloc_coherent(helper->dev, bfr->size,
+						    &bfr->iova, GFP_KERNEL);
+		if (!bfr->buffer)
+			return -ENOMEM;
+		return 0;
+	}
+
+	if (!helper->sram) {
+		dev_err(helper->dev,
+			"RTKit buffer request with no SRAM region: %pR", &res);
+		return -EFAULT;
+	}
+
+	res.flags = helper->sram->flags;
+
+	if (res.end < res.start || !resource_contains(helper->sram, &res)) {
+		dev_err(helper->dev,
+			"RTKit buffer request outside SRAM region: %pR", &res);
+		return -EFAULT;
+	}
+
+	bfr->iomem = helper->sram_base + (res.start - helper->sram->start);
+	bfr->is_mapped = true;
+
+	return 0;
+}
+
+static void apple_rtkit_helper_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	// no-op
+}
+
+static const struct apple_rtkit_ops apple_rtkit_helper_ops = {
+	.shmem_setup = apple_rtkit_helper_shmem_setup,
+	.shmem_destroy = apple_rtkit_helper_shmem_destroy,
+};
+
+static int apple_rtkit_helper_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct apple_rtkit_helper *helper;
+	int ret;
+
+	/* 44 bits for addresses in standard RTKit requests */
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(44));
+	if (ret)
+		return ret;
+
+	helper = devm_kzalloc(dev, sizeof(*helper), GFP_KERNEL);
+	if (!helper)
+		return -ENOMEM;
+
+	helper->dev = dev;
+	platform_set_drvdata(pdev, helper);
+
+	helper->asc_base = devm_platform_ioremap_resource_byname(pdev, "asc");
+	if (IS_ERR(helper->asc_base))
+		return PTR_ERR(helper->asc_base);
+
+	helper->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+	if (helper->sram) {
+		helper->sram_base = devm_ioremap_resource(dev, helper->sram);
+		if (IS_ERR(helper->sram_base))
+			return dev_err_probe(dev, PTR_ERR(helper->sram_base),
+					"Failed to map SRAM region");
+	}
+
+	helper->rtk =
+		devm_apple_rtkit_init(dev, helper, NULL, 0, &apple_rtkit_helper_ops);
+	if (IS_ERR(helper->rtk))
+		return dev_err_probe(dev, PTR_ERR(helper->rtk),
+				     "Failed to intialize RTKit");
+
+	writel_relaxed(APPLE_ASC_CPU_CONTROL_RUN,
+		       helper->asc_base + APPLE_ASC_CPU_CONTROL);
+
+	/* Works for both wake and boot */
+	ret = apple_rtkit_wake(helper->rtk);
+	if (ret != 0)
+		return dev_err_probe(dev, ret, "Failed to wake up coprocessor");
+
+	return 0;
+}
+
+static void apple_rtkit_helper_remove(struct platform_device *pdev)
+{
+	struct apple_rtkit_helper *helper = platform_get_drvdata(pdev);
+
+	if (apple_rtkit_is_running(helper->rtk))
+		apple_rtkit_quiesce(helper->rtk);
+
+	writel_relaxed(0, helper->asc_base + APPLE_ASC_CPU_CONTROL);
+}
+
+static const struct of_device_id apple_rtkit_helper_of_match[] = {
+	{ .compatible = "apple,rtk-helper-asc4" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, apple_rtkit_helper_of_match);
+
+static struct platform_driver apple_rtkit_helper_driver = {
+	.driver = {
+		.name = "rtkit-helper",
+		.of_match_table = apple_rtkit_helper_of_match,
+	},
+	.probe = apple_rtkit_helper_probe,
+	.remove = apple_rtkit_helper_remove,
+};
+module_platform_driver(apple_rtkit_helper_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple RTKit helper driver");

From 08f10a1760b3f707a492551fd74f4a2323e8cba2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 20:08:45 +0100
Subject: [PATCH 0436/1027] HID: transport: spi: Check status message after
 transmits

Probably pointless but might be helpful to debug issues.
Gets rid of 'spi_hid_apple_status_ok' is unused compiler warning.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/spi-hid/spi-hid-apple-core.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
index cd018df4f38715..29317a824c7270 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-core.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -248,7 +248,17 @@ static int spihid_apple_request(struct spihid_apple *spihid, u8 target, u8 unk0,
 	pkt->crc16 = cpu_to_le16(crc16(0, spihid->tx_buf,
 				 offsetof(struct spihid_transfer_packet, crc16)));
 
+	memset(spihid->status_buf, 0, sizeof(spi_hid_apple_status_ok));
+
 	err = spi_sync(spihid->spidev, &spihid->tx_msg);
+
+	if (memcmp(spihid->status_buf, spi_hid_apple_status_ok,
+		   sizeof(spi_hid_apple_status_ok))) {
+		u8 *b = spihid->status_buf;
+		dev_warn_ratelimited(&spihid->spidev->dev, "status message "
+				     "mismatch: %02x %02x %02x %02x\n",
+				     b[0], b[1], b[2], b[3]);
+	}
 	mutex_unlock(&spihid->tx_lock);
 	if (err < 0)
 		return err;

From 17f9b97f16589286a934cd3b7e4d670fa487594f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 22:56:16 +0100
Subject: [PATCH 0437/1027] HID: magicmouse: Add .reset_resume for SPI
 trackpads

The trackpad has to request multi touch reports during resume.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/hid-magicmouse.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 07038054f06e1d..00320e934883a8 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -1271,6 +1271,16 @@ static const struct hid_device_id magic_mice[] = {
 };
 MODULE_DEVICE_TABLE(hid, magic_mice);
 
+#ifdef CONFIG_PM
+static int magicmouse_reset_resume(struct hid_device *hdev)
+{
+	if (hdev->bus == BUS_SPI)
+		return magicmouse_enable_multitouch(hdev);
+
+	return 0;
+}
+#endif
+
 static struct hid_driver magicmouse_driver = {
 	.name = "magicmouse",
 	.id_table = magic_mice,
@@ -1281,6 +1291,10 @@ static struct hid_driver magicmouse_driver = {
 	.event = magicmouse_event,
 	.input_mapping = magicmouse_input_mapping,
 	.input_configured = magicmouse_input_configured,
+#ifdef CONFIG_PM
+        .reset_resume = magicmouse_reset_resume,
+#endif
+
 };
 module_hid_driver(magicmouse_driver);
 

From bd66dcb38ca0c4a6f08c96c8716135c98b5ac24e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 9 Oct 2023 23:36:27 +0900
Subject: [PATCH 0438/1027] macaudio: speaker volume safety interlocks

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 389 ++++++++++++++++++++++++++++++++++++-
 1 file changed, 379 insertions(+), 10 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 46476e234c299e..4fe97d3afde50d 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -55,11 +55,29 @@
  */
 #define MACAUDIO_MAX_BCLK_FREQ	24576000
 
+#define SPEAKER_MAGIC_VALUE (s32)0xdec1be15
+/* milliseconds */
+#define SPEAKER_LOCK_TIMEOUT 250
+
+#define MAX_LIMITS 6
+
+struct macaudio_limit_cfg {
+	const char *match;
+	int max_limited;
+	int max_unlimited;
+};
+
+struct macaudio_platform_cfg {
+	struct macaudio_limit_cfg limits[MAX_LIMITS];
+	int (*fixup)(struct snd_soc_card *card);
+};
+
 struct macaudio_snd_data {
 	struct snd_soc_card card;
 	struct snd_soc_jack jack;
 	int jack_plugin_state;
 
+	const struct macaudio_platform_cfg *cfg;
 	bool has_speakers;
 	unsigned int max_channels;
 
@@ -76,6 +94,18 @@ struct macaudio_snd_data {
 
 	int speaker_sample_rate;
 	struct snd_kcontrol *speaker_sample_rate_kctl;
+
+	bool speaker_volume_unlocked;
+	bool speaker_volume_was_locked;
+	struct snd_kcontrol *speaker_lock_kctl;
+	struct snd_ctl_file *speaker_lock_owner;
+	u64 bes_active;
+	bool speaker_lock_timeout_enabled;
+	ktime_t speaker_lock_timeout;
+	ktime_t speaker_lock_remain;
+	struct delayed_work lock_timeout_work;
+	struct work_struct lock_update_work;
+
 };
 
 static bool please_blow_up_my_speakers;
@@ -165,6 +195,159 @@ static struct macaudio_link_props macaudio_fe_link_props[] = {
 	}
 };
 
+static void macaudio_vlimit_unlock(struct macaudio_snd_data *ma, bool unlock)
+{
+	int i, ret, max;
+
+	for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) {
+		const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i];
+
+		if (!limit->match)
+			break;
+
+		if (unlock)
+			max = limit->max_unlimited;
+		else
+			max = limit->max_limited;
+
+		ret = snd_soc_limit_volume(&ma->card, limit->match, max);
+		if (ret < 0)
+			dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n",
+				unlock ? "un" : "", limit->match, ret);
+	}
+}
+
+static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
+{
+	int i;
+	bool unlock = true;
+	struct snd_kcontrol *kctl;
+	const char *reason;
+
+	/* Do nothing if there are no limits configured */
+	if (!ma->cfg->limits[0].match)
+		return;
+
+	/* Check that someone is holding the main lock */
+	if (!ma->speaker_lock_owner) {
+		reason = "Main control not locked";
+		unlock = false;
+	}
+
+	/* Check that the control has been pinged within the timeout */
+	if (ma->speaker_lock_remain <= 0) {
+		reason = "Lock timeout";
+		unlock = false;
+	}
+
+	/* Check that *every* limited control is locked by the same owner */
+	list_for_each_entry(kctl, &ma->card.snd_card->controls, list) {
+		bool is_limit = false;
+
+		for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) {
+			const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i];
+			if (!limit->match)
+				break;
+
+			is_limit = snd_soc_control_matches(kctl, limit->match);
+			if (is_limit)
+				break;
+		}
+
+		if (!is_limit)
+			continue;
+
+		for (i = 0; i < kctl->count; i++) {
+			if (kctl->vd[i].owner != ma->speaker_lock_owner) {
+				reason = "Not all child controls locked by the same process";
+				unlock = false;
+			}
+		}
+	}
+
+
+	if (unlock != ma->speaker_volume_unlocked) {
+		if (unlock) {
+			dev_info(ma->card.dev, "Speaker volumes unlocked\n");
+		} else  {
+			dev_info(ma->card.dev, "Speaker volumes locked: %s\n", reason);
+			ma->speaker_volume_was_locked = true;
+		}
+
+		macaudio_vlimit_unlock(ma, unlock);
+		ma->speaker_volume_unlocked = unlock;
+	}
+}
+
+static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma)
+{
+	if (ma->speaker_lock_timeout_enabled)
+		return;
+
+	down_write(&ma->card.snd_card->controls_rwsem);
+
+	if (ma->speaker_lock_remain > 0) {
+		ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain);
+		schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain)));
+		dev_dbg(ma->card.dev, "Enabling volume limit timeout: %ld us left\n",
+			(long)ktime_to_us(ma->speaker_lock_remain));
+	}
+
+	macaudio_vlimit_update(ma);
+
+	up_write(&ma->card.snd_card->controls_rwsem);
+	ma->speaker_lock_timeout_enabled = true;
+}
+
+static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma)
+{
+	ktime_t now = ktime_get();
+
+	if (!ma->speaker_lock_timeout_enabled)
+		return;
+
+	down_write(&ma->card.snd_card->controls_rwsem);
+
+	cancel_delayed_work(&ma->lock_timeout_work);
+
+	if (ktime_after(now, ma->speaker_lock_timeout))
+		ma->speaker_lock_remain = 0;
+	else if (ma->speaker_lock_remain > 0)
+		ma->speaker_lock_remain = ktime_sub(ma->speaker_lock_timeout, now);
+
+	dev_dbg(ma->card.dev, "Disabling volume limit timeout: %ld us left\n",
+		(long)ktime_to_us(ma->speaker_lock_remain));
+
+	macaudio_vlimit_update(ma);
+
+	up_write(&ma->card.snd_card->controls_rwsem);
+	ma->speaker_lock_timeout_enabled = false;
+}
+
+static void macaudio_vlimit_timeout_work(struct work_struct *wrk)
+{
+        struct macaudio_snd_data *ma = container_of(to_delayed_work(wrk),
+						    struct macaudio_snd_data, lock_timeout_work);
+
+	down_write(&ma->card.snd_card->controls_rwsem);
+
+	ma->speaker_lock_remain = 0;
+	macaudio_vlimit_update(ma);
+
+	up_write(&ma->card.snd_card->controls_rwsem);
+}
+
+static void macaudio_vlimit_update_work(struct work_struct *wrk)
+{
+        struct macaudio_snd_data *ma = container_of(wrk,
+						    struct macaudio_snd_data, lock_update_work);
+
+	if (ma->bes_active)
+		macaudio_vlimit_enable_timeout(ma);
+	else
+		macaudio_vlimit_disable_timeout(ma);
+}
+
 static int macaudio_copy_link(struct device *dev, struct snd_soc_dai_link *target,
 			       struct snd_soc_dai_link *source)
 {
@@ -623,7 +806,34 @@ static int macaudio_be_hw_free(struct snd_pcm_substream *substream)
 		ma->speaker_sample_rate = 0;
 		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
 			       &ma->speaker_sample_rate_kctl->id);
+	}
+
+	return 0;
+}
+
+static int macaudio_be_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct snd_soc_pcm_runtime *rtd = snd_soc_substream_to_rtd(substream);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(rtd->card);
+	struct macaudio_link_props *props = &ma->link_props[rtd->dai_link->id];
+
+	if (props->is_speakers && substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		switch (cmd) {
+		case SNDRV_PCM_TRIGGER_START:
+		case SNDRV_PCM_TRIGGER_RESUME:
+		case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+			ma->bes_active |= BIT(rtd->dai_link->id);
+			break;
+		case SNDRV_PCM_TRIGGER_SUSPEND:
+		case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		case SNDRV_PCM_TRIGGER_STOP:
+			ma->bes_active &= ~BIT(rtd->dai_link->id);
+			break;
+		default:
+			return -EINVAL;
+		}
 
+		schedule_work(&ma->lock_update_work);
 	}
 
 	return 0;
@@ -639,6 +849,7 @@ static const struct snd_soc_ops macaudio_be_ops = {
 	.hw_free	= macaudio_be_hw_free,
 	.shutdown	= macaudio_dpcm_shutdown,
 	.hw_params	= macaudio_dpcm_hw_params,
+	.trigger	= macaudio_be_trigger,
 };
 
 static int macaudio_be_assign_tdm(struct snd_soc_pcm_runtime *rtd)
@@ -868,10 +1079,14 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 	}
 
 	ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate");
+	ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock");
 
 	return 0;
 }
 
+#define TAS2764_0DB 201
+#define TAS2764_DB_REDUCTION(x) (TAS2764_0DB - 2 * (x))
+
 #define CHECK(call, pattern, value) \
 	{ \
 		int ret = call(card, pattern, value); \
@@ -882,7 +1097,6 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 		dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \
 	}
 
-
 static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -894,6 +1108,10 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
 	return 0;	
 }
 
+struct macaudio_platform_cfg macaudio_j274_cfg = {
+	.fixup = macaudio_j274_fixup_controls,
+};
+
 static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
@@ -919,11 +1137,17 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
 		 */
 		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
 		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
+
+		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
 }
 
+struct macaudio_platform_cfg macaudio_j313_cfg = {
+	.fixup = macaudio_j313_fixup_controls,
+};
+
 static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -957,11 +1181,41 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
 		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
 #endif
+
+		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
 }
 
+
+struct macaudio_platform_cfg macaudio_j314_cfg = {
+	.fixup = macaudio_j314_fixup_controls,
+	.limits = {
+		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+	}
+};
+
+struct macaudio_platform_cfg macaudio_j413_cfg = {
+	.fixup = macaudio_j314_fixup_controls,
+	.limits = {
+		/* Min gain: -17.47 dB */
+		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+		/* Min gain: -10.63 dB */
+		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(14), TAS2764_0DB},
+	}
+};
+
+struct macaudio_platform_cfg macaudio_j415_cfg = {
+	.fixup = macaudio_j314_fixup_controls,
+	.limits = {
+		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+		{.match = "* Woofer 1 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+		{.match = "* Woofer 2 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
+	}
+};
+
 static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -973,11 +1227,17 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 		}
 
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+
+		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
 }
 
+struct macaudio_platform_cfg macaudio_j375_cfg = {
+	.fixup = macaudio_j375_fixup_controls,
+};
+
 static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -989,11 +1249,17 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
 		}
 
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
+
+		macaudio_vlimit_update(ma);
 	}
 
 	return 0;	
 }
 
+struct macaudio_platform_cfg macaudio_j493_cfg = {
+	.fixup = macaudio_j493_fixup_controls
+};
+
 static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -1006,6 +1272,10 @@ static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
 	return 0;
 }
 
+struct macaudio_platform_cfg macaudio_fallback_cfg = {
+	.fixup = macaudio_fallback_fixup_controls
+};
+
 #undef CHECK
 
 static const char * const macaudio_spk_mux_texts[] = {
@@ -1069,10 +1339,91 @@ static int macaudio_sss_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
 	return 0;
 }
 
+static int macaudio_slk_info(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 1;
+	uinfo->value.integer.min = INT_MIN;
+	uinfo->value.integer.max = INT_MAX;
+
+	return 0;
+}
+
+static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	if (!ma->speaker_lock_owner)
+		return -EPERM;
+
+	if (uvalue->value.integer.value[0] != SPEAKER_MAGIC_VALUE)
+		return -EINVAL;
+
+	/* Serves as a notification that the lock was lost at some point */
+	if (ma->speaker_volume_was_locked) {
+		ma->speaker_volume_was_locked = false;
+		return -ETIMEDOUT;
+	}
+
+	cancel_delayed_work(&ma->lock_timeout_work);
+
+	ma->speaker_lock_remain = ms_to_ktime(SPEAKER_LOCK_TIMEOUT);
+	ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain);
+	macaudio_vlimit_update(ma);
+
+	if (ma->speaker_lock_timeout_enabled) {
+		dev_dbg(ma->card.dev, "Volume limit timeout ping: %ld us left\n",
+			(long)ktime_to_us(ma->speaker_lock_remain));
+		schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain)));
+	}
+
+	return 0;
+}
+
+int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	ma->speaker_lock_owner = owner;
+	macaudio_vlimit_update(ma);
+
+	/*
+	 * Reset the unintended lock flag when the control is first locked.
+	 * At this point the state is locked and cannot be unlocked until
+	 * userspace writes to this control, so this cannot spuriously become
+	 * true again until that point.
+	 */
+	ma->speaker_volume_was_locked = false;
+
+	return 0;
+}
+
+static void macaudio_slk_unlock(struct snd_kcontrol *kcontrol)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	ma->speaker_lock_owner = NULL;
+	ma->speaker_lock_timeout = 0;
+	macaudio_vlimit_update(ma);
+}
+
+/* Speaker limit controls go last */
+#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 2
+
 static const struct snd_kcontrol_new macaudio_controls[] = {
 	SOC_DAPM_PIN_SWITCH("Speaker"),
 	SOC_DAPM_PIN_SWITCH("Headphone"),
 	SOC_DAPM_PIN_SWITCH("Headset Mic"),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.name = "Speaker Volume Unlock",
+		.info = macaudio_slk_info, .put = macaudio_slk_put,
+		.lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock,
+	},
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.access = SNDRV_CTL_ELEM_ACCESS_READ |
@@ -1103,13 +1454,13 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 };
 
 static const struct of_device_id macaudio_snd_device_id[]  = {
-	{ .compatible = "apple,j274-macaudio", .data = macaudio_j274_fixup_controls },
-	{ .compatible = "apple,j313-macaudio", .data = macaudio_j313_fixup_controls },
-	{ .compatible = "apple,j314-macaudio", .data = macaudio_j314_fixup_controls },
-	{ .compatible = "apple,j375-macaudio", .data = macaudio_j375_fixup_controls },
-	{ .compatible = "apple,j413-macaudio", .data = macaudio_j314_fixup_controls },
-	{ .compatible = "apple,j415-macaudio", .data = macaudio_j314_fixup_controls },
-	{ .compatible = "apple,j493-macaudio", .data = macaudio_j493_fixup_controls },
+	{ .compatible = "apple,j274-macaudio", .data = &macaudio_j274_cfg },
+	{ .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg },
+	{ .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg },
+	{ .compatible = "apple,j375-macaudio", .data = &macaudio_j375_cfg },
+	{ .compatible = "apple,j413-macaudio", .data = &macaudio_j413_cfg },
+	{ .compatible = "apple,j415-macaudio", .data = &macaudio_j415_cfg },
+	{ .compatible = "apple,j493-macaudio", .data = &macaudio_j493_cfg },
 	{ .compatible = "apple,macaudio"},
 	{ }
 };
@@ -1134,6 +1485,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 		return -ENOMEM;
 	card = &data->card;
 	snd_soc_card_set_drvdata(card, data);
+	dev_set_drvdata(&pdev->dev, data);
 
 	card->owner = THIS_MODULE;
 	card->driver_name = "macaudio";
@@ -1150,9 +1502,15 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 	card->fully_routed = true;
 
 	if (of_id->data)
-		card->fixup_controls = of_id->data;
+		data->cfg = of_id->data;
 	else
-		card->fixup_controls = macaudio_fallback_fixup_controls;
+		data->cfg = &macaudio_fallback_cfg;
+
+	/* Remove speaker safety controls if we have no declared limits */
+	if (!data->cfg->limits[0].match)
+		card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS;
+
+	card->fixup_controls = data->cfg->fixup;
 
 	ret = macaudio_parse_of(data);
 	if (ret)
@@ -1169,11 +1527,22 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 		}
 	}
 
+	INIT_WORK(&data->lock_update_work, macaudio_vlimit_update_work);
+	INIT_DELAYED_WORK(&data->lock_timeout_work, macaudio_vlimit_timeout_work);
+
 	return devm_snd_soc_register_card(dev, card);
 }
 
+static void macaudio_snd_platform_remove(struct platform_device *pdev)
+{
+	struct macaudio_snd_data *ma = dev_get_drvdata(&pdev->dev);
+
+	cancel_delayed_work_sync(&ma->lock_timeout_work);
+}
+
 static struct platform_driver macaudio_snd_driver = {
 	.probe = macaudio_snd_platform_probe,
+	.remove = macaudio_snd_platform_remove,
 	.driver = {
 		.name = DRIVER_NAME,
 		.of_match_table = macaudio_snd_device_id,

From ae875e3ec4f4fa7653938ef52335a347f01b9537 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 22:53:57 +0900
Subject: [PATCH 0439/1027] alsa: pcm: Remove the qos request only if active

Fixes warning:

[    8.502802] ------------[ cut here ]------------
[    8.503445] cpu_latency_qos_remove_request called for unknown object
[    8.504269] WARNING: CPU: 5 PID: 2790 at kernel/power/qos.c:322 cpu_latency_qos_remove_request+0x48/0x98
[    8.505499] CPU: 5 PID: 2790 Comm: wireplumber Tainted: G        W          6.5.0-asahi-00708-gb9b88240f7ae #2291
[    8.506777] Hardware name: Apple MacBook Air (13-inch, M2, 2022) (DT)
<snip regs>
[    8.519099] Call trace:
[    8.519402]  cpu_latency_qos_remove_request+0x48/0x98
[    8.520027]  snd_pcm_ioctl+0x86c/0x182c
[    8.520519]  __arm64_sys_ioctl+0xf8/0xbd0
[    8.521020]  invoke_syscall.constprop.0+0x78/0xc8
[    8.521604]  do_el0_svc+0x58/0x154
[    8.522026]  el0_svc+0x34/0xe4
[    8.522409]  el0t_64_sync_handler+0x120/0x12c
[    8.522951]  el0t_64_sync+0x190/0x194
[    8.523408] ---[ end trace 0000000000000000 ]---

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/core/pcm_native.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 4057f9f10aeec2..a650d2a0a36bae 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -922,8 +922,9 @@ static int snd_pcm_hw_free(struct snd_pcm_substream *substream)
 		goto unlock;
 	result = do_hw_free(substream);
 	snd_pcm_set_state(substream, SNDRV_PCM_STATE_OPEN);
-	cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
- unlock:
+	if (cpu_latency_qos_request_active(&substream->latency_pm_qos_req))
+		cpu_latency_qos_remove_request(&substream->latency_pm_qos_req);
+unlock:
 	snd_pcm_buffer_access_unlock(runtime);
 	return result;
 }

From 07aedc7b91e5d9b8f0d68a156c03a079b3e251dd Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 12 Oct 2023 23:01:12 +0900
Subject: [PATCH 0440/1027] macaudio: Add a getter for the interlock

alsamixer/etc really don't like write-only controls...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 21 ++++++++++++++++++---
 1 file changed, 18 insertions(+), 3 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 4fe97d3afde50d..888ef75e9fc8c2 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -276,6 +276,8 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
 
 		macaudio_vlimit_unlock(ma, unlock);
 		ma->speaker_volume_unlocked = unlock;
+		snd_ctl_notify(ma->card.snd_card, SNDRV_CTL_EVENT_MASK_VALUE,
+			       &ma->speaker_lock_kctl->id);
 	}
 }
 
@@ -1381,7 +1383,17 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
 	return 0;
 }
 
-int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner)
+static int macaudio_slk_get(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *uvalue)
+{
+	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
+	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+
+	uvalue->value.integer.value[0] = ma->speaker_volume_unlocked ? 1 : 0;
+
+	return 0;
+}
+
+static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file *owner)
 {
 	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
@@ -1419,9 +1431,12 @@ static const struct snd_kcontrol_new macaudio_controls[] = {
 	SOC_DAPM_PIN_SWITCH("Headset Mic"),
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-		.access = SNDRV_CTL_ELEM_ACCESS_WRITE,
+		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_WRITE |
+			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
 		.name = "Speaker Volume Unlock",
-		.info = macaudio_slk_info, .put = macaudio_slk_put,
+		.info = macaudio_slk_info,
+		.put = macaudio_slk_put, .get = macaudio_slk_get,
 		.lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock,
 	},
 	{

From 2a5b0deae24179c872cfc9c5916d5e7be80eb3d1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 01:12:55 +0900
Subject: [PATCH 0441/1027] ASoC: apple: mca: Do not mark clocks in use for
 non-providers

On the speakers PCM, this sequence:

1. Open playback
2. Open sense
3. Close playback
4. Close sense

would result in the sense FE being marked as clocks in use at (2), since
there is a clock provider (playback FE). Then at (4) this would WARN since
there is no driver any more when closing the in use clocks.

If (1) and (2) are reversed this does not happen, since the sense PCM is
not marked as using the clocks when there is no provider yet. So, check
explicitly whether the substream FE is a clock provider in be_prepare,
and skip everything if it isn't.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 67 ++++++++++++++++++++++++-------------------
 1 file changed, 37 insertions(+), 30 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 50745d2d41bdcd..c6b5ecc367c860 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -352,36 +352,6 @@ static bool mca_fe_clocks_in_use(struct mca_cluster *cl)
 	return false;
 }
 
-static int mca_be_prepare(struct snd_pcm_substream *substream,
-			  struct snd_soc_dai *dai)
-{
-	struct mca_cluster *cl = mca_dai_to_cluster(dai);
-	struct mca_data *mca = cl->host;
-	struct mca_cluster *fe_cl;
-	int ret;
-
-	if (cl->port_clk_driver < 0)
-		return 0;
-
-	fe_cl = &mca->clusters[cl->port_clk_driver];
-
-	/*
-	 * Typically the CODECs we are paired with will require clocks
-	 * to be present at time of unmute with the 'mute_stream' op
-	 * or at time of DAPM widget power-up. We need to enable clocks
-	 * here at the latest (frontend prepare would be too late).
-	 */
-	if (!mca_fe_clocks_in_use(fe_cl)) {
-		ret = mca_fe_enable_clocks(fe_cl);
-		if (ret < 0)
-			return ret;
-	}
-
-	cl->clocks_in_use[substream->stream] = true;
-
-	return 0;
-}
-
 static int mca_fe_prepare(struct snd_pcm_substream *substream,
 			  struct snd_soc_dai *dai)
 {
@@ -787,6 +757,43 @@ static struct snd_soc_pcm_runtime *mca_be_get_fe(struct snd_soc_pcm_runtime *be,
 	return fe;
 }
 
+static int mca_be_prepare(struct snd_pcm_substream *substream,
+			  struct snd_soc_dai *dai)
+{
+	struct snd_soc_pcm_runtime *be = snd_soc_substream_to_rtd(substream);
+	struct snd_soc_pcm_runtime *fe = mca_be_get_fe(be, substream->stream);
+	struct mca_cluster *cl = mca_dai_to_cluster(dai);
+	struct mca_data *mca = cl->host;
+	struct mca_cluster *fe_cl, *fe_clk_cl;
+	int ret;
+
+	fe_cl = mca_dai_to_cluster(snd_soc_rtd_to_cpu(fe, 0));
+
+	if (!fe_cl->clk_provider)
+		return 0;
+
+	if (cl->port_clk_driver < 0)
+		return 0;
+
+	fe_clk_cl = &mca->clusters[cl->port_clk_driver];
+
+	/*
+	 * Typically the CODECs we are paired with will require clocks
+	 * to be present at time of unmute with the 'mute_stream' op
+	 * or at time of DAPM widget power-up. We need to enable clocks
+	 * here at the latest (frontend prepare would be too late).
+	 */
+	if (!mca_fe_clocks_in_use(fe_clk_cl)) {
+		ret = mca_fe_enable_clocks(fe_clk_cl);
+		if (ret < 0)
+			return ret;
+	}
+
+	cl->clocks_in_use[substream->stream] = true;
+
+	return 0;
+}
+
 static int mca_be_startup(struct snd_pcm_substream *substream,
 			  struct snd_soc_dai *dai)
 {

From 58914fe6d550078df4d8b10e0ddf13a79fd2cecf Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 02:31:55 +0900
Subject: [PATCH 0442/1027] macaudio: Allow DT enabled speakers and gate them
 off in the driver

For machines where we do not consider things safe yet, require the
commandline argument. Without it, speakers are simply disabled, we don't
refuse probe entirely.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 37 +++++++++++++++----------------------
 1 file changed, 15 insertions(+), 22 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 888ef75e9fc8c2..3f14525395e9ee 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -70,6 +70,7 @@ struct macaudio_limit_cfg {
 struct macaudio_platform_cfg {
 	struct macaudio_limit_cfg limits[MAX_LIMITS];
 	int (*fixup)(struct snd_soc_card *card);
+	bool enable_speakers;
 };
 
 struct macaudio_snd_data {
@@ -487,7 +488,6 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 	if (!card->dai_link || !ma->link_props)
 		return -ENOMEM;
 
-	card->num_links = num_links;
 	link = card->dai_link;
 	link_props = ma->link_props;
 
@@ -503,6 +503,9 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 	for (i = 0; i < num_links; i++)
 		card->dai_link[i].id = i;
 
+	/* We might disable the speakers, so count again */
+	num_links = ARRAY_SIZE(macaudio_fe_links);
+
 	/* Fill in the BEs */
 	for_each_available_child_of_node(dev->of_node, np) {
 		const char *link_name;
@@ -520,8 +523,13 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 
 		speakers = !strcmp(link_name, "Speaker")
 			   || !strcmp(link_name, "Speakers");
-		if (speakers)
+		if (speakers) {
+			if (!ma->cfg->enable_speakers  && !please_blow_up_my_speakers) {
+				dev_err(card->dev, "driver can't assure safety on this model, disabling speakers\n");
+				continue;
+			}
 			ma->has_speakers = 1;
+		}
 
 		cpu = of_get_child_by_name(np, "cpu");
 		codec = of_get_child_by_name(np, "codec");
@@ -615,11 +623,15 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 		of_node_put(codec);
 		of_node_put(cpu);
 		cpu = codec = NULL;
+
+		num_links += num_bes;
 	}
 
 	for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++)
 		card->dai_link[i].platforms->of_node = platform;
 
+	card->num_links = num_links;
+
 	return 0;
 
 err_free:
@@ -1112,17 +1124,13 @@ static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
 
 struct macaudio_platform_cfg macaudio_j274_cfg = {
 	.fixup = macaudio_j274_fixup_controls,
+	.enable_speakers = true,
 };
 
 static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
-		if (!please_blow_up_my_speakers) {
-			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
-			return -EINVAL;
-		}
-
 		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
 		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
 
@@ -1155,11 +1163,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
-		if (!please_blow_up_my_speakers) {
-			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
-			return -EINVAL;
-		}
-
 		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
 		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
@@ -1223,11 +1226,6 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
-		if (!please_blow_up_my_speakers) {
-			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
-			return -EINVAL;
-		}
-
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
 
 		macaudio_vlimit_update(ma);
@@ -1245,11 +1243,6 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
 	if (ma->has_speakers) {
-		if (!please_blow_up_my_speakers) {
-			dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
-			return -EINVAL;
-		}
-
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
 
 		macaudio_vlimit_update(ma);

From 6af43845aa0c1f946ed0fb139b278fb3aadf1195 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 02:43:12 +0900
Subject: [PATCH 0443/1027] macaudio: Enable VSENSE switches

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 20 --------------------
 1 file changed, 20 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 3f14525395e9ee..c038887b40258b 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1139,15 +1139,6 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
 		 */
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14);
 
-		/*
-		 * Since we don't set the right slots yet to avoid
-		 * driver conflict on the I2S bus sending ISENSE/VSENSE
-		 * samples from the codecs back to us, disable the
-		 * controls.
-		 */
-		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
-		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
-
 		macaudio_vlimit_update(ma);
 	}
 
@@ -1176,17 +1167,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 		CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry");
 		CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0);
 
-		/*
-		 * Since we don't set the right slots yet to avoid
-		 * driver conflict on the I2S bus sending ISENSE/VSENSE
-		 * samples from the codecs back to us, disable the
-		 * controls.
-		 */
-#if 0
-		CHECK(snd_soc_deactivate_kctl, "* VSENSE Switch", 0);
-		CHECK(snd_soc_deactivate_kctl, "* ISENSE Switch", 0);
-#endif
-
 		macaudio_vlimit_update(ma);
 	}
 

From ce047616ad754bb3749077859fea6fa653805090 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 06:22:24 +0900
Subject: [PATCH 0444/1027] macaudio: Initialize speaker lock properly

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 10 ++--------
 1 file changed, 2 insertions(+), 8 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index c038887b40258b..1b89d347a5208f 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1095,6 +1095,8 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 	ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate");
 	ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock");
 
+	macaudio_vlimit_unlock(ma, false);
+
 	return 0;
 }
 
@@ -1138,8 +1140,6 @@ static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
 		 *     what macOS sets.
 		 */
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14);
-
-		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
@@ -1166,8 +1166,6 @@ static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
 		 */
 		CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry");
 		CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0);
-
-		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
@@ -1207,8 +1205,6 @@ static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
 
 	if (ma->has_speakers) {
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
-
-		macaudio_vlimit_update(ma);
 	}
 
 	return 0;
@@ -1224,8 +1220,6 @@ static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
 
 	if (ma->has_speakers) {
 		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
-
-		macaudio_vlimit_update(ma);
 	}
 
 	return 0;	

From 68fa273ed04191d2137e0ede89791485735f6a4e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 16:57:46 +0900
Subject: [PATCH 0445/1027] macaudio: Use the same volume limit for all amps

These are unintentionally aliased. Pending a solution for this, let's
just use the same limit for now.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 1b89d347a5208f..1fdb96a12046b4 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1186,7 +1186,8 @@ struct macaudio_platform_cfg macaudio_j413_cfg = {
 		/* Min gain: -17.47 dB */
 		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
 		/* Min gain: -10.63 dB */
-		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(14), TAS2764_0DB},
+		/* FIXME: These structures are aliased so we can't set different max volumes */
+		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
 	}
 };
 

From d7d36f0463fea5d3d6cd0cd37bc7873ce716cf4f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 23:39:15 +0900
Subject: [PATCH 0446/1027] macaudio: Disable debug

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 1fdb96a12046b4..9e98afb1257d38 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -20,7 +20,7 @@
  * reparenting of live BEs.)
  */
 
-#define DEBUG
+/* #define DEBUG */
 
 #include <linux/module.h>
 #include <linux/of_device.h>

From 08cf729ec01bf7db646710290686ed518c493a30 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 13 Oct 2023 23:39:41 +0900
Subject: [PATCH 0447/1027] ASoC: tas2764: Enable main IRQs

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index de618ddd742a3e..ed76e5b2e0438d 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -651,7 +651,7 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 	regmap_reinit_cache(tas2764->regmap, &tas2764_i2c_regmap);
 
 	if (tas2764->irq) {
-		ret = snd_soc_component_write(tas2764->component, TAS2764_INT_MASK0, 0xff);
+		ret = snd_soc_component_write(tas2764->component, TAS2764_INT_MASK0, 0x00);
 		if (ret < 0)
 			return ret;
 

From 805b53c56258ba0c3181d2c8b60fa86f24e3cc1a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 21 Oct 2023 01:03:07 +0900
Subject: [PATCH 0448/1027] ASoC: tas2764: Power up/down amp on mute ops

The ASoC convention is that clocks are removed after codec mute, and
power up/down is more about top level power management. For these chips,
the "mute" state still expects a TDM clock, and yanking the clock in
this state will trigger clock errors. So, do the full
shutdown<->mute<->active transition on the mute operation, so the amp is
in software shutdown by the time the clocks are removed.

This fixes TDM clock errors when streams are stopped.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 51 ++++++++++++++++----------------------
 1 file changed, 21 insertions(+), 30 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index ed76e5b2e0438d..7978cb885db91c 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -199,33 +199,6 @@ static SOC_ENUM_SINGLE_DECL(
 static const struct snd_kcontrol_new tas2764_asi1_mux =
 	SOC_DAPM_ENUM("ASI1 Source", tas2764_ASI1_src_enum);
 
-static int tas2764_dac_event(struct snd_soc_dapm_widget *w,
-			     struct snd_kcontrol *kcontrol, int event)
-{
-	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
-	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
-	int ret;
-
-	switch (event) {
-	case SND_SOC_DAPM_POST_PMU:
-		tas2764->dac_powered = true;
-		ret = tas2764_update_pwr_ctrl(tas2764);
-		break;
-	case SND_SOC_DAPM_PRE_PMD:
-		tas2764->dac_powered = false;
-		ret = tas2764_update_pwr_ctrl(tas2764);
-		break;
-	default:
-		dev_err(tas2764->dev, "Unsupported event\n");
-		return -EINVAL;
-	}
-
-	if (ret < 0)
-		return ret;
-
-	return 0;
-}
-
 static const struct snd_kcontrol_new isense_switch =
 	SOC_DAPM_SINGLE("Switch", TAS2764_PWR_CTRL, TAS2764_ISENSE_POWER_EN, 1, 1);
 static const struct snd_kcontrol_new vsense_switch =
@@ -238,8 +211,7 @@ static const struct snd_soc_dapm_widget tas2764_dapm_widgets[] = {
 			    1, &isense_switch),
 	SND_SOC_DAPM_SWITCH("VSENSE", TAS2764_PWR_CTRL, TAS2764_VSENSE_POWER_EN,
 			    1, &vsense_switch),
-	SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2764_dac_event,
-			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
+	SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
 	SND_SOC_DAPM_OUTPUT("OUT"),
 	SND_SOC_DAPM_SIGGEN("VMON"),
 	SND_SOC_DAPM_SIGGEN("IMON")
@@ -260,9 +232,28 @@ static int tas2764_mute(struct snd_soc_dai *dai, int mute, int direction)
 {
 	struct tas2764_priv *tas2764 =
 			snd_soc_component_get_drvdata(dai->component);
+	int ret;
+
+	if (!mute) {
+		tas2764->dac_powered = true;
+		ret = tas2764_update_pwr_ctrl(tas2764);
+		if (ret)
+			return ret;
+	}
 
 	tas2764->unmuted = !mute;
-	return tas2764_update_pwr_ctrl(tas2764);
+	ret = tas2764_update_pwr_ctrl(tas2764);
+	if (ret)
+		return ret;
+
+	if (mute) {
+		tas2764->dac_powered = false;
+		ret = tas2764_update_pwr_ctrl(tas2764);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
 }
 
 static int tas2764_set_bitwidth(struct tas2764_priv *tas2764, int bitwidth)

From 749953df4373f9fd0803dc96c23340968d9309c4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 21 Oct 2023 22:16:32 +0900
Subject: [PATCH 0449/1027] ASoC: tas2764: Add SDZ regulator

Multiple amps can be connected to the same SDZ GPIO. Using raw GPIOs for
this breaks, as there is no concept of refcounting/sharing. In order to
model these platforms, introduce support for an SDZ "regulator". This
allows us to represent the SDZ GPIO as a simple regulator-fixed, and
then the regulator core takes care of refcounting so that all codecs are
only powered down once all the driver instances are in the suspend
state.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 39 +++++++++++++++++++++++++++++++-------
 1 file changed, 32 insertions(+), 7 deletions(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 7978cb885db91c..e41ac2e8b51bfa 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -35,6 +35,7 @@ struct tas2764_priv {
 	struct snd_soc_component *component;
 	struct gpio_desc *reset_gpio;
 	struct gpio_desc *sdz_gpio;
+	struct regulator *sdz_reg;
 	struct regmap *regmap;
 	struct device *dev;
 	int irq;
@@ -158,6 +159,8 @@ static int tas2764_codec_suspend(struct snd_soc_component *component)
 	if (tas2764->sdz_gpio)
 		gpiod_set_value_cansleep(tas2764->sdz_gpio, 0);
 
+	regulator_disable(tas2764->sdz_reg);
+
 	regcache_cache_only(tas2764->regmap, true);
 	regcache_mark_dirty(tas2764->regmap);
 
@@ -169,19 +172,26 @@ static int tas2764_codec_resume(struct snd_soc_component *component)
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
 	int ret;
 
+	ret = regulator_enable(tas2764->sdz_reg);
+
+	if (ret) {
+		dev_err(tas2764->dev, "Failed to enable regulator\n");
+		return ret;
+	}
+
 	if (tas2764->sdz_gpio) {
 		gpiod_set_value_cansleep(tas2764->sdz_gpio, 1);
-		usleep_range(1000, 2000);
 	}
 
-	ret = tas2764_update_pwr_ctrl(tas2764);
+	usleep_range(1000, 2000);
 
+	regcache_cache_only(tas2764->regmap, false);
+
+	ret = regcache_sync(tas2764->regmap);
 	if (ret < 0)
 		return ret;
 
-	regcache_cache_only(tas2764->regmap, false);
-
-	return regcache_sync(tas2764->regmap);
+	return tas2764_update_pwr_ctrl(tas2764);
 }
 #else
 #define tas2764_codec_suspend NULL
@@ -214,7 +224,7 @@ static const struct snd_soc_dapm_widget tas2764_dapm_widgets[] = {
 	SND_SOC_DAPM_DAC("DAC", NULL, SND_SOC_NOPM, 0, 0),
 	SND_SOC_DAPM_OUTPUT("OUT"),
 	SND_SOC_DAPM_SIGGEN("VMON"),
-	SND_SOC_DAPM_SIGGEN("IMON")
+	SND_SOC_DAPM_SIGGEN("IMON"),
 };
 
 static const struct snd_soc_dapm_route tas2764_audio_map[] = {
@@ -633,11 +643,18 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 
 	tas2764->component = component;
 
+	ret = regulator_enable(tas2764->sdz_reg);
+	if (ret != 0) {
+		dev_err(tas2764->dev, "Failed to enable regulator: %d\n", ret);
+		return ret;
+	}
+
 	if (tas2764->sdz_gpio) {
 		gpiod_set_value_cansleep(tas2764->sdz_gpio, 1);
-		usleep_range(1000, 2000);
 	}
 
+	usleep_range(1000, 2000);
+
 	tas2764_reset(tas2764);
 	regmap_reinit_cache(tas2764->regmap, &tas2764_i2c_regmap);
 
@@ -709,6 +726,9 @@ static int tas2764_codec_probe(struct snd_soc_component *component)
 
 static void tas2764_codec_remove(struct snd_soc_component *component)
 {
+	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
+
+	regulator_disable(tas2764->sdz_reg);
 	sysfs_remove_groups(&component->dev->kobj, tas2764_sysfs_groups);
 }
 
@@ -811,6 +831,11 @@ static int tas2764_parse_dt(struct device *dev, struct tas2764_priv *tas2764)
 {
 	int ret = 0;
 
+	tas2764->sdz_reg = devm_regulator_get(dev, "SDZ");
+	if (IS_ERR(tas2764->sdz_reg))
+		return dev_err_probe(dev, PTR_ERR(tas2764->sdz_reg),
+				"Failed to get SDZ supply\n");
+
 	tas2764->reset_gpio = devm_gpiod_get_optional(tas2764->dev, "reset",
 						      GPIOD_OUT_HIGH);
 	if (IS_ERR(tas2764->reset_gpio)) {

From 8b4fb648936c31d5e59b1649227078673ee6de9b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 21 Oct 2023 22:38:36 +0900
Subject: [PATCH 0450/1027] macaudio: Use an explicit mutex for the speaker
 volume lock

Otherwise we can end up recursively locking the controls lock in the
start/stop path, since it can be called from a control change.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 38 ++++++++++++++++++++++++++++----------
 1 file changed, 28 insertions(+), 10 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 9e98afb1257d38..358c28246c5872 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -96,6 +96,7 @@ struct macaudio_snd_data {
 	int speaker_sample_rate;
 	struct snd_kcontrol *speaker_sample_rate_kctl;
 
+	struct mutex volume_lock_mutex;
 	bool speaker_volume_unlocked;
 	bool speaker_volume_was_locked;
 	struct snd_kcontrol *speaker_lock_kctl;
@@ -284,10 +285,12 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
 
 static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma)
 {
-	if (ma->speaker_lock_timeout_enabled)
-		return;
+	mutex_lock(&ma->volume_lock_mutex);
 
-	down_write(&ma->card.snd_card->controls_rwsem);
+	if (ma->speaker_lock_timeout_enabled) {
+		mutex_unlock(&ma->volume_lock_mutex);
+		return;
+	}
 
 	if (ma->speaker_lock_remain > 0) {
 		ma->speaker_lock_timeout = ktime_add(ktime_get(), ma->speaker_lock_remain);
@@ -298,18 +301,22 @@ static void macaudio_vlimit_enable_timeout(struct macaudio_snd_data *ma)
 
 	macaudio_vlimit_update(ma);
 
-	up_write(&ma->card.snd_card->controls_rwsem);
 	ma->speaker_lock_timeout_enabled = true;
+	mutex_unlock(&ma->volume_lock_mutex);
 }
 
 static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma)
 {
-	ktime_t now = ktime_get();
+	ktime_t now;
+
+	mutex_lock(&ma->volume_lock_mutex);
 
-	if (!ma->speaker_lock_timeout_enabled)
+	if (!ma->speaker_lock_timeout_enabled) {
+		mutex_unlock(&ma->volume_lock_mutex);
 		return;
+	}
 
-	down_write(&ma->card.snd_card->controls_rwsem);
+	now = ktime_get();
 
 	cancel_delayed_work(&ma->lock_timeout_work);
 
@@ -323,8 +330,9 @@ static void macaudio_vlimit_disable_timeout(struct macaudio_snd_data *ma)
 
 	macaudio_vlimit_update(ma);
 
-	up_write(&ma->card.snd_card->controls_rwsem);
 	ma->speaker_lock_timeout_enabled = false;
+
+	mutex_unlock(&ma->volume_lock_mutex);
 }
 
 static void macaudio_vlimit_timeout_work(struct work_struct *wrk)
@@ -332,12 +340,12 @@ static void macaudio_vlimit_timeout_work(struct work_struct *wrk)
         struct macaudio_snd_data *ma = container_of(to_delayed_work(wrk),
 						    struct macaudio_snd_data, lock_timeout_work);
 
-	down_write(&ma->card.snd_card->controls_rwsem);
+	mutex_lock(&ma->volume_lock_mutex);
 
 	ma->speaker_lock_remain = 0;
 	macaudio_vlimit_update(ma);
 
-	up_write(&ma->card.snd_card->controls_rwsem);
+	mutex_unlock(&ma->volume_lock_mutex);
 }
 
 static void macaudio_vlimit_update_work(struct work_struct *wrk)
@@ -1095,7 +1103,9 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 	ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate");
 	ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock");
 
+	mutex_lock(&ma->volume_lock_mutex);
 	macaudio_vlimit_unlock(ma, false);
+	mutex_unlock(&ma->volume_lock_mutex);
 
 	return 0;
 }
@@ -1336,6 +1346,8 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
 		return -ETIMEDOUT;
 	}
 
+	mutex_lock(&ma->volume_lock_mutex);
+
 	cancel_delayed_work(&ma->lock_timeout_work);
 
 	ma->speaker_lock_remain = ms_to_ktime(SPEAKER_LOCK_TIMEOUT);
@@ -1348,6 +1360,8 @@ static int macaudio_slk_put(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_v
 		schedule_delayed_work(&ma->lock_timeout_work, usecs_to_jiffies(ktime_to_us(ma->speaker_lock_remain)));
 	}
 
+	mutex_unlock(&ma->volume_lock_mutex);
+
 	return 0;
 }
 
@@ -1366,6 +1380,7 @@ static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file
 	struct snd_soc_card *card = snd_kcontrol_chip(kcontrol);
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
+	mutex_lock(&ma->volume_lock_mutex);
 	ma->speaker_lock_owner = owner;
 	macaudio_vlimit_update(ma);
 
@@ -1377,6 +1392,8 @@ static int macaudio_slk_lock(struct snd_kcontrol *kcontrol, struct snd_ctl_file
 	 */
 	ma->speaker_volume_was_locked = false;
 
+	mutex_unlock(&ma->volume_lock_mutex);
+
 	return 0;
 }
 
@@ -1469,6 +1486,7 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 	card = &data->card;
 	snd_soc_card_set_drvdata(card, data);
 	dev_set_drvdata(&pdev->dev, data);
+	mutex_init(&data->volume_lock_mutex);
 
 	card->owner = THIS_MODULE;
 	card->driver_name = "macaudio";

From c4bf149e005f9f09105e5a3e937dfae72d1c865e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 05:28:27 +0900
Subject: [PATCH 0451/1027] ASoC: tas2764: Add reg defaults for
 TAS2764_INT_CLK_CFG

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index e41ac2e8b51bfa..ef395db556bcfe 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -787,6 +787,7 @@ static const struct reg_default tas2764_reg_defaults[] = {
 	{ TAS2764_TDM_CFG2, 0x0a },
 	{ TAS2764_TDM_CFG3, 0x10 },
 	{ TAS2764_TDM_CFG5, 0x42 },
+	{ TAS2764_INT_CLK_CFG, 0x19 },
 };
 
 static const struct regmap_range_cfg tas2764_regmap_ranges[] = {

From b4ab8224c09e8c73a500f948e8e869d0bab0d5aa Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 05:29:07 +0900
Subject: [PATCH 0452/1027] ASoC: tas2764: Mark SW_RESET as volatile

Since the bit is self-clearing.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index ef395db556bcfe..052b94f15c23f5 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -805,6 +805,7 @@ static const struct regmap_range_cfg tas2764_regmap_ranges[] = {
 static bool tas2764_volatile_register(struct device *dev, unsigned int reg)
 {
 	switch (reg) {
+	case TAS2764_SW_RST:
 	case TAS2764_INT_LTCH0 ... TAS2764_INT_LTCH4:
 	case TAS2764_INT_CLK_CFG:
 		return true;

From 640b2c5d30d617c6ed1c096b8ec746a2ad448243 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 05:29:54 +0900
Subject: [PATCH 0453/1027] ASoC: tas2764: Fix power control mask

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index dbe3f7fa901879..786d81eb5b1e71 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -25,7 +25,7 @@
 
 /* Power Control */
 #define TAS2764_PWR_CTRL		TAS2764_REG(0X0, 0x02)
-#define TAS2764_PWR_CTRL_MASK		GENMASK(1, 0)
+#define TAS2764_PWR_CTRL_MASK		GENMASK(2, 0)
 #define TAS2764_PWR_CTRL_ACTIVE		0x0
 #define TAS2764_PWR_CTRL_MUTE		BIT(0)
 #define TAS2764_PWR_CTRL_SHUTDOWN	BIT(1)

From d2e7faf233208ddad95ab8a0923095f6783be72d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 07:07:40 +0900
Subject: [PATCH 0454/1027] ASoC: apple: mca: Increase reset timeout

Saw this fail once, let's be safer.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index c6b5ecc367c860..194b1430cf1176 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -216,9 +216,9 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd,
 			   SERDES_STATUS_RST);
 		/*
 		 * Experiments suggest that it takes at most ~1 us
-		 * for the bit to clear, so wait 2 us for good measure.
+		 * for the bit to clear, so wait 5 us for good measure.
 		 */
-		udelay(2);
+		udelay(5);
 		WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) &
 			SERDES_STATUS_RST);
 		mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL,

From 445a093e567d8b66f197f972620d8596a5b0d1c7 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 08:24:10 +0900
Subject: [PATCH 0455/1027] ALSA: dmaengine: Always terminate DMA when a PCM is
 closed

When a PCM is suspended, we pause the DMA. If the PCM is then closed
while in this state, it does not receive the STOP trigger (as it is not
running). In this case, we fail to properly terminate the DMA, calling
dmaengine_synchronize() nonetheless, which is undefined behavior.

Make sure we always call dmaengine_terminate_async() on PCM close,
regardless of whether it has been called previously or not in the
trigger callbacks.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/core/pcm_dmaengine.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c
index b134a51b3fd587..4c5c0f92e4ff57 100644
--- a/sound/core/pcm_dmaengine.c
+++ b/sound/core/pcm_dmaengine.c
@@ -374,6 +374,11 @@ static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream,
 	if (status == DMA_PAUSED)
 		dmaengine_terminate_async(prtd->dma_chan);
 
+	/*
+	 * The PCM might have been closed while suspended, which would
+	 * skip the STOP trigger. Make sure we terminate.
+	 */
+	dmaengine_terminate_async(prtd->dma_chan);
 	dmaengine_synchronize(prtd->dma_chan);
 	if (release_channel)
 		dma_release_channel(prtd->dma_chan);

From 40ef1f9cf530f2a1b818c3cc7f5b24e9b5a3d7b2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 22 Oct 2023 09:00:09 +0900
Subject: [PATCH 0456/1027] ASoC: tas2764: Wait for ramp-down after shutdown

When we shut down the amp, we need to wait for the built-in ramp-down
before we can remove the TDM clocks. There is no documented status
regiter to poll, so the best we can do is a delay. Datasheet says 5.9ms
for ramp-down and 1.5ms between shutdown and next startup, so let's do
6ms after mute and 2ms after shutdown. That gives us a cumulative 8ms
for ramp-down and guaratees the required minimum shutdown time.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 052b94f15c23f5..261460bb057766 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -164,6 +164,8 @@ static int tas2764_codec_suspend(struct snd_soc_component *component)
 	regcache_cache_only(tas2764->regmap, true);
 	regcache_mark_dirty(tas2764->regmap);
 
+	usleep_range(6000, 7000);
+
 	return 0;
 }
 
@@ -257,10 +259,16 @@ static int tas2764_mute(struct snd_soc_dai *dai, int mute, int direction)
 		return ret;
 
 	if (mute) {
+		/* Wait for ramp-down */
+		usleep_range(6000, 7000);
+
 		tas2764->dac_powered = false;
 		ret = tas2764_update_pwr_ctrl(tas2764);
 		if (ret)
 			return ret;
+
+		/* Wait a bit after shutdown */
+		usleep_range(2000, 3000);
 	}
 
 	return 0;

From e9730c3346b1ad00416cb38466d5677d5e15538d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 21:00:13 +0900
Subject: [PATCH 0457/1027] ASoC: tas2764: Enable some Apple quirks by default

0xf seems to fix the random overcurrent behavior.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 261460bb057766..24f7d05e540699 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -49,7 +49,7 @@ struct tas2764_priv {
 	bool unmuted;
 };
 
-static int apple_quirks;
+static int apple_quirks = 0xf;
 module_param(apple_quirks, int, 0644);
 MODULE_PARM_DESC(apple_quirks, "Mask of quirks to mimic after Apple's SN012776 driver");
 

From 30cea92fe29e9f00f988f521248e7335c4c8bc42 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 28 Oct 2023 22:10:32 +0900
Subject: [PATCH 0458/1027] macaudio: Rework platform config & add all
 remaining platforms

Instead of open-coding a fixup function for each platform, let's make it
declarative. This is a lot less error-prone.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 418 ++++++++++++++++++++++---------------
 1 file changed, 250 insertions(+), 168 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 358c28246c5872..ffc381ea9826aa 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -59,20 +59,45 @@
 /* milliseconds */
 #define SPEAKER_LOCK_TIMEOUT 250
 
-#define MAX_LIMITS 6
+enum macaudio_amp_type {
+	AMP_NONE,
+	AMP_TAS5770,
+	AMP_SN012776,
+	AMP_SSM3515,
+};
 
-struct macaudio_limit_cfg {
-	const char *match;
-	int max_limited;
-	int max_unlimited;
+enum macaudio_spkr_config {
+	SPKR_NONE,	/* No speakers */
+	SPKR_1W,	/* 1 woofer / ch */
+	SPKR_2W,	/* 2 woofers / ch */
+	SPKR_1W1T,	/* 1 woofer + 1 tweeter / ch */
+	SPKR_2W1T,	/* 2 woofers + 1 tweeter / ch */
 };
 
 struct macaudio_platform_cfg {
-	struct macaudio_limit_cfg limits[MAX_LIMITS];
-	int (*fixup)(struct snd_soc_card *card);
 	bool enable_speakers;
+	enum macaudio_amp_type amp;
+	enum macaudio_spkr_config speakers;
+	bool stereo;
+	int amp_gain;
+	int safe_vol;
+};
+
+static const char *volume_control_names[] = {
+	[AMP_TAS5770] = "* Speaker Playback Volume",
+	[AMP_SN012776] = "* Speaker Volume",
+	[AMP_SSM3515] = "* DAC Playback Volume",
 };
 
+#define SN012776_0DB 201
+#define SN012776_DB(x) (SN012776_0DB + 2 * (x))
+/* Same as SN012776 */
+#define TAS5770_0DB SN012776_0DB
+#define TAS5770_DB(x) SN012776_DB(x)
+
+#define SSM3515_0DB (255 - 64) /* +24dB max, steps of 3/8 dB */
+#define SSM3515_DB(x) (SSM3515_0DB + (8 * (x) / 3))
+
 struct macaudio_snd_data {
 	struct snd_soc_card card;
 	struct snd_soc_jack jack;
@@ -80,6 +105,7 @@ struct macaudio_snd_data {
 
 	const struct macaudio_platform_cfg *cfg;
 	bool has_speakers;
+	bool has_safety;
 	unsigned int max_channels;
 
 	struct macaudio_link_props {
@@ -199,24 +225,42 @@ static struct macaudio_link_props macaudio_fe_link_props[] = {
 
 static void macaudio_vlimit_unlock(struct macaudio_snd_data *ma, bool unlock)
 {
-	int i, ret, max;
+	int ret, max;
+	const char *name = volume_control_names[ma->cfg->amp];
 
-	for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) {
-		const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i];
-
-		if (!limit->match)
-			break;
+	if (!name) {
+		WARN_ON_ONCE(1);
+		return;
+	}
 
+	switch (ma->cfg->amp) {
+	case AMP_NONE:
+		WARN_ON_ONCE(1);
+		return;
+	case AMP_TAS5770:
 		if (unlock)
-			max = limit->max_unlimited;
+			max = TAS5770_0DB;
 		else
-			max = limit->max_limited;
-
-		ret = snd_soc_limit_volume(&ma->card, limit->match, max);
-		if (ret < 0)
-			dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n",
-				unlock ? "un" : "", limit->match, ret);
+			max = 1; //TAS5770_DB(ma->cfg->safe_vol);
+		break;
+	case AMP_SN012776:
+		if (unlock)
+			max = SN012776_0DB;
+		else
+			max = 1; //SN012776_DB(ma->cfg->safe_vol);
+		break;
+	case AMP_SSM3515:
+		if (unlock)
+			max = SSM3515_0DB;
+		else
+			max = SSM3515_DB(ma->cfg->safe_vol);
+		break;
 	}
+
+	ret = snd_soc_limit_volume(&ma->card, name, max);
+	if (ret < 0)
+		dev_err(ma->card.dev, "Failed to %slock volume %s: %d\n",
+			unlock ? "un" : "", name, ret);
 }
 
 static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
@@ -226,8 +270,8 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
 	struct snd_kcontrol *kctl;
 	const char *reason;
 
-	/* Do nothing if there are no limits configured */
-	if (!ma->cfg->limits[0].match)
+	/* Do nothing if there is no safety configured */
+	if (!ma->has_safety)
 		return;
 
 	/* Check that someone is holding the main lock */
@@ -244,19 +288,7 @@ static void macaudio_vlimit_update(struct macaudio_snd_data *ma)
 
 	/* Check that *every* limited control is locked by the same owner */
 	list_for_each_entry(kctl, &ma->card.snd_card->controls, list) {
-		bool is_limit = false;
-
-		for (i = 0; i < ARRAY_SIZE(ma->cfg->limits); i++) {
-			const struct macaudio_limit_cfg *limit = &ma->cfg->limits[i];
-			if (!limit->match)
-				break;
-
-			is_limit = snd_soc_control_matches(kctl, limit->match);
-			if (is_limit)
-				break;
-		}
-
-		if (!is_limit)
+		if(!snd_soc_control_matches(kctl, volume_control_names[ma->cfg->amp]))
 			continue;
 
 		for (i = 0; i < kctl->count; i++) {
@@ -1100,19 +1132,21 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 		}
 	}
 
-	ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card, "Speaker Sample Rate");
-	ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card, "Speaker Volume Unlock");
+	if (ma->has_speakers)
+		ma->speaker_sample_rate_kctl = snd_soc_card_get_kcontrol(card,
+									 "Speaker Sample Rate");
+	if (ma->has_safety) {
+		ma->speaker_lock_kctl = snd_soc_card_get_kcontrol(card,
+								  "Speaker Volume Unlock");
 
-	mutex_lock(&ma->volume_lock_mutex);
-	macaudio_vlimit_unlock(ma, false);
-	mutex_unlock(&ma->volume_lock_mutex);
+		mutex_lock(&ma->volume_lock_mutex);
+		macaudio_vlimit_unlock(ma, false);
+		mutex_unlock(&ma->volume_lock_mutex);
+	}
 
 	return 0;
 }
 
-#define TAS2764_0DB 201
-#define TAS2764_DB_REDUCTION(x) (TAS2764_0DB - 2 * (x))
-
 #define CHECK(call, pattern, value) \
 	{ \
 		int ret = call(card, pattern, value); \
@@ -1123,141 +1157,90 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 		dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \
 	}
 
-static int macaudio_j274_fixup_controls(struct snd_soc_card *card)
-{
-	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
-
-	if (ma->has_speakers) {
-		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
-	}
-
-	return 0;	
-}
-
-struct macaudio_platform_cfg macaudio_j274_cfg = {
-	.fixup = macaudio_j274_fixup_controls,
-	.enable_speakers = true,
-};
-
-static int macaudio_j313_fixup_controls(struct snd_soc_card *card) {
-	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
-
-	if (ma->has_speakers) {
-		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
-		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
-
-		/* !!! This is copied from j274, not obtained by looking at
-		 *     what macOS sets.
-		 */
-		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14);
+#define CHECK_CONCAT(call, suffix, value) \
+	{ \
+		snprintf(buf, sizeof(buf), "%s%s", prefix, suffix); \
+		CHECK(call, buf, value); \
 	}
 
-	return 0;
-}
-
-struct macaudio_platform_cfg macaudio_j313_cfg = {
-	.fixup = macaudio_j313_fixup_controls,
-};
-
-static int macaudio_j314_fixup_controls(struct snd_soc_card *card)
+static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, bool tweeter)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	char buf[256];
 
-	if (ma->has_speakers) {
-		CHECK(snd_soc_set_enum_kctl, "* ASI1 Sel", "Left");
-		CHECK(snd_soc_deactivate_kctl, "* ASI1 Sel", 0);
-		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
-		CHECK(snd_soc_set_enum_kctl, "* Tweeter HPF Corner Frequency", "800 Hz");
-		CHECK(snd_soc_deactivate_kctl, "* Tweeter HPF Corner Frequency", 0);
-
-		/*
-		 * The speaker amps suffer from spurious overcurrent
-		 * events on their unmute, so enable autoretry.
-		 */
-		CHECK(snd_soc_set_enum_kctl, "* OCE Handling", "Retry");
-		CHECK(snd_soc_deactivate_kctl, "* OCE Handling", 0);
-	}
+	if (!ma->has_speakers)
+		return 0;
 
-	return 0;
-}
+	switch (ma->cfg->amp) {
+	case AMP_TAS5770:
+		if (ma->cfg->stereo) {
+			CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left");
+			CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0);
+		}
 
+		CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain);
+		break;
+	case AMP_SN012776:
+		if (ma->cfg->stereo) {
+			CHECK_CONCAT(snd_soc_set_enum_kctl, "ASI1 Sel", "Left");
+			CHECK_CONCAT(snd_soc_deactivate_kctl, "ASI1 Sel", 0);
+		}
 
-struct macaudio_platform_cfg macaudio_j314_cfg = {
-	.fixup = macaudio_j314_fixup_controls,
-	.limits = {
-		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-	}
-};
+		CHECK_CONCAT(snd_soc_limit_volume, "Amp Gain Volume", ma->cfg->amp_gain);
+		CHECK_CONCAT(snd_soc_set_enum_kctl, "HPF Corner Frequency",
+			     tweeter ? "800 Hz" : "2 Hz");
 
-struct macaudio_platform_cfg macaudio_j413_cfg = {
-	.fixup = macaudio_j314_fixup_controls,
-	.limits = {
-		/* Min gain: -17.47 dB */
-		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-		/* Min gain: -10.63 dB */
-		/* FIXME: These structures are aliased so we can't set different max volumes */
-		{.match = "* Woofer Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-	}
-};
+		if (!please_blow_up_my_speakers)
+			CHECK_CONCAT(snd_soc_deactivate_kctl, "HPF Corner Frequency", 0);
 
-struct macaudio_platform_cfg macaudio_j415_cfg = {
-	.fixup = macaudio_j314_fixup_controls,
-	.limits = {
-		{.match = "* Tweeter Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-		{.match = "* Woofer 1 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-		{.match = "* Woofer 2 Speaker Volume", TAS2764_DB_REDUCTION(20), TAS2764_0DB},
-	}
-};
+		CHECK_CONCAT(snd_soc_set_enum_kctl, "OCE Handling", "Retry");
+		CHECK_CONCAT(snd_soc_deactivate_kctl, "OCE Handling", 0);
+		break;
+	case AMP_SSM3515:
+		/* TODO: check */
+		CHECK_CONCAT(snd_soc_set_enum_kctl, "DAC Analog Gain Select", "8.4 V Span");
 
-static int macaudio_j375_fixup_controls(struct snd_soc_card *card)
-{
-	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+		if (!please_blow_up_my_speakers)
+			CHECK_CONCAT(snd_soc_deactivate_kctl, "DAC Analog Gain Select", 0);
 
-	if (ma->has_speakers) {
-		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 14); // 20 set by macOS, this is 3 dB below
+		/* TODO: HPF, needs new call to set */
+		break;
+	default:
+		return -EINVAL;
 	}
 
 	return 0;
 }
 
-struct macaudio_platform_cfg macaudio_j375_cfg = {
-	.fixup = macaudio_j375_fixup_controls,
-};
-
-static int macaudio_j493_fixup_controls(struct snd_soc_card *card)
+static int macaudio_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
 
-	if (ma->has_speakers) {
-		CHECK(snd_soc_limit_volume, "* Amp Gain Volume", 9); // 15 set by macOS, this is 3 dB below
-	}
-
-	return 0;	
-}
-
-struct macaudio_platform_cfg macaudio_j493_cfg = {
-	.fixup = macaudio_j493_fixup_controls
-};
-
-static int macaudio_fallback_fixup_controls(struct snd_soc_card *card)
-{
-	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	if (!ma->has_speakers)
+		return 0;
 
-	if (ma->has_speakers && !please_blow_up_my_speakers) {
-		dev_err(card->dev, "driver can't assure safety on this model, refusing probe\n");
-		return -EINVAL;
+	switch(ma->cfg->speakers) {
+	case SPKR_NONE:
+		WARN_ON(!please_blow_up_my_speakers);
+		return please_blow_up_my_speakers ? 0 : -EINVAL;
+	case SPKR_1W:
+	case SPKR_2W:
+		CHECK(macaudio_set_speaker, "* ", false);
+		break;
+	case SPKR_1W1T:
+		CHECK(macaudio_set_speaker, "* Tweeter ", true);
+		CHECK(macaudio_set_speaker, "* Woofer ", false);
+		break;
+	case SPKR_2W1T:
+		CHECK(macaudio_set_speaker, "* Tweeter ", true);
+		CHECK(macaudio_set_speaker, "* Woofer 1 ", false);
+		CHECK(macaudio_set_speaker, "* Woofer 2 ", false);
+		break;
 	}
 
 	return 0;
 }
 
-struct macaudio_platform_cfg macaudio_fallback_cfg = {
-	.fixup = macaudio_fallback_fixup_controls
-};
-
-#undef CHECK
-
 static const char * const macaudio_spk_mux_texts[] = {
 	"Primary",
 	"Secondary"
@@ -1407,8 +1390,17 @@ static void macaudio_slk_unlock(struct snd_kcontrol *kcontrol)
 	macaudio_vlimit_update(ma);
 }
 
-/* Speaker limit controls go last */
-#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 2
+/*
+ * Speaker limit controls go last. We only drop the unlock control,
+ * leaving sample rate, since that can be useful for safety
+ * bring-up before the kernel-side caps are ready.
+ */
+#define MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS 1
+/*
+ * If there are no speakers configured at all, we can drop both
+ * controls.
+ */
+#define MACAUDIO_NUM_SPEAKER_CONTROLS 2
 
 static const struct snd_kcontrol_new macaudio_controls[] = {
 	SOC_DAPM_PIN_SWITCH("Speaker"),
@@ -1417,19 +1409,19 @@ static const struct snd_kcontrol_new macaudio_controls[] = {
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.access = SNDRV_CTL_ELEM_ACCESS_READ |
-			SNDRV_CTL_ELEM_ACCESS_WRITE |
 			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
-		.name = "Speaker Volume Unlock",
-		.info = macaudio_slk_info,
-		.put = macaudio_slk_put, .get = macaudio_slk_get,
-		.lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock,
+		.name = "Speaker Sample Rate",
+		.info = macaudio_sss_info, .get = macaudio_sss_get,
 	},
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.access = SNDRV_CTL_ELEM_ACCESS_READ |
+			SNDRV_CTL_ELEM_ACCESS_WRITE |
 			SNDRV_CTL_ELEM_ACCESS_VOLATILE,
-		.name = "Speaker Sample Rate",
-		.info = macaudio_sss_info, .get = macaudio_sss_get,
+		.name = "Speaker Volume Unlock",
+		.info = macaudio_slk_info,
+		.put = macaudio_slk_put, .get = macaudio_slk_get,
+		.lock = macaudio_slk_lock, .unlock = macaudio_slk_unlock,
 	},
 };
 
@@ -1453,14 +1445,100 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 	{ "PCM2 RX", NULL, "Speaker Sense Capture" },	
 };
 
+/*	enable	amp		speakers	stereo	gain	safe_vol */
+struct macaudio_platform_cfg macaudio_j180_cfg = {
+	false,	AMP_SN012776,	SPKR_1W1T,	false,	4,	-20,
+};
+struct macaudio_platform_cfg macaudio_j274_cfg = {
+	true,	AMP_TAS5770,	SPKR_1W,	false,	14,	0, /* TODO: safety */
+};
+
+struct macaudio_platform_cfg macaudio_j293_cfg = {
+	false,	AMP_TAS5770,	SPKR_2W,	true,	9,	-20, /* TODO: check */
+};
+
+struct macaudio_platform_cfg macaudio_j313_cfg = {
+	false,	AMP_TAS5770,	SPKR_1W,	true,	4,	-20, /* TODO: check */
+};
+
+struct macaudio_platform_cfg macaudio_j314_j316_cfg = {
+	false,	AMP_SN012776,	SPKR_2W1T,	true,	9,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
+	false,	AMP_SN012776,	SPKR_1W,	false,	14,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_j413_cfg = {
+	false,	AMP_SN012776,	SPKR_1W1T,	true,	9,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_j415_cfg = {
+	false,	AMP_SN012776,	SPKR_2W1T,	true,	9,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_j45x_cfg = {
+	false,	AMP_SSM3515,	SPKR_1W1T,	true,	9,	-20, /* TODO: gain?? */
+};
+
+struct macaudio_platform_cfg macaudio_j493_cfg = {
+	false,	AMP_SN012776,	SPKR_2W,	true,	9,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_fallback_cfg = {
+	false,	AMP_NONE,	SPKR_NONE,	false,	0,	0,
+};
+
+/*
+ * DT compatible/ID table rules:
+ *
+ * 1. Machines with **identical** speaker configurations (amps, models, chassis)
+ *    are allowed to declare compatibility with the first model (chronologically),
+ *    and are not enumerated in this array.
+ *
+ * 2. Machines with identical amps and speakers (=identical speaker protection
+ *    rules) but a different chassis must use different compatibles, but may share
+ *    the private data structure here. They are explicitly enumerated.
+ *
+ * 3. Machines with different amps or speaker layouts must use separate
+ *    data structures.
+ *
+ * 4. Machines with identical speaker layouts and amps (but possibly different
+ *    speaker models/chassis) may share the data structure, since only userspace
+ *    cares about that (assuming our general -20dB safe level standard holds).
+ */
 static const struct of_device_id macaudio_snd_device_id[]  = {
+	/* Model   ID      Amp         Gain    Speakers */
+	/* j180    AID19   sn012776    10      1× 1W+1T */
+	{ .compatible = "apple,j180-macaudio", .data = &macaudio_j180_cfg },
+	/* j274    AID6    tas5770     20      1× 1W */
 	{ .compatible = "apple,j274-macaudio", .data = &macaudio_j274_cfg },
+	/* j293    AID3    tas5770     15      2× 2W */
+	{ .compatible = "apple,j293-macaudio", .data = &macaudio_j293_cfg },
+	/* j313    AID4    tas5770     10      2× 1W */
 	{ .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg },
-	{ .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg },
-	{ .compatible = "apple,j375-macaudio", .data = &macaudio_j375_cfg },
+	/* j314    AID8    sn012776    15      2× 2W+1T */
+	{ .compatible = "apple,j314-macaudio", .data = &macaudio_j314_j316_cfg },
+	/* j316    AID9    sn012776    15      2× 2W+1T */
+	{ .compatible = "apple,j316-macaudio", .data = &macaudio_j314_j316_cfg },
+	/* j375    AID10   sn012776    15      1× 1W */
+	{ .compatible = "apple,j375-macaudio", .data = &macaudio_j37x_j47x_cfg },
+	/* j413    AID13   sn012776    15      2× 1W+1T */
 	{ .compatible = "apple,j413-macaudio", .data = &macaudio_j413_cfg },
+	/* j414    AID14   sn012776    15      2× 2W+1T Compat: apple,j314-macaudio */
+	/* j415    AID27   sn012776    15      2× 2W+1T */
 	{ .compatible = "apple,j415-macaudio", .data = &macaudio_j415_cfg },
+	/* j416    AID15   sn012776    15      2× 2W+1T Compat: apple,j316-macaudio */
+	/* j456    AID5    ssm3515     15      2× 1W+1T */
+	{ .compatible = "apple,j456-macaudio", .data = &macaudio_j45x_cfg },
+	/* j457    AID7    ssm3515     15      2× 1W+1T Compat: apple,j456-macaudio */
+	/* j473    AID12   sn012776    20      1× 1W */
+	{ .compatible = "apple,j473-macaudio", .data = &macaudio_j37x_j47x_cfg },
+	/* j474    AID26   sn012776    20      1× 1W    Compat: apple,j473-macaudio */
+	/* j475    AID25   sn012776    20      1× 1W    Compat: apple,j375-macaudio */
+	/* j493    AID18   sn012776    15      2× 2W */
 	{ .compatible = "apple,j493-macaudio", .data = &macaudio_j493_cfg },
+	/* Fallback, jack only */
 	{ .compatible = "apple,macaudio"},
 	{ }
 };
@@ -1507,16 +1585,20 @@ static int macaudio_snd_platform_probe(struct platform_device *pdev)
 	else
 		data->cfg = &macaudio_fallback_cfg;
 
-	/* Remove speaker safety controls if we have no declared limits */
-	if (!data->cfg->limits[0].match)
-		card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS;
-
-	card->fixup_controls = data->cfg->fixup;
+	card->fixup_controls = macaudio_fixup_controls;
 
 	ret = macaudio_parse_of(data);
 	if (ret)
 		return ret;
 
+	/* Remove useless controls */
+	if (!data->has_speakers) /* No speakers, remove both */
+		card->num_controls -= MACAUDIO_NUM_SPEAKER_CONTROLS;
+	else if (!data->cfg->safe_vol) /* No safety, remove unlock */
+		card->num_controls -= MACAUDIO_NUM_SPEAKER_LIMIT_CONTROLS;
+	else /* Speakers with safety, mark us as such */
+		data->has_safety = true;
+
 	for_each_card_prelinks(card, i, link) {
 		if (link->no_pcm) {
 			link->ops = &macaudio_be_ops;

From be49ef9055921406856b6ccf1f0a846366f0c4bc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 17:31:48 +0900
Subject: [PATCH 0459/1027] ASoC: tas2770: Add SDZ regulator

Multiple amps can be connected to the same SDZ GPIO. Using raw GPIOs for
this breaks, as there is no concept of refcounting/sharing. In order to
model these platforms, introduce support for an SDZ "regulator". This
allows us to represent the SDZ GPIO as a simple regulator-fixed, and
then the regulator core takes care of refcounting so that all codecs are
only powered down once all the driver instances are in the suspend
state.

This also reworks the sleep/resume logic to copy what tas2764 does,
which makes more sense.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 72 ++++++++++++++++++++++++++------------
 sound/soc/codecs/tas2770.h |  1 +
 2 files changed, 50 insertions(+), 23 deletions(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index f64d44ac71fc13..88b2010574d883 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -72,23 +72,21 @@ static int tas2770_codec_suspend(struct snd_soc_component *component)
 	struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component);
 	int ret = 0;
 
-	regcache_cache_only(tas2770->regmap, true);
-	regcache_mark_dirty(tas2770->regmap);
+	ret = snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
+					    TAS2770_PWR_CTRL_MASK,
+					    TAS2770_PWR_CTRL_SHUTDOWN);
+	if (ret < 0)
+		return ret;
 
-	if (tas2770->sdz_gpio) {
+	if (tas2770->sdz_gpio)
 		gpiod_set_value_cansleep(tas2770->sdz_gpio, 0);
-	} else {
-		ret = snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
-						    TAS2770_PWR_CTRL_MASK,
-						    TAS2770_PWR_CTRL_SHUTDOWN);
-		if (ret < 0) {
-			regcache_cache_only(tas2770->regmap, false);
-			regcache_sync(tas2770->regmap);
-			return ret;
-		}
 
-		ret = 0;
-	}
+	regulator_disable(tas2770->sdz_reg);
+
+	regcache_cache_only(tas2770->regmap, true);
+	regcache_mark_dirty(tas2770->regmap);
+
+	usleep_range(6000, 7000);
 
 	return ret;
 }
@@ -98,18 +96,26 @@ static int tas2770_codec_resume(struct snd_soc_component *component)
 	struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component);
 	int ret;
 
-	if (tas2770->sdz_gpio) {
-		gpiod_set_value_cansleep(tas2770->sdz_gpio, 1);
-		usleep_range(1000, 2000);
-	} else {
-		ret = tas2770_update_pwr_ctrl(tas2770);
-		if (ret < 0)
-			return ret;
+	ret = regulator_enable(tas2770->sdz_reg);
+
+	if (ret) {
+		dev_err(tas2770->dev, "Failed to enable regulator\n");
+		return ret;
 	}
 
+	if (tas2770->sdz_gpio)
+		gpiod_set_value_cansleep(tas2770->sdz_gpio, 1);
+
+
+	usleep_range(1000, 2000);
+
 	regcache_cache_only(tas2770->regmap, false);
 
-	return regcache_sync(tas2770->regmap);
+	ret = regcache_sync(tas2770->regmap);
+	if (ret < 0)
+		return ret;
+
+	return tas2770_update_pwr_ctrl(tas2770);
 }
 #else
 #define tas2770_codec_suspend NULL
@@ -544,11 +550,18 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 
 	tas2770->component = component;
 
+	ret = regulator_enable(tas2770->sdz_reg);
+	if (ret != 0) {
+		dev_err(tas2770->dev, "Failed to enable regulator: %d\n", ret);
+		return ret;
+	}
+
 	if (tas2770->sdz_gpio) {
 		gpiod_set_value_cansleep(tas2770->sdz_gpio, 1);
-		usleep_range(1000, 2000);
 	}
 
+	usleep_range(1000, 2000);
+
 	tas2770_reset(tas2770);
 	regmap_reinit_cache(tas2770->regmap, &tas2770_i2c_regmap);
 
@@ -568,6 +581,13 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 	return 0;
 }
 
+static void tas2770_codec_remove(struct snd_soc_component *component)
+{
+	struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component);
+
+	regulator_disable(tas2770->sdz_reg);
+}
+
 static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0);
 static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0);
 
@@ -580,6 +600,7 @@ static const struct snd_kcontrol_new tas2770_snd_controls[] = {
 
 static const struct snd_soc_component_driver soc_component_driver_tas2770 = {
 	.probe			= tas2770_codec_probe,
+	.remove			= tas2770_codec_remove,
 	.suspend		= tas2770_codec_suspend,
 	.resume			= tas2770_codec_resume,
 	.controls		= tas2770_snd_controls,
@@ -704,6 +725,11 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770)
 		tas2770->v_sense_slot = -1;
 	}
 
+	tas2770->sdz_reg = devm_regulator_get(dev, "SDZ");
+	if (IS_ERR(tas2770->sdz_reg))
+		return dev_err_probe(dev, PTR_ERR(tas2770->sdz_reg),
+				     "Failed to get SDZ supply\n");
+
 	tas2770->sdz_gpio = devm_gpiod_get_optional(dev, "shutdown", GPIOD_OUT_HIGH);
 	if (IS_ERR(tas2770->sdz_gpio)) {
 		if (PTR_ERR(tas2770->sdz_gpio) == -EPROBE_DEFER)
diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h
index f75f40781ab136..f75baf23caf3a1 100644
--- a/sound/soc/codecs/tas2770.h
+++ b/sound/soc/codecs/tas2770.h
@@ -134,6 +134,7 @@ struct tas2770_priv {
 	struct snd_soc_component *component;
 	struct gpio_desc *reset_gpio;
 	struct gpio_desc *sdz_gpio;
+	struct regulator *sdz_reg;
 	struct regmap *regmap;
 	struct device *dev;
 	int v_sense_slot;

From 423e1ce45c301f4a14729398315d8a6d8a90a158 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 21:57:42 +0900
Subject: [PATCH 0460/1027] ASoC: tas2770: Power cycle amp on ISENSE/VSENSE
 change

The ISENSE/VSENSE blocks are only powered up when the amplifier
transitions from shutdown to active. This means that if those controls
are flipped on while the amplifier is already playing back audio, they
will have no effect.

Fix this by forcing a power cycle around transitions in those controls.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 34 ++++++++++++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 88b2010574d883..98999e3687c314 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -164,11 +164,41 @@ static const struct snd_kcontrol_new isense_switch =
 static const struct snd_kcontrol_new vsense_switch =
 	SOC_DAPM_SINGLE("Switch", TAS2770_PWR_CTRL, 2, 1, 1);
 
+static int sense_event(struct snd_soc_dapm_widget *w,
+                          struct snd_kcontrol *kcontrol, int event)
+{
+	struct snd_soc_component *component = snd_soc_dapm_to_component(w->dapm);
+	struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component);
+	int ret = 0;
+
+	/*
+	 * Powering up ISENSE/VSENSE requires a trip through the shutdown state.
+	 * Do that here to ensure that our changes are applied properly, otherwise
+	 * we might end up with non-functional IVSENSE if playback started earlier,
+	 * which would break software speaker protection.
+	 */
+
+	switch (event) {
+	case SND_SOC_DAPM_PRE_REG:
+		ret = snd_soc_component_update_bits(component, TAS2770_PWR_CTRL,
+						    TAS2770_PWR_CTRL_MASK,
+						    TAS2770_PWR_CTRL_SHUTDOWN);
+		break;
+	case SND_SOC_DAPM_POST_REG:
+		ret = tas2770_update_pwr_ctrl(tas2770);
+		break;
+	}
+
+	return 0;
+}
+
 static const struct snd_soc_dapm_widget tas2770_dapm_widgets[] = {
 	SND_SOC_DAPM_AIF_IN("ASI1", "ASI1 Playback", 0, SND_SOC_NOPM, 0, 0),
 	SND_SOC_DAPM_MUX("ASI1 Sel", SND_SOC_NOPM, 0, 0, &tas2770_asi1_mux),
-	SND_SOC_DAPM_SWITCH("ISENSE", TAS2770_PWR_CTRL, 3, 1, &isense_switch),
-	SND_SOC_DAPM_SWITCH("VSENSE", TAS2770_PWR_CTRL, 2, 1, &vsense_switch),
+	SND_SOC_DAPM_SWITCH_E("ISENSE", TAS2770_PWR_CTRL, 3, 1, &isense_switch,
+		sense_event, SND_SOC_DAPM_PRE_REG | SND_SOC_DAPM_POST_REG),
+	SND_SOC_DAPM_SWITCH_E("VSENSE", TAS2770_PWR_CTRL, 2, 1, &vsense_switch,
+		sense_event, SND_SOC_DAPM_PRE_REG | SND_SOC_DAPM_POST_REG),
 	SND_SOC_DAPM_DAC_E("DAC", NULL, SND_SOC_NOPM, 0, 0, tas2770_dac_event,
 			   SND_SOC_DAPM_POST_PMU | SND_SOC_DAPM_PRE_PMD),
 	SND_SOC_DAPM_OUTPUT("OUT"),

From e1c6161ce12553f3c89fb8c5cd630c7ed38d3127 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 22:00:01 +0900
Subject: [PATCH 0461/1027] ASoC: tas2770: Add zero-fill and pull-down controls

Expose the bits that control the behavior of the SDOUT pin when not
actively transmitting slot data. Zero-fill is useful when there is a
single amp on the SDOUT bus (e.g. Apple machines with mono speakers or a
single stereo pair, where L/R are on separate buses).

Pull-down is useful, though not perfect, when multiple amps share a
bus. It typically takes around 2 bits for the line to transition from
high to low after going Hi-Z, with the pull-down.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 17 +++++++++++++++++
 sound/soc/codecs/tas2770.h | 13 +++++++++++++
 2 files changed, 30 insertions(+)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 98999e3687c314..df9347deb23a6c 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -603,6 +603,20 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 			return ret;
 	}
 
+	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4,
+					    TAS2770_TDM_CFG_REG4_TX_FILL,
+					    tas2770->sdout_zfill ? 0 :
+					    TAS2770_TDM_CFG_REG4_TX_FILL);
+	if (ret < 0)
+		return ret;
+
+	ret = snd_soc_component_update_bits(component, TAS2770_DIN_PD,
+					    TAS2770_DIN_PD_SDOUT,
+					    tas2770->sdout_pd ?
+					    TAS2770_DIN_PD_SDOUT : 0);
+	if (ret < 0)
+		return ret;
+
 	ret = sysfs_create_groups(&component->dev->kobj, tas2770_sysfs_groups);
 
 	if (ret < 0)
@@ -755,6 +769,9 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770)
 		tas2770->v_sense_slot = -1;
 	}
 
+	tas2770->sdout_pd = fwnode_property_read_bool(dev->fwnode, "ti,sdout-pull-down");
+	tas2770->sdout_zfill = fwnode_property_read_bool(dev->fwnode, "ti,sdout-zero-fill");
+
 	tas2770->sdz_reg = devm_regulator_get(dev, "SDZ");
 	if (IS_ERR(tas2770->sdz_reg))
 		return dev_err_probe(dev, PTR_ERR(tas2770->sdz_reg),
diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h
index f75baf23caf3a1..2c2cd777f4bcba 100644
--- a/sound/soc/codecs/tas2770.h
+++ b/sound/soc/codecs/tas2770.h
@@ -67,6 +67,14 @@
 #define TAS2770_TDM_CFG_REG3_RXS_SHIFT 0x4
 #define TAS2770_TDM_CFG_REG3_30_MASK  GENMASK(3, 0)
 #define TAS2770_TDM_CFG_REG3_30_SHIFT 0
+    /* TDM Configuration Reg4 */
+#define TAS2770_TDM_CFG_REG4  TAS2770_REG(0X0, 0x0E)
+#define TAS2770_TDM_CFG_REG4_TX_LSB_CFG BIT(7)
+#define TAS2770_TDM_CFG_REG4_TX_KEEPER_CFG BIT(6)
+#define TAS2770_TDM_CFG_REG4_TX_KEEPER BIT(5)
+#define TAS2770_TDM_CFG_REG4_TX_FILL BIT(4)
+#define TAS2770_TDM_CFG_REG4_TX_OFFSET_MASK GENMASK(3, 1)
+#define TAS2770_TDM_CFG_REG4_TX_EDGE_FALLING BIT(0)
     /* TDM Configuration Reg5 */
 #define TAS2770_TDM_CFG_REG5  TAS2770_REG(0X0, 0x0F)
 #define TAS2770_TDM_CFG_REG5_VSNS_MASK  BIT(6)
@@ -110,6 +118,9 @@
 #define TAS2770_TEMP_LSB  TAS2770_REG(0X0, 0x2A)
     /* Interrupt Configuration */
 #define TAS2770_INT_CFG  TAS2770_REG(0X0, 0x30)
+    /* Data In Pull-Down */
+#define TAS2770_DIN_PD  TAS2770_REG(0X0, 0x31)
+#define TAS2770_DIN_PD_SDOUT BIT(7)
     /* Misc IRQ */
 #define TAS2770_MISC_IRQ  TAS2770_REG(0X0, 0x32)
     /* Clock Configuration */
@@ -139,6 +150,8 @@ struct tas2770_priv {
 	struct device *dev;
 	int v_sense_slot;
 	int i_sense_slot;
+	bool sdout_pd;
+	bool sdout_zfill;
 	bool dac_powered;
 	bool unmuted;
 };

From 5e68b2f7f9ffa5e2a3849858f66a562a3f4865ce Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 29 Oct 2023 22:02:30 +0900
Subject: [PATCH 0462/1027] ASoC: tas2770: Support setting the PDM TX slot

We don't actually support configuring the PDM input right now. Rather,
this is useful as a hack.

On Apple Silicon machines, amps are split between two I2S buses which
are logically ANDed internally at the SoC. Odd and even slot groups are
driven by amps on either bus respectively. Since the signals are ANDed,
unused slot groups must be driven as zero to avoid corrupting the data
from the other side.

On most recent machines (TAS2764-based), this is accomplished using the
"SDOUT zero mask" feature of that chip. Unfortunately, TAS2770 does not
support this. It does support zeroing out *all* unused slots, which
works well for machines with a single amp per I2S bus. That is all,
except one.

The 13" M1 MacBook Pro is the only machine using TAS2764 and two amps
per I2S bus:

L Bus: SPK0I SPK0V Hi-Z  Hi-Z  SPK2I SPK2V Hi-Z  Hi-Z
R Bus: Hi-Z  Hi-Z  SPK1I SPK2V Hi-Z  Hi-Z  SPK3I SPK3V

To ensure uncorrupted data, we need to force all the Hi-Z periods to
zero. We cannot use the "force all zero" feature, as that would cause a
bus conflict between both amps. We can use the pull-down feature, but
that leaves a few bits of garbage on the trailing edge of the speaker
data, since the pull-down is weak.

This is where the PDM transmit feature comes in. With PDM grounded and
disabled (the default state), the PDM slot is transmitted as all zeroes.
We can use that to force a zero 16-bit slot after the voltage data for
each speaker, cleaning it up. Then the pull-down ensures the line stays
low for the subsequent slot:

L Bus: SPK0I SPK0V PDM0  PulDn SPK2I SPK2V PDM0  PulDn
R Bus: PDM0  PulDn SPK1I SPK2V PDM0  PulDn SPK3I SPK3V

Yes, this is a horrible hack, but it beats adding dummy slots that would
be visible to the userspace capture side. There may be some other way to
fix the logical AND behavior on the MCA side... that would make this
unnecessary.

("How does Apple deal with this"? - they don't, macOS does not use
IVSENSE on TAS2764 machines even though it's physically wired up,
but we want to do so on Linux.)

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 26 ++++++++++++++++++++++++++
 sound/soc/codecs/tas2770.h |  6 ++++++
 2 files changed, 32 insertions(+)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index df9347deb23a6c..87848d5f8f28aa 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -252,6 +252,19 @@ static int tas2770_set_ivsense_transmit(struct tas2770_priv *tas2770,
 	return 0;
 }
 
+static int tas2770_set_pdm_transmit(struct tas2770_priv *tas2770, int slot)
+{
+	struct snd_soc_component *component = tas2770->component;
+	int ret;
+
+	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG7,
+					    TAS2770_TDM_CFG_REG7_PDM_MASK |
+					    TAS2770_TDM_CFG_REG7_50_MASK,
+					    TAS2770_TDM_CFG_REG7_PDM_ENABLE |
+					    slot);
+	return ret;
+}
+
 static int tas2770_set_bitwidth(struct tas2770_priv *tas2770, int bitwidth)
 {
 	int ret;
@@ -603,6 +616,13 @@ static int tas2770_codec_probe(struct snd_soc_component *component)
 			return ret;
 	}
 
+	if (tas2770->pdm_slot != -1) {
+		ret = tas2770_set_pdm_transmit(tas2770, tas2770->pdm_slot);
+
+		if (ret < 0)
+			return ret;
+	}
+
 	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4,
 					    TAS2770_TDM_CFG_REG4_TX_FILL,
 					    tas2770->sdout_zfill ? 0 :
@@ -769,6 +789,12 @@ static int tas2770_parse_dt(struct device *dev, struct tas2770_priv *tas2770)
 		tas2770->v_sense_slot = -1;
 	}
 
+	rc = fwnode_property_read_u32(dev->fwnode, "ti,pdm-slot-no",
+				      &tas2770->pdm_slot);
+	if (rc) {
+		tas2770->pdm_slot = -1;
+	}
+
 	tas2770->sdout_pd = fwnode_property_read_bool(dev->fwnode, "ti,sdout-pull-down");
 	tas2770->sdout_zfill = fwnode_property_read_bool(dev->fwnode, "ti,sdout-zero-fill");
 
diff --git a/sound/soc/codecs/tas2770.h b/sound/soc/codecs/tas2770.h
index 2c2cd777f4bcba..b309d19c58e1da 100644
--- a/sound/soc/codecs/tas2770.h
+++ b/sound/soc/codecs/tas2770.h
@@ -85,6 +85,11 @@
 #define TAS2770_TDM_CFG_REG6_ISNS_MASK  BIT(6)
 #define TAS2770_TDM_CFG_REG6_ISNS_ENABLE  BIT(6)
 #define TAS2770_TDM_CFG_REG6_50_MASK  GENMASK(5, 0)
+    /* TDM Configuration Reg10 */
+#define TAS2770_TDM_CFG_REG7  TAS2770_REG(0X0, 0x11)
+#define TAS2770_TDM_CFG_REG7_PDM_MASK  BIT(6)
+#define TAS2770_TDM_CFG_REG7_PDM_ENABLE  BIT(6)
+#define TAS2770_TDM_CFG_REG7_50_MASK	GENMASK(5, 0)
     /* Brown Out Prevention Reg0 */
 #define TAS2770_BO_PRV_REG0  TAS2770_REG(0X0, 0x1B)
     /* Interrupt MASK Reg0 */
@@ -150,6 +155,7 @@ struct tas2770_priv {
 	struct device *dev;
 	int v_sense_slot;
 	int i_sense_slot;
+	int pdm_slot;
 	bool sdout_pd;
 	bool sdout_zfill;
 	bool dac_powered;

From f7110fd2854a71c22d44101603e06d5d664cb7a9 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 30 Oct 2023 00:12:22 +0900
Subject: [PATCH 0463/1027] ASoC: tas2770: Fix volume scale

The scale starts at -100dB, not -128dB.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 87848d5f8f28aa..7018a8570c23e0 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -653,7 +653,7 @@ static void tas2770_codec_remove(struct snd_soc_component *component)
 }
 
 static DECLARE_TLV_DB_SCALE(tas2770_digital_tlv, 1100, 50, 0);
-static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -12750, 50, 0);
+static DECLARE_TLV_DB_SCALE(tas2770_playback_volume, -10050, 50, 0);
 
 static const struct snd_kcontrol_new tas2770_snd_controls[] = {
 	SOC_SINGLE_TLV("Speaker Playback Volume", TAS2770_PLAY_CFG_REG2,

From 6cfbc03044e46d7b98a02a222c157a4a7c33cc7a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 30 Oct 2023 00:26:59 +0900
Subject: [PATCH 0464/1027] macaudio: Remove -3dB safety pad from j313

This one already uses a gain lower than the others. It doesn't look like
full scale no-DSP output with typical music is particularly dangerous here,
and we probably want the headroom for DSP, so let's not do it for this
one.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index ffc381ea9826aa..d77142e18e08fd 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1458,7 +1458,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j313_cfg = {
-	false,	AMP_TAS5770,	SPKR_1W,	true,	4,	-20, /* TODO: check */
+	false,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j314_j316_cfg = {

From 8deab3079c722c6ed836a516cb08d139ec873f01 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 3 Nov 2023 21:10:11 +0900
Subject: [PATCH 0465/1027] macaudio: Skip speaker sense PCM if no sense or no
 speakers

This PCM triggers speakersafetyd, so hide it if it can't work.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index d77142e18e08fd..a2733f35cff20a 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -105,6 +105,7 @@ struct macaudio_snd_data {
 
 	const struct macaudio_platform_cfg *cfg;
 	bool has_speakers;
+	bool has_sense;
 	bool has_safety;
 	unsigned int max_channels;
 
@@ -569,6 +570,8 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 				continue;
 			}
 			ma->has_speakers = 1;
+			if (ma->cfg->amp != AMP_SSM3515 && ma->cfg->safe_vol != 0)
+				ma->has_sense = 1;
 		}
 
 		cpu = of_get_child_by_name(np, "cpu");
@@ -670,6 +673,18 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 	for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++)
 		card->dai_link[i].platforms->of_node = platform;
 
+	/* Skip the speaker sense PCM link if this amp has no sense (or no speakers) */
+	if (!ma->has_sense) {
+		for (i = 0; i < ARRAY_SIZE(macaudio_fe_links); i++) {
+			if (ma->link_props[i].is_sense) {
+				memmove(&card->dai_link[i], &card->dai_link[i + 1],
+					(num_links - i - 1) * sizeof (struct snd_soc_dai_link));
+				num_links--;
+				break;
+			}
+		}
+	}
+
 	card->num_links = num_links;
 
 	return 0;

From c4b6fafe9476944d6e746dd58742d443620c4f65 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 7 Nov 2023 21:16:57 +0900
Subject: [PATCH 0466/1027] macaudio: Officially enable j313 speakers

Still hard gated on speakersafetyd for now.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index a2733f35cff20a..fa4f1b48740a4d 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1473,7 +1473,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j313_cfg = {
-	false,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
+	true,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j314_j316_cfg = {

From 923d7421beecd543b4d05e128a49f013fa20991f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 9 Nov 2023 18:32:57 +0900
Subject: [PATCH 0467/1027] ASoC: tas2764: Set the SDOUT polarity correctly

TX launch polarity needs to be the opposite of RX capture polarity, to
generate the right bit slot alignment.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2764.c | 10 +++++++++-
 sound/soc/codecs/tas2764.h |  6 ++++++
 2 files changed, 15 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 24f7d05e540699..79c5ba7af3d029 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -408,7 +408,7 @@ static int tas2764_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 {
 	struct snd_soc_component *component = dai->component;
 	struct tas2764_priv *tas2764 = snd_soc_component_get_drvdata(component);
-	u8 tdm_rx_start_slot = 0, asi_cfg_0 = 0, asi_cfg_1 = 0;
+	u8 tdm_rx_start_slot = 0, asi_cfg_0 = 0, asi_cfg_1 = 0, asi_cfg_4 = 0;
 	int ret;
 
 	switch (fmt & SND_SOC_DAIFMT_INV_MASK) {
@@ -417,12 +417,14 @@ static int tas2764_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 		fallthrough;
 	case SND_SOC_DAIFMT_NB_NF:
 		asi_cfg_1 = TAS2764_TDM_CFG1_RX_RISING;
+		asi_cfg_4 = TAS2764_TDM_CFG4_TX_FALLING;
 		break;
 	case SND_SOC_DAIFMT_IB_IF:
 		asi_cfg_0 ^= TAS2764_TDM_CFG0_FRAME_START;
 		fallthrough;
 	case SND_SOC_DAIFMT_IB_NF:
 		asi_cfg_1 = TAS2764_TDM_CFG1_RX_FALLING;
+		asi_cfg_4 = TAS2764_TDM_CFG4_TX_RISING;
 		break;
 	}
 
@@ -432,6 +434,12 @@ static int tas2764_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	if (ret < 0)
 		return ret;
 
+	ret = snd_soc_component_update_bits(component, TAS2764_TDM_CFG4,
+					    TAS2764_TDM_CFG4_TX_MASK,
+					    asi_cfg_4);
+	if (ret < 0)
+		return ret;
+
 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 	case SND_SOC_DAIFMT_I2S:
 		asi_cfg_0 ^= TAS2764_TDM_CFG0_FRAME_START;
diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 786d81eb5b1e71..8d9579cffd3e20 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -84,6 +84,12 @@
 #define TAS2764_TDM_CFG3_RXS_SHIFT	0x4
 #define TAS2764_TDM_CFG3_MASK		GENMASK(3, 0)
 
+/* TDM Configuration Reg4 */
+#define TAS2764_TDM_CFG4		TAS2764_REG(0X0, 0x0c)
+#define TAS2764_TDM_CFG4_TX_MASK	BIT(0)
+#define TAS2764_TDM_CFG4_TX_RISING	0x0
+#define TAS2764_TDM_CFG4_TX_FALLING	BIT(0)
+
 /* TDM Configuration Reg5 */
 #define TAS2764_TDM_CFG5		TAS2764_REG(0X0, 0x0e)
 #define TAS2764_TDM_CFG5_VSNS_MASK	BIT(6)

From cb67a586f494082bf7c8ec7829f6e93e1b9c58fc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 9 Nov 2023 18:33:41 +0900
Subject: [PATCH 0468/1027] ASoC: tas2770: Set the SDOUT polarity correctly

TX launch polarity needs to be the opposite of RX capture polarity, to
generate the right bit slot alignment.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/codecs/tas2770.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index 7018a8570c23e0..eb4be94c6ffcd6 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -363,7 +363,7 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	struct snd_soc_component *component = dai->component;
 	struct tas2770_priv *tas2770 =
 			snd_soc_component_get_drvdata(component);
-	u8 tdm_rx_start_slot = 0, invert_fpol = 0, fpol_preinv = 0, asi_cfg_1 = 0;
+	u8 tdm_rx_start_slot = 0, invert_fpol = 0, fpol_preinv = 0, asi_cfg_1 = 0, asi_cfg_4 = 0;
 	int ret;
 
 	switch (fmt & SND_SOC_DAIFMT_CLOCK_PROVIDER_MASK) {
@@ -380,6 +380,7 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 		fallthrough;
 	case SND_SOC_DAIFMT_NB_NF:
 		asi_cfg_1 |= TAS2770_TDM_CFG_REG1_RX_RSING;
+		asi_cfg_4 |= TAS2770_TDM_CFG_REG4_TX_EDGE_FALLING;
 		break;
 	case SND_SOC_DAIFMT_IB_IF:
 		invert_fpol = 1;
@@ -398,6 +399,12 @@ static int tas2770_set_fmt(struct snd_soc_dai *dai, unsigned int fmt)
 	if (ret < 0)
 		return ret;
 
+	ret = snd_soc_component_update_bits(component, TAS2770_TDM_CFG_REG4,
+					    TAS2770_TDM_CFG_REG4_TX_EDGE_FALLING,
+					    asi_cfg_4);
+	if (ret < 0)
+		return ret;
+
 	switch (fmt & SND_SOC_DAIFMT_FORMAT_MASK) {
 	case SND_SOC_DAIFMT_I2S:
 		tdm_rx_start_slot = 1;

From f28f7c226ffd7f3a0ce87c35f6d062647892ea2f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 25 Nov 2023 11:30:44 +0100
Subject: [PATCH 0469/1027] fixup! ASoC: tas2764: Add optional 'Apple quirks'

Fix fallthrough warning and remove stray spaces before tabs for
indenting.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 sound/soc/codecs/tas2764-quirks.h | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/sound/soc/codecs/tas2764-quirks.h b/sound/soc/codecs/tas2764-quirks.h
index 7cb860e7e9c252..9cbbc2a9e5944f 100644
--- a/sound/soc/codecs/tas2764-quirks.h
+++ b/sound/soc/codecs/tas2764-quirks.h
@@ -40,7 +40,7 @@ struct reg_sequence tas2764_dmod_rst_seq[] = {
 
 struct reg_sequence tas2764_unk_seq0[] = {
 	REG_SEQ0(TAS2764_REG(0x1, 0x33), 0x80),
- 	REG_SEQ0(TAS2764_REG(0x1, 0x37), 0x3a),
+	REG_SEQ0(TAS2764_REG(0x1, 0x37), 0x3a),
 };
 
 /*
@@ -137,17 +137,17 @@ static int tas2764_do_quirky_pwr_ctrl_change(struct tas2764_priv *tas2764,
 
 		ret = snd_soc_component_update_bits(tas2764->component,
 						    TAS2764_PWR_CTRL,
-					    	    TAS2764_PWR_CTRL_MASK,
-					    	    TAS2764_PWR_CTRL_SHUTDOWN);
+						    TAS2764_PWR_CTRL_MASK,
+						    TAS2764_PWR_CTRL_SHUTDOWN);
 		if (ret > 0)
 			break;
 
 		ret = regmap_multi_reg_write(tas2764->regmap, tas2764_post_shutdown_seq,
 					     ARRAY_SIZE(tas2764_post_shutdown_seq));
-
+		fallthrough;
 	default:
 		ret = snd_soc_component_update_bits(tas2764->component, TAS2764_PWR_CTRL,
-					    	    TAS2764_PWR_CTRL_MASK, target);
+						    TAS2764_PWR_CTRL_MASK, target);
 	}
 #undef TRANSITION
 

From 097c50507fc8d78776e46f6ca96038e3365dc9ff Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 28 Nov 2023 19:18:55 +0900
Subject: [PATCH 0470/1027] fixup! ASoC: tas2764: Set the SDOUT polarity
 correctly

---
 sound/soc/codecs/tas2764.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.h b/sound/soc/codecs/tas2764.h
index 8d9579cffd3e20..4a419c11d4b08e 100644
--- a/sound/soc/codecs/tas2764.h
+++ b/sound/soc/codecs/tas2764.h
@@ -85,7 +85,7 @@
 #define TAS2764_TDM_CFG3_MASK		GENMASK(3, 0)
 
 /* TDM Configuration Reg4 */
-#define TAS2764_TDM_CFG4		TAS2764_REG(0X0, 0x0c)
+#define TAS2764_TDM_CFG4		TAS2764_REG(0X0, 0x0d)
 #define TAS2764_TDM_CFG4_TX_MASK	BIT(0)
 #define TAS2764_TDM_CFG4_TX_RISING	0x0
 #define TAS2764_TDM_CFG4_TX_FALLING	BIT(0)

From 84c3f6fa7a4dd6b54cf2f2337daf159c7f494da6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 28 Nov 2023 23:13:01 +0900
Subject: [PATCH 0471/1027] fixup! ASoC: tas2764: Enable some Apple quirks by
 default

---
 sound/soc/codecs/tas2764.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/codecs/tas2764.c b/sound/soc/codecs/tas2764.c
index 79c5ba7af3d029..84f5db38e0fdab 100644
--- a/sound/soc/codecs/tas2764.c
+++ b/sound/soc/codecs/tas2764.c
@@ -49,7 +49,7 @@ struct tas2764_priv {
 	bool unmuted;
 };
 
-static int apple_quirks = 0xf;
+static int apple_quirks = 0x3f;
 module_param(apple_quirks, int, 0644);
 MODULE_PARM_DESC(apple_quirks, "Mask of quirks to mimic after Apple's SN012776 driver");
 

From d4c08ec43ffeca1dcf74521bd38e46b4c8c077cd Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 4 Dec 2023 01:21:33 +0900
Subject: [PATCH 0472/1027] macaudio: Set the card name explicitly

This might fix a udev race, and also makes it possible to switch to a
more descriptive "AppleJxxx" name (but before that we need to update
userspace to avoid breaking users).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index fa4f1b48740a4d..ed1006e1f43141 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1230,6 +1230,14 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b
 static int macaudio_fixup_controls(struct snd_soc_card *card)
 {
 	struct macaudio_snd_data *ma = snd_soc_card_get_drvdata(card);
+	const char *p;
+
+	/* Set the card ID early to avoid races with udev */
+	p = strrchr(card->name, ' ');
+	if (p) {
+		snprintf(card->snd_card->id, sizeof(card->snd_card->id),
+			 "%s", p + 1);
+	}
 
 	if (!ma->has_speakers)
 		return 0;

From cc6938f4ffdf7b4785acce76465f75bf4499ec91 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 5 Dec 2023 12:36:52 +0900
Subject: [PATCH 0473/1027] macaudio: Change device ID form Jxxx to AppleJxxx

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index ed1006e1f43141..74164d85c3655b 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1236,7 +1236,7 @@ static int macaudio_fixup_controls(struct snd_soc_card *card)
 	p = strrchr(card->name, ' ');
 	if (p) {
 		snprintf(card->snd_card->id, sizeof(card->snd_card->id),
-			 "%s", p + 1);
+			 "Apple%s", p + 1);
 	}
 
 	if (!ma->has_speakers)

From 30fb543b8ef3f0cc1c121bd2d9154dc3e0f8eb66 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Dec 2023 22:34:15 +0900
Subject: [PATCH 0474/1027] macaudio: Turn please_blow_up_my_speakers into an
 int

1 enables new models, 2 further removes safeties. Mostly so that people
who set it to 1 for early access and forget don't get stuck without
safety nets.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 74164d85c3655b..57d62be1c104ba 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -137,8 +137,8 @@ struct macaudio_snd_data {
 
 };
 
-static bool please_blow_up_my_speakers;
-module_param(please_blow_up_my_speakers, bool, 0644);
+static int please_blow_up_my_speakers;
+module_param(please_blow_up_my_speakers, int, 0644);
 MODULE_PARM_DESC(please_blow_up_my_speakers, "Allow unsafe or untested operating configurations");
 
 SND_SOC_DAILINK_DEFS(primary,
@@ -1165,7 +1165,7 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 #define CHECK(call, pattern, value) \
 	{ \
 		int ret = call(card, pattern, value); \
-		if (ret < 1 && !please_blow_up_my_speakers) { \
+		if (ret < 1 && (please_blow_up_my_speakers < 2)) { \
 			dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \
 			return ret; \
 		} \
@@ -1205,7 +1205,7 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b
 		CHECK_CONCAT(snd_soc_set_enum_kctl, "HPF Corner Frequency",
 			     tweeter ? "800 Hz" : "2 Hz");
 
-		if (!please_blow_up_my_speakers)
+		if (please_blow_up_my_speakers < 2)
 			CHECK_CONCAT(snd_soc_deactivate_kctl, "HPF Corner Frequency", 0);
 
 		CHECK_CONCAT(snd_soc_set_enum_kctl, "OCE Handling", "Retry");
@@ -1215,7 +1215,7 @@ static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, b
 		/* TODO: check */
 		CHECK_CONCAT(snd_soc_set_enum_kctl, "DAC Analog Gain Select", "8.4 V Span");
 
-		if (!please_blow_up_my_speakers)
+		if (please_blow_up_my_speakers < 2)
 			CHECK_CONCAT(snd_soc_deactivate_kctl, "DAC Analog Gain Select", 0);
 
 		/* TODO: HPF, needs new call to set */
@@ -1244,8 +1244,8 @@ static int macaudio_fixup_controls(struct snd_soc_card *card)
 
 	switch(ma->cfg->speakers) {
 	case SPKR_NONE:
-		WARN_ON(!please_blow_up_my_speakers);
-		return please_blow_up_my_speakers ? 0 : -EINVAL;
+		WARN_ON(please_blow_up_my_speakers < 2);
+		return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL;
 	case SPKR_1W:
 	case SPKR_2W:
 		CHECK(macaudio_set_speaker, "* ", false);

From 7584c6b071e1bf09fe04fa77984c2f63d44fd378 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 11 Dec 2023 22:34:56 +0900
Subject: [PATCH 0475/1027] macaudio: Sync all gains with macOS

We want the extra headroom, and speakersafetyd seems to be reliable. 3dB
lower gain isn't going to buy us much safety at this point.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 57d62be1c104ba..70d13af7d48530 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1470,14 +1470,14 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 
 /*	enable	amp		speakers	stereo	gain	safe_vol */
 struct macaudio_platform_cfg macaudio_j180_cfg = {
-	false,	AMP_SN012776,	SPKR_1W1T,	false,	4,	-20,
+	false,	AMP_SN012776,	SPKR_1W1T,	false,	10,	-20,
 };
 struct macaudio_platform_cfg macaudio_j274_cfg = {
-	true,	AMP_TAS5770,	SPKR_1W,	false,	14,	0, /* TODO: safety */
+	true,	AMP_TAS5770,	SPKR_1W,	false,	20,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j293_cfg = {
-	false,	AMP_TAS5770,	SPKR_2W,	true,	9,	-20, /* TODO: check */
+	false,	AMP_TAS5770,	SPKR_2W,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j313_cfg = {
@@ -1485,19 +1485,19 @@ struct macaudio_platform_cfg macaudio_j313_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j314_j316_cfg = {
-	false,	AMP_SN012776,	SPKR_2W1T,	true,	9,	-20,
+	false,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
-	false,	AMP_SN012776,	SPKR_1W,	false,	14,	-20,
+	false,	AMP_SN012776,	SPKR_1W,	false,	20,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j413_cfg = {
-	false,	AMP_SN012776,	SPKR_1W1T,	true,	9,	-20,
+	false,	AMP_SN012776,	SPKR_1W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j415_cfg = {
-	false,	AMP_SN012776,	SPKR_2W1T,	true,	9,	-20,
+	false,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j45x_cfg = {
@@ -1505,7 +1505,7 @@ struct macaudio_platform_cfg macaudio_j45x_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j493_cfg = {
-	false,	AMP_SN012776,	SPKR_2W,	true,	9,	-20,
+	false,	AMP_SN012776,	SPKR_2W,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_fallback_cfg = {

From 45b89c9dab8dfd3e36fcf450764d58d97e22b431 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 12 Dec 2023 19:57:05 +0900
Subject: [PATCH 0476/1027] fixup! ASoC: macaudio: Start speaker sense capture
 support

---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 70d13af7d48530..813d20c160fffd 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1465,7 +1465,7 @@ static const struct snd_soc_dapm_route macaudio_dapm_routes[] = {
 	{ "PCM0 RX", NULL, "Headset Capture" },
 
 	/* Sense paths */
-	{ "PCM2 RX", NULL, "Speaker Sense Capture" },	
+	{ "PCM2 RX", NULL, "Speaker Sense Capture" },
 };
 
 /*	enable	amp		speakers	stereo	gain	safe_vol */

From 73c8c48cf3a781091e6fe97298dc01ca0463496f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 12 Dec 2023 19:57:23 +0900
Subject: [PATCH 0477/1027] macaudio: Fix CHECK return condition checking

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 35 ++++++++++++++++++++---------------
 1 file changed, 20 insertions(+), 15 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 813d20c160fffd..644a37bcb8a579 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1162,20 +1162,25 @@ static int macaudio_late_probe(struct snd_soc_card *card)
 	return 0;
 }
 
-#define CHECK(call, pattern, value) \
-	{ \
-		int ret = call(card, pattern, value); \
-		if (ret < 1 && (please_blow_up_my_speakers < 2)) { \
-			dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, ret); \
-			return ret; \
-		} \
-		dev_dbg(card->dev, "%s on '%s': %d hits\n", #call, pattern, ret); \
+#define CHECK(call, pattern, value, min)                                       \
+	{                                                                      \
+		int ret = call(card, pattern, value);                          \
+		int err = (ret >= 0 && ret < min) ? -ERANGE : ret;             \
+		if (err < 0) {                                                 \
+			dev_err(card->dev, "%s on '%s': %d\n", #call, pattern, \
+				ret);                                          \
+			if (please_blow_up_my_speakers < 2)                    \
+				return err;                                    \
+		} else {                                                       \
+			dev_dbg(card->dev, "%s on '%s': %d hits\n", #call,     \
+				pattern, ret);                                 \
+		}                                                              \
 	}
 
 #define CHECK_CONCAT(call, suffix, value) \
 	{ \
 		snprintf(buf, sizeof(buf), "%s%s", prefix, suffix); \
-		CHECK(call, buf, value); \
+		CHECK(call, buf, value, 1); \
 	}
 
 static int macaudio_set_speaker(struct snd_soc_card *card, const char *prefix, bool tweeter)
@@ -1248,16 +1253,16 @@ static int macaudio_fixup_controls(struct snd_soc_card *card)
 		return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL;
 	case SPKR_1W:
 	case SPKR_2W:
-		CHECK(macaudio_set_speaker, "* ", false);
+		CHECK(macaudio_set_speaker, "* ", false, 0);
 		break;
 	case SPKR_1W1T:
-		CHECK(macaudio_set_speaker, "* Tweeter ", true);
-		CHECK(macaudio_set_speaker, "* Woofer ", false);
+		CHECK(macaudio_set_speaker, "* Tweeter ", true, 0);
+		CHECK(macaudio_set_speaker, "* Woofer ", false, 0);
 		break;
 	case SPKR_2W1T:
-		CHECK(macaudio_set_speaker, "* Tweeter ", true);
-		CHECK(macaudio_set_speaker, "* Woofer 1 ", false);
-		CHECK(macaudio_set_speaker, "* Woofer 2 ", false);
+		CHECK(macaudio_set_speaker, "* Tweeter ", true, 0);
+		CHECK(macaudio_set_speaker, "* Woofer 1 ", false, 0);
+		CHECK(macaudio_set_speaker, "* Woofer 2 ", false, 0);
 		break;
 	}
 

From 55882661cccb7c2b0eeff3b6da888170928761ca Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 12 Dec 2023 23:26:16 +0100
Subject: [PATCH 0478/1027] macaudio: Avoid matches against cs42l84's constrols

On systems with cs42l84 headset codec "* " can't be used as control name
pattern since it would match "Jack HPF Corner Frequency". Its control is
not an enum and thus will always return -EINVAL.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 sound/soc/apple/macaudio.c | 14 +++++++++++++-
 1 file changed, 13 insertions(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 644a37bcb8a579..54d4531c460154 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1247,13 +1247,25 @@ static int macaudio_fixup_controls(struct snd_soc_card *card)
 	if (!ma->has_speakers)
 		return 0;
 
+	/*
+	 * This needs some care to avoid matches against cs42l84's
+	 * "Jack HPF Corner Frequency".
+	 */
 	switch(ma->cfg->speakers) {
 	case SPKR_NONE:
 		WARN_ON(please_blow_up_my_speakers < 2);
 		return please_blow_up_my_speakers >= 2 ? 0 : -EINVAL;
 	case SPKR_1W:
+		/* only 1W stereo system (J313) is uses cs42l83 */
+		if (ma->cfg->stereo) {
+			CHECK(macaudio_set_speaker, "* ", false, 0);
+		} else {
+			CHECK(macaudio_set_speaker, "", false, 0);
+		}
+		break;
 	case SPKR_2W:
-		CHECK(macaudio_set_speaker, "* ", false, 0);
+		CHECK(macaudio_set_speaker, "* Front ", false, 0);
+		CHECK(macaudio_set_speaker, "* Rear ", false, 0);
 		break;
 	case SPKR_1W1T:
 		CHECK(macaudio_set_speaker, "* Tweeter ", true, 0);

From 766d34bd2614622c5cb5b7a29cd31e9a89a2d7d2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 14 Dec 2023 21:21:12 +0900
Subject: [PATCH 0479/1027] ASoC: apple: mca: Add delay after configuring clock

Right after the early FE setup, ADMAC gets told to start the DMA. This
can end up in a weird "slip" state with the channels transposed. Waiting
a bit fixes this; presumably this allows the clock to stabilize.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 194b1430cf1176..aff52ec1990b1b 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -225,6 +225,12 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd,
 			   FIELD_PREP(SERDES_CONF_SYNC_SEL, 0));
 		mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL,
 			   FIELD_PREP(SERDES_CONF_SYNC_SEL, cl->no + 1));
+		/*
+		 * ADMAC gets started right after this. This delay seems
+		 * to be needed for that to be reliable, e.g. ensure the
+		 * clock is stable?
+		 */
+		udelay(10);
 		break;
 	default:
 		break;

From 98f6dcea234d19b649024642a4ea9a90044a7de4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 15 Dec 2023 20:27:42 +0900
Subject: [PATCH 0480/1027] macaudio: Disable j313 and j274

We are going to enable these out of band. If you are a distro packager:

** WARNING: **
** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE **

https://github.com/lsp-plugins/lsp-dsp-lib/pull/20

Do NOT enable speakers without that patch, on any model. It can/will
result in nasty noise that could damage them.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 54d4531c460154..ff898cc419c18e 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1490,7 +1490,7 @@ struct macaudio_platform_cfg macaudio_j180_cfg = {
 	false,	AMP_SN012776,	SPKR_1W1T,	false,	10,	-20,
 };
 struct macaudio_platform_cfg macaudio_j274_cfg = {
-	true,	AMP_TAS5770,	SPKR_1W,	false,	20,	-20,
+	false,	AMP_TAS5770,	SPKR_1W,	false,	20,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j293_cfg = {
@@ -1498,7 +1498,7 @@ struct macaudio_platform_cfg macaudio_j293_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j313_cfg = {
-	true,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
+	false,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j314_j316_cfg = {

From eea44e245745abcc1bd4e9757fb7f57a73a029e0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 17 Dec 2023 14:35:33 +0900
Subject: [PATCH 0481/1027] ASoC: apple: mca: Add more delay after configuring
 clock

Sigh... hope this works.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index aff52ec1990b1b..3d70e7299a63a8 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -230,7 +230,7 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd,
 		 * to be needed for that to be reliable, e.g. ensure the
 		 * clock is stable?
 		 */
-		udelay(10);
+		udelay(100);
 		break;
 	default:
 		break;

From a02c108fa5e33bf26b67ef692a9eef0240800e9c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 19 Dec 2023 18:21:53 +0900
Subject: [PATCH 0482/1027] ASoC: apple: mca: More delay
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

¯\_(ツ)_/¯

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/mca.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/mca.c b/sound/soc/apple/mca.c
index 3d70e7299a63a8..aa93be31cbde9d 100644
--- a/sound/soc/apple/mca.c
+++ b/sound/soc/apple/mca.c
@@ -218,7 +218,7 @@ static void mca_fe_early_trigger(struct snd_pcm_substream *substream, int cmd,
 		 * Experiments suggest that it takes at most ~1 us
 		 * for the bit to clear, so wait 5 us for good measure.
 		 */
-		udelay(5);
+		udelay(50);
 		WARN_ON(readl_relaxed(cl->base + serdes_unit + REG_SERDES_STATUS) &
 			SERDES_STATUS_RST);
 		mca_modify(cl, serdes_conf, SERDES_CONF_SYNC_SEL,

From 0602b407fa37a6fad397b3e1868bbb08181e0601 Mon Sep 17 00:00:00 2001
From: Sasha Finkelstein <fnkl.kernel@gmail.com>
Date: Sun, 17 Dec 2023 16:16:03 +0100
Subject: [PATCH 0483/1027] macaudio: Fix missing kconfig requirement

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 sound/soc/apple/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index 9fb902340fff6e..d95a6341dc3fdf 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -18,6 +18,7 @@ config SND_SOC_APPLE_MACAUDIO
 	select SND_SOC_TAS2770
 	select SND_SOC_CS42L83
 	select SND_SOC_CS42L84
+	select REGULATOR_FIXED_VOLTAGE
 	default ARCH_APPLE
 	help
 	  This option enables an ASoC machine-level driver for Apple Silicon Macs

From 2ab8de13589a158150b013e1c085665f14374fb4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Sun, 4 Dec 2022 15:34:53 +0100
Subject: [PATCH 0484/1027] fixup! dmaengine: apple-admac: Add Apple ADMAC
 driver

---
 drivers/dma/apple-admac.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/dma/apple-admac.c b/drivers/dma/apple-admac.c
index 9588773dd2eb67..610bc6994dd330 100644
--- a/drivers/dma/apple-admac.c
+++ b/drivers/dma/apple-admac.c
@@ -252,7 +252,7 @@ static struct dma_async_tx_descriptor *admac_prep_dma_cyclic(
 		size_t period_len, enum dma_transfer_direction direction,
 		unsigned long flags)
 {
-	struct admac_chan *adchan = container_of(chan, struct admac_chan, chan);
+	struct admac_chan *adchan = to_admac_chan(chan);
 	struct admac_tx *adtx;
 
 	if (direction != admac_chan_direction(adchan->no))

From 65b5d8391ddcb7e898fc7abf279af23190cf3d1c Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 23 Feb 2023 17:57:56 +0100
Subject: [PATCH 0485/1027] HACK: ALSA: Export 'snd_pcm_known_rates'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/core/pcm_native.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index a650d2a0a36bae..c0309c7f15e39b 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -2432,6 +2432,7 @@ const struct snd_pcm_hw_constraint_list snd_pcm_known_rates = {
 	.count = ARRAY_SIZE(rates),
 	.list = rates,
 };
+EXPORT_SYMBOL_GPL(snd_pcm_known_rates);
 
 static int snd_pcm_hw_rule_rate(struct snd_pcm_hw_params *params,
 				struct snd_pcm_hw_rule *rule)

From b84a7086d959ffdb1184c2022e5e0f0941467055 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 28 Nov 2022 09:55:07 +0100
Subject: [PATCH 0486/1027] dmaengine: apple-sio: Add Apple SIO driver
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add a dmaengine driver for the Apple SIO coprocessor found on Apple
SoCs where it provides DMA services. Have the driver support cyclic
transactions so that ALSA drivers can rely on it in audio output to
HDMI and DisplayPort.

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 MAINTAINERS             |   2 +
 drivers/dma/Kconfig     |  11 +
 drivers/dma/Makefile    |   1 +
 drivers/dma/apple-sio.c | 912 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 926 insertions(+)
 create mode 100644 drivers/dma/apple-sio.c

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd10..cdb93da02de5fe 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2034,8 +2034,10 @@ M:	Martin Povišer <povik+lin@cutebit.org>
 L:	asahi@lists.linux.dev
 L:	alsa-devel@alsa-project.org (moderated for non-subscribers)
 S:	Maintained
+F:	Documentation/devicetree/bindings/dma/apple,sio.yaml
 F:	Documentation/devicetree/bindings/sound/adi,ssm3515.yaml
 F:	Documentation/devicetree/bindings/sound/apple,*
+F:	drivers/dma/apple-sio.c
 F:	sound/soc/apple/*
 F:	sound/soc/codecs/cs42l83-i2c.c
 F:	sound/soc/codecs/ssm3515.c
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index cc0a62c348616b..b8bcc751cbbfbf 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -89,10 +89,21 @@ config APPLE_ADMAC
 	tristate "Apple ADMAC support"
 	depends on ARCH_APPLE || COMPILE_TEST
 	select DMA_ENGINE
+	select DMA_VIRTUAL_CHANNELS
 	default ARCH_APPLE
 	help
 	  Enable support for Audio DMA Controller found on Apple Silicon SoCs.
 
+config APPLE_SIO
+	tristate "Apple SIO support"
+	depends on ARCH_APPLE || COMPILE_TEST
+	depends on APPLE_RTKIT
+	select DMA_ENGINE
+	default m if ARCH_APPLE
+	help
+	  Enable support for the SIO coprocessor found on Apple Silicon SoCs
+	  where it provides DMA services.
+
 config AT_HDMAC
 	tristate "Atmel AHB DMA support"
 	depends on ARCH_AT91
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 374ea98faf43e2..5337ca82efc59c 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_AMBA_PL08X) += amba-pl08x.o
 obj-$(CONFIG_AMCC_PPC440SPE_ADMA) += ppc4xx/
 obj-$(CONFIG_AMD_PTDMA) += ptdma/
 obj-$(CONFIG_APPLE_ADMAC) += apple-admac.o
+obj-$(CONFIG_APPLE_SIO) += apple-sio.o
 obj-$(CONFIG_AT_HDMAC) += at_hdmac.o
 obj-$(CONFIG_AT_XDMAC) += at_xdmac.o
 obj-$(CONFIG_AXI_DMAC) += dma-axi-dmac.o
diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c
new file mode 100644
index 00000000000000..ba2f9c02fa0891
--- /dev/null
+++ b/drivers/dma/apple-sio.c
@@ -0,0 +1,912 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Driver for SIO coprocessor on t8103 (M1) and other Apple SoCs
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/rtkit.h>
+
+#include "dmaengine.h"
+#include "virt-dma.h"
+
+#define NCHANNELS_MAX	0x80
+
+#define REG_CPU_CONTROL	0x44
+#define CPU_CONTROL_RUN BIT(4)
+
+#define SIOMSG_DATA	GENMASK(63, 32)
+#define SIOMSG_TYPE	GENMASK(23, 16)
+#define SIOMSG_PARAM	GENMASK(31, 24)
+#define SIOMSG_TAG	GENMASK(13, 8)
+#define SIOMSG_EP	GENMASK(7, 0)
+
+#define EP_SIO		0x20
+
+#define MSG_START	0x2
+#define MSG_SETUP	0x3
+#define MSG_CONFIGURE	0x5
+#define MSG_ISSUE	0x6
+#define MSG_TERMINATE	0x8
+#define MSG_ACK		0x65
+#define MSG_NACK	0x66
+#define MSG_STARTED	0x67
+#define MSG_REPORT	0x68
+
+#define SIO_CALL_TIMEOUT_MS	100
+#define SIO_SHMEM_SIZE		0x1000
+#define SIO_NO_DESC_SLOTS	64
+
+/*
+ * There are two kinds of 'transaction descriptors' in play here.
+ *
+ * There's the struct sio_tx, and the struct dma_async_tx_descriptor embedded
+ * inside, which jointly represent a transaction to the dmaengine subsystem.
+ * At this time we only support those transactions to be cyclic.
+ *
+ * Then there are the coprocessor descriptors, which is what the coprocessor
+ * knows and understands. These don't seem to have a cyclic regime, so we can't
+ * map the dmaengine transaction on an exact coprocessor counterpart. Instead
+ * we continually queue up many coprocessor descriptors to implement a cyclic
+ * transaction.
+ *
+ * The number below is the maximum of how far ahead (how many) coprocessor
+ * descriptors we should be queuing up, per channel, for a cyclic transaction.
+ * Basically it's a made-up number.
+ */
+#define SIO_MAX_NINFLIGHT	4
+
+struct sio_coproc_desc {
+	u32 pad1;
+	u32 flag;
+	u64 unk;
+	u64 iova;
+	u64 size;
+	u64 pad2;
+	u64 pad3;
+} __packed;
+static_assert(sizeof(struct sio_coproc_desc) == 48);
+
+struct sio_shmem_chan_config {
+	u32 datashape;
+	u32 timeout;
+	u32 fifo;
+	u32 threshold;
+	u32 limit;
+} __packed;
+
+struct sio_data;
+struct sio_tx;
+
+struct sio_chan {
+	unsigned int no;
+	struct sio_data *host;
+	struct virt_dma_chan vc;
+	struct work_struct terminate_wq;
+
+	bool configured;
+	struct sio_shmem_chan_config cfg;
+
+	struct sio_tx *current_tx;
+};
+
+#define SIO_NTAGS		16
+
+typedef void (*sio_ack_callback)(struct sio_chan *, void *, bool);
+
+struct sio_data {
+	void __iomem *base;
+	struct dma_device dma;
+	struct device *dev;
+	struct apple_rtkit *rtk;
+	void *shmem;
+	struct sio_coproc_desc *shmem_desc_base;
+	unsigned long *desc_allocated;
+
+	struct sio_tagdata {
+		DECLARE_BITMAP(allocated, SIO_NTAGS);
+		int last_tag;
+
+		struct completion completions[SIO_NTAGS];
+		bool atomic[SIO_NTAGS];
+		bool acked[SIO_NTAGS];
+
+		sio_ack_callback ack_callback[SIO_NTAGS];
+		void *cookie[SIO_NTAGS];
+	} tags;
+
+	int nchannels;
+	struct sio_chan channels[];
+};
+
+struct sio_tx {
+	struct virt_dma_desc vd;
+	struct completion done;
+
+	bool terminated;
+	size_t period_len;
+	int nperiods;
+	int ninflight;
+	int next;
+
+	struct sio_coproc_desc *siodesc[];
+};
+
+static int sio_send_siomsg(struct sio_data *sio, u64 msg);
+static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg,
+				  sio_ack_callback ack_callback,
+				  void *cookie);
+static int sio_call(struct sio_data *sio, u64 msg);
+
+static struct sio_chan *to_sio_chan(struct dma_chan *chan)
+{
+	return container_of(chan, struct sio_chan, vc.chan);
+}
+
+static struct sio_tx *to_sio_tx(struct dma_async_tx_descriptor *tx)
+{
+	return container_of(tx, struct sio_tx, vd.tx);
+}
+
+static int sio_alloc_tag(struct sio_data *sio)
+{
+	struct sio_tagdata *tags = &sio->tags;
+	int tag, i;
+
+	/*
+	 * Because tag number 0 is special, the usable tag range
+	 * is 1...(SIO_NTAGS - 1). So, to pick the next usable tag,
+	 * we do modulo (SIO_NTAGS - 1) *then* plus one.
+	 */
+
+#define SIO_USABLE_TAGS (SIO_NTAGS - 1)
+	tag = (READ_ONCE(tags->last_tag) % SIO_USABLE_TAGS) + 1;
+
+	for (i = 0; i < SIO_USABLE_TAGS; i++) {
+		if (!test_and_set_bit(tag, tags->allocated))
+			break;
+
+		tag = (tag % SIO_USABLE_TAGS) + 1;
+	}
+
+	WRITE_ONCE(tags->last_tag, tag);
+
+	if (i < SIO_USABLE_TAGS)
+		return tag;
+	else
+		return -EBUSY;
+#undef SIO_USABLE_TAGS
+}
+
+static void sio_free_tag(struct sio_data *sio, int tag)
+{
+	struct sio_tagdata *tags = &sio->tags;
+
+	if (WARN_ON(tag >= SIO_NTAGS))
+		return;
+
+	tags->atomic[tag] = false;
+	tags->ack_callback[tag] = NULL;
+
+	WARN_ON(!test_and_clear_bit(tag, tags->allocated));
+}
+
+static void sio_set_tag_atomic(struct sio_data *sio, int tag,
+			       sio_ack_callback ack_callback,
+			       void *cookie)
+{
+	struct sio_tagdata *tags = &sio->tags;
+
+	tags->atomic[tag] = true;
+	tags->ack_callback[tag] = ack_callback;
+	tags->cookie[tag] = cookie;
+}
+
+static struct sio_coproc_desc *sio_alloc_desc(struct sio_data *sio)
+{
+	int i;
+
+	for (i = 0; i < SIO_NO_DESC_SLOTS; i++)
+		if (!test_and_set_bit(i, sio->desc_allocated))
+			return sio->shmem_desc_base + i;
+
+	return NULL;
+}
+
+static void sio_free_desc(struct sio_data *sio, struct sio_coproc_desc *desc)
+{
+	clear_bit(desc - sio->shmem_desc_base, sio->desc_allocated);
+}
+
+static int sio_coproc_desc_slot(struct sio_data *sio, struct sio_coproc_desc *desc)
+{
+	return (desc - sio->shmem_desc_base) * 4;
+}
+
+static enum dma_transfer_direction sio_chan_direction(int channo)
+{
+	/* Channel directions are fixed based on channel number */
+	return (channo & 1) ? DMA_DEV_TO_MEM : DMA_MEM_TO_DEV;
+}
+
+static void sio_tx_free(struct virt_dma_desc *vd)
+{
+	struct sio_data *sio = to_sio_chan(vd->tx.chan)->host;
+	struct sio_tx *siotx = to_sio_tx(&vd->tx);
+	int i;
+
+	for (i = 0; i < siotx->nperiods; i++)
+		if (siotx->siodesc[i])
+			sio_free_desc(sio, siotx->siodesc[i]);
+	kfree(siotx);
+}
+
+static struct dma_async_tx_descriptor *sio_prep_dma_cyclic(
+		struct dma_chan *chan, dma_addr_t buf_addr, size_t buf_len,
+		size_t period_len, enum dma_transfer_direction direction,
+		unsigned long flags)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+	struct sio_tx *siotx = NULL;
+	int i, nperiods = buf_len / period_len;
+
+	if (direction != sio_chan_direction(siochan->no))
+		return NULL;
+
+	siotx = kzalloc(struct_size(siotx, siodesc, nperiods), GFP_NOWAIT);
+	if (!siotx)
+		return NULL;
+
+	init_completion(&siotx->done);
+	siotx->period_len = period_len;
+	siotx->nperiods = nperiods;
+
+	for (i = 0; i < nperiods; i++) {
+		struct sio_coproc_desc *d;
+
+		siotx->siodesc[i] = d = sio_alloc_desc(siochan->host);
+		if (!d) {
+			sio_tx_free(&siotx->vd);
+			return NULL;
+		}
+
+		d->flag = 1; /* not sure what's up with this */
+		d->iova = buf_addr + period_len * i;
+		d->size = period_len;
+	}
+	dma_wmb();
+
+	return vchan_tx_prep(&siochan->vc, &siotx->vd, flags);
+}
+
+static enum dma_status sio_tx_status(struct dma_chan *chan, dma_cookie_t cookie,
+				     struct dma_tx_state *txstate)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+	struct virt_dma_desc *vd;
+	struct sio_tx *siotx;
+	enum dma_status ret;
+	unsigned long flags;
+	int periods_residue;
+	size_t residue;
+
+	ret = dma_cookie_status(chan, cookie, txstate);
+	if (ret == DMA_COMPLETE || !txstate)
+		return ret;
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	siotx = siochan->current_tx;
+
+	if (siotx && siotx->vd.tx.cookie == cookie) {
+		ret = DMA_IN_PROGRESS;
+		periods_residue = siotx->next - siotx->ninflight;
+		while (periods_residue < 0)
+			periods_residue += siotx->nperiods;
+		residue = (siotx->nperiods - periods_residue) * siotx->period_len;
+	} else {
+		ret = DMA_IN_PROGRESS;
+		residue = 0;
+		vd = vchan_find_desc(&siochan->vc, cookie);
+		if (vd) {
+			siotx = to_sio_tx(&vd->tx);
+			residue = siotx->period_len * siotx->nperiods;
+		}
+	}
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+	dma_set_residue(txstate, residue);
+
+	return ret;
+}
+
+static bool sio_fill_in_locked(struct sio_chan *siochan);
+
+static void sio_handle_issue_ack(struct sio_chan *siochan, void *cookie, bool ok)
+{
+	dma_cookie_t tx_cookie = (unsigned long) cookie;
+	unsigned long flags;
+	struct sio_tx *tx;
+
+	if (!ok) {
+		dev_err(siochan->host->dev, "nacked issue on chan %d\n", siochan->no);
+		return;
+	}
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	if (!siochan->current_tx || tx_cookie != siochan->current_tx->vd.tx.cookie ||
+			siochan->current_tx->terminated)
+		goto out;
+
+	tx = siochan->current_tx;
+	tx->next = (tx->next + 1) % tx->nperiods;
+	tx->ninflight++;
+	sio_fill_in_locked(siochan);
+
+out:
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+}
+
+static bool sio_fill_in_locked(struct sio_chan *siochan)
+{
+	struct sio_data *sio = siochan->host;
+	struct sio_tx *tx = siochan->current_tx;
+	struct sio_coproc_desc *d = tx->siodesc[tx->next];
+	int ret;
+
+	if (tx->ninflight >= SIO_MAX_NINFLIGHT || tx->terminated)
+		return false;
+
+	static_assert(sizeof(dma_cookie_t) <= sizeof(void *));
+	ret = sio_send_siomsg_atomic(sio, FIELD_PREP(SIOMSG_EP, siochan->no) |
+				     FIELD_PREP(SIOMSG_TYPE, MSG_ISSUE) |
+				     FIELD_PREP(SIOMSG_DATA, sio_coproc_desc_slot(sio, d)),
+				     sio_handle_issue_ack, (void *) (uintptr_t) tx->vd.tx.cookie);
+	if (ret < 0)
+		dev_err_ratelimited(sio->dev, "can't issue on chan %d ninflight %d: %d\n",
+				    siochan->no, tx->ninflight, ret);
+	return true;
+}
+
+static void sio_update_current_tx_locked(struct sio_chan *siochan)
+{
+	struct virt_dma_desc *vd = vchan_next_desc(&siochan->vc);
+
+	if (vd && !siochan->current_tx) {
+		list_del(&vd->node);
+		siochan->current_tx = to_sio_tx(&vd->tx);
+		sio_fill_in_locked(siochan);
+	}
+}
+
+static void sio_issue_pending(struct dma_chan *chan)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+	unsigned long flags;
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	vchan_issue_pending(&siochan->vc);
+	sio_update_current_tx_locked(siochan);
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+}
+
+static int sio_terminate_all(struct dma_chan *chan)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+	unsigned long flags;
+	LIST_HEAD(to_free);
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	if (siochan->current_tx && !siochan->current_tx->terminated) {
+		dma_cookie_complete(&siochan->current_tx->vd.tx);
+		siochan->current_tx->terminated = true;
+		schedule_work(&siochan->terminate_wq);
+	}
+	vchan_get_all_descriptors(&siochan->vc, &to_free);
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+
+	vchan_dma_desc_free_list(&siochan->vc, &to_free);
+
+	return 0;
+}
+
+static void sio_terminate_work(struct work_struct *wq)
+{
+	struct sio_chan *siochan = container_of(wq, struct sio_chan, terminate_wq);
+	struct sio_tx *tx;
+	unsigned long flags;
+	int ret;
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	tx = siochan->current_tx;
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+
+	if (WARN_ON(!tx))
+		return;
+
+	ret = sio_call(siochan->host, FIELD_PREP(SIOMSG_EP, siochan->no) |
+				      FIELD_PREP(SIOMSG_TYPE, MSG_TERMINATE));
+	if (ret < 0)
+		dev_err(siochan->host->dev, "terminate call on chan %d failed: %d\n",
+			siochan->no, ret);
+
+	ret = wait_for_completion_timeout(&tx->done, msecs_to_jiffies(500));
+	if (!ret)
+		dev_err(siochan->host->dev, "terminate descriptor wait timed out\n");
+
+	tasklet_kill(&siochan->vc.task);
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	WARN_ON(siochan->current_tx != tx);
+	siochan->current_tx = NULL;
+	sio_update_current_tx_locked(siochan);
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+
+	sio_tx_free(&tx->vd);
+}
+
+static void sio_synchronize(struct dma_chan *chan)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+
+	flush_work(&siochan->terminate_wq);
+}
+
+static void sio_free_chan_resources(struct dma_chan *chan)
+{
+	sio_terminate_all(chan);
+	sio_synchronize(chan);
+	vchan_free_chan_resources(&to_sio_chan(chan)->vc);
+}
+
+static struct dma_chan *sio_dma_of_xlate(struct of_phandle_args *dma_spec,
+					 struct of_dma *ofdma)
+{
+	struct sio_data *sio = (struct sio_data *) ofdma->of_dma_data;
+	unsigned int index = dma_spec->args[0];
+
+	if (dma_spec->args_count != 1 || index >= sio->nchannels)
+		return ERR_PTR(-EINVAL);
+
+	return dma_get_slave_channel(&sio->channels[index].vc.chan);
+}
+
+static void sio_rtk_crashed(void *cookie)
+{
+	struct sio_data *sio = cookie;
+
+	dev_err(sio->dev, "SIO down (crashed)");
+}
+
+static void sio_process_report(struct sio_chan *siochan)
+{
+	unsigned long flags;
+
+	spin_lock_irqsave(&siochan->vc.lock, flags);
+	if (siochan->current_tx) {
+		struct sio_tx *tx = siochan->current_tx;
+
+		if (tx->ninflight)
+			tx->ninflight--;
+		vchan_cyclic_callback(&tx->vd);
+		if (!sio_fill_in_locked(siochan) && !tx->ninflight)
+			complete(&tx->done);
+	}
+	spin_unlock_irqrestore(&siochan->vc.lock, flags);
+}
+
+static void sio_recv_msg(void *cookie, u8 ep, u64 msg)
+{
+	struct sio_data *sio = cookie;
+	struct sio_tagdata *tags = &sio->tags;
+	u32 data;
+	u8 param, type, tag, sioep;
+
+	if (ep != EP_SIO)
+		goto unknown;
+
+	data  = FIELD_GET(SIOMSG_DATA, msg);
+	param = FIELD_GET(SIOMSG_PARAM, msg);
+	type  = FIELD_GET(SIOMSG_TYPE, msg);
+	tag   = FIELD_GET(SIOMSG_TAG, msg);
+	sioep = FIELD_GET(SIOMSG_EP, msg);
+
+	switch (type) {
+	case MSG_STARTED:
+		dev_info(sio->dev, "SIO protocol v%u\n", data);
+		type = MSG_ACK; /* Pretend this is an ACK */
+		fallthrough;
+	case MSG_ACK:
+	case MSG_NACK:
+		if (WARN_ON(tag >= SIO_NTAGS))
+			break;
+
+		if (tags->atomic[tag]) {
+			sio_ack_callback callback = tags->ack_callback[tag];
+
+			if (callback && !WARN_ON(sioep >= sio->nchannels))
+				callback(&sio->channels[sioep],
+					 tags->cookie[tag], type == MSG_ACK);
+			if (type == MSG_NACK)
+				dev_err(sio->dev, "got a NACK on channel %d\n", sioep);
+			sio_free_tag(sio, tag);
+		} else {
+			tags->acked[tag] = (type == MSG_ACK);
+			complete(&tags->completions[tag]);
+		}
+		break;
+
+	case MSG_REPORT:
+		if (WARN_ON(sioep >= sio->nchannels))
+			break;
+
+		sio_process_report(&sio->channels[sioep]);
+		break;
+
+	default:
+		goto unknown;
+	}
+	return;
+
+unknown:
+	dev_warn(sio->dev, "received unknown message: ep %x data %016llx\n",
+		 ep, msg);
+}
+
+static int _sio_send_siomsg(struct sio_data *sio, u64 msg, bool atomic,
+			    sio_ack_callback ack_callback, void *cookie)
+{
+	int tag, ret;
+
+	tag = sio_alloc_tag(sio);
+	if (tag < 0)
+		return tag;
+
+	if (atomic)
+		sio_set_tag_atomic(sio, tag, ack_callback, cookie);
+	else
+		reinit_completion(&sio->tags.completions[tag]);
+
+	msg &= ~SIOMSG_TAG;
+	msg |= FIELD_PREP(SIOMSG_TAG, tag);
+	ret = apple_rtkit_send_message(sio->rtk, EP_SIO, msg, NULL,
+				       atomic);
+	if (ret < 0) {
+		sio_free_tag(sio, tag);
+		return ret;
+	}
+
+	return tag;
+}
+
+static int sio_send_siomsg(struct sio_data *sio, u64 msg)
+{
+	return _sio_send_siomsg(sio, msg, false, NULL, NULL);
+}
+
+static int sio_send_siomsg_atomic(struct sio_data *sio, u64 msg,
+				  sio_ack_callback ack_callback,
+				  void *cookie)
+{
+	return _sio_send_siomsg(sio, msg, true, ack_callback, cookie);
+}
+
+static int sio_call(struct sio_data *sio, u64 msg)
+{
+	int tag, ret;
+
+	tag = sio_send_siomsg(sio, msg);
+	if (tag < 0)
+		return tag;
+
+	ret = wait_for_completion_timeout(&sio->tags.completions[tag],
+					  msecs_to_jiffies(SIO_CALL_TIMEOUT_MS));
+	if (!ret) {
+		dev_warn(sio->dev, "call %8llx timed out\n", msg);
+		sio_free_tag(sio, tag);
+		return -ETIME;
+	}
+
+	ret = sio->tags.acked[tag];
+	sio_free_tag(sio, tag);
+
+	return ret;
+}
+
+static const struct apple_rtkit_ops sio_rtkit_ops = {
+	.crashed = sio_rtk_crashed,
+	.recv_message = sio_recv_msg,
+};
+
+static int sio_device_config(struct dma_chan *chan,
+			     struct dma_slave_config *config)
+{
+	struct sio_chan *siochan = to_sio_chan(chan);
+	struct sio_data *sio = siochan->host;
+	bool is_tx = sio_chan_direction(siochan->no) == DMA_MEM_TO_DEV;
+	struct sio_shmem_chan_config *cfg_shmem = sio->shmem;
+	struct sio_shmem_chan_config cfg;
+	int ret;
+
+	switch (is_tx ? config->dst_addr_width : config->src_addr_width) {
+	case DMA_SLAVE_BUSWIDTH_1_BYTE:
+		cfg.datashape = 0;
+		break;
+	case DMA_SLAVE_BUSWIDTH_2_BYTES:
+		cfg.datashape = 1;
+		break;
+	case DMA_SLAVE_BUSWIDTH_4_BYTES:
+		cfg.datashape = 2;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	cfg.timeout = 0;
+	cfg.fifo = 0x800;
+	cfg.limit = 0x800;
+	cfg.threshold = 0x800;
+
+	/*
+	 * Dmaengine prescribes we ought to apply the new configuration only
+	 * to newly-queued descriptors.
+	 *
+	 * To comply with dmaengine's interface we take the lazy path here:
+	 * we apply the configuration right away, we only allow the channel
+	 * to be configured once, which means subsequent calls to `device_config`
+	 * either return -EBUSY if the configuration differs, or they are
+	 * a no-op if the configuration is the same as the starting one.
+	 *
+	 * This is the reasonable thing to do given that these sio channels
+	 * are tied to fixed peripherals, and what's more given that the
+	 * only planned consumer of this dmaengine driver in the kernel is
+	 * diplayport audio support, where the DMA configuration is fixed,
+	 * and no more than a single descriptor (a cyclic one) gets ever issued
+	 * at the same time.
+	 *
+	 * The code complexity cost of tracking to which descriptor
+	 * the configuration relates would be significant here, especially
+	 * since we need to do a non-atomic operation to apply it (a call to
+	 * the coprocessor) and dmaengine has its bunch of atomicity
+	 * restrictions. And this complexity would be for naught since it
+	 * doesn't even get exercised by the only planned consumer.
+	 */
+	if (siochan->configured && memcmp(&siochan->cfg, &cfg, sizeof(cfg)))
+		return -EBUSY;
+
+	*cfg_shmem = cfg;
+	dma_wmb();
+
+	ret = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_CONFIGURE) |
+			    FIELD_PREP(SIOMSG_EP, siochan->no));
+
+	if (ret == 1)
+		ret = 0;
+	else if (ret == 0)
+		ret = -EINVAL;
+
+	if (ret == 0) {
+		siochan->configured = true;
+		siochan->cfg = cfg;
+	}
+
+	return ret;
+}
+
+static int sio_alloc_shmem(struct sio_data *sio)
+{
+	dma_addr_t iova;
+	int err;
+
+	sio->shmem = dma_alloc_coherent(sio->dev, SIO_SHMEM_SIZE,
+					&iova, GFP_KERNEL | __GFP_ZERO);
+	if (!sio->shmem)
+		return -ENOMEM;
+
+	sio->shmem_desc_base = (struct sio_coproc_desc *) (sio->shmem + 56);
+	sio->desc_allocated = devm_kzalloc(sio->dev, SIO_NO_DESC_SLOTS / 32,
+					   GFP_KERNEL);
+	if (!sio->desc_allocated)
+		return -ENOMEM;
+
+	err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) |
+			    FIELD_PREP(SIOMSG_PARAM, 1) |
+			    FIELD_PREP(SIOMSG_DATA, iova >> 12));
+	if (err != 1) {
+		if (err == 0)
+			err = -EINVAL;
+		return err;
+	}
+
+	err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) |
+			    FIELD_PREP(SIOMSG_PARAM, 2) |
+			    FIELD_PREP(SIOMSG_DATA, SIO_SHMEM_SIZE));
+	if (err != 1) {
+		if (err == 0)
+			err = -EINVAL;
+		return err;
+	}
+
+	return 0;
+}
+
+static int sio_send_dt_params(struct sio_data *sio)
+{
+	struct device_node *np = sio->dev->of_node;
+	const char *propname = "apple,sio-firmware-params";
+	int nparams, err, i;
+
+	nparams = of_property_count_u32_elems(np, propname);
+	if (nparams < 0) {
+		err = nparams;
+		goto badprop;
+	}
+
+	for (i = 0; i < nparams / 2; i++) {
+		u32 key, val;
+
+		err = of_property_read_u32_index(np, propname, 2 * i, &key);
+		if (err)
+			goto badprop;
+		err = of_property_read_u32_index(np, propname, 2 * i + 1, &val);
+		if (err)
+			goto badprop;
+
+		err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_SETUP) |
+				    FIELD_PREP(SIOMSG_PARAM, key & 0xff) |
+				    FIELD_PREP(SIOMSG_EP, key >> 8) |
+				    FIELD_PREP(SIOMSG_DATA, val));
+		if (err < 1) {
+			if (err == 0)
+				err = -ENXIO;
+			return dev_err_probe(sio->dev, err, "sending SIO parameter %#x value %#x\n",
+					     key, val);
+		}
+	}
+
+	return 0;
+
+badprop:
+	return dev_err_probe(sio->dev, err, "failed to read '%s'\n", propname);
+}
+
+static int sio_probe(struct platform_device *pdev)
+{
+	struct device_node *np = pdev->dev.of_node;
+	struct sio_data *sio;
+	struct dma_device *dma;
+	int nchannels;
+	int err, i;
+
+	err = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
+	if (err)
+		return dev_err_probe(&pdev->dev, err, "Failed to set DMA mask\n");
+
+	err = of_property_read_u32(np, "dma-channels", &nchannels);
+	if (err || nchannels > NCHANNELS_MAX)
+		return dev_err_probe(&pdev->dev, -EINVAL,
+				     "missing or invalid dma-channels property\n");
+
+	sio = devm_kzalloc(&pdev->dev, struct_size(sio, channels, nchannels), GFP_KERNEL);
+	if (!sio)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, sio);
+	sio->dev = &pdev->dev;
+	sio->nchannels = nchannels;
+
+	sio->base = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(sio->base))
+		return PTR_ERR(sio->base);
+
+	sio->rtk = devm_apple_rtkit_init(&pdev->dev, sio, NULL, 0, &sio_rtkit_ops);
+	if (IS_ERR(sio->rtk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(sio->rtk),
+				     "couldn't initialize rtkit\n");
+	for (i = 1; i < SIO_NTAGS; i++)
+		init_completion(&sio->tags.completions[i]);
+
+	dma = &sio->dma;
+	dma_cap_set(DMA_PRIVATE, dma->cap_mask);
+	dma_cap_set(DMA_CYCLIC, dma->cap_mask);
+
+	dma->dev = &pdev->dev;
+	dma->device_free_chan_resources = sio_free_chan_resources;
+	dma->device_tx_status = sio_tx_status;
+	dma->device_issue_pending = sio_issue_pending;
+	dma->device_terminate_all = sio_terminate_all;
+	dma->device_synchronize = sio_synchronize;
+	dma->device_prep_dma_cyclic = sio_prep_dma_cyclic;
+	dma->device_config = sio_device_config;
+
+	dma->directions = BIT(DMA_MEM_TO_DEV);
+	dma->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;
+	dma->dst_addr_widths = BIT(DMA_SLAVE_BUSWIDTH_1_BYTE) |
+			       BIT(DMA_SLAVE_BUSWIDTH_2_BYTES) |
+			       BIT(DMA_SLAVE_BUSWIDTH_4_BYTES);
+
+	INIT_LIST_HEAD(&dma->channels);
+	for (i = 0; i < nchannels; i++) {
+		struct sio_chan *siochan = &sio->channels[i];
+
+		siochan->host = sio;
+		siochan->no = i;
+		siochan->vc.desc_free = sio_tx_free;
+		INIT_WORK(&siochan->terminate_wq, sio_terminate_work);
+		vchan_init(&siochan->vc, dma);
+	}
+
+	writel(CPU_CONTROL_RUN, sio->base + REG_CPU_CONTROL);
+
+	err = apple_rtkit_boot(sio->rtk);
+	if (err)
+		return dev_err_probe(&pdev->dev, err, "SIO did not boot\n");
+
+	err = apple_rtkit_start_ep(sio->rtk, EP_SIO);
+	if (err)
+		return dev_err_probe(&pdev->dev, err, "starting SIO endpoint\n");
+
+	err = sio_call(sio, FIELD_PREP(SIOMSG_TYPE, MSG_START));
+	if (err < 1) {
+		if (err == 0)
+			err = -ENXIO;
+		return dev_err_probe(&pdev->dev, err, "starting SIO service\n");
+	}
+
+	err = sio_send_dt_params(sio);
+	if (err < 0)
+		return dev_err_probe(&pdev->dev, err, "failed to send boot-up parameters\n");
+
+	err = sio_alloc_shmem(sio);
+	if (err < 0)
+		return err;
+
+	err = dma_async_device_register(&sio->dma);
+	if (err)
+		return dev_err_probe(&pdev->dev, err, "failed to register DMA device\n");
+
+	err = of_dma_controller_register(pdev->dev.of_node, sio_dma_of_xlate, sio);
+	if (err) {
+		dma_async_device_unregister(&sio->dma);
+		return dev_err_probe(&pdev->dev, err, "failed to register with OF\n");
+	}
+
+	return 0;
+}
+
+static void sio_remove(struct platform_device *pdev)
+{
+	struct sio_data *sio = platform_get_drvdata(pdev);
+
+	of_dma_controller_free(pdev->dev.of_node);
+	dma_async_device_unregister(&sio->dma);
+}
+
+static const struct of_device_id sio_of_match[] = {
+	{ .compatible = "apple,sio", },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, sio_of_match);
+
+static struct platform_driver apple_sio_driver = {
+	.driver = {
+		.name = "apple-sio",
+		.of_match_table = sio_of_match,
+	},
+	.probe = sio_probe,
+	.remove = sio_remove,
+};
+module_platform_driver(apple_sio_driver);
+
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_DESCRIPTION("Driver for SIO coprocessor on Apple SoCs");
+MODULE_LICENSE("Dual MIT/GPL");

From 5f3720a180b564da54be9a6676df1c3ced2bf65e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 22:10:41 +0900
Subject: [PATCH 0487/1027] dmaengine: apple-sio: Fix chan freeing in error
 path

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/dma/apple-sio.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/dma/apple-sio.c b/drivers/dma/apple-sio.c
index ba2f9c02fa0891..54a750d60cadc2 100644
--- a/drivers/dma/apple-sio.c
+++ b/drivers/dma/apple-sio.c
@@ -277,6 +277,7 @@ static struct dma_async_tx_descriptor *sio_prep_dma_cyclic(
 
 		siotx->siodesc[i] = d = sio_alloc_desc(siochan->host);
 		if (!d) {
+			siotx->vd.tx.chan = &siochan->vc.chan;
 			sio_tx_free(&siotx->vd);
 			return NULL;
 		}

From ecf7685a4b71aae210cf3cf61ba950da21ddfde5 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 11 Apr 2024 09:51:20 +0900
Subject: [PATCH 0488/1027] prctl: Introduce PR_{SET,GET}_MEM_MODEL

On some architectures, it is possible to query and/or change the CPU
memory model. This allows userspace to switch to a stricter memory model
for performance reasons, such as when emulating code for another
architecture where that model is the default.

Introduce two prctls to allow userspace to query and set the memory
model for a thread. Two models are initially defined:

- PR_SET_MEM_MODEL_DEFAULT requests the default memory model for the
  architecture.
- PR_SET_MEM_MODEL_TSO requests the x86 TSO memory model.

PR_SET_MEM_MODEL is allowed to set a stricter memory model than
requested if available, in which case it will return successfully. If
the requested memory model cannot be fulfilled, it will return an error.
The memory model that was actually set can be queried by a subsequent
call to PR_GET_MEM_MODEL.

Examples:
- On a CPU with not support for a memory model at least as strong as
  TSO, PR_SET_MEM_MODEL(PR_SET_MEM_MODEL_TSO) fails.
- On a CPU with runtime-configurable TSO support, PR_SET_MEM_MODEL can
  toggle the memory model between DEFAULT and TSO at will.
- On a CPU where the only memory model is at least as strict as TSO,
  PR_GET_MEM_MODEL will return PR_SET_MEM_MODEL_DEFAULT, and
  PR_SET_MEM_MODEL(PR_SET_MEM_MODEL_TSO) will return success but leave
  the memory model at PR_SET_MEM_MODEL_DEFAULT. This implies that the
  default is in fact at least as strict as TSO.

Signed-off-by: Hector Martin <marcan@marcan.st>
Reviewed-by: Neal Gompa <neal@gompa.dev>
---
 include/linux/memory_ordering_model.h | 11 +++++++++++
 include/uapi/linux/prctl.h            |  5 +++++
 kernel/sys.c                          | 21 +++++++++++++++++++++
 3 files changed, 37 insertions(+)
 create mode 100644 include/linux/memory_ordering_model.h

diff --git a/include/linux/memory_ordering_model.h b/include/linux/memory_ordering_model.h
new file mode 100644
index 00000000000000..267a12ca66307e
--- /dev/null
+++ b/include/linux/memory_ordering_model.h
@@ -0,0 +1,11 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef __ASM_MEMORY_ORDERING_MODEL_H
+#define __ASM_MEMORY_ORDERING_MODEL_H
+
+/* Arch hooks to implement the PR_{GET_SET}_MEM_MODEL prctls */
+
+struct task_struct;
+int arch_prctl_mem_model_get(struct task_struct *t);
+int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val);
+
+#endif
diff --git a/include/uapi/linux/prctl.h b/include/uapi/linux/prctl.h
index 35791791a879b2..36c278683cd620 100644
--- a/include/uapi/linux/prctl.h
+++ b/include/uapi/linux/prctl.h
@@ -328,4 +328,9 @@ struct prctl_mm_map {
 # define PR_PPC_DEXCR_CTRL_CLEAR_ONEXEC	0x10 /* Clear the aspect on exec */
 # define PR_PPC_DEXCR_CTRL_MASK		0x1f
 
+#define PR_GET_MEM_MODEL	0x6d4d444c
+#define PR_SET_MEM_MODEL	0x4d4d444c
+# define PR_SET_MEM_MODEL_DEFAULT	0
+# define PR_SET_MEM_MODEL_TSO		1
+
 #endif /* _LINUX_PRCTL_H */
diff --git a/kernel/sys.c b/kernel/sys.c
index 3a2df1bd9f640e..d28e2a8177db68 100644
--- a/kernel/sys.c
+++ b/kernel/sys.c
@@ -45,6 +45,7 @@
 #include <linux/version.h>
 #include <linux/ctype.h>
 #include <linux/syscall_user_dispatch.h>
+#include <linux/memory_ordering_model.h>
 
 #include <linux/compat.h>
 #include <linux/syscalls.h>
@@ -2454,6 +2455,16 @@ static int prctl_get_auxv(void __user *addr, unsigned long len)
 	return sizeof(mm->saved_auxv);
 }
 
+int __weak arch_prctl_mem_model_get(struct task_struct *t)
+{
+	return -EINVAL;
+}
+
+int __weak arch_prctl_mem_model_set(struct task_struct *t, unsigned long val)
+{
+	return -EINVAL;
+}
+
 SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 		unsigned long, arg4, unsigned long, arg5)
 {
@@ -2782,6 +2793,16 @@ SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3,
 	case PR_RISCV_SET_ICACHE_FLUSH_CTX:
 		error = RISCV_SET_ICACHE_FLUSH_CTX(arg2, arg3);
 		break;
+	case PR_GET_MEM_MODEL:
+		if (arg2 || arg3 || arg4 || arg5)
+			return -EINVAL;
+		error = arch_prctl_mem_model_get(me);
+		break;
+	case PR_SET_MEM_MODEL:
+		if (arg3 || arg4 || arg5)
+			return -EINVAL;
+		error = arch_prctl_mem_model_set(me, arg2);
+		break;
 	default:
 		error = -EINVAL;
 		break;

From 5a4fcf33a5c85e3a661a075591f1101bc119c21b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 11 Apr 2024 09:51:21 +0900
Subject: [PATCH 0489/1027] arm64: Implement PR_{GET,SET}_MEM_MODEL for
 always-TSO CPUs

Some ARM64 implementations are known to always use the TSO memory model.
Add trivial support for the PR_{GET,SET}_MEM_MODEL prctl, which allows
userspace to learn this fact.

Known TSO implementations:
- Nvidia Denver
- Nvidia Carmel
- Fujitsu A64FX

Signed-off-by: Hector Martin <marcan@marcan.st>
Reviewed-by: Neal Gompa <neal@gompa.dev>
---
 arch/arm64/Kconfig                    |  9 +++++++
 arch/arm64/include/asm/cpufeature.h   |  4 +++
 arch/arm64/kernel/Makefile            |  3 ++-
 arch/arm64/kernel/cpufeature.c        | 11 ++++----
 arch/arm64/kernel/cpufeature_impdef.c | 38 +++++++++++++++++++++++++++
 arch/arm64/kernel/process.c           | 24 +++++++++++++++++
 arch/arm64/tools/cpucaps              |  1 +
 7 files changed, 84 insertions(+), 6 deletions(-)
 create mode 100644 arch/arm64/kernel/cpufeature_impdef.c

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index a2f8ff354ca670..53552a56b689f7 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -2204,6 +2204,15 @@ config ARM64_DEBUG_PRIORITY_MASKING
 	  If unsure, say N
 endif # ARM64_PSEUDO_NMI
 
+config ARM64_MEMORY_MODEL_CONTROL
+	bool "Runtime memory model control"
+	help
+	  Some ARM64 CPUs support runtime switching of the CPU memory
+	  model, which can be useful to emulate other CPU architectures
+	  which have different memory models. Say Y to enable support
+	  for the PR_SET_MEM_MODEL/PR_GET_MEM_MODEL prctl() calls on
+	  CPUs with this feature.
+
 config RELOCATABLE
 	bool "Build a relocatable kernel image" if EXPERT
 	select ARCH_HAS_RELR
diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index 55843426727156..70401ae5472407 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -1032,6 +1032,10 @@ static inline bool cpu_has_lpa2(void)
 #endif
 }
 
+void __init init_cpucap_indirect_list_impdef(void);
+void __init init_cpucap_indirect_list_from_array(const struct arm64_cpu_capabilities *caps);
+bool cpufeature_matches(u64 reg, const struct arm64_cpu_capabilities *entry);
+
 #endif /* __ASSEMBLY__ */
 
 #endif
diff --git a/arch/arm64/kernel/Makefile b/arch/arm64/kernel/Makefile
index 2b112f3b75109a..2a11cdefbe041b 100644
--- a/arch/arm64/kernel/Makefile
+++ b/arch/arm64/kernel/Makefile
@@ -33,7 +33,8 @@ obj-y			:= debug-monitors.o entry.o irq.o fpsimd.o		\
 			   return_address.o cpuinfo.o cpu_errata.o		\
 			   cpufeature.o alternative.o cacheinfo.o		\
 			   smp.o smp_spin_table.o topology.o smccc-call.o	\
-			   syscall.o proton-pack.o idle.o patching.o pi/
+			   syscall.o proton-pack.o idle.o patching.o		\
+			   cpufeature_impdef.o pi/
 
 obj-$(CONFIG_COMPAT)			+= sys32.o signal32.o			\
 					   sys_compat.o
diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 646ecd3069fdd9..31134846ea5641 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -1029,7 +1029,7 @@ static void init_cpu_ftr_reg(u32 sys_reg, u64 new)
 extern const struct arm64_cpu_capabilities arm64_errata[];
 static const struct arm64_cpu_capabilities arm64_features[];
 
-static void __init
+void __init
 init_cpucap_indirect_list_from_array(const struct arm64_cpu_capabilities *caps)
 {
 	for (; caps->matches; caps++) {
@@ -1541,8 +1541,8 @@ has_always(const struct arm64_cpu_capabilities *entry, int scope)
 	return true;
 }
 
-static bool
-feature_matches(u64 reg, const struct arm64_cpu_capabilities *entry)
+bool
+cpufeature_matches(u64 reg, const struct arm64_cpu_capabilities *entry)
 {
 	int val, min, max;
 	u64 tmp;
@@ -1595,14 +1595,14 @@ has_user_cpuid_feature(const struct arm64_cpu_capabilities *entry, int scope)
 	if (!mask)
 		return false;
 
-	return feature_matches(val, entry);
+	return cpufeature_matches(val, entry);
 }
 
 static bool
 has_cpuid_feature(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	u64 val = read_scoped_sysreg(entry, scope);
-	return feature_matches(val, entry);
+	return cpufeature_matches(val, entry);
 }
 
 const struct cpumask *system_32bit_el0_cpumask(void)
@@ -3495,6 +3495,7 @@ void __init setup_boot_cpu_features(void)
 	 * handle the boot CPU.
 	 */
 	init_cpucap_indirect_list();
+	init_cpucap_indirect_list_impdef();
 
 	/*
 	 * Detect broken pseudo-NMI. Must be called _before_ the call to
diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
new file mode 100644
index 00000000000000..bb04a8e3d79d85
--- /dev/null
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -0,0 +1,38 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Contains implementation-defined CPU feature definitions.
+ */
+
+#include <asm/cpufeature.h>
+
+#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	/* List of CPUs that always use the TSO memory model */
+	static const struct midr_range fixed_tso_list[] = {
+		MIDR_ALL_VERSIONS(MIDR_NVIDIA_DENVER),
+		MIDR_ALL_VERSIONS(MIDR_NVIDIA_CARMEL),
+		MIDR_ALL_VERSIONS(MIDR_FUJITSU_A64FX),
+		{ /* sentinel */ }
+	};
+
+	return is_midr_in_range_list(read_cpuid_id(), fixed_tso_list);
+}
+#endif
+
+static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
+#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+	{
+		.desc = "TSO memory model (Fixed)",
+		.capability = ARM64_HAS_TSO_FIXED,
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.matches = has_tso_fixed,
+	},
+#endif
+	{},
+};
+
+void __init init_cpucap_indirect_list_impdef(void)
+{
+	init_cpucap_indirect_list_from_array(arm64_impdef_features);
+}
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 4ae31b7af6c311..7920056bad3ed1 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -41,6 +41,7 @@
 #include <linux/thread_info.h>
 #include <linux/prctl.h>
 #include <linux/stacktrace.h>
+#include <linux/memory_ordering_model.h>
 
 #include <asm/alternative.h>
 #include <asm/compat.h>
@@ -513,6 +514,25 @@ void update_sctlr_el1(u64 sctlr)
 	isb();
 }
 
+#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+int arch_prctl_mem_model_get(struct task_struct *t)
+{
+	return PR_SET_MEM_MODEL_DEFAULT;
+}
+
+int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val)
+{
+	if (alternative_has_cap_unlikely(ARM64_HAS_TSO_FIXED) &&
+	    val == PR_SET_MEM_MODEL_TSO)
+		return 0;
+
+	if (val == PR_SET_MEM_MODEL_DEFAULT)
+		return 0;
+
+	return -EINVAL;
+}
+#endif
+
 /*
  * Thread switching.
  */
@@ -651,6 +671,10 @@ void arch_setup_new_exec(void)
 		arch_prctl_spec_ctrl_set(current, PR_SPEC_STORE_BYPASS,
 					 PR_SPEC_ENABLE);
 	}
+
+#ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+	arch_prctl_mem_model_set(current, PR_SET_MEM_MODEL_DEFAULT);
+#endif
 }
 
 #ifdef CONFIG_ARM64_TAGGED_ADDR_ABI
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index ac3429d892b9a7..774b649c7a6bda 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -52,6 +52,7 @@ HAS_STAGE2_FWB
 HAS_TCR2
 HAS_TIDCP1
 HAS_TLB_RANGE
+HAS_TSO_FIXED
 HAS_VA52
 HAS_VIRT_HOST_EXTN
 HAS_WFXT

From a4b32e6821b603720942a6e90dab352e793befaa Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 11 Apr 2024 09:51:22 +0900
Subject: [PATCH 0490/1027] arm64: Introduce scaffolding to add ACTLR_EL1 to
 thread state

Some CPUs expose IMPDEF features in ACTLR_EL1 that can be meaningfully
controlled per-thread (like TSO control on Apple cores). Add the basic
scaffolding to save/restore this register as part of context switching.

This mechanism is disabled by default both by config symbol and via a
runtime check, which ensures it is never triggered unless the system is
known to need it for some feature (which also implies that the layout of
ACTLR_EL1 is uniform between all CPU core types).

Signed-off-by: Hector Martin <marcan@marcan.st>
Reviewed-by: Neal Gompa <neal@gompa.dev>
---
 arch/arm64/Kconfig                  |  3 +++
 arch/arm64/include/asm/cpufeature.h |  5 +++++
 arch/arm64/include/asm/processor.h  |  3 +++
 arch/arm64/kernel/process.c         | 25 +++++++++++++++++++++++++
 arch/arm64/kernel/setup.c           |  8 ++++++++
 5 files changed, 44 insertions(+)

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index 53552a56b689f7..dc7236a499adf5 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -412,6 +412,9 @@ config KASAN_SHADOW_OFFSET
 config UNWIND_TABLES
 	bool
 
+config ARM64_ACTLR_STATE
+	bool
+
 source "arch/arm64/Kconfig.platforms"
 
 menu "Kernel Features"
diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index 70401ae5472407..668ddddc9e8835 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -909,6 +909,11 @@ static inline unsigned int get_vmid_bits(u64 mmfr1)
 	return 8;
 }
 
+static __always_inline bool system_has_actlr_state(void)
+{
+	return false;
+}
+
 s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur);
 struct arm64_ftr_reg *get_arm64_ftr_reg(u32 sys_id);
 
diff --git a/arch/arm64/include/asm/processor.h b/arch/arm64/include/asm/processor.h
index f77371232d8c6d..d43c5791a35e22 100644
--- a/arch/arm64/include/asm/processor.h
+++ b/arch/arm64/include/asm/processor.h
@@ -184,6 +184,9 @@ struct thread_struct {
 	u64			sctlr_user;
 	u64			svcr;
 	u64			tpidr2_el0;
+#ifdef CONFIG_ARM64_ACTLR_STATE
+	u64			actlr;
+#endif
 };
 
 static inline unsigned int thread_get_vl(struct thread_struct *thread,
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 7920056bad3ed1..117f80e16aac87 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -372,6 +372,11 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
 		if (system_supports_tpidr2())
 			p->thread.tpidr2_el0 = read_sysreg_s(SYS_TPIDR2_EL0);
 
+#ifdef CONFIG_ARM64_ACTLR_STATE
+		if (system_has_actlr_state())
+			p->thread.actlr = read_sysreg(actlr_el1);
+#endif
+
 		if (stack_start) {
 			if (is_compat_thread(task_thread_info(p)))
 				childregs->compat_sp = stack_start;
@@ -533,6 +538,25 @@ int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val)
 }
 #endif
 
+#ifdef CONFIG_ARM64_ACTLR_STATE
+/*
+ * IMPDEF control register ACTLR_EL1 handling. Some CPUs use this to
+ * expose features that can be controlled by userspace.
+ */
+static void actlr_thread_switch(struct task_struct *next)
+{
+	if (!system_has_actlr_state())
+		return;
+
+	current->thread.actlr = read_sysreg(actlr_el1);
+	write_sysreg(next->thread.actlr, actlr_el1);
+}
+#else
+static inline void actlr_thread_switch(struct task_struct *next)
+{
+}
+#endif
+
 /*
  * Thread switching.
  */
@@ -550,6 +574,7 @@ struct task_struct *__switch_to(struct task_struct *prev,
 	ssbs_thread_switch(next);
 	erratum_1418040_thread_switch(next);
 	ptrauth_thread_switch_user(next);
+	actlr_thread_switch(next);
 
 	/*
 	 * Complete any pending TLB or cache maintenance on this CPU in case
diff --git a/arch/arm64/kernel/setup.c b/arch/arm64/kernel/setup.c
index b22d28ec80284b..df587e39bfc11c 100644
--- a/arch/arm64/kernel/setup.c
+++ b/arch/arm64/kernel/setup.c
@@ -363,6 +363,14 @@ void __init __no_sanitize_address setup_arch(char **cmdline_p)
 	 */
 	init_task.thread_info.ttbr0 = phys_to_ttbr(__pa_symbol(reserved_pg_dir));
 #endif
+#ifdef CONFIG_ARM64_ACTLR_STATE
+	/* Store the boot CPU ACTLR_EL1 value as the default. This will only
+	 * be actually restored during context switching iff the platform is
+	 * known to use ACTLR_EL1 for exposable features and its layout is
+	 * known to be the same on all CPUs.
+	 */
+	init_task.thread.actlr = read_sysreg(actlr_el1);
+#endif
 
 	if (boot_args[1] || boot_args[2] || boot_args[3]) {
 		pr_err("WARNING: x1-x3 nonzero in violation of boot protocol:\n"

From c2de2bb120d9e995d279a6e47bfbb995a0d51a44 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 11 Apr 2024 09:51:23 +0900
Subject: [PATCH 0491/1027] arm64: Implement Apple IMPDEF TSO memory model
 control

Apple CPUs may implement the TSO memory model as an optional
configurable mode. This allows x86 emulators to simplify their
load/store handling, greatly increasing performance.

Expose this via the prctl PR_SET_MEM_MODEL_TSO mechanism. We use the
Apple IMPDEF AIDR_EL1 register to check for the availability of TSO
mode, and enable this codepath on all CPUs with an Apple implementer.

This relies on the ACTLR_EL1 thread state scaffolding introduced
earlier.

Signed-off-by: Hector Martin <marcan@marcan.st>
Reviewed-by: Neal Gompa <neal@gompa.dev>
---
 arch/arm64/Kconfig                        |  2 ++
 arch/arm64/include/asm/apple_cpufeature.h | 15 +++++++++++++++
 arch/arm64/include/asm/cpufeature.h       |  3 ++-
 arch/arm64/kernel/cpufeature_impdef.c     | 23 +++++++++++++++++++++++
 arch/arm64/kernel/process.c               | 22 ++++++++++++++++++++++
 arch/arm64/tools/cpucaps                  |  1 +
 6 files changed, 65 insertions(+), 1 deletion(-)
 create mode 100644 arch/arm64/include/asm/apple_cpufeature.h

diff --git a/arch/arm64/Kconfig b/arch/arm64/Kconfig
index dc7236a499adf5..69e635a32ba708 100644
--- a/arch/arm64/Kconfig
+++ b/arch/arm64/Kconfig
@@ -2209,6 +2209,8 @@ endif # ARM64_PSEUDO_NMI
 
 config ARM64_MEMORY_MODEL_CONTROL
 	bool "Runtime memory model control"
+	default ARCH_APPLE
+	select ARM64_ACTLR_STATE
 	help
 	  Some ARM64 CPUs support runtime switching of the CPU memory
 	  model, which can be useful to emulate other CPU architectures
diff --git a/arch/arm64/include/asm/apple_cpufeature.h b/arch/arm64/include/asm/apple_cpufeature.h
new file mode 100644
index 00000000000000..4370d91ffa3ec9
--- /dev/null
+++ b/arch/arm64/include/asm/apple_cpufeature.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#ifndef __ASM_APPLE_CPUFEATURES_H
+#define __ASM_APPLE_CPUFEATURES_H
+
+#include <linux/bits.h>
+#include <asm/sysreg.h>
+
+#define AIDR_APPLE_TSO_SHIFT	9
+#define AIDR_APPLE_TSO		BIT(9)
+
+#define ACTLR_APPLE_TSO_SHIFT	1
+#define ACTLR_APPLE_TSO		BIT(1)
+
+#endif
diff --git a/arch/arm64/include/asm/cpufeature.h b/arch/arm64/include/asm/cpufeature.h
index 668ddddc9e8835..c2cd79de92f388 100644
--- a/arch/arm64/include/asm/cpufeature.h
+++ b/arch/arm64/include/asm/cpufeature.h
@@ -911,7 +911,8 @@ static inline unsigned int get_vmid_bits(u64 mmfr1)
 
 static __always_inline bool system_has_actlr_state(void)
 {
-	return false;
+	return IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
+		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE);
 }
 
 s64 arm64_ftr_safe_value(const struct arm64_ftr_bits *ftrp, s64 new, s64 cur);
diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
index bb04a8e3d79d85..9325d1eb12f402 100644
--- a/arch/arm64/kernel/cpufeature_impdef.c
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -4,8 +4,21 @@
  */
 
 #include <asm/cpufeature.h>
+#include <asm/apple_cpufeature.h>
 
 #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+static bool has_apple_feature(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	u64 val;
+	WARN_ON(scope != SCOPE_SYSTEM);
+
+	if (read_cpuid_implementor() != ARM_CPU_IMP_APPLE)
+		return false;
+
+	val = read_sysreg(aidr_el1);
+	return cpufeature_matches(val, entry);
+}
+
 static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	/* List of CPUs that always use the TSO memory model */
@@ -22,6 +35,16 @@ static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope)
 
 static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
+	{
+		.desc = "TSO memory model (Apple)",
+		.capability = ARM64_HAS_TSO_APPLE,
+		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.matches = has_apple_feature,
+		.field_pos = AIDR_APPLE_TSO_SHIFT,
+		.field_width = 1,
+		.sign = FTR_UNSIGNED,
+		.min_field_value = 1,
+	},
 	{
 		.desc = "TSO memory model (Fixed)",
 		.capability = ARM64_HAS_TSO_FIXED,
diff --git a/arch/arm64/kernel/process.c b/arch/arm64/kernel/process.c
index 117f80e16aac87..34a19ecfb630ba 100644
--- a/arch/arm64/kernel/process.c
+++ b/arch/arm64/kernel/process.c
@@ -44,6 +44,7 @@
 #include <linux/memory_ordering_model.h>
 
 #include <asm/alternative.h>
+#include <asm/apple_cpufeature.h>
 #include <asm/compat.h>
 #include <asm/cpufeature.h>
 #include <asm/cacheflush.h>
@@ -522,6 +523,10 @@ void update_sctlr_el1(u64 sctlr)
 #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
 int arch_prctl_mem_model_get(struct task_struct *t)
 {
+	if (alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE) &&
+		t->thread.actlr & ACTLR_APPLE_TSO)
+		return PR_SET_MEM_MODEL_TSO;
+
 	return PR_SET_MEM_MODEL_DEFAULT;
 }
 
@@ -531,6 +536,23 @@ int arch_prctl_mem_model_set(struct task_struct *t, unsigned long val)
 	    val == PR_SET_MEM_MODEL_TSO)
 		return 0;
 
+	if (alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE)) {
+		WARN_ON(!system_has_actlr_state());
+
+		switch (val) {
+		case PR_SET_MEM_MODEL_TSO:
+			t->thread.actlr |= ACTLR_APPLE_TSO;
+			break;
+		case PR_SET_MEM_MODEL_DEFAULT:
+			t->thread.actlr &= ~ACTLR_APPLE_TSO;
+			break;
+		default:
+			return -EINVAL;
+		}
+		write_sysreg(t->thread.actlr, actlr_el1);
+		return 0;
+	}
+
 	if (val == PR_SET_MEM_MODEL_DEFAULT)
 		return 0;
 
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index 774b649c7a6bda..a913c0aed32fdf 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -52,6 +52,7 @@ HAS_STAGE2_FWB
 HAS_TCR2
 HAS_TIDCP1
 HAS_TLB_RANGE
+HAS_TSO_APPLE
 HAS_TSO_FIXED
 HAS_VA52
 HAS_VIRT_HOST_EXTN

From f78a46da78fff2dcdaedd2618dbe974286ce73b4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 25 May 2024 20:22:29 +0900
Subject: [PATCH 0492/1027] KVM: arm64: Expose TSO capability to guests and
 context switch

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/include/asm/kvm_emulate.h       | 3 +++
 arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h | 9 +++++++++
 2 files changed, 12 insertions(+)

diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index a601a9305b104f..691559b3331eb6 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -80,6 +80,9 @@ static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu)
 {
 	if (!vcpu_has_run_once(vcpu))
 		vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS;
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
+		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
+		vcpu->arch.hcr_el2 &= ~HCR_TACR;
 
 	/*
 	 * For non-FWB CPUs, we trap VM ops (HCR_EL2.TVM) until M+C
diff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
index 4c0fdabaf8aec1..ff4295af8ff05c 100644
--- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
+++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
@@ -16,6 +16,8 @@
 #include <asm/kvm_hyp.h>
 #include <asm/kvm_mmu.h>
 
+#define SYS_IMP_APL_ACTLR_EL12		sys_reg(3, 6, 15, 14, 6)
+
 static inline void __sysreg_save_common_state(struct kvm_cpu_context *ctxt)
 {
 	ctxt_sys_reg(ctxt, MDSCR_EL1)	= read_sysreg(mdscr_el1);
@@ -101,6 +103,9 @@ static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt)
 	ctxt_sys_reg(ctxt, SP_EL1)	= read_sysreg(sp_el1);
 	ctxt_sys_reg(ctxt, ELR_EL1)	= read_sysreg_el1(SYS_ELR);
 	ctxt_sys_reg(ctxt, SPSR_EL1)	= read_sysreg_el1(SYS_SPSR);
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
+		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
+		ctxt_sys_reg(ctxt, ACTLR_EL1)	= read_sysreg_s(SYS_IMP_APL_ACTLR_EL12);
 }
 
 static inline void __sysreg_save_el2_return_state(struct kvm_cpu_context *ctxt)
@@ -171,6 +176,10 @@ static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt)
 	write_sysreg(ctxt_sys_reg(ctxt, PAR_EL1),	par_el1);
 	write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL1),	tpidr_el1);
 
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
+		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
+		write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1),	SYS_IMP_APL_ACTLR_EL12);
+
 	if (ctxt_has_mte(ctxt)) {
 		write_sysreg_el1(ctxt_sys_reg(ctxt, TFSR_EL1), SYS_TFSR);
 		write_sysreg_s(ctxt_sys_reg(ctxt, TFSRE0_EL1), SYS_TFSRE0_EL1);

From f8823db6667af021b37904a81ce80072c5ef906d Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Fri, 27 Aug 2021 11:19:51 -0400
Subject: [PATCH 0493/1027] WIP: drm/apple: Add DCP display driver

Add a DRM/KMS driver for Apple system on chips using the DCP
coprocessor, namely the Apple M1. The DCP was added in Apple A14; this
driver does not apply to older iDevices.

This driver targets the DCP firmware API shipped by macOS 12.1.
Currently no incompatibilities with macOS 12.0.1 or 12.2.1 are known.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 MAINTAINERS                          |    7 +
 drivers/gpu/drm/Kconfig              |    2 +
 drivers/gpu/drm/Makefile             |    1 +
 drivers/gpu/drm/apple/Kconfig        |   11 +
 drivers/gpu/drm/apple/Makefile       |    9 +
 drivers/gpu/drm/apple/apple_drv.c    |  441 ++++++++
 drivers/gpu/drm/apple/dcp.c          | 1393 ++++++++++++++++++++++++++
 drivers/gpu/drm/apple/dcp.h          |   44 +
 drivers/gpu/drm/apple/dcpep.h        |  406 ++++++++
 drivers/gpu/drm/apple/dummy-piodma.c |   31 +
 drivers/gpu/drm/apple/parser.c       |  451 +++++++++
 drivers/gpu/drm/apple/parser.h       |   32 +
 12 files changed, 2828 insertions(+)
 create mode 100644 drivers/gpu/drm/apple/Kconfig
 create mode 100644 drivers/gpu/drm/apple/Makefile
 create mode 100644 drivers/gpu/drm/apple/apple_drv.c
 create mode 100644 drivers/gpu/drm/apple/dcp.c
 create mode 100644 drivers/gpu/drm/apple/dcp.h
 create mode 100644 drivers/gpu/drm/apple/dcpep.h
 create mode 100644 drivers/gpu/drm/apple/dummy-piodma.c
 create mode 100644 drivers/gpu/drm/apple/parser.c
 create mode 100644 drivers/gpu/drm/apple/parser.h

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd10..2267400810fe84 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1555,6 +1555,13 @@ L:	linux-input@vger.kernel.org
 S:	Odd fixes
 F:	drivers/input/mouse/bcm5974.c
 
+APPLE DRM DISPLAY DRIVER
+M:	Janne Grunau <j@jannau.net>
+L:	dri-devel@lists.freedesktop.org
+S:	Maintained
+T:	git git://anongit.freedesktop.org/drm/drm-misc
+F:	drivers/gpu/drm/apple/
+
 APPLE PCIE CONTROLLER DRIVER
 M:	Alyssa Rosenzweig <alyssa@rosenzweig.io>
 M:	Marc Zyngier <maz@kernel.org>
diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 6b2c6b91f96250..1054b39b024f55 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -436,6 +436,8 @@ source "drivers/gpu/drm/solomon/Kconfig"
 
 source "drivers/gpu/drm/sprd/Kconfig"
 
+source "drivers/gpu/drm/apple/Kconfig"
+
 source "drivers/gpu/drm/imagination/Kconfig"
 
 config DRM_HYPERV
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index fa432a1ac9e2b7..80bd53aabd15e3 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -208,6 +208,7 @@ obj-$(CONFIG_DRM_VBOXVIDEO) += vboxvideo/
 obj-$(CONFIG_DRM_LIMA)  += lima/
 obj-$(CONFIG_DRM_PANFROST) += panfrost/
 obj-$(CONFIG_DRM_PANTHOR) += panthor/
+obj-$(CONFIG_DRM_APPLE) += apple/
 obj-$(CONFIG_DRM_ASPEED_GFX) += aspeed/
 obj-$(CONFIG_DRM_MCDE) += mcde/
 obj-$(CONFIG_DRM_TIDSS) += tidss/
diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
new file mode 100644
index 00000000000000..53c7d4edfd017e
--- /dev/null
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config DRM_APPLE
+	tristate "DRM Support for Apple display controllers"
+	depends on DRM && OF && ARM64
+	depends on ARCH_APPLE || COMPILE_TEST
+	select DRM_KMS_HELPER
+	select DRM_KMS_DMA_HELPER
+	select DRM_GEM_DMA_HELPER
+	select VIDEOMODE_HELPERS
+	help
+	  Say Y if you have an Apple Silicon chipset.
diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
new file mode 100644
index 00000000000000..db7ef359987d3d
--- /dev/null
+++ b/drivers/gpu/drm/apple/Makefile
@@ -0,0 +1,9 @@
+# SPDX-License-Identifier: GPL-2.0-only
+
+appledrm-y := apple_drv.o
+apple_dcp-y := dcp.o parser.o
+apple_piodma-y := dummy-piodma.o
+
+obj-$(CONFIG_DRM_APPLE) += appledrm.o
+obj-$(CONFIG_DRM_APPLE) += apple_dcp.o
+obj-$(CONFIG_DRM_APPLE) += apple_piodma.o
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
new file mode 100644
index 00000000000000..38750adc4c6bd1
--- /dev/null
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -0,0 +1,441 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+/* Based on meson driver which is
+ * Copyright (C) 2016 BayLibre, SAS
+ * Author: Neil Armstrong <narmstrong@baylibre.com>
+ * Copyright (C) 2015 Amlogic, Inc. All rights reserved.
+ * Copyright (C) 2014 Endless Mobile
+ */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_device.h>
+#include <linux/of_platform.h>
+
+#include <drm/drm_aperture.h>
+#include <drm/drm_atomic.h>
+#include <drm/drm_atomic_helper.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_fb_helper.h>
+#include <drm/drm_fbdev_dma.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_gem_framebuffer_helper.h>
+#include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_modeset_helper.h>
+#include <drm/drm_of.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+#include <drm/drm_fixed.h>
+
+#include "dcp.h"
+
+#define DRIVER_NAME     "apple"
+#define DRIVER_DESC     "Apple display controller DRM driver"
+
+#define FRAC_16_16(mult, div)    (((mult) << 16) / (div))
+
+#define MAX_COPROCESSORS 2
+
+struct apple_drm_private {
+	struct drm_device drm;
+};
+
+DEFINE_DRM_GEM_DMA_FOPS(apple_fops);
+
+static const struct drm_driver apple_drm_driver = {
+	DRM_GEM_DMA_DRIVER_OPS,
+	.name			= DRIVER_NAME,
+	.desc			= DRIVER_DESC,
+	.date			= "20210901",
+	.major			= 1,
+	.minor			= 0,
+	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,
+	.fops			= &apple_fops,
+};
+
+static int apple_plane_atomic_check(struct drm_plane *plane,
+				    struct drm_atomic_state *state)
+{
+	struct drm_plane_state *new_plane_state;
+	struct drm_crtc_state *crtc_state;
+
+	new_plane_state = drm_atomic_get_new_plane_state(state, plane);
+
+	if (!new_plane_state->crtc)
+		return 0;
+
+	crtc_state = drm_atomic_get_crtc_state(state, new_plane_state->crtc);
+	if (IS_ERR(crtc_state))
+		return PTR_ERR(crtc_state);
+
+	/*
+	 * DCP limits downscaling to 2x and upscaling to 4x. Attempting to
+	 * scale outside these bounds errors out when swapping.
+	 *
+	 * This function also takes care of clipping the src/dest rectangles,
+	 * which is required for correct operation. Partially off-screen
+	 * surfaces may appear corrupted.
+	 *
+	 * DCP does not distinguish plane types in the hardware, so we set
+	 * can_position. If the primary plane does not fill the screen, the
+	 * hardware will fill in zeroes (black).
+	 */
+	return drm_atomic_helper_check_plane_state(new_plane_state,
+						   crtc_state,
+						   FRAC_16_16(1, 4),
+						   FRAC_16_16(2, 1),
+						   true, true);
+}
+
+static void apple_plane_atomic_update(struct drm_plane *plane,
+				      struct drm_atomic_state *state)
+{
+	/* Handled in atomic_flush */
+}
+
+static const struct drm_plane_helper_funcs apple_plane_helper_funcs = {
+	.atomic_check	= apple_plane_atomic_check,
+	.atomic_update	= apple_plane_atomic_update,
+};
+
+static const struct drm_plane_funcs apple_plane_funcs = {
+	.update_plane		= drm_atomic_helper_update_plane,
+	.disable_plane		= drm_atomic_helper_disable_plane,
+	.destroy		= drm_plane_cleanup,
+	.reset			= drm_atomic_helper_plane_reset,
+	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
+};
+
+/*
+ * Table of supported formats, mapping from DRM fourccs to DCP fourccs.
+ *
+ * For future work, DCP supports more formats not listed, including YUV
+ * formats, an extra RGBA format, and a biplanar RGB10_A8 format (fourcc b3a8)
+ * used for HDR.
+ *
+ * Note: we don't have non-alpha formats but userspace breaks without XRGB. It
+ * doesn't matter for the primary plane, but cursors/overlays must not
+ * advertise formats without alpha.
+ */
+static const u32 dcp_formats[] = {
+	DRM_FORMAT_XRGB8888,
+	DRM_FORMAT_ARGB8888,
+	DRM_FORMAT_XBGR8888,
+	DRM_FORMAT_ABGR8888,
+};
+
+u64 apple_format_modifiers[] = {
+	DRM_FORMAT_MOD_LINEAR,
+	DRM_FORMAT_MOD_INVALID
+};
+
+static struct drm_plane *apple_plane_init(struct drm_device *dev,
+					  enum drm_plane_type type)
+{
+	int ret;
+	struct drm_plane *plane;
+
+	plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
+
+	ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs,
+				       dcp_formats, ARRAY_SIZE(dcp_formats),
+				       apple_format_modifiers, type, NULL);
+	if (ret)
+		return ERR_PTR(ret);
+
+	drm_plane_helper_add(plane, &apple_plane_helper_funcs);
+
+	return plane;
+}
+
+static int apple_enable_vblank(struct drm_crtc *crtc)
+{
+	to_apple_crtc(crtc)->vsync_disabled = false;
+
+	return 0;
+}
+
+static void apple_disable_vblank(struct drm_crtc *crtc)
+{
+	to_apple_crtc(crtc)->vsync_disabled = true;
+}
+
+static enum drm_connector_status
+apple_connector_detect(struct drm_connector *connector, bool force)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+
+	return apple_connector->connected ? connector_status_connected :
+						  connector_status_disconnected;
+}
+
+static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
+				     struct drm_atomic_state *state)
+{
+	drm_crtc_vblank_on(crtc);
+}
+
+static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
+				      struct drm_atomic_state *state)
+{
+	drm_crtc_vblank_off(crtc);
+
+	if (crtc->state->event && !crtc->state->active) {
+		spin_lock_irq(&crtc->dev->event_lock);
+		drm_crtc_send_vblank_event(crtc, crtc->state->event);
+		spin_unlock_irq(&crtc->dev->event_lock);
+
+		crtc->state->event = NULL;
+	}
+}
+
+static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
+				    struct drm_atomic_state *state)
+{
+	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+	unsigned long flags;
+
+	if (crtc->state->event) {
+		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
+
+		spin_lock_irqsave(&crtc->dev->event_lock, flags);
+		apple_crtc->event = crtc->state->event;
+		spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
+		crtc->state->event = NULL;
+	}
+}
+
+void apple_crtc_vblank(struct apple_crtc *crtc)
+{
+	unsigned long flags;
+
+	if (crtc->vsync_disabled)
+		return;
+
+	drm_crtc_handle_vblank(&crtc->base);
+
+	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
+	if (crtc->event) {
+		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
+		drm_crtc_vblank_put(&crtc->base);
+		crtc->event = NULL;
+	}
+	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
+}
+
+static const struct drm_crtc_funcs apple_crtc_funcs = {
+	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
+	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
+	.destroy		= drm_crtc_cleanup,
+	.page_flip		= drm_atomic_helper_page_flip,
+	.reset			= drm_atomic_helper_crtc_reset,
+	.set_config             = drm_atomic_helper_set_config,
+	.enable_vblank		= apple_enable_vblank,
+	.disable_vblank		= apple_disable_vblank,
+};
+
+static const struct drm_mode_config_funcs apple_mode_config_funcs = {
+	.atomic_check		= drm_atomic_helper_check,
+	.atomic_commit		= drm_atomic_helper_commit,
+	.fb_create		= drm_gem_fb_create,
+};
+
+static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = {
+	.atomic_commit_tail	= drm_atomic_helper_commit_tail_rpm,
+};
+
+static const struct drm_connector_funcs apple_connector_funcs = {
+	.fill_modes		= drm_helper_probe_single_connector_modes,
+	.destroy		= drm_connector_cleanup,
+	.reset			= drm_atomic_helper_connector_reset,
+	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
+	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
+	.detect			= apple_connector_detect,
+};
+
+static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
+	.get_modes		= dcp_get_modes,
+	.mode_valid		= dcp_mode_valid,
+};
+
+static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
+	.atomic_begin		= apple_crtc_atomic_begin,
+	.atomic_flush		= dcp_flush,
+	.atomic_enable		= apple_crtc_atomic_enable,
+	.atomic_disable		= apple_crtc_atomic_disable,
+};
+
+static int apple_probe_per_dcp(struct device *dev,
+			       struct drm_device *drm,
+			       struct platform_device *dcp)
+{
+	struct apple_crtc *crtc;
+	struct apple_connector *connector;
+	struct drm_encoder *encoder;
+	struct drm_plane *primary;
+	int ret;
+
+	primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
+
+	if (IS_ERR(primary))
+		return PTR_ERR(primary);
+
+	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+	ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL,
+					&apple_crtc_funcs, NULL);
+	if (ret)
+		return ret;
+
+	drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs);
+
+	encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
+	encoder->possible_crtcs = drm_crtc_mask(&crtc->base);
+	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
+	if (ret)
+		return ret;
+
+	connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
+	drm_connector_helper_add(&connector->base,
+				 &apple_connector_helper_funcs);
+
+	ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs,
+				 DRM_MODE_CONNECTOR_HDMIA);
+	if (ret)
+		return ret;
+
+	connector->base.polled = DRM_CONNECTOR_POLL_HPD;
+	connector->connected = true; /* XXX */
+	connector->dcp = dcp;
+
+	INIT_WORK(&connector->hotplug_wq, dcp_hotplug);
+
+	crtc->dcp = dcp;
+	dcp_link(dcp, crtc, connector);
+
+	return drm_connector_attach_encoder(&connector->base, encoder);
+}
+
+static int apple_platform_probe(struct platform_device *pdev)
+{
+	struct apple_drm_private *apple;
+	struct platform_device *dcp[MAX_COPROCESSORS];
+	int ret, nr_dcp, i;
+
+	for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) {
+		struct device_node *np;
+
+		np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors",
+				      nr_dcp);
+
+		if (!np)
+			break;
+
+		dcp[nr_dcp] = of_find_device_by_node(np);
+
+		if (!dcp[nr_dcp])
+			return -ENODEV;
+
+		/* DCP needs to be initialized before KMS can come online */
+		if (!platform_get_drvdata(dcp[nr_dcp]))
+			return -EPROBE_DEFER;
+
+		if (!dcp_is_initialized(dcp[nr_dcp]))
+			return -EPROBE_DEFER;
+	}
+
+	/* Need at least 1 DCP for a display subsystem */
+	if (nr_dcp < 1)
+		return -ENODEV;
+
+	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	if (ret)
+		return ret;
+
+	apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver,
+				   struct apple_drm_private, drm);
+	if (IS_ERR(apple))
+		return PTR_ERR(apple);
+
+	ret = drm_vblank_init(&apple->drm, 1);
+	if (ret)
+		return ret;
+
+	ret = drmm_mode_config_init(&apple->drm);
+	if (ret)
+		goto err_unload;
+
+	/*
+	 * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane
+	 * requires a minimum of 32x32 for the source buffer" if smaller
+	 */
+	apple->drm.mode_config.min_width = 32;
+	apple->drm.mode_config.min_height = 32;
+
+	/* Unknown maximum, use the iMac (24-inch, 2021) display resolution as
+	 * maximum.
+	 */
+	apple->drm.mode_config.max_width = 4480;
+	apple->drm.mode_config.max_height = 2520;
+
+	apple->drm.mode_config.funcs = &apple_mode_config_funcs;
+	apple->drm.mode_config.helper_private = &apple_mode_config_helpers;
+
+	for (i = 0; i < nr_dcp; ++i) {
+		ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]);
+
+		if (ret)
+			goto err_unload;
+	}
+
+	drm_mode_config_reset(&apple->drm);
+
+	ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver);
+	if (ret)
+		return ret;
+
+	ret = drm_dev_register(&apple->drm, 0);
+	if (ret)
+		goto err_unload;
+
+	drm_fbdev_dma_setup(&apple->drm, 32);
+
+	return 0;
+
+err_unload:
+	drm_dev_put(&apple->drm);
+	return ret;
+}
+
+static int apple_platform_remove(struct platform_device *pdev)
+{
+	struct drm_device *drm = platform_get_drvdata(pdev);
+
+	drm_dev_unregister(drm);
+
+	return 0;
+}
+
+static const struct of_device_id of_match[] = {
+	{ .compatible = "apple,display-subsystem" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_match);
+
+static struct platform_driver apple_platform_driver = {
+	.driver	= {
+		.name = "apple-drm",
+		.of_match_table	= of_match,
+	},
+	.probe		= apple_platform_probe,
+	.remove		= apple_platform_remove,
+};
+
+module_platform_driver(apple_platform_driver);
+
+MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
new file mode 100644
index 00000000000000..d43ed9021bd64c
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -0,0 +1,1393 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/align.h>
+#include <linux/soc/apple/rtkit.h>
+
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_probe_helper.h>
+
+#include "dcpep.h"
+#include "dcp.h"
+#include "parser.h"
+
+struct apple_dcp;
+
+/* Register defines used in bandwidth setup structure */
+#define REG_SCRATCH (0x14)
+#define REG_DOORBELL (0x0)
+#define REG_DOORBELL_BIT (2)
+
+#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000)
+
+/* Limit on call stack depth (arbitrary). Some nesting is required */
+#define DCP_MAX_CALL_DEPTH 8
+
+typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
+
+struct dcp_call_channel {
+	dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH];
+	void *cookies[DCP_MAX_CALL_DEPTH];
+	void *output[DCP_MAX_CALL_DEPTH];
+	u16 end[DCP_MAX_CALL_DEPTH];
+
+	/* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */
+	u8 depth;
+};
+
+struct dcp_cb_channel {
+	u8 depth;
+	void *output[DCP_MAX_CALL_DEPTH];
+};
+
+/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */
+struct dcp_chunks {
+	size_t length;
+	void *data;
+};
+
+#define DCP_MAX_MAPPINGS (128) /* should be enough */
+#define MAX_DISP_REGISTERS (7)
+
+struct apple_dcp {
+	struct device *dev;
+	struct platform_device *piodma;
+	struct apple_rtkit *rtk;
+	struct apple_crtc *crtc;
+	struct apple_connector *connector;
+
+	/* DCP has crashed */
+	bool crashed;
+
+	/* DCP shared memory */
+	void *shmem;
+
+	/* Display registers mappable to the DCP */
+	struct resource *disp_registers[MAX_DISP_REGISTERS];
+	unsigned int nr_disp_registers;
+
+	/* Number of memory mappings made by the DCP, used as an ID */
+	u32 nr_mappings;
+
+	/* Indexed table of mappings */
+	struct sg_table mappings[DCP_MAX_MAPPINGS];
+
+	struct dcp_call_channel ch_cmd, ch_oobcmd;
+	struct dcp_cb_channel ch_cb, ch_oobcb, ch_async;
+
+	/* Active chunked transfer. There can only be one at a time. */
+	struct dcp_chunks chunks;
+
+	/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
+	struct dcp_swap_submit_req swap;
+
+	/* Current display mode */
+	bool valid_mode;
+	struct dcp_set_digital_out_mode_req mode;
+
+	/* Is the DCP booted? */
+	bool active;
+
+	/* Modes valid for the connected display */
+	struct dcp_display_mode *modes;
+	unsigned int nr_modes;
+
+	/* Attributes of the connected display */
+	int width_mm, height_mm;
+};
+
+/*
+ * A channel is busy if we have sent a message that has yet to be
+ * acked. The driver must not sent a message to a busy channel.
+ */
+static bool dcp_channel_busy(struct dcp_call_channel *ch)
+{
+	return (ch->depth != 0);
+}
+
+/* Get a call channel for a context */
+static struct dcp_call_channel *
+dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context)
+{
+	switch (context) {
+	case DCP_CONTEXT_CMD:
+	case DCP_CONTEXT_CB:
+		return &dcp->ch_cmd;
+	case DCP_CONTEXT_OOBCMD:
+	case DCP_CONTEXT_OOBCB:
+		return &dcp->ch_oobcmd;
+	default:
+		return NULL;
+	}
+}
+
+/*
+ * Get the context ID passed to the DCP for a command we push. The rule is
+ * simple: callback contexts are used when replying to the DCP, command
+ * contexts are used otherwise. That corresponds to a non/zero call stack
+ * depth. This rule frees the caller from tracking the call context manually.
+ */
+static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
+{
+	u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth;
+
+	if (depth)
+		return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB;
+	else
+		return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
+}
+
+/* Get a callback channel for a context */
+static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp,
+						 enum dcp_context_id context)
+{
+	switch (context) {
+	case DCP_CONTEXT_CB:
+		return &dcp->ch_cb;
+	case DCP_CONTEXT_OOBCB:
+		return &dcp->ch_oobcb;
+	case DCP_CONTEXT_ASYNC:
+		return &dcp->ch_async;
+	default:
+		return NULL;
+	}
+}
+
+/* Get the start of a packet: after the end of the previous packet */
+static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth)
+{
+	if (depth > 0)
+		return ch->end[depth - 1];
+	else
+		return 0;
+}
+
+/* Pushes and pops the depth of the call stack with safety checks */
+static u8 dcp_push_depth(u8 *depth)
+{
+	u8 ret = (*depth)++;
+
+	WARN_ON(ret >= DCP_MAX_CALL_DEPTH);
+	return ret;
+}
+
+static u8 dcp_pop_depth(u8 *depth)
+{
+	WARN_ON((*depth) == 0);
+
+	return --(*depth);
+}
+
+#define DCP_METHOD(tag, name) [name] = { #name, tag }
+
+const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
+	DCP_METHOD("A000", dcpep_late_init_signal),
+	DCP_METHOD("A029", dcpep_setup_video_limits),
+	DCP_METHOD("A034", dcpep_update_notify_clients_dcp),
+	DCP_METHOD("A357", dcpep_set_create_dfb),
+	DCP_METHOD("A401", dcpep_start_signal),
+	DCP_METHOD("A407", dcpep_swap_start),
+	DCP_METHOD("A408", dcpep_swap_submit),
+	DCP_METHOD("A410", dcpep_set_display_device),
+	DCP_METHOD("A412", dcpep_set_digital_out_mode),
+	DCP_METHOD("A443", dcpep_create_default_fb),
+	DCP_METHOD("A454", dcpep_first_client_open),
+	DCP_METHOD("A460", dcpep_set_display_refresh_properties),
+	DCP_METHOD("A463", dcpep_flush_supports_power),
+	DCP_METHOD("A468", dcpep_set_power_state),
+};
+
+/* Call a DCP function given by a tag */
+static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
+		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
+		     void *cookie)
+{
+	struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd;
+	enum dcp_context_id context = dcp_call_context(dcp, oob);
+
+	struct dcp_packet_header header = {
+		.in_len = in_len,
+		.out_len = out_len,
+
+		/* Tag is reversed due to endianness of the fourcc */
+		.tag[0] = dcp_methods[method].tag[3],
+		.tag[1] = dcp_methods[method].tag[2],
+		.tag[2] = dcp_methods[method].tag[1],
+		.tag[3] = dcp_methods[method].tag[0],
+	};
+
+	u8 depth = dcp_push_depth(&ch->depth);
+	u16 offset = dcp_packet_start(ch, depth);
+
+	void *out = dcp->shmem + dcp_tx_offset(context) + offset;
+	void *out_data = out + sizeof(header);
+	size_t data_len = sizeof(header) + in_len + out_len;
+
+	memcpy(out, &header, sizeof(header));
+
+	if (in_len > 0)
+		memcpy(out_data, data, in_len);
+
+	dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n",
+		dcp_methods[method].name, context, offset, depth);
+
+	ch->callbacks[depth] = cb;
+	ch->cookies[depth] = cookie;
+	ch->output[depth] = out + sizeof(header) + in_len;
+	ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
+
+	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT,
+				 dcpep_msg(context, data_len, offset),
+				 NULL, false);
+}
+
+#define DCP_THUNK_VOID(func, handle)                                           \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb,   \
+			 void *cookie)                                         \
+	{                                                                      \
+		dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie);            \
+	}
+
+#define DCP_THUNK_OUT(func, handle, T)                                         \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb,   \
+			 void *cookie)                                         \
+	{                                                                      \
+		dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie);    \
+	}
+
+#define DCP_THUNK_IN(func, handle, T)                                          \
+	static void func(struct apple_dcp *dcp, bool oob, T *data,             \
+			 dcp_callback_t cb, void *cookie)                      \
+	{                                                                      \
+		dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie);    \
+	}
+
+#define DCP_THUNK_INOUT(func, handle, T_in, T_out)                             \
+	static void func(struct apple_dcp *dcp, bool oob, T_in *data,          \
+			 dcp_callback_t cb, void *cookie)                      \
+	{                                                                      \
+		dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data,  \
+			 cb, cookie);                                          \
+	}
+
+DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
+		struct dcp_swap_submit_resp);
+
+DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
+		struct dcp_swap_start_resp);
+
+DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
+		struct dcp_set_power_state_req,
+		struct dcp_set_power_state_resp);
+
+DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
+		struct dcp_set_digital_out_mode_req, u32);
+
+DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
+
+DCP_THUNK_OUT(dcp_set_display_refresh_properties,
+	      dcpep_set_display_refresh_properties, u32);
+
+DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
+DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
+DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
+DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
+DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
+DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
+DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
+
+__attribute__((unused))
+DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp,
+	     struct dcp_update_notify_clients_dcp);
+
+/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
+static int dcp_parse_tag(char tag[4])
+{
+	u32 d[3];
+	int i;
+
+	if (tag[3] != 'D')
+		return -EINVAL;
+
+	for (i = 0; i < 3; ++i) {
+		d[i] = (u32)(tag[i] - '0');
+
+		if (d[i] > 9)
+			return -EINVAL;
+	}
+
+	return d[0] + (d[1] * 10) + (d[2] * 100);
+}
+
+/* Ack a callback from the DCP */
+static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
+{
+	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+
+	dcp_pop_depth(&ch->depth);
+	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context),
+				 NULL, false);
+}
+
+/* DCP callback handlers */
+static void dcpep_cb_nop(struct apple_dcp *dcp)
+{
+	/* No operation */
+}
+
+static u8 dcpep_cb_true(struct apple_dcp *dcp)
+{
+	return true;
+}
+
+static u8 dcpep_cb_false(struct apple_dcp *dcp)
+{
+	return false;
+}
+
+static u32 dcpep_cb_zero(struct apple_dcp *dcp)
+{
+	return 0;
+}
+
+static void dcpep_cb_swap_complete(struct apple_dcp *dcp)
+{
+	apple_crtc_vblank(dcp->crtc);
+}
+
+static struct dcp_get_uint_prop_resp
+dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
+{
+	/* unimplemented for now */
+	return (struct dcp_get_uint_prop_resp) {
+		.value = 0
+	};
+}
+
+/*
+ * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
+ * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
+ * stream of the display DART, rather than the expected DCP DART.
+ *
+ * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
+ * is a "fundamentally unsafe" operation according to the docs. And yet
+ * everyone does it...
+ */
+static struct dcp_map_buf_resp
+dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req)
+{
+	struct sg_table *map;
+	int ret;
+
+	if (req->buffer >= ARRAY_SIZE(dcp->mappings))
+		goto reject;
+
+	map = &dcp->mappings[req->buffer];
+
+	if (!map->sgl)
+		goto reject;
+
+	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
+	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+
+	if (ret)
+		goto reject;
+
+	return (struct dcp_map_buf_resp) {
+		.dva = sg_dma_address(map->sgl)
+	};
+
+reject:
+	dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
+		req->buffer);
+	return (struct dcp_map_buf_resp) {
+		.ret = EINVAL
+	};
+}
+
+/*
+ * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
+ * physically contigiuous, however we should save the sgtable in case the
+ * buffer needs to be later mapped for PIODMA.
+ */
+static struct dcp_allocate_buffer_resp
+dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req)
+{
+	struct dcp_allocate_buffer_resp resp = { 0 };
+	void *buf;
+
+	resp.dva_size = ALIGN(req->size, 4096);
+	resp.mem_desc_id = ++dcp->nr_mappings;
+
+	if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) {
+		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
+		return resp;
+	}
+
+	buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva,
+				 GFP_KERNEL);
+
+	dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf,
+			resp.dva, resp.dva_size);
+	return resp;
+}
+
+/* Validate that the specified region is a display register */
+static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
+{
+	int i;
+
+	for (i = 0; i < dcp->nr_disp_registers; ++i) {
+		struct resource *r = dcp->disp_registers[i];
+
+		if ((start >= r->start) && (end <= r->end))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Map contiguous physical memory into the DCP's address space. The firmware
+ * uses this to map the display registers we advertise in
+ * sr_map_device_memory_with_index, so we bounds check against that to guard
+ * safe against malicious coprocessors.
+ */
+static struct dcp_map_physical_resp
+dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
+{
+	int size = ALIGN(req->size, 4096);
+
+	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
+		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
+			req->paddr, req->size);
+		return (struct dcp_map_physical_resp) { };
+	}
+
+	return (struct dcp_map_physical_resp) {
+		.dva_size = size,
+		.mem_desc_id = ++dcp->nr_mappings,
+		.dva = dma_map_resource(dcp->dev, req->paddr, size,
+					DMA_BIDIRECTIONAL, 0),
+	};
+}
+
+static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
+{
+	/* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
+	return 533333328;
+}
+
+static struct dcp_map_reg_resp
+dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req)
+{
+	if (req->index >= dcp->nr_disp_registers) {
+		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
+			 req->index);
+
+		return (struct dcp_map_reg_resp) {
+			.ret = 1
+		};
+	} else {
+		struct resource *rsrc = dcp->disp_registers[req->index];
+
+		return (struct dcp_map_reg_resp) {
+			.addr = rsrc->start,
+			.length = resource_size(rsrc)
+		};
+	}
+}
+
+/* Chunked data transfer for property dictionaries */
+static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
+{
+	if (dcp->chunks.data != NULL) {
+		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
+		return false;
+	}
+
+	dcp->chunks.length = *length;
+	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "failed to allocate chunks\n");
+		return false;
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
+			      struct dcp_set_dcpav_prop_chunk_req *req)
+{
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious chunk\n");
+		return false;
+	}
+
+	if (req->offset + req->length > dcp->chunks.length) {
+		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
+		return false;
+	}
+
+	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
+	return true;
+}
+
+static void dcp_set_dimensions(struct apple_dcp *dcp)
+{
+	int i;
+
+	/* Set the connector info */
+	if (dcp->connector) {
+		struct drm_connector *connector = &dcp->connector->base;
+
+		mutex_lock(&connector->dev->mode_config.mutex);
+		connector->display_info.width_mm = dcp->width_mm;
+		connector->display_info.height_mm = dcp->height_mm;
+		mutex_unlock(&connector->dev->mode_config.mutex);
+	}
+
+	/*
+	 * Fix up any probed modes. Modes are created when parsing
+	 * TimingElements, dimensions are calculated when parsing
+	 * DisplayAttributes, and TimingElements may be sent first
+	 */
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		dcp->modes[i].mode.width_mm = dcp->width_mm;
+		dcp->modes[i].mode.height_mm = dcp->height_mm;
+	}
+}
+
+static bool dcpep_process_chunks(struct apple_dcp *dcp,
+				 struct dcp_set_dcpav_prop_end_req *req)
+{
+	struct dcp_parse_ctx ctx;
+	int ret;
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious end\n");
+		return false;
+	}
+
+	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
+
+	if (ret) {
+		dev_warn(dcp->dev, "bad header on dcpav props\n");
+		return false;
+	}
+
+	if (!strcmp(req->key, "TimingElements")) {
+		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
+					     dcp->width_mm, dcp->height_mm);
+
+		if (IS_ERR(dcp->modes)) {
+			dev_warn(dcp->dev, "failed to parse modes\n");
+			dcp->modes = NULL;
+			dcp->nr_modes = 0;
+			return false;
+		}
+	} else if (!strcmp(req->key, "DisplayAttributes")) {
+		ret = parse_display_attributes(&ctx, &dcp->width_mm,
+					       &dcp->height_mm);
+
+		if (ret) {
+			dev_warn(dcp->dev, "failed to parse display attribs\n");
+			return false;
+		}
+
+		dcp_set_dimensions(dcp);
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
+			    struct dcp_set_dcpav_prop_end_req *req)
+{
+	u8 resp = dcpep_process_chunks(dcp, req);
+
+	/* Reset for the next transfer */
+	devm_kfree(dcp->dev, dcp->chunks.data);
+	dcp->chunks.data = NULL;
+
+	return resp;
+}
+
+/* Boot sequence */
+static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_cb_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+
+	*succ = true;
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
+}
+
+static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_late_init_signal(dcp, false, boot_5, NULL);
+}
+
+static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 v_true = true;
+
+	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
+}
+
+static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_setup_video_limits(dcp, false, boot_3, NULL);
+}
+
+static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_create_default_fb(dcp, false, boot_2, NULL);
+}
+
+/* Use special function signature to defer the ACK */
+static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in)
+{
+	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
+	return false;
+}
+
+static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
+{
+	return (struct dcp_rt_bandwidth) {
+		.reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH,
+		.reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL,
+		.doorbell_bit = REG_DOORBELL_BIT,
+
+		.padding[3] = 0x4, // XXX: required by 11.x firmware
+	};
+}
+
+/* Callback to get the current time as milliseconds since the UNIX epoch */
+static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
+{
+	return ktime_to_ms(ktime_get_real());
+}
+
+/*
+ * Helper to send a DRM hotplug event. The DCP is accessed from a single
+ * (RTKit) thread. To handle hotplug callbacks, we need to call
+ * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and
+ * waits for vblank (a DCP callback). That means we deadlock if we call from
+ * the RTKit thread! Instead, move the call to another thread via a workqueue.
+ */
+void dcp_hotplug(struct work_struct *work)
+{
+	struct apple_connector *connector;
+	struct drm_device *dev;
+
+	connector = container_of(work, struct apple_connector, hotplug_wq);
+	dev = connector->base.dev;
+
+	/*
+	 * DCP defers link training until we set a display mode. But we set
+	 * display modes from atomic_flush, so userspace needs to trigger a
+	 * flush, or the CRTC gets no signal.
+	 */
+	if (connector->connected) {
+		drm_connector_set_link_status_property(
+			&connector->base, DRM_MODE_LINK_STATUS_BAD);
+	}
+
+	if (dev && dev->registered)
+		drm_kms_helper_hotplug_event(dev);
+}
+EXPORT_SYMBOL_GPL(dcp_hotplug);
+
+static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
+{
+	struct apple_connector *connector = dcp->connector;
+
+	/* Hotplug invalidates mode. DRM doesn't always handle this. */
+	dcp->valid_mode = false;
+
+	if (connector) {
+		connector->connected = !!(*connected);
+		schedule_work(&connector->hotplug_wq);
+	}
+}
+
+#define DCPEP_MAX_CB (1000)
+
+/*
+ * Define type-safe trampolines. Define typedefs to enforce type-safety on the
+ * input data (so if the types don't match, gcc errors out).
+ */
+
+#define TRAMPOLINE_VOID(func, handler)                                         \
+	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	{                                                                      \
+		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		handler(dcp);                                                  \
+		return true;                                                   \
+	}
+
+#define TRAMPOLINE_IN(func, handler, T_in)                                     \
+	typedef void (*callback_##name)(struct apple_dcp *, T_in *);           \
+                                                                               \
+	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	{                                                                      \
+		callback_##name cb = handler;                                  \
+                                                                               \
+		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		cb(dcp, in);                                                   \
+		return true;                                                   \
+	}
+
+#define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                           \
+	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);       \
+                                                                               \
+	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	{                                                                      \
+		T_out *typed_out = out;                                        \
+		callback_##handler cb = handler;                               \
+                                                                               \
+		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		*typed_out = cb(dcp, in);                                      \
+		return true;                                                   \
+	}
+
+#define TRAMPOLINE_OUT(func, handler, T_out)                                   \
+	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	{                                                                      \
+		T_out *typed_out = out;                                        \
+                                                                               \
+		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		*typed_out = handler(dcp);                                     \
+		return true;                                                   \
+	}
+
+TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
+TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
+TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
+TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
+TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete);
+TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
+		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
+TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
+		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
+TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
+		 struct dcp_allocate_buffer_req,
+		 struct dcp_allocate_buffer_resp);
+TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
+		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
+TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
+		 struct dcp_map_reg_resp);
+TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
+TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
+		 struct dcp_set_dcpav_prop_chunk_req, u8);
+TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
+		 struct dcp_set_dcpav_prop_end_req, u8);
+TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
+	       struct dcp_rt_bandwidth);
+TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
+TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
+TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
+
+bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = {
+	[0] = trampoline_true, /* did_boot_signal */
+	[1] = trampoline_true, /* did_power_on_signal */
+	[2] = trampoline_nop, /* will_power_off_signal */
+	[3] = trampoline_rt_bandwidth,
+	[100] = trampoline_nop, /* match_pmu_service */
+	[101] = trampoline_zero, /* get_display_default_stride */
+	[103] = trampoline_nop, /* set_boolean_property */
+	[106] = trampoline_nop, /* remove_property */
+	[107] = trampoline_true, /* create_provider_service */
+	[108] = trampoline_true, /* create_product_service */
+	[109] = trampoline_true, /* create_pmu_service */
+	[110] = trampoline_true, /* create_iomfb_service */
+	[111] = trampoline_false, /* create_backlight_service */
+	[116] = dcpep_cb_boot_1,
+	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
+	[120] = trampoline_false, /* read_edt_data */
+	[122] = trampoline_prop_start,
+	[123] = trampoline_prop_chunk,
+	[124] = trampoline_prop_end,
+	[201] = trampoline_map_piodma,
+	[206] = trampoline_true, /* match_pmu_service_2 */
+	[207] = trampoline_true, /* match_backlight_service */
+	[208] = trampoline_get_time,
+	[211] = trampoline_nop, /* update_backlight_factor_prop */
+	[300] = trampoline_nop, /* pr_publish */
+	[401] = trampoline_get_uint_prop,
+	[406] = trampoline_nop, /* set_fx_prop */
+	[408] = trampoline_get_frequency,
+	[411] = trampoline_map_reg,
+	[413] = trampoline_true, /* sr_set_property_dict */
+	[414] = trampoline_true, /* sr_set_property_int */
+	[415] = trampoline_true, /* sr_set_property_bool */
+	[451] = trampoline_allocate_buffer,
+	[452] = trampoline_map_physical,
+	[552] = trampoline_true, /* set_property_dict_0 */
+	[561] = trampoline_true, /* set_property_dict */
+	[563] = trampoline_true, /* set_property_int */
+	[565] = trampoline_true, /* set_property_bool */
+	[567] = trampoline_true, /* set_property_str */
+	[574] = trampoline_zero, /* power_up_dart */
+	[576] = trampoline_hotplug,
+	[577] = trampoline_nop, /* powerstate_notify */
+	[582] = trampoline_true, /* create_default_fb_surface */
+	[589] = trampoline_swap_complete,
+	[591] = trampoline_nop, /* swap_complete_intent_gated */
+	[598] = trampoline_nop, /* find_swap_function_gated */
+};
+
+static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
+			    void *data, u32 length)
+{
+	struct device *dev = dcp->dev;
+	struct dcp_packet_header *hdr = data;
+	void *in, *out;
+	int tag = dcp_parse_tag(hdr->tag);
+	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+	u8 depth;
+
+	if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) {
+		dev_warn(dev, "received unknown callback %c%c%c%c\n",
+			 hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
+		return;
+	}
+
+	in = data + sizeof(*hdr);
+	out = in + hdr->in_len;
+
+	depth = dcp_push_depth(&ch->depth);
+	ch->output[depth] = out;
+
+	if (dcpep_cb_handlers[tag](dcp, out, in))
+		dcp_ack(dcp, context);
+}
+
+static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
+			     void *data, u32 length)
+{
+	struct dcp_packet_header *header = data;
+	struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context);
+	void *cookie;
+	dcp_callback_t cb;
+
+	if (!ch) {
+		dev_warn(dcp->dev, "ignoring ack on context %X\n", context);
+		return;
+	}
+
+	dcp_pop_depth(&ch->depth);
+
+	cb = ch->callbacks[ch->depth];
+	cookie = ch->cookies[ch->depth];
+
+	if (cb)
+		cb(dcp, data + sizeof(*header) + header->in_len, cookie);
+}
+
+static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
+{
+	enum dcp_context_id ctx_id;
+	u16 offset;
+	u32 length;
+	int channel_offset;
+	void *data;
+
+	ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT;
+	offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT;
+	length = (message >> DCPEP_LENGTH_SHIFT);
+
+	channel_offset = dcp_channel_offset(ctx_id);
+
+	if (channel_offset < 0) {
+		dev_warn(dcp->dev, "invalid context received %u", ctx_id);
+		return;
+	}
+
+	data = dcp->shmem + channel_offset + offset;
+
+	if (message & DCPEP_ACK)
+		dcpep_handle_ack(dcp, ctx_id, data, length);
+	else
+		dcpep_handle_cb(dcp, ctx_id, data, length);
+}
+
+/*
+ * Callback for swap requests. If a swap failed, we'll never get a swap
+ * complete event so we need to fake a vblank event early to avoid a hang.
+ */
+
+static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_submit_resp *resp = data;
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
+		apple_crtc_vblank(dcp->crtc);
+	}
+}
+
+static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+
+	dcp->swap.swap.swap_id = resp->swap_id;
+
+	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL);
+}
+
+/*
+ * DRM specifies rectangles as start and end coordinates.  DCP specifies
+ * rectangles as a start coordinate and a width/height. Convert a DRM rectangle
+ * to a DCP rectangle.
+ */
+static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
+{
+	return (struct dcp_rect) {
+		.x = rect->x1,
+		.y = rect->y1,
+		.w = drm_rect_width(rect),
+		.h = drm_rect_height(rect)
+	};
+}
+
+static u32 drm_format_to_dcp(u32 drm)
+{
+	switch (drm) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		return fourcc_code('A', 'R', 'G', 'B');
+
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return fourcc_code('A', 'B', 'G', 'R');
+	}
+
+	pr_warn("DRM format %X not supported in DCP\n", drm);
+	return 0;
+}
+
+int dcp_get_modes(struct drm_connector *connector)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+	struct platform_device *pdev = apple_connector->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	struct drm_device *dev = connector->dev;
+	struct drm_display_mode *mode;
+	int i;
+
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		mode = drm_mode_duplicate(dev, &dcp->modes[i].mode);
+
+		if (!mode) {
+			dev_err(dev->dev, "Failed to duplicate display mode\n");
+			return 0;
+		}
+
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return dcp->nr_modes;
+}
+EXPORT_SYMBOL_GPL(dcp_get_modes);
+
+/* The user may own drm_display_mode, so we need to search for our copy */
+static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
+					    struct drm_display_mode *mode)
+{
+	int i;
+
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		if (drm_mode_match(mode, &dcp->modes[i].mode,
+				   DRM_MODE_MATCH_TIMINGS |
+				   DRM_MODE_MATCH_CLOCK))
+			return &dcp->modes[i];
+	}
+
+	return NULL;
+}
+
+int dcp_mode_valid(struct drm_connector *connector,
+		   struct drm_display_mode *mode)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+	struct platform_device *pdev = apple_connector->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD;
+}
+EXPORT_SYMBOL_GPL(dcp_mode_valid);
+
+/* Helpers to modeset and swap, used to flush */
+static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_req start_req = { 0 };
+
+	dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
+}
+
+static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL);
+}
+
+void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct drm_plane *plane;
+	struct drm_plane_state *new_state, *old_state;
+	struct drm_crtc_state *crtc_state;
+	struct dcp_swap_submit_req *req = &dcp->swap;
+	int l;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
+	    WARN(!dcp->connector->connected, "can't flush if disconnected")) {
+		apple_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	/* Reset to defaults */
+	memset(req, 0, sizeof(*req));
+	for (l = 0; l < SWAP_SURFACES; l++)
+		req->surf_null[l] = true;
+
+	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) {
+		struct drm_framebuffer *fb = new_state->fb;
+		struct drm_rect src_rect;
+
+		WARN_ON(l >= SWAP_SURFACES);
+
+		req->swap.swap_enabled |= BIT(l);
+
+		if (!new_state->fb) {
+			if (old_state->fb)
+				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
+
+			continue;
+		}
+		req->surf_null[l] = false;
+
+		// XXX: awful hack! race condition between a framebuffer unbind
+		// getting swapped out and GEM unreferencing a framebuffer. If
+		// we lose the race, the display gets IOVA faults and the DCP
+		// crashes. We need to extend the lifetime of the
+		// drm_framebuffer (and hence the GEM object) until after we
+		// get a swap complete for the swap unbinding it.
+		drm_framebuffer_get(fb);
+
+		drm_rect_fp_to_int(&src_rect, &new_state->src);
+
+		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
+		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
+
+		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
+
+		req->surf[l] = (struct dcp_surface) {
+			.format = drm_format_to_dcp(fb->format->format),
+			.xfer_func = 13,
+			.colorspace = 1,
+			.stride = fb->pitches[0],
+			.width = fb->width,
+			.height = fb->height,
+			.buf_size = fb->height * fb->pitches[0],
+			.surface_id = req->swap.surf_ids[l],
+
+			/* Only used for compressed or multiplanar surfaces */
+			.pix_size = 1,
+			.pel_w = 1,
+			.pel_h = 1,
+			.has_comp = 1,
+			.has_planes = 1,
+		};
+	}
+
+	/* These fields should be set together */
+	req->swap.swap_completed = req->swap.swap_enabled;
+
+	if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) {
+		struct dcp_display_mode *mode;
+		u32 handle = 2;
+
+		mode = lookup_mode(dcp, &crtc_state->mode);
+		if (!mode) {
+			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
+				 DRM_MODE_ARG(&crtc_state->mode));
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		dcp->mode = (struct dcp_set_digital_out_mode_req) {
+			.color_mode_id = mode->color_mode_id,
+			.timing_mode_id = mode->timing_mode_id
+		};
+
+		dcp->valid_mode = true;
+
+		dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL);
+	} else
+		do_swap(dcp, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(dcp_flush);
+
+bool dcp_is_initialized(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	return dcp->active;
+}
+EXPORT_SYMBOL_GPL(dcp_is_initialized);
+
+static void init_done(struct apple_dcp *dcp, void *out, void *cookie)
+{
+}
+
+static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_power_state_req req = {
+		.unklong = 1,
+	};
+	dcp_set_power_state(dcp, false, &req, init_done, NULL);
+}
+
+static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_first_client_open(dcp, false, init_3, NULL);
+}
+
+static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	dev_info(dcp->dev, "DCP booted\n");
+
+	init_2(dcp, data, cookie);
+
+	dcp->active = true;
+}
+
+static void dcp_got_msg(void *cookie, u8 endpoint, u64 message)
+{
+	struct apple_dcp *dcp = cookie;
+	enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK;
+
+	WARN_ON(endpoint != DCP_ENDPOINT);
+
+	if (type == DCPEP_TYPE_INITIALIZED)
+		dcp_start_signal(dcp, false, dcp_started, NULL);
+	else if (type == DCPEP_TYPE_MESSAGE)
+		dcpep_got_msg(dcp, message);
+	else
+		dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
+}
+
+static void dcp_rtk_crashed(void *cookie)
+{
+	struct apple_dcp *dcp = cookie;
+
+	dcp->crashed = true;
+	dev_err(dcp->dev, "DCP has crashed");
+}
+
+static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	struct apple_dcp *dcp = cookie;
+
+	if (bfr->iova) {
+		struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev);
+		phys_addr_t phy_addr;
+
+		if (!domain)
+			return -ENOMEM;
+
+		// TODO: get map from device-tree
+		phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF);
+		if (!phy_addr)
+			return -ENOMEM;
+
+		// TODO: verify phy_addr, cache attribute
+		bfr->buffer = memremap(phy_addr, bfr->size, MEMREMAP_WB);
+		if (!bfr->buffer)
+			return -ENOMEM;
+
+		bfr->is_mapped = true;
+		dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx",
+			(uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer);
+	} else {
+		bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL);
+		if (!bfr->buffer)
+			return -ENOMEM;
+
+		dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx",
+			 (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer);
+	}
+
+	return 0;
+}
+
+static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	struct apple_dcp *dcp = cookie;
+
+	if (bfr->is_mapped)
+		memunmap(bfr->buffer);
+	else
+		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova);
+}
+
+static struct apple_rtkit_ops rtkit_ops = {
+	.crashed = dcp_rtk_crashed,
+	.recv_message = dcp_got_msg,
+	.shmem_setup = dcp_rtk_shmem_setup,
+	.shmem_destroy = dcp_rtk_shmem_destroy,
+};
+
+void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
+	      struct apple_connector *connector)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	dcp->crtc = crtc;
+	dcp->connector = connector;
+
+	/* Dimensions might already be parsed */
+	dcp_set_dimensions(dcp);
+}
+EXPORT_SYMBOL_GPL(dcp_link);
+
+static struct platform_device *dcp_get_dev(struct device *dev, const char *name)
+{
+	struct device_node *node = of_get_child_by_name(dev->of_node, name);
+
+	if (!node)
+		return NULL;
+
+	return of_find_device_by_node(node);
+}
+
+static int dcp_get_disp_regs(struct apple_dcp *dcp)
+{
+	struct platform_device *pdev = to_platform_device(dcp->dev);
+	int count = pdev->num_resources - 1;
+	int i;
+
+	if (count <= 0 || count > MAX_DISP_REGISTERS)
+		return -EINVAL;
+
+	for (i = 0; i < count; ++i) {
+		dcp->disp_registers[i] =
+			platform_get_resource(pdev, IORESOURCE_MEM, 1 + i);
+	}
+
+	dcp->nr_disp_registers = count;
+	return 0;
+}
+
+static int dcp_platform_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct resource *res;
+	struct apple_dcp *dcp;
+	dma_addr_t shmem_iova;
+	int ret;
+
+	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
+	if (!dcp)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, dcp);
+	dcp->dev = dev;
+
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
+	if (ret)
+		return ret;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc");
+	if (!res)
+		return -EINVAL;
+
+	of_platform_default_populate(dev->of_node, NULL, dev);
+
+	dcp->piodma = dcp_get_dev(dev, "piodma");
+	if (!dcp->piodma) {
+		dev_err(dev, "failed to find piodma\n");
+		return -ENODEV;
+	}
+
+	ret = dcp_get_disp_regs(dcp);
+	if (ret) {
+		dev_err(dev, "failed to find display registers\n");
+		return ret;
+	}
+
+	dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops);
+	if (IS_ERR(dcp->rtk))
+		return dev_err_probe(dev, PTR_ERR(dcp->rtk),
+				     "Failed to intialize RTKit");
+
+	ret = apple_rtkit_wake(dcp->rtk);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to boot RTKit: %d", ret);
+
+	apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT);
+
+	dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova,
+					GFP_KERNEL);
+
+	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT,
+				 dcpep_set_shmem(shmem_iova), NULL, false);
+
+	return ret;
+}
+
+/*
+ * We need to shutdown DCP before tearing down the display subsystem. Otherwise
+ * the DCP will crash and briefly flash a green screen of death.
+ */
+static void dcp_platform_shutdown(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	struct dcp_set_power_state_req req = {
+		/* defaults are ok */
+	};
+
+	dcp_set_power_state(dcp, false, &req, NULL, NULL);
+}
+
+static const struct of_device_id of_match[] = {
+	{ .compatible = "apple,dcp" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_match);
+
+static struct platform_driver apple_platform_driver = {
+	.probe		= dcp_platform_probe,
+	.shutdown	= dcp_platform_shutdown,
+	.driver	= {
+		.name = "apple-dcp",
+		.of_match_table	= of_match,
+	},
+};
+
+module_platform_driver(apple_platform_driver);
+
+MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
+MODULE_DESCRIPTION("Apple Display Controller DRM driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
new file mode 100644
index 00000000000000..4582fe984c8aa5
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#ifndef __APPLE_DCP_H__
+#define __APPLE_DCP_H__
+
+#include <drm/drm_atomic.h>
+#include "parser.h"
+
+struct apple_crtc {
+	struct drm_crtc base;
+	struct drm_pending_vblank_event *event;
+	bool vsync_disabled;
+
+	/* Reference to the DCP device owning this CRTC */
+	struct platform_device *dcp;
+};
+
+#define to_apple_crtc(x) container_of(x, struct apple_crtc, base)
+
+void dcp_hotplug(struct work_struct *work);
+
+struct apple_connector {
+	struct drm_connector base;
+	bool connected;
+
+	struct platform_device *dcp;
+
+	/* Workqueue for sending hotplug events to the associated device */
+	struct work_struct hotplug_wq;
+};
+
+#define to_apple_connector(x) container_of(x, struct apple_connector, base)
+
+void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
+	      struct apple_connector *connector);
+void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
+bool dcp_is_initialized(struct platform_device *pdev);
+void apple_crtc_vblank(struct apple_crtc *apple);
+int dcp_get_modes(struct drm_connector *connector);
+int dcp_mode_valid(struct drm_connector *connector,
+		   struct drm_display_mode *mode);
+
+#endif
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
new file mode 100644
index 00000000000000..6301bf8f8c21d1
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -0,0 +1,406 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#ifndef __APPLE_DCPEP_H__
+#define __APPLE_DCPEP_H__
+
+/* Endpoint for general DCP traffic (dcpep in macOS) */
+#define DCP_ENDPOINT 0x37
+
+/* Fixed size of shared memory between DCP and AP */
+#define DCP_SHMEM_SIZE 0x100000
+
+/* DCP message contexts */
+enum dcp_context_id {
+	/* Callback */
+	DCP_CONTEXT_CB = 0,
+
+	/* Command */
+	DCP_CONTEXT_CMD = 2,
+
+	/* Asynchronous */
+	DCP_CONTEXT_ASYNC = 3,
+
+	/* Out-of-band callback */
+	DCP_CONTEXT_OOBCB = 4,
+
+	/* Out-of-band command */
+	DCP_CONTEXT_OOBCMD = 6,
+
+	DCP_NUM_CONTEXTS
+};
+
+static int dcp_tx_offset(enum dcp_context_id id)
+{
+	switch (id) {
+	case DCP_CONTEXT_CB:
+	case DCP_CONTEXT_CMD:    return 0x00000;
+	case DCP_CONTEXT_OOBCB:
+	case DCP_CONTEXT_OOBCMD: return 0x08000;
+	default:		 return -EINVAL;
+	}
+}
+
+static int dcp_channel_offset(enum dcp_context_id id)
+{
+	switch (id) {
+	case DCP_CONTEXT_ASYNC:  return 0x40000;
+	case DCP_CONTEXT_CB:     return 0x60000;
+	case DCP_CONTEXT_OOBCB:  return 0x68000;
+	default:		 return dcp_tx_offset(id);
+	}
+}
+
+/* RTKit endpoint message types */
+enum dcpep_type {
+	/* Set shared memory */
+	DCPEP_TYPE_SET_SHMEM = 0,
+
+	/* DCP is initialized */
+	DCPEP_TYPE_INITIALIZED = 1,
+
+	/* Remote procedure call */
+	DCPEP_TYPE_MESSAGE = 2,
+};
+
+/* Message */
+#define DCPEP_TYPE_SHIFT (0)
+#define DCPEP_TYPE_MASK GENMASK(1, 0)
+#define DCPEP_ACK BIT_ULL(6)
+#define DCPEP_CONTEXT_SHIFT (8)
+#define DCPEP_CONTEXT_MASK GENMASK(11, 8)
+#define DCPEP_OFFSET_SHIFT (16)
+#define DCPEP_OFFSET_MASK GENMASK(31, 16)
+#define DCPEP_LENGTH_SHIFT (32)
+
+/* Set shmem */
+#define DCPEP_DVA_SHIFT (16)
+#define DCPEP_FLAG_SHIFT (4)
+#define DCPEP_FLAG_VALUE (4)
+
+struct dcp_packet_header {
+	char tag[4];
+	u32 in_len;
+	u32 out_len;
+} __packed;
+
+#define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0)
+#define DCP_PACKET_ALIGNMENT (0x40)
+
+static inline u64
+dcpep_set_shmem(u64 dart_va)
+{
+	return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) |
+	       (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) |
+	       (dart_va << DCPEP_DVA_SHIFT);
+}
+
+static inline u64
+dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
+{
+	return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) |
+	       ((u64) id << DCPEP_CONTEXT_SHIFT) |
+	       ((u64) offset << DCPEP_OFFSET_SHIFT) |
+	       ((u64) length << DCPEP_LENGTH_SHIFT);
+}
+
+static inline u64
+dcpep_ack(enum dcp_context_id id)
+{
+	return dcpep_msg(id, 0, 0) | DCPEP_ACK;
+}
+
+/* Structures used in v12.0 firmware */
+
+#define SWAP_SURFACES 4
+#define MAX_PLANES 3
+
+struct dcp_iouserclient {
+	/* Handle for the IOUserClient. macOS sets this to a kernel VA. */
+	u64 handle;
+	u32 unk;
+	u8 flag1;
+	u8 flag2;
+	u8 padding[2];
+} __packed;
+
+struct dcp_rect {
+	u32 x;
+	u32 y;
+	u32 w;
+	u32 h;
+} __packed;
+
+/*
+ * Set in the swap_{enabled,completed} field to remove missing
+ * layers. Without this flag, the DCP will assume missing layers have
+ * not changed since the previous frame and will preserve their
+ * content.
+  */
+#define DCP_REMOVE_LAYERS BIT(31)
+
+struct dcp_swap {
+	u64 ts1;
+	u64 ts2;
+	u64 unk_10[6];
+	u64 flags1;
+	u64 flags2;
+
+	u32 swap_id;
+
+	u32 surf_ids[SWAP_SURFACES];
+	struct dcp_rect src_rect[SWAP_SURFACES];
+	u32 surf_flags[SWAP_SURFACES];
+	u32 surf_unk[SWAP_SURFACES];
+	struct dcp_rect dst_rect[SWAP_SURFACES];
+	u32 swap_enabled;
+	u32 swap_completed;
+
+	u32 unk_10c;
+	u8 unk_110[0x1b8];
+	u32 unk_2c8;
+	u8 unk_2cc[0x14];
+	u32 unk_2e0;
+	u8 unk_2e4[0x3c];
+} __packed;
+
+/* Information describing a plane of a planar compressed surface */
+struct dcp_plane_info {
+	u32 width;
+	u32 height;
+	u32 base;
+	u32 offset;
+	u32 stride;
+	u32 size;
+	u16 tile_size;
+	u8 tile_w;
+	u8 tile_h;
+	u32 unk[13];
+} __packed;
+
+struct dcp_component_types {
+	u8 count;
+	u8 types[7];
+} __packed;
+
+/* Information describing a surface */
+struct dcp_surface {
+	u8 is_tiled;
+	u8 unk_1;
+	u8 unk_2;
+	u32 plane_cnt;
+	u32 plane_cnt2;
+	u32 format; /* DCP fourcc */
+	u32 unk_f;
+	u8 xfer_func;
+	u8 colorspace;
+	u32 stride;
+	u16 pix_size;
+	u8 pel_w;
+	u8 pel_h;
+	u32 offset;
+	u32 width;
+	u32 height;
+	u32 buf_size;
+	u32 unk_2d;
+	u32 unk_31;
+	u32 surface_id;
+	struct dcp_component_types comp_types[MAX_PLANES];
+	u64 has_comp;
+	struct dcp_plane_info planes[MAX_PLANES];
+	u64 has_planes;
+	u32 compression_info[MAX_PLANES][13];
+	u64 has_compr_info;
+	u64 unk_1f5;
+	u8 padding[7];
+} __packed;
+
+struct dcp_rt_bandwidth {
+	u64 unk1;
+	u64 reg_scratch;
+	u64 reg_doorbell;
+	u32 unk2;
+	u32 doorbell_bit;
+	u32 padding[7];
+} __packed;
+
+/* Method calls */
+
+enum dcpep_method {
+	dcpep_late_init_signal,
+	dcpep_setup_video_limits,
+	dcpep_set_create_dfb,
+	dcpep_start_signal,
+	dcpep_swap_start,
+	dcpep_swap_submit,
+	dcpep_set_display_device,
+	dcpep_set_digital_out_mode,
+	dcpep_create_default_fb,
+	dcpep_set_display_refresh_properties,
+	dcpep_flush_supports_power,
+	dcpep_set_power_state,
+	dcpep_first_client_open,
+	dcpep_update_notify_clients_dcp,
+	dcpep_num_methods
+};
+
+struct dcp_method_entry {
+	const char *name;
+	char tag[4];
+};
+
+/* Prototypes */
+
+struct dcp_set_digital_out_mode_req {
+	u32 color_mode_id;
+	u32 timing_mode_id;
+} __packed;
+
+struct dcp_map_buf_req {
+	u64 buffer;
+	u8 unk;
+	u8 buf_null;
+	u8 vaddr_null;
+	u8 dva_null;
+} __packed;
+
+struct dcp_map_buf_resp {
+	u64 vaddr;
+	u64 dva;
+	u32 ret;
+} __packed;
+
+struct dcp_allocate_buffer_req {
+	u32 unk0;
+	u64 size;
+	u32 unk2;
+	u8 paddr_null;
+	u8 dva_null;
+	u8 dva_size_null;
+	u8 padding;
+} __packed;
+
+struct dcp_allocate_buffer_resp {
+	u64 paddr;
+	u64 dva;
+	u64 dva_size;
+	u32 mem_desc_id;
+} __packed;
+
+struct dcp_map_physical_req {
+	u64 paddr;
+	u64 size;
+	u32 flags;
+	u8 dva_null;
+	u8 dva_size_null;
+	u8 padding[2];
+} __packed;
+
+struct dcp_map_physical_resp {
+	u64 dva;
+	u64 dva_size;
+	u32 mem_desc_id;
+} __packed;
+
+struct dcp_map_reg_req {
+	char obj[4];
+	u32 index;
+	u32 flags;
+	u8 addr_null;
+	u8 length_null;
+	u8 padding[2];
+} __packed;
+
+struct dcp_map_reg_resp {
+	u64 addr;
+	u64 length;
+	u32 ret;
+} __packed;
+
+struct dcp_swap_start_req {
+	u32 swap_id;
+	struct dcp_iouserclient client;
+	u8 swap_id_null;
+	u8 client_null;
+	u8 padding[2];
+} __packed;
+
+struct dcp_swap_start_resp {
+	u32 swap_id;
+	struct dcp_iouserclient client;
+	u32 ret;
+} __packed;
+
+struct dcp_swap_submit_req {
+	struct dcp_swap swap;
+	struct dcp_surface surf[SWAP_SURFACES];
+	u64 surf_iova[SWAP_SURFACES];
+	u8 unkbool;
+	u64 unkdouble;
+	u32 unkint;
+	u8 swap_null;
+	u8 surf_null[SWAP_SURFACES];
+	u8 unkoutbool_null;
+	u8 padding[1];
+} __packed;
+
+struct dcp_swap_submit_resp {
+	u8 unkoutbool;
+	u32 ret;
+	u8 padding[3];
+} __packed;
+
+struct dcp_get_uint_prop_req {
+	char obj[4];
+	char key[0x40];
+	u64 value;
+	u8 value_null;
+	u8 padding[3];
+} __packed;
+
+struct dcp_get_uint_prop_resp {
+	u64 value;
+	u8 ret;
+	u8 padding[3];
+} __packed;
+
+struct dcp_set_power_state_req {
+	u64 unklong;
+	u8 unkbool;
+	u8 unkint_null;
+	u8 padding[2];
+} __packed;
+
+struct dcp_set_power_state_resp {
+	u32 unkint;
+	u32 ret;
+} __packed;
+
+struct dcp_set_dcpav_prop_chunk_req {
+	char data[0x1000];
+	u32 offset;
+	u32 length;
+} __packed;
+
+struct dcp_set_dcpav_prop_end_req {
+	char key[0x40];
+} __packed;
+
+struct dcp_update_notify_clients_dcp {
+	u32 client_0;
+	u32 client_1;
+	u32 client_2;
+	u32 client_3;
+	u32 client_4;
+	u32 client_5;
+	u32 client_6;
+	u32 client_7;
+	u32 client_8;
+	u32 client_9;
+	u32 client_a;
+	u32 client_b;
+	u32 client_c;
+	u32 client_d;
+} __packed;
+
+#endif
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
new file mode 100644
index 00000000000000..a84a28b4dc2ae0
--- /dev/null
+++ b/drivers/gpu/drm/apple/dummy-piodma.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#include <linux/module.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_device.h>
+
+static int dcp_piodma_probe(struct platform_device *pdev)
+{
+	return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
+}
+
+static const struct of_device_id of_match[] = {
+	{ .compatible = "apple,dcp-piodma" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, of_match);
+
+static struct platform_driver dcp_piodma_platform_driver = {
+	.probe		= dcp_piodma_probe,
+	.driver	= {
+		.name = "apple,dcp-piodma",
+		.of_match_table	= of_match,
+	},
+};
+
+module_platform_driver(dcp_piodma_platform_driver);
+
+MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
+MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
new file mode 100644
index 00000000000000..7205179e7785aa
--- /dev/null
+++ b/drivers/gpu/drm/apple/parser.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/math.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include "parser.h"
+
+#define DCP_PARSE_HEADER 0xd3
+
+enum dcp_parse_type {
+	DCP_TYPE_DICTIONARY = 1,
+	DCP_TYPE_ARRAY = 2,
+	DCP_TYPE_INT64 = 4,
+	DCP_TYPE_STRING = 9,
+	DCP_TYPE_BLOB = 10,
+	DCP_TYPE_BOOL = 11
+};
+
+struct dcp_parse_tag {
+	unsigned int size : 24;
+	enum dcp_parse_type type : 5;
+	unsigned int padding : 2;
+	bool last : 1;
+} __packed;
+
+static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count)
+{
+	void *ptr = ctx->blob + ctx->pos;
+
+	if (ctx->pos + count > ctx->len)
+		return ERR_PTR(-EINVAL);
+
+	ctx->pos += count;
+	return ptr;
+}
+
+static u32 *parse_u32(struct dcp_parse_ctx *ctx)
+{
+	return parse_bytes(ctx, sizeof(u32));
+}
+
+static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx)
+{
+	struct dcp_parse_tag *tag;
+
+	/* Align to 32-bits */
+	ctx->pos = round_up(ctx->pos, 4);
+
+	tag = parse_bytes(ctx, sizeof(struct dcp_parse_tag));
+
+	if (IS_ERR(tag))
+		return tag;
+
+	if (tag->padding)
+		return ERR_PTR(-EINVAL);
+
+	return tag;
+}
+
+static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx,
+					       enum dcp_parse_type type)
+{
+	struct dcp_parse_tag *tag = parse_tag(ctx);
+
+	if (IS_ERR(tag))
+		return tag;
+
+	if (tag->type != type)
+		return ERR_PTR(-EINVAL);
+
+	return tag;
+}
+
+static int skip(struct dcp_parse_ctx *handle)
+{
+	struct dcp_parse_tag *tag = parse_tag(handle);
+	int ret = 0;
+	int i;
+
+	if (IS_ERR(tag))
+		return PTR_ERR(tag);
+
+	switch (tag->type) {
+	case DCP_TYPE_DICTIONARY:
+		for (i = 0; i < tag->size; ++i) {
+			ret |= skip(handle); /* key */
+			ret |= skip(handle); /* value */
+		}
+
+		return ret;
+
+	case DCP_TYPE_ARRAY:
+		for (i = 0; i < tag->size; ++i)
+			ret |= skip(handle);
+
+		return ret;
+
+	case DCP_TYPE_INT64:
+		handle->pos += sizeof(s64);
+		return 0;
+
+	case DCP_TYPE_STRING:
+	case DCP_TYPE_BLOB:
+		handle->pos += tag->size;
+		return 0;
+
+	case DCP_TYPE_BOOL:
+		return 0;
+
+	default:
+		return -EINVAL;
+	}
+}
+
+/* Caller must free the result */
+static char *parse_string(struct dcp_parse_ctx *handle)
+{
+	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING);
+	const char *in;
+	char *out;
+
+	if (IS_ERR(tag))
+		return (void *)tag;
+
+	in = parse_bytes(handle, tag->size);
+	if (IS_ERR(in))
+		return (void *)in;
+
+	out = kmalloc(tag->size + 1, GFP_KERNEL);
+
+	memcpy(out, in, tag->size);
+	out[tag->size] = '\0';
+	return out;
+}
+
+static int parse_int(struct dcp_parse_ctx *handle, s64 *value)
+{
+	void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64);
+	s64 *in;
+
+	if (IS_ERR(tag))
+		return PTR_ERR(tag);
+
+	in = parse_bytes(handle, sizeof(s64));
+
+	if (IS_ERR(in))
+		return PTR_ERR(in);
+
+	memcpy(value, in, sizeof(*value));
+	return 0;
+}
+
+static int parse_bool(struct dcp_parse_ctx *handle, bool *b)
+{
+	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL);
+
+	if (IS_ERR(tag))
+		return PTR_ERR(tag);
+
+	*b = !!tag->size;
+	return 0;
+}
+
+struct iterator {
+	struct dcp_parse_ctx *handle;
+	u32 idx, len;
+};
+
+static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it,
+			  bool dict)
+{
+	struct dcp_parse_tag *tag;
+	enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY;
+
+	*it = (struct iterator) {
+		.handle = handle,
+		.idx = 0
+	};
+
+	tag = parse_tag_of_type(it->handle, type);
+	if (IS_ERR(tag))
+		return PTR_ERR(tag);
+
+	it->len = tag->size;
+	return 0;
+}
+
+#define dcp_parse_foreach_in_array(handle, it)                                 \
+	for (iterator_begin(handle, &it, false); it.idx < it.len; ++it.idx)
+#define dcp_parse_foreach_in_dict(handle, it)                                  \
+	for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx)
+
+int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx)
+{
+	u32 *header;
+
+	*ctx = (struct dcp_parse_ctx) {
+		.blob = blob,
+		.len = size,
+		.pos = 0,
+	};
+
+	header = parse_u32(ctx);
+	if (IS_ERR(header))
+		return PTR_ERR(header);
+
+	if (*header != DCP_PARSE_HEADER)
+		return -EINVAL;
+
+	return 0;
+}
+
+struct dimension {
+	s64 total, front_porch, sync_width, active;
+	s64 precise_sync_rate;
+};
+
+static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim)
+{
+	struct iterator it;
+	int ret = 0;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		char *key = parse_string(it.handle);
+
+		if (IS_ERR(key))
+			ret = PTR_ERR(handle);
+		else if (!strcmp(key, "Active"))
+			ret = parse_int(it.handle, &dim->active);
+		else if (!strcmp(key, "Total"))
+			ret = parse_int(it.handle, &dim->total);
+		else if (!strcmp(key, "FrontPorch"))
+			ret = parse_int(it.handle, &dim->front_porch);
+		else if (!strcmp(key, "SyncWidth"))
+			ret = parse_int(it.handle, &dim->sync_width);
+		else if (!strcmp(key, "PreciseSyncRate"))
+			ret = parse_int(it.handle, &dim->precise_sync_rate);
+		else
+			skip(it.handle);
+
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+
+static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
+{
+	struct iterator outer_it;
+	int ret = 0;
+	s64 best_score = -1;
+
+	*best_id = -1;
+
+	dcp_parse_foreach_in_array(handle, outer_it) {
+		struct iterator it;
+		s64 score = -1, id = -1;
+
+		dcp_parse_foreach_in_dict(handle, it) {
+			char *key = parse_string(it.handle);
+
+			if (IS_ERR(key))
+				ret = PTR_ERR(key);
+			else if (!strcmp(key, "Score"))
+				ret = parse_int(it.handle, &score);
+			else if (!strcmp(key, "ID"))
+				ret = parse_int(it.handle, &id);
+			else
+				skip(it.handle);
+
+			if (ret)
+				return ret;
+		}
+
+		/* Skip partial entries */
+		if (score < 0 || id < 0)
+			continue;
+
+		if (score > best_score) {
+			best_score = score;
+			*best_id = id;
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * 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
+ * specifies the clock in kHz. The intermediate result may overflow a u32, so
+ * use a u64 where required.
+ */
+static u32 calculate_clock(struct dimension *horiz, struct dimension *vert)
+{
+	u32 pixels = horiz->total * vert->total;
+	u64 clock = mul_u32_u32(pixels, vert->precise_sync_rate);
+
+	return DIV_ROUND_CLOSEST_ULL(clock >> 16, 1000);
+}
+
+static int parse_mode(struct dcp_parse_ctx *handle,
+		      struct dcp_display_mode *out, s64 *score, int width_mm,
+		      int height_mm)
+{
+	int ret = 0;
+	struct iterator it;
+	struct dimension horiz, vert;
+	s64 id = -1;
+	s64 best_color_mode = -1;
+	bool is_virtual = false;
+	struct drm_display_mode *mode = &out->mode;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		char *key = parse_string(it.handle);
+
+		if (IS_ERR(key))
+			ret = PTR_ERR(key);
+		else if (!strcmp(key, "HorizontalAttributes"))
+			ret = parse_dimension(it.handle, &horiz);
+		else if (!strcmp(key, "VerticalAttributes"))
+			ret = parse_dimension(it.handle, &vert);
+		else if (!strcmp(key, "ColorModes"))
+			ret = parse_color_modes(it.handle, &best_color_mode);
+		else if (!strcmp(key, "ID"))
+			ret = parse_int(it.handle, &id);
+		else if (!strcmp(key, "IsVirtual"))
+			ret = parse_bool(it.handle, &is_virtual);
+		else if (!strcmp(key, "Score"))
+			ret = parse_int(it.handle, score);
+		else
+			skip(it.handle);
+
+		if (ret)
+			return ret;
+	}
+
+	/*
+	 * We need to skip virtual modes. In some cases, virtual modes are "too
+	 * big" for the monitor and can cause breakage. It is unclear why the
+	 * DCP reports these modes at all. Treat as a recoverable error.
+	 */
+	if (is_virtual)
+		return -EINVAL;
+
+	/* From here we must succeed. Start filling out the mode. */
+	*mode = (struct drm_display_mode) {
+		.type = DRM_MODE_TYPE_DRIVER,
+		.clock = calculate_clock(&horiz, &vert),
+
+		.vdisplay = vert.active,
+		.vsync_start = vert.active + vert.front_porch,
+		.vsync_end = vert.active + vert.front_porch + vert.sync_width,
+		.vtotal = vert.total,
+
+		.hdisplay = horiz.active,
+		.hsync_start = horiz.active + horiz.front_porch,
+		.hsync_end = horiz.active + horiz.front_porch +
+			     horiz.sync_width,
+		.htotal = horiz.total,
+
+		.width_mm = width_mm,
+		.height_mm = height_mm,
+	};
+
+	drm_mode_set_name(mode);
+
+	out->timing_mode_id = id;
+	out->color_mode_id = best_color_mode;
+
+	return 0;
+}
+
+struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
+					 unsigned int *count, int width_mm,
+					 int height_mm)
+{
+	struct iterator it;
+	int ret;
+	struct dcp_display_mode *mode, *modes;
+	struct dcp_display_mode *best_mode = NULL;
+	s64 score, best_score = -1;
+
+	ret = iterator_begin(handle, &it, false);
+
+	if (ret)
+		return ERR_PTR(ret);
+
+	/* Start with a worst case allocation */
+	modes = kmalloc_array(it.len, sizeof(*modes), GFP_KERNEL);
+	*count = 0;
+
+	if (!modes)
+		return ERR_PTR(-ENOMEM);
+
+	for (; it.idx < it.len; ++it.idx) {
+		mode = &modes[*count];
+		ret = parse_mode(it.handle, mode, &score, width_mm, height_mm);
+
+		/* Errors for a single mode are recoverable -- just skip it. */
+		if (ret)
+			continue;
+
+		/* Process a successful mode */
+		(*count)++;
+
+		if (score > best_score) {
+			best_score = score;
+			best_mode = mode;
+		}
+	}
+
+	if (best_mode != NULL)
+		best_mode->mode.type |= DRM_MODE_TYPE_PREFERRED;
+
+	return modes;
+}
+
+int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
+			     int *height_mm)
+{
+	int ret = 0;
+	struct iterator it;
+	s64 width_cm = 0, height_cm = 0;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		char *key = parse_string(it.handle);
+
+		if (IS_ERR(key))
+			ret = PTR_ERR(key);
+		else if (!strcmp(key, "MaxHorizontalImageSize"))
+			ret = parse_int(it.handle, &width_cm);
+		else if (!strcmp(key, "MaxVerticalImageSize"))
+			ret = parse_int(it.handle, &height_cm);
+		else
+			skip(it.handle);
+
+		if (ret)
+			return ret;
+	}
+
+	/* 1cm = 10mm */
+	*width_mm = 10 * width_cm;
+	*height_mm = 10 * height_cm;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
new file mode 100644
index 00000000000000..afe3ed4700add7
--- /dev/null
+++ b/drivers/gpu/drm/apple/parser.h
@@ -0,0 +1,32 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#ifndef __APPLE_DCP_PARSER_H__
+#define __APPLE_DCP_PARSER_H__
+
+/* For mode parsing */
+#include <drm/drm_modes.h>
+
+struct dcp_parse_ctx {
+	void *blob;
+	u32 pos, len;
+};
+
+/*
+ * Represents a single display mode. These mode objects are populated at
+ * runtime based on the TimingElements dictionary sent by the DCP.
+ */
+struct dcp_display_mode {
+	struct drm_display_mode mode;
+	u32 color_mode_id;
+	u32 timing_mode_id;
+};
+
+int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx);
+struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
+					 unsigned int *count, int width_mm,
+					 int height_mm);
+int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
+			     int *height_mm);
+
+#endif

From 86b45c4e13f0c0f193d9b2a6dcb6f1d9403a4504 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 20 Mar 2022 18:44:00 +0100
Subject: [PATCH 0494/1027] drm: apple: Relicense DCP driver as dual MIT / GPL
 v2.0

Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747564
Link: https://oftc.irclog.whitequark.org/asahi-dev/2022-03-20#30747570

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Kconfig        | 2 +-
 drivers/gpu/drm/apple/Makefile       | 2 +-
 drivers/gpu/drm/apple/apple_drv.c    | 4 ++--
 drivers/gpu/drm/apple/dcp.c          | 4 ++--
 drivers/gpu/drm/apple/dcp.h          | 2 +-
 drivers/gpu/drm/apple/dcpep.h        | 2 +-
 drivers/gpu/drm/apple/dummy-piodma.c | 4 ++--
 drivers/gpu/drm/apple/parser.c       | 2 +-
 drivers/gpu/drm/apple/parser.h       | 2 +-
 9 files changed, 12 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index 53c7d4edfd017e..9b9bcb7b5433e0 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: GPL-2.0-only
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
 config DRM_APPLE
 	tristate "DRM Support for Apple display controllers"
 	depends on DRM && OF && ARM64
diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index db7ef359987d3d..8c758d5720b642 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -1,4 +1,4 @@
-# SPDX-License-Identifier: GPL-2.0-only
+# SPDX-License-Identifier: GPL-2.0-only OR MIT
 
 appledrm-y := apple_drv.o
 apple_dcp-y := dcp.o parser.o
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 38750adc4c6bd1..a312996cb553d1 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 /* Based on meson driver which is
  * Copyright (C) 2016 BayLibre, SAS
@@ -438,4 +438,4 @@ module_platform_driver(apple_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION(DRIVER_DESC);
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d43ed9021bd64c..9f4f13e01c138a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #include <linux/module.h>
@@ -1390,4 +1390,4 @@ module_platform_driver(apple_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION("Apple Display Controller DRM driver");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 4582fe984c8aa5..794544456df963 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #ifndef __APPLE_DCP_H__
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
index 6301bf8f8c21d1..f3d62d1d5f0328 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #ifndef __APPLE_DCPEP_H__
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
index a84a28b4dc2ae0..3d4454df4a25da 100644
--- a/drivers/gpu/drm/apple/dummy-piodma.c
+++ b/drivers/gpu/drm/apple/dummy-piodma.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #include <linux/module.h>
@@ -28,4 +28,4 @@ module_platform_driver(dcp_piodma_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim");
-MODULE_LICENSE("GPL v2");
+MODULE_LICENSE("Dual MIT/GPL");
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 7205179e7785aa..f0cd38c2a81048 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -1,4 +1,4 @@
-// SPDX-License-Identifier: GPL-2.0-only
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #include <linux/kernel.h>
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index afe3ed4700add7..66a675079dc164 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -1,4 +1,4 @@
-/* SPDX-License-Identifier: GPL-2.0-only */
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
 #ifndef __APPLE_DCP_PARSER_H__

From d4be6916b2117eafdd2cb4d2ec51111845b2660f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 25 Jul 2022 21:36:38 +0200
Subject: [PATCH 0495/1027] drm/apple: Start coprocessor on probe

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 18 ++++++++++++++----
 1 file changed, 14 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9f4f13e01c138a..6f36138e13cebc 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -20,6 +20,9 @@
 
 struct apple_dcp;
 
+#define APPLE_DCP_COPROC_CPU_CONTROL	 0x44
+#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4)
+
 /* Register defines used in bandwidth setup structure */
 #define REG_SCRATCH (0x14)
 #define REG_DOORBELL (0x0)
@@ -69,6 +72,9 @@ struct apple_dcp {
 	/* DCP shared memory */
 	void *shmem;
 
+	/* Coprocessor control register */
+	void __iomem *coproc_reg;
+
 	/* Display registers mappable to the DCP */
 	struct resource *disp_registers[MAX_DISP_REGISTERS];
 	unsigned int nr_disp_registers;
@@ -1301,9 +1307,9 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp)
 static int dcp_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
-	struct resource *res;
 	struct apple_dcp *dcp;
 	dma_addr_t shmem_iova;
+	u32 cpu_ctrl;
 	int ret;
 
 	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
@@ -1317,9 +1323,9 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "coproc");
-	if (!res)
-		return -EINVAL;
+	dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc");
+	if (IS_ERR(dcp->coproc_reg))
+		return PTR_ERR(dcp->coproc_reg);
 
 	of_platform_default_populate(dev->of_node, NULL, dev);
 
@@ -1335,6 +1341,10 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
+	writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN,
+		       dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
+
 	dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops);
 	if (IS_ERR(dcp->rtk))
 		return dev_err_probe(dev, PTR_ERR(dcp->rtk),

From 37b4d95a803292fd1796d9fdc9f6220a453dd4d6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 15 Jan 2022 18:29:55 +0100
Subject: [PATCH 0496/1027] HACK: drm/apple: avoid DCP swaps without attached
 surfaces

Xorg startup with modesetting driver triggers this. Move vblank
signalling to dcp to avoid a circular dependency between apple_drv
and dcp.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 18 ----------
 drivers/gpu/drm/apple/dcp.c       | 60 +++++++++++++++++++++++++++++--
 2 files changed, 57 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index a312996cb553d1..db1dcd83e2a21b 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -209,24 +209,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
 	}
 }
 
-void apple_crtc_vblank(struct apple_crtc *crtc)
-{
-	unsigned long flags;
-
-	if (crtc->vsync_disabled)
-		return;
-
-	drm_crtc_handle_vblank(&crtc->base);
-
-	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
-	if (crtc->event) {
-		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
-		drm_crtc_vblank_put(&crtc->base);
-		crtc->event = NULL;
-	}
-	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
-}
-
 static const struct drm_crtc_funcs apple_crtc_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6f36138e13cebc..5b2edf2579f4bd 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -4,6 +4,7 @@
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/of_device.h>
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/iommu.h>
 #include <linux/align.h>
@@ -13,6 +14,7 @@
 #include <drm/drm_fourcc.h>
 #include <drm/drm_framebuffer.h>
 #include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
 
 #include "dcpep.h"
 #include "dcp.h"
@@ -107,6 +109,9 @@ struct apple_dcp {
 
 	/* Attributes of the connected display */
 	int width_mm, height_mm;
+
+	/* Workqueue for sending vblank events when a dcp swap is not possible */
+	struct work_struct vblank_wq;
 };
 
 /*
@@ -363,9 +368,28 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp)
 	return 0;
 }
 
+/* HACK: moved here to avoid circular dependency between apple_drv and dcp */
+void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
+{
+	unsigned long flags;
+
+	if (crtc->vsync_disabled)
+		return;
+
+	drm_crtc_handle_vblank(&crtc->base);
+
+	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
+	if (crtc->event) {
+		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
+		drm_crtc_vblank_put(&crtc->base);
+		crtc->event = NULL;
+	}
+	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
+}
+
 static void dcpep_cb_swap_complete(struct apple_dcp *dcp)
 {
-	apple_crtc_vblank(dcp->crtc);
+	dcp_drm_crtc_vblank(dcp->crtc);
 }
 
 static struct dcp_get_uint_prop_resp
@@ -731,6 +755,21 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 	}
 }
 
+/*
+ * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp
+ * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks
+ * send a vblank event via a workqueue.
+ */
+static void dcp_delayed_vblank(struct work_struct *work)
+{
+	struct apple_dcp *dcp;
+
+	dcp = container_of(work, struct apple_dcp, vblank_wq);
+	mdelay(5);
+	dcp_drm_crtc_vblank(dcp->crtc);
+}
+
+
 #define DCPEP_MAX_CB (1000)
 
 /*
@@ -943,7 +982,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
 
 	if (resp->ret) {
 		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
-		apple_crtc_vblank(dcp->crtc);
+		dcp_drm_crtc_vblank(dcp->crtc);
 	}
 }
 
@@ -1061,12 +1100,16 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	struct drm_crtc_state *crtc_state;
 	struct dcp_swap_submit_req *req = &dcp->swap;
 	int l;
+	int has_surface = 0;
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
 	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
 	    WARN(!dcp->connector->connected, "can't flush if disconnected")) {
-		apple_crtc_vblank(dcp->crtc);
+		/* HACK: issue a delayed vblank event to avoid timeouts in
+		 * drm_atomic_helper_wait_for_vblanks().
+		 */
+		schedule_work(&dcp->vblank_wq);
 		return;
 	}
 
@@ -1090,6 +1133,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			continue;
 		}
 		req->surf_null[l] = false;
+		has_surface = 1;
 
 		// XXX: awful hack! race condition between a framebuffer unbind
 		// getting swapped out and GEM unreferencing a framebuffer. If
@@ -1148,6 +1192,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		dcp->valid_mode = true;
 
 		dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL);
+	}
+	else if (!has_surface) {
+		dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled);
+		/* HACK: issue a delayed vblank event to avoid timeouts in
+		 * drm_atomic_helper_wait_for_vblanks(). It's currently unkown
+		 * if and how DCP supports swaps without attached surfaces.
+		 */
+		schedule_work(&dcp->vblank_wq);
 	} else
 		do_swap(dcp, NULL, NULL);
 }
@@ -1341,6 +1393,8 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
+
 	cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
 	writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN,
 		       dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);

From 0b2ac98ba9c4a347a590590ffaaed252ef95f8b5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 18:34:58 +0200
Subject: [PATCH 0497/1027] drm/apple: Use a device tree defined clock for
 dcpep_cb_get_frequency

Frequency differs between M1 and M1 Pro/Max/Ultra.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 5b2edf2579f4bd..7535e817f90846 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/slab.h>
 #include <linux/of_device.h>
@@ -71,6 +72,9 @@ struct apple_dcp {
 	/* DCP has crashed */
 	bool crashed;
 
+	/* clock rate request by dcp in */
+	struct clk *clk;
+
 	/* DCP shared memory */
 	void *shmem;
 
@@ -511,8 +515,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 
 static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
 {
-	/* Pixel clock frequency in Hz (compare: 4K@60 VGA clock 533.250 MHz) */
-	return 533333328;
+	return clk_get_rate(dcp->clk);
 }
 
 static struct dcp_map_reg_resp
@@ -1393,6 +1396,10 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		return ret;
 	}
 
+	dcp->clk = devm_clk_get(dev, NULL);
+	if (IS_ERR(dcp->clk))
+		return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n");
+
 	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
 
 	cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);

From d8deec9d4ee0d748913d143e29a15f4cf36351bc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 12 Mar 2022 11:40:32 +0100
Subject: [PATCH 0498/1027] drm/apple: Fix rt_bandwidth for t600x

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 7535e817f90846..009f6a3880e3dd 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -28,6 +28,7 @@ struct apple_dcp;
 
 /* Register defines used in bandwidth setup structure */
 #define REG_SCRATCH (0x14)
+#define REG_SCRATCH_T600X (0x988)
 #define REG_DOORBELL (0x0)
 #define REG_DOORBELL_BIT (2)
 
@@ -700,13 +701,26 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in)
 
 static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 {
-	return (struct dcp_rt_bandwidth) {
-		.reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH,
-		.reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL,
-		.doorbell_bit = REG_DOORBELL_BIT,
+	if (dcp->disp_registers[5] && dcp->disp_registers[6])
+		return (struct dcp_rt_bandwidth) {
+			.reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH,
+			.reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL,
+			.doorbell_bit = REG_DOORBELL_BIT,
 
-		.padding[3] = 0x4, // XXX: required by 11.x firmware
-	};
+			.padding[3] = 0x4, // XXX: required by 11.x firmware
+		};
+	else if (dcp->disp_registers[4])
+		return (struct dcp_rt_bandwidth) {
+			.reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
+	else
+		return (struct dcp_rt_bandwidth) {
+			.reg_scratch = 0,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
 }
 
 /* Callback to get the current time as milliseconds since the UNIX epoch */

From 0daef4b65ff0f5eb081661749437370b96102162 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 12 Mar 2022 11:43:04 +0100
Subject: [PATCH 0499/1027] drm/apple: Add nop sr_set_uint_prop callback for
 t600x-dcp

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 009f6a3880e3dd..9752c9f8abdd19 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -891,6 +891,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *
 	[211] = trampoline_nop, /* update_backlight_factor_prop */
 	[300] = trampoline_nop, /* pr_publish */
 	[401] = trampoline_get_uint_prop,
+	[404] = trampoline_nop, /* sr_set_uint_prop */
 	[406] = trampoline_nop, /* set_fx_prop */
 	[408] = trampoline_get_frequency,
 	[411] = trampoline_map_reg,

From 8d5f859dab33708af3c3db86422635ae64a8722e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 12 Mar 2022 11:46:11 +0100
Subject: [PATCH 0500/1027] drm/apple: Reference only swapped out framebuffers

The framebuffer can be unreferenced by GEM while the display controller
is still using it for scanout resulting in IOVA faults and crashed dcp.
dcp has to hold a reference until the swap is complete.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 48 +++++++++++++++++++++++++++++++------
 1 file changed, 41 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9752c9f8abdd19..e9db4474028dd3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -117,6 +117,16 @@ struct apple_dcp {
 
 	/* Workqueue for sending vblank events when a dcp swap is not possible */
 	struct work_struct vblank_wq;
+
+	/* List of referenced drm_framebuffers which can be unreferenced
+	 * on the next successfully completed swap.
+	 */
+	struct list_head swapped_out_fbs;
+};
+
+struct dcp_fb_reference {
+	struct list_head head;
+	struct drm_framebuffer *fb;
 };
 
 /*
@@ -1001,6 +1011,17 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
 	if (resp->ret) {
 		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
 		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
 	}
 }
 
@@ -1144,6 +1165,24 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 		req->swap.swap_enabled |= BIT(l);
 
+		if (old_state->fb && fb != old_state->fb) {
+			/*
+			 * Race condition between a framebuffer unbind getting
+			 * swapped out and GEM unreferencing a framebuffer. If
+			 * we lose the race, the display gets IOVA faults and
+			 * the DCP crashes. We need to extend the lifetime of
+			 * the drm_framebuffer (and hence the GEM object) until
+			 * after we get a swap complete for the swap unbinding
+			 * it.
+			 */
+			struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
+			if (entry) {
+				entry->fb = old_state->fb;
+				list_add_tail(&entry->head, &dcp->swapped_out_fbs);
+			}
+			drm_framebuffer_get(old_state->fb);
+		}
+
 		if (!new_state->fb) {
 			if (old_state->fb)
 				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
@@ -1153,13 +1192,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->surf_null[l] = false;
 		has_surface = 1;
 
-		// XXX: awful hack! race condition between a framebuffer unbind
-		// getting swapped out and GEM unreferencing a framebuffer. If
-		// we lose the race, the display gets IOVA faults and the DCP
-		// crashes. We need to extend the lifetime of the
-		// drm_framebuffer (and hence the GEM object) until after we
-		// get a swap complete for the swap unbinding it.
-		drm_framebuffer_get(fb);
 
 		drm_rect_fp_to_int(&src_rect, &new_state->src);
 
@@ -1417,6 +1449,8 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
 
+	dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
+
 	cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
 	writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN,
 		       dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);

From f0d394e64b44d330b31a0444f3e9fbe08ef6ab0d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 12 Mar 2022 12:48:34 +0100
Subject: [PATCH 0501/1027] drm/apple: Use "apple,asc-dram-mask" for rtkit
 iovas

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 19 ++++++++++++++++---
 1 file changed, 16 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index e9db4474028dd3..7f5ce8db92620a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -76,6 +76,9 @@ struct apple_dcp {
 	/* clock rate request by dcp in */
 	struct clk *clk;
 
+	/* mask for DCP IO virtual addresses shared over rtkit */
+	u64 asc_dram_mask;
+
 	/* DCP shared memory */
 	void *shmem;
 
@@ -481,6 +484,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *
 
 	dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf,
 			resp.dva, resp.dva_size);
+	resp.dva |= dcp->asc_dram_mask;
 	return resp;
 }
 
@@ -520,7 +524,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 		.dva_size = size,
 		.mem_desc_id = ++dcp->nr_mappings,
 		.dva = dma_map_resource(dcp->dev, req->paddr, size,
-					DMA_BIDIRECTIONAL, 0),
+					DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask,
 	};
 }
 
@@ -1324,7 +1328,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 			return -ENOMEM;
 
 		// TODO: get map from device-tree
-		phy_addr = iommu_iova_to_phys(domain, bfr->iova & 0xFFFFFFFF);
+		phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask);
 		if (!phy_addr)
 			return -ENOMEM;
 
@@ -1341,6 +1345,8 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 		if (!bfr->buffer)
 			return -ENOMEM;
 
+		bfr->iova |= dcp->asc_dram_mask;
+
 		dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx",
 			 (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer);
 	}
@@ -1355,7 +1361,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
 	if (bfr->is_mapped)
 		memunmap(bfr->buffer);
 	else
-		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova);
+		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask);
 }
 
 static struct apple_rtkit_ops rtkit_ops = {
@@ -1447,6 +1453,12 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	if (IS_ERR(dcp->clk))
 		return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n");
 
+	ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask",
+				   &dcp->asc_dram_mask);
+	if (ret)
+		dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret);
+	dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask);
+
 	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
 
 	dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
@@ -1470,6 +1482,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova,
 					GFP_KERNEL);
 
+	shmem_iova |= dcp->asc_dram_mask;
 	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT,
 				 dcpep_set_shmem(shmem_iova), NULL, false);
 

From 99ae5e1e58a6cb2612c274f8635286d1052379b4 Mon Sep 17 00:00:00 2001
From: Alyssa Rosenzweig <alyssa@rosenzweig.io>
Date: Tue, 22 Mar 2022 16:24:53 -0400
Subject: [PATCH 0502/1027] drm/apple: Implement suspend/resume for DCP

Use the firmware setPowerState callback.

Signed-off-by: Alyssa Rosenzweig <alyssa@rosenzweig.io>
---
 drivers/gpu/drm/apple/dcp.c | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 7f5ce8db92620a..d976b1c08cf494 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1501,6 +1501,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 		/* defaults are ok */
 	};
 
+	/* We're going down */
+	dcp->active = false;
+	dcp->valid_mode = false;
+
 	dcp_set_power_state(dcp, false, &req, NULL, NULL);
 }
 
@@ -1510,12 +1514,41 @@ static const struct of_device_id of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, of_match);
 
+#ifdef CONFIG_PM_SLEEP
+/*
+ * We don't hold any useful persistent state, so for suspend/resume it suffices
+ * to power off/on the entire DCP. The firmware will sort out the details for
+ * us.
+ */
+static int dcp_suspend(struct device *dev)
+{
+	dcp_platform_shutdown(to_platform_device(dev));
+	return 0;
+}
+
+static int dcp_resume(struct device *dev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev));
+
+	dcp_start_signal(dcp, false, dcp_started, NULL);
+	return 0;
+}
+
+static const struct dev_pm_ops dcp_pm_ops = {
+	.suspend	= dcp_suspend,
+	.resume		= dcp_resume,
+};
+#endif
+
 static struct platform_driver apple_platform_driver = {
 	.probe		= dcp_platform_probe,
 	.shutdown	= dcp_platform_shutdown,
 	.driver	= {
 		.name = "apple-dcp",
 		.of_match_table	= of_match,
+#ifdef CONFIG_PM_SLEEP
+		.pm = &dcp_pm_ops,
+#endif
 	},
 };
 

From 81be5a4d8cfd07a9a9e2713b26650ae4452eac21 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 14 Apr 2022 18:57:20 +0200
Subject: [PATCH 0503/1027] drm/apple: dcp: fix TRAMPOLINE_IN macro

fixup! WIP: drm/apple: Add DCP display driver

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d976b1c08cf494..eac5a201d29271 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -817,11 +817,11 @@ static void dcp_delayed_vblank(struct work_struct *work)
 	}
 
 #define TRAMPOLINE_IN(func, handler, T_in)                                     \
-	typedef void (*callback_##name)(struct apple_dcp *, T_in *);           \
+	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);        \
                                                                                \
 	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
 	{                                                                      \
-		callback_##name cb = handler;                                  \
+		callback_##handler cb = handler;                               \
                                                                                \
 		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
 		cb(dcp, in);                                                   \

From 81e396069e7175a750f052d98e24519579ce08f1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 18:02:23 +0200
Subject: [PATCH 0504/1027] drm/apple: Switch to nonblocking commit handling

The swap completes only after the async reply from DCP. Uses
drm_atomic_helper_wait_for_flip_done instead of
drm_atomic_helper_wait_for_vblanks. This should allow ius to get rid
of the scheduled fake vblanks.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index db1dcd83e2a21b..f32e47ea93d433 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -209,6 +209,27 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
 	}
 }
 
+static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state)
+{
+	struct drm_device *dev = old_state->dev;
+
+	drm_atomic_helper_commit_modeset_disables(dev, old_state);
+
+	drm_atomic_helper_commit_modeset_enables(dev, old_state);
+
+	drm_atomic_helper_commit_planes(dev, old_state,
+					DRM_PLANE_COMMIT_ACTIVE_ONLY);
+
+	drm_atomic_helper_fake_vblank(old_state);
+
+	drm_atomic_helper_commit_hw_done(old_state);
+
+	drm_atomic_helper_wait_for_flip_done(dev, old_state);
+
+	drm_atomic_helper_cleanup_planes(dev, old_state);
+}
+
+
 static const struct drm_crtc_funcs apple_crtc_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
@@ -227,7 +248,7 @@ static const struct drm_mode_config_funcs apple_mode_config_funcs = {
 };
 
 static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = {
-	.atomic_commit_tail	= drm_atomic_helper_commit_tail_rpm,
+	.atomic_commit_tail	= dcp_atomic_commit_tail,
 };
 
 static const struct drm_connector_funcs apple_connector_funcs = {

From 2c7605e8d8df65c6417d2eede2c893c57914e88c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 18:15:30 +0200
Subject: [PATCH 0505/1027] drm/apple: Log callbacks with their tag as debug
 output

Mostly for the generic callbacks nop, true, false.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index eac5a201d29271..d314abc60ef630 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -707,8 +707,9 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
 }
 
 /* Use special function signature to defer the ACK */
-static bool dcpep_cb_boot_1(struct apple_dcp *dcp, void *out, void *in)
+static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
 {
+	dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__);
 	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
 	return false;
 }
@@ -809,9 +810,9 @@ static void dcp_delayed_vblank(struct work_struct *work)
  */
 
 #define TRAMPOLINE_VOID(func, handler)                                         \
-	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
 	{                                                                      \
-		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
 		handler(dcp);                                                  \
 		return true;                                                   \
 	}
@@ -819,11 +820,11 @@ static void dcp_delayed_vblank(struct work_struct *work)
 #define TRAMPOLINE_IN(func, handler, T_in)                                     \
 	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);        \
                                                                                \
-	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
 	{                                                                      \
 		callback_##handler cb = handler;                               \
                                                                                \
-		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
 		cb(dcp, in);                                                   \
 		return true;                                                   \
 	}
@@ -831,22 +832,22 @@ static void dcp_delayed_vblank(struct work_struct *work)
 #define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                           \
 	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);       \
                                                                                \
-	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
 	{                                                                      \
 		T_out *typed_out = out;                                        \
 		callback_##handler cb = handler;                               \
                                                                                \
-		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
 		*typed_out = cb(dcp, in);                                      \
 		return true;                                                   \
 	}
 
 #define TRAMPOLINE_OUT(func, handler, T_out)                                   \
-	static bool func(struct apple_dcp *dcp, void *out, void *in)           \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
 	{                                                                      \
 		T_out *typed_out = out;                                        \
                                                                                \
-		dev_dbg(dcp->dev, "received callback %s\n", #handler);         \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
 		*typed_out = handler(dcp);                                     \
 		return true;                                                   \
 	}
@@ -878,7 +879,7 @@ TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
 TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
 TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
 
-bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, void *, void *) = {
+bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = {
 	[0] = trampoline_true, /* did_boot_signal */
 	[1] = trampoline_true, /* did_power_on_signal */
 	[2] = trampoline_nop, /* will_power_off_signal */
@@ -950,7 +951,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 	depth = dcp_push_depth(&ch->depth);
 	ch->output[depth] = out;
 
-	if (dcpep_cb_handlers[tag](dcp, out, in))
+	if (dcpep_cb_handlers[tag](dcp, tag, out, in))
 		dcp_ack(dcp, context);
 }
 

From 7ebc05f03bcfc4c137bfa100115280748236d153 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 18:19:28 +0200
Subject: [PATCH 0506/1027] drm/apple: Add DCP interface definitions used on
 t600x

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 12 ++++++++++++
 drivers/gpu/drm/apple/dcpep.h | 26 ++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d314abc60ef630..f94f3e1a923632 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -225,8 +225,11 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	DCP_METHOD("A407", dcpep_swap_start),
 	DCP_METHOD("A408", dcpep_swap_submit),
 	DCP_METHOD("A410", dcpep_set_display_device),
+	DCP_METHOD("A411", dcpep_is_main_display),
 	DCP_METHOD("A412", dcpep_set_digital_out_mode),
+	DCP_METHOD("A439", dcpep_set_parameter_dcp),
 	DCP_METHOD("A443", dcpep_create_default_fb),
+	DCP_METHOD("A447", dcpep_enable_disable_video_power_savings),
 	DCP_METHOD("A454", dcpep_first_client_open),
 	DCP_METHOD("A460", dcpep_set_display_refresh_properties),
 	DCP_METHOD("A463", dcpep_flush_supports_power),
@@ -336,6 +339,15 @@ __attribute__((unused))
 DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp,
 	     struct dcp_update_notify_clients_dcp);
 
+DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
+		struct dcp_set_parameter_dcp, u32);
+
+DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
+		dcpep_enable_disable_video_power_savings,
+		u32, int);
+
+DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
+
 /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
 static int dcp_parse_tag(char tag[4])
 {
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
index f3d62d1d5f0328..d04a1b9ca9c55f 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -241,6 +241,9 @@ enum dcpep_method {
 	dcpep_set_power_state,
 	dcpep_first_client_open,
 	dcpep_update_notify_clients_dcp,
+	dcpep_set_parameter_dcp,
+	dcpep_enable_disable_video_power_savings,
+	dcpep_is_main_display,
 	dcpep_num_methods
 };
 
@@ -350,6 +353,15 @@ struct dcp_swap_submit_resp {
 	u8 padding[3];
 } __packed;
 
+struct dc_swap_complete_resp {
+	u32 swap_id;
+	u8 unkbool;
+	u64 swap_data;
+	u8 swap_info[0x6c4];
+	u32 unkint;
+	u8 swap_info_null;
+} __packed;
+
 struct dcp_get_uint_prop_req {
 	char obj[4];
 	char key[0x40];
@@ -403,4 +415,18 @@ struct dcp_update_notify_clients_dcp {
 	u32 client_d;
 } __packed;
 
+struct dcp_set_parameter_dcp {
+	u32 param;
+	u32 value[8];
+	u32 count;
+} __packed;
+
+struct dcp_swap_complete_intent_gated {
+	u32 swap_id;
+	u8 unkBool;
+	u32 unkInt;
+	u32 width;
+	u32 height;
+} __packed;
+
 #endif

From 5f09f1b3b55097adeec0c17b21ccb11ad1080783 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 18:43:01 +0200
Subject: [PATCH 0507/1027] drm/apple: Clear used callback/cookie on dcp_ack

Avoids unexpected callbacks on nesting state errors. Encountered when
making DCP calls from a second thread for the backlight.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index f94f3e1a923632..7e242e0f7ad747 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -985,6 +985,9 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
 	cb = ch->callbacks[ch->depth];
 	cookie = ch->cookies[ch->depth];
 
+	ch->callbacks[ch->depth] = NULL;
+	ch->cookies[ch->depth] = NULL;
+
 	if (cb)
 		cb(dcp, data + sizeof(*header) + header->in_len, cookie);
 }

From d6d5a2b3c19c6ed68492d5aa3d4d3570253a6751 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 19:31:16 +0200
Subject: [PATCH 0508/1027] drm/apple: Add t600x support

Call power-on/-off handling explicitly from apple_crtc_atomic_enable /
apple_crtc_atomic_disable. This makes DPMS work.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |   4 +
 drivers/gpu/drm/apple/dcp.c       | 302 +++++++++++++++++++++++++++---
 drivers/gpu/drm/apple/dcp.h       |   2 +
 3 files changed, 280 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index f32e47ea93d433..d1b9a7891fba34 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -176,13 +176,17 @@ apple_connector_detect(struct drm_connector *connector, bool force)
 static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 				     struct drm_atomic_state *state)
 {
+	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+	dcp_poweron(apple_crtc->dcp);
 	drm_crtc_vblank_on(crtc);
 }
 
 static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
 				      struct drm_atomic_state *state)
 {
+	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
 	drm_crtc_vblank_off(crtc);
+	dcp_poweroff(apple_crtc->dcp);
 
 	if (crtc->state->event && !crtc->state->active) {
 		spin_lock_irq(&crtc->dev->event_lock);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 7e242e0f7ad747..e766591461fbd3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -10,6 +10,7 @@
 #include <linux/iommu.h>
 #include <linux/align.h>
 #include <linux/soc/apple/rtkit.h>
+#include <linux/completion.h>
 
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
@@ -111,6 +112,11 @@ struct apple_dcp {
 	/* Is the DCP booted? */
 	bool active;
 
+	/* eDP display without DP-HDMI conversion */
+	bool main_display;
+
+	bool ignore_swap_complete;
+
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
@@ -132,6 +138,11 @@ struct dcp_fb_reference {
 	struct drm_framebuffer *fb;
 };
 
+struct dcp_wait_cookie {
+	struct completion done;
+	atomic_t refcount;
+};
+
 /*
  * A channel is busy if we have sent a message that has yet to be
  * acked. The driver must not sent a message to a busy channel.
@@ -417,9 +428,11 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
 }
 
-static void dcpep_cb_swap_complete(struct apple_dcp *dcp)
+static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
+	      struct dc_swap_complete_resp *resp)
 {
-	dcp_drm_crtc_vblank(dcp->crtc);
+	if (!dcp->ignore_swap_complete)
+		dcp_drm_crtc_vblank(dcp->crtc);
 }
 
 static struct dcp_get_uint_prop_resp
@@ -756,6 +769,182 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
 	return ktime_to_ms(ktime_get_real());
 }
 
+struct dcp_swap_cookie {
+	struct completion done;
+	atomic_t refcount;
+	u32 swap_id;
+};
+
+static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_submit_resp *resp = data;
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		complete(&info->done);
+		if (atomic_dec_and_test(&info->refcount))
+			kfree(info);
+	}
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
+		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
+	}
+}
+
+static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
+				   void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+	dcp->swap.swap.swap_id = resp->swap_id;
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		info->swap_id = resp->swap_id;
+	}
+
+	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie);
+}
+
+static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
+}
+
+static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_parameter_dcp param = {
+		.param = 14,
+		.value = { 0 },
+		.count = 1,
+	};
+
+	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_final, cookie);
+}
+
+void dcp_poweron(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct dcp_wait_cookie * cookie;
+	struct dcp_set_power_state_req req = {
+		.unklong = 1,
+	};
+	int ret;
+	u32 handle;
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+
+	init_completion(&cookie->done);
+	atomic_set(&cookie->refcount, 2);
+
+	if (dcp->main_display) {
+		handle = 0;
+		dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie);
+	} else {
+		handle = 2;
+		dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie);
+	}
+	dcp_set_power_state(dcp, true, &req, NULL, NULL);
+
+	ret = wait_for_completion_timeout(&cookie->done,  msecs_to_jiffies(500));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "wait for power timed out");
+
+	if (atomic_dec_and_test(&cookie->refcount))
+		kfree(cookie);
+}
+EXPORT_SYMBOL(dcp_poweron);
+
+static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
+}
+
+void dcp_poweroff(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	int ret, swap_id;
+	struct dcp_set_power_state_req power_req = {
+		.unklong = 0,
+	};
+	struct dcp_swap_cookie *cookie;
+	struct dcp_wait_cookie *poff_cookie;
+	struct dcp_swap_start_req swap_req= { 0 };
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+	init_completion(&cookie->done);
+	atomic_set(&cookie->refcount, 2);
+
+	// clear surfaces
+	memset(&dcp->swap, 0, sizeof(dcp->swap));
+
+	dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7;
+	dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7;
+	dcp->swap.swap.unk_10c = 0xFF000000;
+
+	for (int l = 0; l < SWAP_SURFACES; l++)
+		dcp->swap.surf_null[l] = true;
+
+	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
+
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
+	swap_id = cookie->swap_id;
+	if (atomic_dec_and_test(&cookie->refcount))
+		kfree(cookie);
+	if (ret <= 0) {
+		dcp->crashed = true;
+		return;
+	}
+
+	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
+	if (!poff_cookie)
+		return;
+	init_completion(&poff_cookie->done);
+	atomic_set(&poff_cookie->refcount, 2);
+
+	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie);
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
+	else if (ret > 0)
+		dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare",
+			jiffies_to_msecs(ret));
+
+	if (atomic_dec_and_test(&poff_cookie->refcount))
+		kfree(poff_cookie);
+	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
+}
+EXPORT_SYMBOL(dcp_poweroff);
+
 /*
  * Helper to send a DRM hotplug event. The DCP is accessed from a single
  * (RTKit) thread. To handle hotplug callbacks, we need to call
@@ -767,16 +956,19 @@ void dcp_hotplug(struct work_struct *work)
 {
 	struct apple_connector *connector;
 	struct drm_device *dev;
+	struct apple_dcp *dcp;
 
 	connector = container_of(work, struct apple_connector, hotplug_wq);
 	dev = connector->base.dev;
 
+	dcp = platform_get_drvdata(connector->dcp);
+
 	/*
 	 * DCP defers link training until we set a display mode. But we set
 	 * display modes from atomic_flush, so userspace needs to trigger a
 	 * flush, or the CRTC gets no signal.
 	 */
-	if (connector->connected) {
+	if (!dcp->valid_mode && connector->connected) {
 		drm_connector_set_link_status_property(
 			&connector->base, DRM_MODE_LINK_STATUS_BAD);
 	}
@@ -791,10 +983,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 	struct apple_connector *connector = dcp->connector;
 
 	/* Hotplug invalidates mode. DRM doesn't always handle this. */
-	dcp->valid_mode = false;
+	if (!(*connected)) {
+		dcp->valid_mode = false;
+		/* after unplug swap will not complete until the next
+		 * set_digital_out_mode */
+		schedule_work(&dcp->vblank_wq);
+	}
 
-	if (connector) {
+	if (connector && connector->connected != !!(*connected)) {
 		connector->connected = !!(*connected);
+		dcp->valid_mode = false;
 		schedule_work(&connector->hotplug_wq);
 	}
 }
@@ -868,7 +1066,8 @@ TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
 TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
 TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
 TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
-TRAMPOLINE_VOID(trampoline_swap_complete, dcpep_cb_swap_complete);
+TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
+	      struct dc_swap_complete_resp);
 TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
 		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
 TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
@@ -906,6 +1105,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[110] = trampoline_true, /* create_iomfb_service */
 	[111] = trampoline_false, /* create_backlight_service */
 	[116] = dcpep_cb_boot_1,
+	[117] = trampoline_false, /* is_dark_boot */
 	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
 	[120] = trampoline_false, /* read_edt_data */
 	[122] = trampoline_prop_start,
@@ -938,6 +1138,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[582] = trampoline_true, /* create_default_fb_surface */
 	[589] = trampoline_swap_complete,
 	[591] = trampoline_nop, /* swap_complete_intent_gated */
+	[593] = trampoline_nop, /* enable_backlight_message_ap_gated */
 	[598] = trampoline_nop, /* find_swap_function_gated */
 };
 
@@ -1142,12 +1343,24 @@ static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct dcp_swap_start_req start_req = { 0 };
 
-	dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
+	if (dcp->connector && dcp->connector->connected)
+		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
+	else
+		dcp_drm_crtc_vblank(dcp->crtc);
 }
 
-static void dcp_modeset(struct apple_dcp *dcp, void *out, void *cookie)
+static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
+					  void *cookie)
 {
-	dcp_set_digital_out_mode(dcp, false, &dcp->mode, do_swap, NULL);
+	struct dcp_wait_cookie *wait = cookie;
+
+	dcp->ignore_swap_complete = false;
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
 }
 
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
@@ -1244,7 +1457,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 	if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) {
 		struct dcp_display_mode *mode;
-		u32 handle = 2;
+		struct dcp_wait_cookie *cookie;
+		int ret;
 
 		mode = lookup_mode(dcp, &crtc_state->mode);
 		if (!mode) {
@@ -1259,19 +1473,43 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			.timing_mode_id = mode->timing_mode_id
 		};
 
-		dcp->valid_mode = true;
+		cookie = kzalloc(sizeof(cookie), GFP_KERNEL);
+		if (!cookie) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		init_completion(&cookie->done);
+		atomic_set(&cookie->refcount, 2);
+
+		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
+					 complete_set_digital_out_mode, cookie);
+
+		ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
 
-		dcp_set_display_device(dcp, false, &handle, dcp_modeset, NULL);
+		if (atomic_dec_and_test(&cookie->refcount))
+			kfree(cookie);
+
+		if (ret == 0) {
+			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+		else if (ret > 0) {
+			dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare",
+				jiffies_to_msecs(ret));
+		}
+
+		dcp->valid_mode = true;
 	}
-	else if (!has_surface) {
-		dev_warn(dcp->dev, "can't flush without surfaces, vsync:%d", dcp->crtc->vsync_disabled);
-		/* HACK: issue a delayed vblank event to avoid timeouts in
-		 * drm_atomic_helper_wait_for_vblanks(). It's currently unkown
-		 * if and how DCP supports swaps without attached surfaces.
-		 */
+
+	if (!has_surface) {
+		dev_warn(dcp->dev, "flush without surfaces, vsync:%d",
+				dcp->crtc->vsync_disabled);
 		schedule_work(&dcp->vblank_wq);
-	} else
-		do_swap(dcp, NULL, NULL);
+		return;
+	}
+	do_swap(dcp, NULL, NULL);
 }
 EXPORT_SYMBOL_GPL(dcp_flush);
 
@@ -1283,16 +1521,20 @@ bool dcp_is_initialized(struct platform_device *pdev)
 }
 EXPORT_SYMBOL_GPL(dcp_is_initialized);
 
-static void init_done(struct apple_dcp *dcp, void *out, void *cookie)
+
+static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
 {
+	int result = *(int *)out;
+	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
+
+	dcp->main_display = result != 0;
+
+	dcp->active = true;
 }
 
 static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
 {
-	struct dcp_set_power_state_req req = {
-		.unklong = 1,
-	};
-	dcp_set_power_state(dcp, false, &req, init_done, NULL);
+	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
 }
 
 static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
@@ -1300,13 +1542,17 @@ static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
 	dcp_first_client_open(dcp, false, init_3, NULL);
 }
 
+static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 val = 0;
+	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
+}
+
 static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	dev_info(dcp->dev, "DCP booted\n");
 
-	init_2(dcp, data, cookie);
-
-	dcp->active = true;
+	init_1(dcp, data, cookie);
 }
 
 static void dcp_got_msg(void *cookie, u8 endpoint, u64 message)
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 794544456df963..9e3e3738a39377 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -32,6 +32,8 @@ struct apple_connector {
 
 #define to_apple_connector(x) container_of(x, struct apple_connector, base)
 
+void dcp_poweroff(struct platform_device *pdev);
+void dcp_poweron(struct platform_device *pdev);
 void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 	      struct apple_connector *connector);
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);

From 78b34be00d72498a3b6318554387009a5eb84241 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Jun 2022 22:14:27 +0200
Subject: [PATCH 0509/1027] drm/apple: toggle power only when active state
 changes

squash! WIP: GPU/apple: t600x work in progress

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index d1b9a7891fba34..43d451b1fe9a86 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -176,17 +176,32 @@ apple_connector_detect(struct drm_connector *connector, bool force)
 static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 				     struct drm_atomic_state *state)
 {
-	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
-	dcp_poweron(apple_crtc->dcp);
+	struct drm_crtc_state *crtc_state;
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	if (crtc_state->active_changed && crtc_state->active) {
+		struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+		dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
+		dcp_poweron(apple_crtc->dcp);
+		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
+	}
 	drm_crtc_vblank_on(crtc);
 }
 
 static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
 				      struct drm_atomic_state *state)
 {
-	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+	struct drm_crtc_state *crtc_state;
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
 	drm_crtc_vblank_off(crtc);
-	dcp_poweroff(apple_crtc->dcp);
+
+	if (crtc_state->active_changed && !crtc_state->active) {
+		struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+		dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
+		dcp_poweroff(apple_crtc->dcp);
+		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
+	}
 
 	if (crtc->state->event && !crtc->state->active) {
 		spin_lock_irq(&crtc->dev->event_lock);

From 39ef20a2d1ad573400e33914c268debe8624aaf7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 19:39:10 +0200
Subject: [PATCH 0510/1027] drm/apple: Add somewhat useful debug prints

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 24 +++++++++++++++++++++++-
 1 file changed, 23 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index e766591461fbd3..af4ffb18c09d0b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -431,6 +431,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 	      struct dc_swap_complete_resp *resp)
 {
+	dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u",
+		resp->swap_id, dcp->ignore_swap_complete);
+
 	if (!dcp->ignore_swap_complete)
 		dcp_drm_crtc_vblank(dcp->crtc);
 }
@@ -699,6 +702,7 @@ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
 {
 	struct dcp_cb_channel *ch = &dcp->ch_cb;
 	u8 *succ = ch->output[ch->depth - 1];
+	dev_dbg(dcp->dev, "boot done");
 
 	*succ = true;
 	dcp_ack(dcp, DCP_CONTEXT_CB);
@@ -807,6 +811,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
 				   void *cookie)
 {
 	struct dcp_swap_start_resp *resp = data;
+	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
 	dcp->swap.swap.swap_id = resp->swap_id;
 
 	if (cookie) {
@@ -924,6 +929,8 @@ void dcp_poweroff(struct platform_device *pdev)
 		return;
 	}
 
+	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
+
 	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
 	if (!poff_cookie)
 		return;
@@ -962,6 +969,7 @@ void dcp_hotplug(struct work_struct *work)
 	dev = connector->base.dev;
 
 	dcp = platform_get_drvdata(connector->dcp);
+	dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected);
 
 	/*
 	 * DCP defers link training until we set a display mode. But we set
@@ -997,6 +1005,14 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 	}
 }
 
+static void
+dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
+				    struct dcp_swap_complete_intent_gated *info)
+{
+	dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id,
+		info->width, info->height);
+}
+
 /*
  * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp
  * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks
@@ -1089,6 +1105,9 @@ TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
 TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
 TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
 TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
+TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
+	      dcpep_cb_swap_complete_intent_gated,
+	      struct dcp_swap_complete_intent_gated);
 
 bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = {
 	[0] = trampoline_true, /* did_boot_signal */
@@ -1137,7 +1156,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[577] = trampoline_nop, /* powerstate_notify */
 	[582] = trampoline_true, /* create_default_fb_surface */
 	[589] = trampoline_swap_complete,
-	[591] = trampoline_nop, /* swap_complete_intent_gated */
+	[591] = trampoline_swap_complete_intent_gated,
 	[593] = trampoline_nop, /* enable_backlight_message_ap_gated */
 	[598] = trampoline_nop, /* find_swap_function_gated */
 };
@@ -1468,6 +1487,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			return;
 		}
 
+		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
+			 mode->color_mode_id, mode->timing_mode_id);
 		dcp->mode = (struct dcp_set_digital_out_mode_req) {
 			.color_mode_id = mode->color_mode_id,
 			.timing_mode_id = mode->timing_mode_id
@@ -1485,6 +1506,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
 					 complete_set_digital_out_mode, cookie);
 
+		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
 		ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
 
 		if (atomic_dec_and_test(&cookie->refcount))

From c84fe801a630c5e31426ca325e41310df5b313b6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 31 Jul 2022 19:40:50 +0200
Subject: [PATCH 0511/1027] drm/apple: Add less tons of questionable debug
 prints

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index af4ffb18c09d0b..d05ae66e35748d 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -782,6 +782,7 @@ struct dcp_swap_cookie {
 static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct dcp_swap_submit_resp *resp = data;
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (cookie) {
 		struct dcp_swap_cookie *info = cookie;
@@ -825,6 +826,7 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
 static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
 {
 	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (wait) {
 		complete(&wait->done);
@@ -840,6 +842,7 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
 		.value = { 0 },
 		.count = 1,
 	};
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_final, cookie);
 }
@@ -853,6 +856,7 @@ void dcp_poweron(struct platform_device *pdev)
 	};
 	int ret;
 	u32 handle;
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
 	if (!cookie)
@@ -902,6 +906,8 @@ void dcp_poweroff(struct platform_device *pdev)
 	struct dcp_wait_cookie *poff_cookie;
 	struct dcp_swap_start_req swap_req= { 0 };
 
+	dev_dbg(dcp->dev, "%s", __func__);
+
 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
 	if (!cookie)
 		return;
@@ -1361,6 +1367,7 @@ EXPORT_SYMBOL_GPL(dcp_mode_valid);
 static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct dcp_swap_start_req start_req = { 0 };
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (dcp->connector && dcp->connector->connected)
 		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
@@ -1372,6 +1379,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
 					  void *cookie)
 {
 	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	dcp->ignore_swap_complete = false;
 
@@ -1392,6 +1400,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	struct dcp_swap_submit_req *req = &dcp->swap;
 	int l;
 	int has_surface = 0;
+	dev_dbg(dcp->dev, "%s", __func__);
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 

From 820190442f02cea9698e11c3cd120a841a7fd451 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 5 Jun 2022 19:32:01 +0200
Subject: [PATCH 0512/1027] drm/apple: implement read_edt_data

Handling it with trampoline_false can result in errors due to
uninitialized data.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 13 ++++++++++++-
 drivers/gpu/drm/apple/dcpep.h | 11 +++++++++++
 2 files changed, 23 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d05ae66e35748d..a36217145ec6e9 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -581,6 +581,15 @@ dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req)
 	}
 }
 
+static struct dcp_read_edt_data_resp
+dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
+{
+	return (struct dcp_read_edt_data_resp) {
+		.value[0] = req->value[0],
+		.ret = 0,
+	};
+}
+
 /* Chunked data transfer for property dictionaries */
 static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
 {
@@ -1101,6 +1110,8 @@ TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
 		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
 TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
 		 struct dcp_map_reg_resp);
+TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
+		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
 TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
 TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
 		 struct dcp_set_dcpav_prop_chunk_req, u8);
@@ -1132,7 +1143,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[116] = dcpep_cb_boot_1,
 	[117] = trampoline_false, /* is_dark_boot */
 	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
-	[120] = trampoline_false, /* read_edt_data */
+	[120] = trampoline_read_edt_data,
 	[122] = trampoline_prop_start,
 	[123] = trampoline_prop_chunk,
 	[124] = trampoline_prop_end,
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
index d04a1b9ca9c55f..12d81c7b4e2734 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -429,4 +429,15 @@ struct dcp_swap_complete_intent_gated {
 	u32 height;
 } __packed;
 
+struct dcp_read_edt_data_req {
+	char key[0x40];
+	u32 count;
+	u32 value[8];
+} __packed;
+
+struct dcp_read_edt_data_resp {
+	u32 value[8];
+	u8 ret;
+} __packed;
+
 #endif

From 5b9cd896f038f3364f26424594565ce9cbd52a94 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 5 Jun 2022 19:47:25 +0200
Subject: [PATCH 0513/1027] drm/apple: clear callback's output data

If there is a mismatch in the output size this way we have at leas
consistant results.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index a36217145ec6e9..5250eb5da0d4a3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1197,6 +1197,11 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 	in = data + sizeof(*hdr);
 	out = in + hdr->in_len;
 
+	// TODO: verify that in_len and out_len match our prototypes
+	// for now just clear the out data to have at least consistant results
+	if (hdr->out_len)
+		memset(out, 0, hdr->out_len);
+
 	depth = dcp_push_depth(&ch->depth);
 	ch->output[depth] = out;
 

From 7287c42d8892aca97f13264afc5677aefea80d5e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 5 Jun 2022 20:00:13 +0200
Subject: [PATCH 0514/1027] drm/apple: Support memory unmapping/freeing

Used by dcp on mode changes on the Macbook Pro 14" (M1 Max).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 129 ++++++++++++++++++++++++++++++----
 drivers/gpu/drm/apple/dcpep.h |   8 +++
 2 files changed, 122 insertions(+), 15 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 5250eb5da0d4a3..913664aa1f97c8 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <linux/bitmap.h>
 #include <linux/clk.h>
 #include <linux/module.h>
 #include <linux/slab.h>
@@ -64,6 +65,14 @@ struct dcp_chunks {
 #define DCP_MAX_MAPPINGS (128) /* should be enough */
 #define MAX_DISP_REGISTERS (7)
 
+struct dcp_mem_descriptor {
+	size_t size;
+	void *buf;
+	dma_addr_t dva;
+	struct sg_table map;
+	u64 reg;
+};
+
 struct apple_dcp {
 	struct device *dev;
 	struct platform_device *piodma;
@@ -90,11 +99,11 @@ struct apple_dcp {
 	struct resource *disp_registers[MAX_DISP_REGISTERS];
 	unsigned int nr_disp_registers;
 
-	/* Number of memory mappings made by the DCP, used as an ID */
-	u32 nr_mappings;
+	/* Bitmap of memory descriptors used for mappings made by the DCP */
+	DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS);
 
-	/* Indexed table of mappings */
-	struct sg_table mappings[DCP_MAX_MAPPINGS];
+	/* Indexed table of memory descriptors */
+	struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
 
 	struct dcp_call_channel ch_cmd, ch_oobcmd;
 	struct dcp_cb_channel ch_cb, ch_oobcb, ch_async;
@@ -462,10 +471,10 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req)
 	struct sg_table *map;
 	int ret;
 
-	if (req->buffer >= ARRAY_SIZE(dcp->mappings))
+	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
 		goto reject;
 
-	map = &dcp->mappings[req->buffer];
+	map = &dcp->memdesc[req->buffer].map;
 
 	if (!map->sgl)
 		goto reject;
@@ -488,6 +497,37 @@ dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req)
 	};
 }
 
+static void
+dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp)
+{
+	struct sg_table *map;
+	dma_addr_t dma_addr;
+
+	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
+		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
+			 resp->buffer);
+		return;
+	}
+
+	map = &dcp->memdesc[resp->buffer].map;
+
+	if (!map->sgl) {
+		dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx",
+			 resp->buffer, resp->dva);
+		return;
+	}
+
+	dma_addr = sg_dma_address(map->sgl);
+	if (dma_addr != resp->dva) {
+		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
+			 resp->buffer, dma_addr, resp->dva);
+		return;
+	}
+
+	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
+	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+}
+
 /*
  * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
  * physically contigiuous, however we should save the sgtable in case the
@@ -497,25 +537,68 @@ static struct dcp_allocate_buffer_resp
 dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req)
 {
 	struct dcp_allocate_buffer_resp resp = { 0 };
-	void *buf;
+	struct dcp_mem_descriptor *memdesc;
+	u32 id;
 
 	resp.dva_size = ALIGN(req->size, 4096);
-	resp.mem_desc_id = ++dcp->nr_mappings;
+	resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
 
-	if (resp.mem_desc_id >= ARRAY_SIZE(dcp->mappings)) {
+	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
 		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
+		resp.dva_size = 0;
+		resp.mem_desc_id = 0;
 		return resp;
 	}
+	id = resp.mem_desc_id;
+	set_bit(id, dcp->memdesc_map);
 
-	buf = dma_alloc_coherent(dcp->dev, resp.dva_size, &resp.dva,
+	memdesc = &dcp->memdesc[id];
+
+	memdesc->size = resp.dva_size;
+	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva,
 				 GFP_KERNEL);
 
-	dma_get_sgtable(dcp->dev, &dcp->mappings[resp.mem_desc_id], buf,
-			resp.dva, resp.dva_size);
-	resp.dva |= dcp->asc_dram_mask;
+	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf,
+			memdesc->dva, memdesc->size);
+	resp.dva = memdesc->dva;
+
 	return resp;
 }
 
+static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
+{
+	struct dcp_mem_descriptor *memdesc;
+	u32 id = *mem_desc_id;
+
+	if (id >= DCP_MAX_MAPPINGS) {
+		dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u",
+				id);
+		return 0;
+	}
+
+	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
+		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
+				id);
+		return 0;
+	}
+
+	memdesc = &dcp->memdesc[id];
+	if (memdesc->buf) {
+
+		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
+				 memdesc->dva);
+
+		memdesc->buf = NULL;
+		memset(&memdesc->map, 0, sizeof(memdesc->map));
+	} else {
+		memdesc->reg = 0;
+	}
+
+	memdesc->size = 0;
+
+	return 1;
+}
+
 /* Validate that the specified region is a display register */
 static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
 {
@@ -541,6 +624,7 @@ static struct dcp_map_physical_resp
 dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 {
 	int size = ALIGN(req->size, 4096);
+	u32 id;
 
 	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
 		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
@@ -548,11 +632,16 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 		return (struct dcp_map_physical_resp) { };
 	}
 
+	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+	set_bit(id, dcp->memdesc_map);
+	dcp->memdesc[id].size = size;
+	dcp->memdesc[id].reg = req->paddr;
+
 	return (struct dcp_map_physical_resp) {
 		.dva_size = size,
-		.mem_desc_id = ++dcp->nr_mappings,
+		.mem_desc_id = id,
 		.dva = dma_map_resource(dcp->dev, req->paddr, size,
-					DMA_BIDIRECTIONAL, 0) | dcp->asc_dram_mask,
+					DMA_BIDIRECTIONAL, 0),
 	};
 }
 
@@ -1103,11 +1192,15 @@ TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
 		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
 TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
 		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
+TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
+	      struct dcp_unmap_buf_resp);
 TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
 		 struct dcp_allocate_buffer_req,
 		 struct dcp_allocate_buffer_resp);
 TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
 		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
+TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc,
+		 u32, u8);
 TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
 		 struct dcp_map_reg_resp);
 TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
@@ -1148,6 +1241,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[123] = trampoline_prop_chunk,
 	[124] = trampoline_prop_end,
 	[201] = trampoline_map_piodma,
+	[202] = trampoline_unmap_piodma,
 	[206] = trampoline_true, /* match_pmu_service_2 */
 	[207] = trampoline_true, /* match_backlight_service */
 	[208] = trampoline_get_time,
@@ -1163,6 +1257,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, v
 	[415] = trampoline_true, /* sr_set_property_bool */
 	[451] = trampoline_allocate_buffer,
 	[452] = trampoline_map_physical,
+	[456] = trampoline_release_mem_desc,
 	[552] = trampoline_true, /* set_property_dict_0 */
 	[561] = trampoline_true, /* set_property_dict */
 	[563] = trampoline_true, /* set_property_int */
@@ -1768,6 +1863,10 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret);
 	dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask);
 
+	bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+	// TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry
+	set_bit(0, dcp->memdesc_map);
+
 	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
 
 	dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
index 12d81c7b4e2734..7c4fd97c45e54e 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -273,6 +273,14 @@ struct dcp_map_buf_resp {
 	u32 ret;
 } __packed;
 
+struct dcp_unmap_buf_resp {
+	u64 buffer;
+	u64 vaddr;
+	u64 dva;
+	u8 unk;
+	u8 buf_null;
+} __packed;
+
 struct dcp_allocate_buffer_req {
 	u32 unk0;
 	u64 size;

From e678b161800c9ffce484dbcb52ce9a1ae7a45258 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Jun 2022 12:53:18 +0200
Subject: [PATCH 0515/1027] WIP: drm/apple: Change the way to clear unused
 surfaces

This seems to be incorrect but the flag seems to be related to clearing.

Allows unmapping surfaces after the swap finished.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 10 ++++++----
 drivers/gpu/drm/apple/dcpep.h |  2 +-
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 913664aa1f97c8..4b3a37fbf1e938 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1646,10 +1646,12 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	}
 
 	if (!has_surface) {
-		dev_warn(dcp->dev, "flush without surfaces, vsync:%d",
-				dcp->crtc->vsync_disabled);
-		schedule_work(&dcp->vblank_wq);
-		return;
+		if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		req->clear = 1;
 	}
 	do_swap(dcp, NULL, NULL);
 }
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/dcpep.h
index 7c4fd97c45e54e..a796b16bd55ad7 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/dcpep.h
@@ -348,7 +348,7 @@ struct dcp_swap_submit_req {
 	u64 surf_iova[SWAP_SURFACES];
 	u8 unkbool;
 	u64 unkdouble;
-	u32 unkint;
+	u32 clear; // or maybe switch to default fb?
 	u8 swap_null;
 	u8 surf_null[SWAP_SURFACES];
 	u8 unkoutbool_null;

From 5ed06470096e47a447d3547b0747350996f7669c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 27 Sep 2022 00:02:20 +0200
Subject: [PATCH 0516/1027] drm/apple: laod piodma dev via explicit phandle

Use a device_link to ensure piodma has is bound to its DART.

fixup! WIP: drm/apple: Add DCP display driver

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 4b3a37fbf1e938..d2ac6ee0117604 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -76,6 +76,7 @@ struct dcp_mem_descriptor {
 struct apple_dcp {
 	struct device *dev;
 	struct platform_device *piodma;
+	struct device_link *piodma_link;
 	struct apple_rtkit *rtk;
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
@@ -1792,12 +1793,15 @@ EXPORT_SYMBOL_GPL(dcp_link);
 
 static struct platform_device *dcp_get_dev(struct device *dev, const char *name)
 {
-	struct device_node *node = of_get_child_by_name(dev->of_node, name);
+	struct platform_device *pdev;
+	struct device_node *node = of_parse_phandle(dev->of_node, name, 0);
 
 	if (!node)
 		return NULL;
 
-	return of_find_device_by_node(node);
+	pdev = of_find_device_by_node(node);
+	of_node_put(node);
+	return pdev;
 }
 
 static int dcp_get_disp_regs(struct apple_dcp *dcp)
@@ -1843,12 +1847,22 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	of_platform_default_populate(dev->of_node, NULL, dev);
 
-	dcp->piodma = dcp_get_dev(dev, "piodma");
+	dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper");
 	if (!dcp->piodma) {
 		dev_err(dev, "failed to find piodma\n");
 		return -ENODEV;
 	}
 
+	dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev,
+					   DL_FLAG_AUTOREMOVE_CONSUMER);
+	if (!dcp->piodma_link) {
+		dev_err(dev, "Failed to link to piodma device");
+		return -EINVAL;
+	}
+
+	if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
+		return -EPROBE_DEFER;
+
 	ret = dcp_get_disp_regs(dcp);
 	if (ret) {
 		dev_err(dev, "failed to find display registers\n");

From 090829a510864c51af52f6a475e6e037c7698ca1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 28 Sep 2022 17:47:01 +0900
Subject: [PATCH 0517/1027] drm/apple: Fix kzalloc in dcp_flush()

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/dcp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d2ac6ee0117604..06745116838a6d 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1615,7 +1615,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			.timing_mode_id = mode->timing_mode_id
 		};
 
-		cookie = kzalloc(sizeof(cookie), GFP_KERNEL);
+		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
 		if (!cookie) {
 			schedule_work(&dcp->vblank_wq);
 			return;

From 2aa4e30dab2dfd9ca2735ac21f3e2dd1dfa87b18 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 1 Oct 2022 09:48:08 +0200
Subject: [PATCH 0518/1027] drm/apple: Allow modesets even when disconnected

Fixes a display wakeup issue seen with Plasma/X11.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 06745116838a6d..0f0c7bff31623b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1512,12 +1512,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	struct dcp_swap_submit_req *req = &dcp->swap;
 	int l;
 	int has_surface = 0;
+	bool modeset;
 	dev_dbg(dcp->dev, "%s", __func__);
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
+	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
+
 	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
-	    WARN(!dcp->connector->connected, "can't flush if disconnected")) {
+	    WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) {
 		/* HACK: issue a delayed vblank event to avoid timeouts in
 		 * drm_atomic_helper_wait_for_vblanks().
 		 */
@@ -1595,7 +1598,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	/* These fields should be set together */
 	req->swap.swap_completed = req->swap.swap_enabled;
 
-	if (drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode) {
+	if (modeset) {
 		struct dcp_display_mode *mode;
 		struct dcp_wait_cookie *cookie;
 		int ret;

From d6714a966061823668048cb0201ab1aca8823161 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 1 Oct 2022 11:05:56 +0200
Subject: [PATCH 0519/1027] drm/apple: Mark the connecter on init only with
 modes as connected

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  2 +-
 drivers/gpu/drm/apple/dcp.c       | 10 ++++++++++
 2 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 43d451b1fe9a86..6c0fcb9d33ec84 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -330,7 +330,7 @@ static int apple_probe_per_dcp(struct device *dev,
 		return ret;
 
 	connector->base.polled = DRM_CONNECTOR_POLL_HPD;
-	connector->connected = true; /* XXX */
+	connector->connected = false;
 	connector->dcp = dcp;
 
 	INIT_WORK(&connector->hotplug_wq, dcp_hotplug);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 0f0c7bff31623b..4d2f835d3d338b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1672,12 +1672,19 @@ EXPORT_SYMBOL_GPL(dcp_is_initialized);
 
 static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
 {
+	struct apple_connector *connector;
 	int result = *(int *)out;
 	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
 
 	dcp->main_display = result != 0;
 
 	dcp->active = true;
+
+	connector = dcp->connector;
+	if (connector) {
+		connector->connected = dcp->nr_modes > 0;
+		schedule_work(&connector->hotplug_wq);
+	}
 }
 
 static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
@@ -1789,6 +1796,9 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 	dcp->crtc = crtc;
 	dcp->connector = connector;
 
+	/* init connector status by modes offered by dcp */
+	connector->connected = dcp->nr_modes > 0;
+
 	/* Dimensions might already be parsed */
 	dcp_set_dimensions(dcp);
 }

From 0951b0768530725a960df84923a3e1a5b07b9a08 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 1 Oct 2022 14:46:11 +0200
Subject: [PATCH 0520/1027] drm/apple: make note about
 drm.mode_config.max_width/height

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 6c0fcb9d33ec84..16f6459dadb171 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -399,6 +399,8 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 	/* Unknown maximum, use the iMac (24-inch, 2021) display resolution as
 	 * maximum.
+	 * TODO: this is the max framebuffer size not the maximal supported output
+	 * resolution. DCP reports the maximal framebuffer size take it from there.
 	 */
 	apple->drm.mode_config.max_width = 4480;
 	apple->drm.mode_config.max_height = 2520;

From 3c067dae0fa69a90a5e29ed02b64ca87744cdea7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 2 Oct 2022 18:22:32 +0200
Subject: [PATCH 0521/1027] drm/apple: Split dcpep/iomfb out of dcp.c

For external display support DCP will use more endpoints.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile             |    2 +-
 drivers/gpu/drm/apple/dcp-internal.h       |  133 ++
 drivers/gpu/drm/apple/dcp.c                | 1758 +-------------------
 drivers/gpu/drm/apple/dcp.h                |   11 +
 drivers/gpu/drm/apple/iomfb.c              | 1626 ++++++++++++++++++
 drivers/gpu/drm/apple/{dcpep.h => iomfb.h} |   48 +-
 6 files changed, 1837 insertions(+), 1741 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/dcp-internal.h
 create mode 100644 drivers/gpu/drm/apple/iomfb.c
 rename drivers/gpu/drm/apple/{dcpep.h => iomfb.h} (86%)

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 8c758d5720b642..d1f909792229e5 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only OR MIT
 
 appledrm-y := apple_drv.o
-apple_dcp-y := dcp.o parser.o
+apple_dcp-y := dcp.o iomfb.o parser.o
 apple_piodma-y := dummy-piodma.o
 
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
new file mode 100644
index 00000000000000..648d543b9cd91a
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -0,0 +1,133 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#ifndef __APPLE_DCP_INTERNAL_H__
+#define __APPLE_DCP_INTERNAL_H__
+
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/scatterlist.h>
+
+#include "iomfb.h"
+
+struct apple_dcp;
+
+/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */
+struct dcp_chunks {
+	size_t length;
+	void *data;
+};
+
+#define DCP_MAX_MAPPINGS (128) /* should be enough */
+#define MAX_DISP_REGISTERS (7)
+
+struct dcp_mem_descriptor {
+	size_t size;
+	void *buf;
+	dma_addr_t dva;
+	struct sg_table map;
+	u64 reg;
+};
+
+/* Limit on call stack depth (arbitrary). Some nesting is required */
+#define DCP_MAX_CALL_DEPTH 8
+
+typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
+
+struct dcp_call_channel {
+	dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH];
+	void *cookies[DCP_MAX_CALL_DEPTH];
+	void *output[DCP_MAX_CALL_DEPTH];
+	u16 end[DCP_MAX_CALL_DEPTH];
+
+	/* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */
+	u8 depth;
+};
+
+struct dcp_cb_channel {
+	u8 depth;
+	void *output[DCP_MAX_CALL_DEPTH];
+};
+
+struct dcp_fb_reference {
+	struct list_head head;
+	struct drm_framebuffer *fb;
+};
+
+/* TODO: move IOMFB members to its own struct */
+struct apple_dcp {
+	struct device *dev;
+	struct platform_device *piodma;
+	struct device_link *piodma_link;
+	struct apple_rtkit *rtk;
+	struct apple_crtc *crtc;
+	struct apple_connector *connector;
+
+	/* Coprocessor control register */
+	void __iomem *coproc_reg;
+
+	/* mask for DCP IO virtual addresses shared over rtkit */
+	u64 asc_dram_mask;
+
+	/* DCP has crashed */
+	bool crashed;
+
+	/************* IOMFB **************************************************
+	 * everything below is mostly used inside IOMFB but it could make     *
+	 * sense keep some of the the members in apple_dcp.                   *
+	 **********************************************************************/
+
+	/* clock rate request by dcp in */
+	struct clk *clk;
+
+	/* DCP shared memory */
+	void *shmem;
+
+	/* Display registers mappable to the DCP */
+	struct resource *disp_registers[MAX_DISP_REGISTERS];
+	unsigned int nr_disp_registers;
+
+	/* Bitmap of memory descriptors used for mappings made by the DCP */
+	DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS);
+
+	/* Indexed table of memory descriptors */
+	struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
+
+	struct dcp_call_channel ch_cmd, ch_oobcmd;
+	struct dcp_cb_channel ch_cb, ch_oobcb, ch_async;
+
+	/* Active chunked transfer. There can only be one at a time. */
+	struct dcp_chunks chunks;
+
+	/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
+	struct dcp_swap_submit_req swap;
+
+	/* Current display mode */
+	bool valid_mode;
+	struct dcp_set_digital_out_mode_req mode;
+
+	/* Is the DCP booted? */
+	bool active;
+
+	/* eDP display without DP-HDMI conversion */
+	bool main_display;
+
+	bool ignore_swap_complete;
+
+	/* Modes valid for the connected display */
+	struct dcp_display_mode *modes;
+	unsigned int nr_modes;
+
+	/* Attributes of the connected display */
+	int width_mm, height_mm;
+
+	/* Workqueue for sending vblank events when a dcp swap is not possible */
+	struct work_struct vblank_wq;
+
+	/* List of referenced drm_framebuffers which can be unreferenced
+	 * on the next successfully completed swap.
+	 */
+	struct list_head swapped_out_fbs;
+};
+
+#endif /* __APPLE_DCP_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 4d2f835d3d338b..6ba5b6e93bddf2 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -19,1105 +19,59 @@
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
 
-#include "dcpep.h"
 #include "dcp.h"
+#include "dcp-internal.h"
 #include "parser.h"
 
-struct apple_dcp;
-
-#define APPLE_DCP_COPROC_CPU_CONTROL	 0x44
-#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4)
-
-/* Register defines used in bandwidth setup structure */
-#define REG_SCRATCH (0x14)
-#define REG_SCRATCH_T600X (0x988)
-#define REG_DOORBELL (0x0)
-#define REG_DOORBELL_BIT (2)
-
-#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000)
-
-/* Limit on call stack depth (arbitrary). Some nesting is required */
-#define DCP_MAX_CALL_DEPTH 8
-
-typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
-
-struct dcp_call_channel {
-	dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH];
-	void *cookies[DCP_MAX_CALL_DEPTH];
-	void *output[DCP_MAX_CALL_DEPTH];
-	u16 end[DCP_MAX_CALL_DEPTH];
-
-	/* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */
-	u8 depth;
-};
-
-struct dcp_cb_channel {
-	u8 depth;
-	void *output[DCP_MAX_CALL_DEPTH];
-};
-
-/* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */
-struct dcp_chunks {
-	size_t length;
-	void *data;
-};
-
-#define DCP_MAX_MAPPINGS (128) /* should be enough */
-#define MAX_DISP_REGISTERS (7)
-
-struct dcp_mem_descriptor {
-	size_t size;
-	void *buf;
-	dma_addr_t dva;
-	struct sg_table map;
-	u64 reg;
-};
-
-struct apple_dcp {
-	struct device *dev;
-	struct platform_device *piodma;
-	struct device_link *piodma_link;
-	struct apple_rtkit *rtk;
-	struct apple_crtc *crtc;
-	struct apple_connector *connector;
-
-	/* DCP has crashed */
-	bool crashed;
-
-	/* clock rate request by dcp in */
-	struct clk *clk;
-
-	/* mask for DCP IO virtual addresses shared over rtkit */
-	u64 asc_dram_mask;
-
-	/* DCP shared memory */
-	void *shmem;
-
-	/* Coprocessor control register */
-	void __iomem *coproc_reg;
-
-	/* Display registers mappable to the DCP */
-	struct resource *disp_registers[MAX_DISP_REGISTERS];
-	unsigned int nr_disp_registers;
-
-	/* Bitmap of memory descriptors used for mappings made by the DCP */
-	DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS);
-
-	/* Indexed table of memory descriptors */
-	struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
-
-	struct dcp_call_channel ch_cmd, ch_oobcmd;
-	struct dcp_cb_channel ch_cb, ch_oobcb, ch_async;
-
-	/* Active chunked transfer. There can only be one at a time. */
-	struct dcp_chunks chunks;
-
-	/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
-	struct dcp_swap_submit_req swap;
-
-	/* Current display mode */
-	bool valid_mode;
-	struct dcp_set_digital_out_mode_req mode;
-
-	/* Is the DCP booted? */
-	bool active;
-
-	/* eDP display without DP-HDMI conversion */
-	bool main_display;
-
-	bool ignore_swap_complete;
-
-	/* Modes valid for the connected display */
-	struct dcp_display_mode *modes;
-	unsigned int nr_modes;
-
-	/* Attributes of the connected display */
-	int width_mm, height_mm;
-
-	/* Workqueue for sending vblank events when a dcp swap is not possible */
-	struct work_struct vblank_wq;
-
-	/* List of referenced drm_framebuffers which can be unreferenced
-	 * on the next successfully completed swap.
-	 */
-	struct list_head swapped_out_fbs;
-};
-
-struct dcp_fb_reference {
-	struct list_head head;
-	struct drm_framebuffer *fb;
-};
-
-struct dcp_wait_cookie {
-	struct completion done;
-	atomic_t refcount;
-};
-
-/*
- * A channel is busy if we have sent a message that has yet to be
- * acked. The driver must not sent a message to a busy channel.
- */
-static bool dcp_channel_busy(struct dcp_call_channel *ch)
-{
-	return (ch->depth != 0);
-}
-
-/* Get a call channel for a context */
-static struct dcp_call_channel *
-dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context)
-{
-	switch (context) {
-	case DCP_CONTEXT_CMD:
-	case DCP_CONTEXT_CB:
-		return &dcp->ch_cmd;
-	case DCP_CONTEXT_OOBCMD:
-	case DCP_CONTEXT_OOBCB:
-		return &dcp->ch_oobcmd;
-	default:
-		return NULL;
-	}
-}
-
-/*
- * Get the context ID passed to the DCP for a command we push. The rule is
- * simple: callback contexts are used when replying to the DCP, command
- * contexts are used otherwise. That corresponds to a non/zero call stack
- * depth. This rule frees the caller from tracking the call context manually.
- */
-static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
-{
-	u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth;
-
-	if (depth)
-		return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB;
-	else
-		return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
-}
-
-/* Get a callback channel for a context */
-static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp,
-						 enum dcp_context_id context)
-{
-	switch (context) {
-	case DCP_CONTEXT_CB:
-		return &dcp->ch_cb;
-	case DCP_CONTEXT_OOBCB:
-		return &dcp->ch_oobcb;
-	case DCP_CONTEXT_ASYNC:
-		return &dcp->ch_async;
-	default:
-		return NULL;
-	}
-}
-
-/* Get the start of a packet: after the end of the previous packet */
-static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth)
-{
-	if (depth > 0)
-		return ch->end[depth - 1];
-	else
-		return 0;
-}
-
-/* Pushes and pops the depth of the call stack with safety checks */
-static u8 dcp_push_depth(u8 *depth)
-{
-	u8 ret = (*depth)++;
-
-	WARN_ON(ret >= DCP_MAX_CALL_DEPTH);
-	return ret;
-}
-
-static u8 dcp_pop_depth(u8 *depth)
-{
-	WARN_ON((*depth) == 0);
-
-	return --(*depth);
-}
-
-#define DCP_METHOD(tag, name) [name] = { #name, tag }
-
-const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
-	DCP_METHOD("A000", dcpep_late_init_signal),
-	DCP_METHOD("A029", dcpep_setup_video_limits),
-	DCP_METHOD("A034", dcpep_update_notify_clients_dcp),
-	DCP_METHOD("A357", dcpep_set_create_dfb),
-	DCP_METHOD("A401", dcpep_start_signal),
-	DCP_METHOD("A407", dcpep_swap_start),
-	DCP_METHOD("A408", dcpep_swap_submit),
-	DCP_METHOD("A410", dcpep_set_display_device),
-	DCP_METHOD("A411", dcpep_is_main_display),
-	DCP_METHOD("A412", dcpep_set_digital_out_mode),
-	DCP_METHOD("A439", dcpep_set_parameter_dcp),
-	DCP_METHOD("A443", dcpep_create_default_fb),
-	DCP_METHOD("A447", dcpep_enable_disable_video_power_savings),
-	DCP_METHOD("A454", dcpep_first_client_open),
-	DCP_METHOD("A460", dcpep_set_display_refresh_properties),
-	DCP_METHOD("A463", dcpep_flush_supports_power),
-	DCP_METHOD("A468", dcpep_set_power_state),
-};
-
-/* Call a DCP function given by a tag */
-static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
-		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
-		     void *cookie)
-{
-	struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd;
-	enum dcp_context_id context = dcp_call_context(dcp, oob);
-
-	struct dcp_packet_header header = {
-		.in_len = in_len,
-		.out_len = out_len,
-
-		/* Tag is reversed due to endianness of the fourcc */
-		.tag[0] = dcp_methods[method].tag[3],
-		.tag[1] = dcp_methods[method].tag[2],
-		.tag[2] = dcp_methods[method].tag[1],
-		.tag[3] = dcp_methods[method].tag[0],
-	};
-
-	u8 depth = dcp_push_depth(&ch->depth);
-	u16 offset = dcp_packet_start(ch, depth);
-
-	void *out = dcp->shmem + dcp_tx_offset(context) + offset;
-	void *out_data = out + sizeof(header);
-	size_t data_len = sizeof(header) + in_len + out_len;
-
-	memcpy(out, &header, sizeof(header));
-
-	if (in_len > 0)
-		memcpy(out_data, data, in_len);
-
-	dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n",
-		dcp_methods[method].name, context, offset, depth);
-
-	ch->callbacks[depth] = cb;
-	ch->cookies[depth] = cookie;
-	ch->output[depth] = out + sizeof(header) + in_len;
-	ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
-
-	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT,
-				 dcpep_msg(context, data_len, offset),
-				 NULL, false);
-}
-
-#define DCP_THUNK_VOID(func, handle)                                           \
-	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb,   \
-			 void *cookie)                                         \
-	{                                                                      \
-		dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie);            \
-	}
-
-#define DCP_THUNK_OUT(func, handle, T)                                         \
-	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb,   \
-			 void *cookie)                                         \
-	{                                                                      \
-		dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie);    \
-	}
-
-#define DCP_THUNK_IN(func, handle, T)                                          \
-	static void func(struct apple_dcp *dcp, bool oob, T *data,             \
-			 dcp_callback_t cb, void *cookie)                      \
-	{                                                                      \
-		dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie);    \
-	}
-
-#define DCP_THUNK_INOUT(func, handle, T_in, T_out)                             \
-	static void func(struct apple_dcp *dcp, bool oob, T_in *data,          \
-			 dcp_callback_t cb, void *cookie)                      \
-	{                                                                      \
-		dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data,  \
-			 cb, cookie);                                          \
-	}
-
-DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
-		struct dcp_swap_submit_resp);
-
-DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
-		struct dcp_swap_start_resp);
-
-DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
-		struct dcp_set_power_state_req,
-		struct dcp_set_power_state_resp);
-
-DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
-		struct dcp_set_digital_out_mode_req, u32);
-
-DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
-
-DCP_THUNK_OUT(dcp_set_display_refresh_properties,
-	      dcpep_set_display_refresh_properties, u32);
-
-DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
-DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
-DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
-DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
-DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
-DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
-DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
-
-__attribute__((unused))
-DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp,
-	     struct dcp_update_notify_clients_dcp);
-
-DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
-		struct dcp_set_parameter_dcp, u32);
-
-DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
-		dcpep_enable_disable_video_power_savings,
-		u32, int);
-
-DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
-
-/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
-static int dcp_parse_tag(char tag[4])
-{
-	u32 d[3];
-	int i;
-
-	if (tag[3] != 'D')
-		return -EINVAL;
-
-	for (i = 0; i < 3; ++i) {
-		d[i] = (u32)(tag[i] - '0');
-
-		if (d[i] > 9)
-			return -EINVAL;
-	}
-
-	return d[0] + (d[1] * 10) + (d[2] * 100);
-}
-
-/* Ack a callback from the DCP */
-static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
-{
-	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
-
-	dcp_pop_depth(&ch->depth);
-	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT, dcpep_ack(context),
-				 NULL, false);
-}
-
-/* DCP callback handlers */
-static void dcpep_cb_nop(struct apple_dcp *dcp)
-{
-	/* No operation */
-}
-
-static u8 dcpep_cb_true(struct apple_dcp *dcp)
-{
-	return true;
-}
-
-static u8 dcpep_cb_false(struct apple_dcp *dcp)
-{
-	return false;
-}
-
-static u32 dcpep_cb_zero(struct apple_dcp *dcp)
-{
-	return 0;
-}
-
-/* HACK: moved here to avoid circular dependency between apple_drv and dcp */
-void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
-{
-	unsigned long flags;
-
-	if (crtc->vsync_disabled)
-		return;
-
-	drm_crtc_handle_vblank(&crtc->base);
-
-	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
-	if (crtc->event) {
-		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
-		drm_crtc_vblank_put(&crtc->base);
-		crtc->event = NULL;
-	}
-	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
-}
-
-static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
-	      struct dc_swap_complete_resp *resp)
-{
-	dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u",
-		resp->swap_id, dcp->ignore_swap_complete);
-
-	if (!dcp->ignore_swap_complete)
-		dcp_drm_crtc_vblank(dcp->crtc);
-}
-
-static struct dcp_get_uint_prop_resp
-dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
-{
-	/* unimplemented for now */
-	return (struct dcp_get_uint_prop_resp) {
-		.value = 0
-	};
-}
-
-/*
- * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
- * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
- * stream of the display DART, rather than the expected DCP DART.
- *
- * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
- * is a "fundamentally unsafe" operation according to the docs. And yet
- * everyone does it...
- */
-static struct dcp_map_buf_resp
-dcpep_cb_map_piodma(struct apple_dcp *dcp, struct dcp_map_buf_req *req)
-{
-	struct sg_table *map;
-	int ret;
-
-	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
-		goto reject;
-
-	map = &dcp->memdesc[req->buffer].map;
-
-	if (!map->sgl)
-		goto reject;
-
-	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
-	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
-
-	if (ret)
-		goto reject;
-
-	return (struct dcp_map_buf_resp) {
-		.dva = sg_dma_address(map->sgl)
-	};
-
-reject:
-	dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
-		req->buffer);
-	return (struct dcp_map_buf_resp) {
-		.ret = EINVAL
-	};
-}
-
-static void
-dcpep_cb_unmap_piodma(struct apple_dcp *dcp, struct dcp_unmap_buf_resp *resp)
-{
-	struct sg_table *map;
-	dma_addr_t dma_addr;
-
-	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
-		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
-			 resp->buffer);
-		return;
-	}
-
-	map = &dcp->memdesc[resp->buffer].map;
-
-	if (!map->sgl) {
-		dev_warn(dcp->dev, "unmap for non-mapped buffer %llu iova:0x%08llx",
-			 resp->buffer, resp->dva);
-		return;
-	}
-
-	dma_addr = sg_dma_address(map->sgl);
-	if (dma_addr != resp->dva) {
-		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
-			 resp->buffer, dma_addr, resp->dva);
-		return;
-	}
-
-	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
-	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
-}
-
-/*
- * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
- * physically contigiuous, however we should save the sgtable in case the
- * buffer needs to be later mapped for PIODMA.
- */
-static struct dcp_allocate_buffer_resp
-dcpep_cb_allocate_buffer(struct apple_dcp *dcp, struct dcp_allocate_buffer_req *req)
-{
-	struct dcp_allocate_buffer_resp resp = { 0 };
-	struct dcp_mem_descriptor *memdesc;
-	u32 id;
-
-	resp.dva_size = ALIGN(req->size, 4096);
-	resp.mem_desc_id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
-
-	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
-		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
-		resp.dva_size = 0;
-		resp.mem_desc_id = 0;
-		return resp;
-	}
-	id = resp.mem_desc_id;
-	set_bit(id, dcp->memdesc_map);
-
-	memdesc = &dcp->memdesc[id];
-
-	memdesc->size = resp.dva_size;
-	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size, &memdesc->dva,
-				 GFP_KERNEL);
-
-	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf,
-			memdesc->dva, memdesc->size);
-	resp.dva = memdesc->dva;
-
-	return resp;
-}
-
-static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
-{
-	struct dcp_mem_descriptor *memdesc;
-	u32 id = *mem_desc_id;
-
-	if (id >= DCP_MAX_MAPPINGS) {
-		dev_warn(dcp->dev, "unmap request for out of range mem_desc_id %u",
-				id);
-		return 0;
-	}
-
-	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
-		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
-				id);
-		return 0;
-	}
-
-	memdesc = &dcp->memdesc[id];
-	if (memdesc->buf) {
-
-		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
-				 memdesc->dva);
-
-		memdesc->buf = NULL;
-		memset(&memdesc->map, 0, sizeof(memdesc->map));
-	} else {
-		memdesc->reg = 0;
-	}
-
-	memdesc->size = 0;
-
-	return 1;
-}
-
-/* Validate that the specified region is a display register */
-static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
-{
-	int i;
-
-	for (i = 0; i < dcp->nr_disp_registers; ++i) {
-		struct resource *r = dcp->disp_registers[i];
-
-		if ((start >= r->start) && (end <= r->end))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Map contiguous physical memory into the DCP's address space. The firmware
- * uses this to map the display registers we advertise in
- * sr_map_device_memory_with_index, so we bounds check against that to guard
- * safe against malicious coprocessors.
- */
-static struct dcp_map_physical_resp
-dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
-{
-	int size = ALIGN(req->size, 4096);
-	u32 id;
-
-	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
-		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
-			req->paddr, req->size);
-		return (struct dcp_map_physical_resp) { };
-	}
-
-	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
-	set_bit(id, dcp->memdesc_map);
-	dcp->memdesc[id].size = size;
-	dcp->memdesc[id].reg = req->paddr;
-
-	return (struct dcp_map_physical_resp) {
-		.dva_size = size,
-		.mem_desc_id = id,
-		.dva = dma_map_resource(dcp->dev, req->paddr, size,
-					DMA_BIDIRECTIONAL, 0),
-	};
-}
-
-static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
-{
-	return clk_get_rate(dcp->clk);
-}
-
-static struct dcp_map_reg_resp
-dcpep_cb_map_reg(struct apple_dcp *dcp, struct dcp_map_reg_req *req)
-{
-	if (req->index >= dcp->nr_disp_registers) {
-		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
-			 req->index);
-
-		return (struct dcp_map_reg_resp) {
-			.ret = 1
-		};
-	} else {
-		struct resource *rsrc = dcp->disp_registers[req->index];
-
-		return (struct dcp_map_reg_resp) {
-			.addr = rsrc->start,
-			.length = resource_size(rsrc)
-		};
-	}
-}
-
-static struct dcp_read_edt_data_resp
-dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
-{
-	return (struct dcp_read_edt_data_resp) {
-		.value[0] = req->value[0],
-		.ret = 0,
-	};
-}
-
-/* Chunked data transfer for property dictionaries */
-static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
-{
-	if (dcp->chunks.data != NULL) {
-		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
-		return false;
-	}
-
-	dcp->chunks.length = *length;
-	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
-
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "failed to allocate chunks\n");
-		return false;
-	}
-
-	return true;
-}
-
-static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
-			      struct dcp_set_dcpav_prop_chunk_req *req)
-{
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "ignoring spurious chunk\n");
-		return false;
-	}
-
-	if (req->offset + req->length > dcp->chunks.length) {
-		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
-		return false;
-	}
-
-	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
-	return true;
-}
-
-static void dcp_set_dimensions(struct apple_dcp *dcp)
-{
-	int i;
-
-	/* Set the connector info */
-	if (dcp->connector) {
-		struct drm_connector *connector = &dcp->connector->base;
-
-		mutex_lock(&connector->dev->mode_config.mutex);
-		connector->display_info.width_mm = dcp->width_mm;
-		connector->display_info.height_mm = dcp->height_mm;
-		mutex_unlock(&connector->dev->mode_config.mutex);
-	}
-
-	/*
-	 * Fix up any probed modes. Modes are created when parsing
-	 * TimingElements, dimensions are calculated when parsing
-	 * DisplayAttributes, and TimingElements may be sent first
-	 */
-	for (i = 0; i < dcp->nr_modes; ++i) {
-		dcp->modes[i].mode.width_mm = dcp->width_mm;
-		dcp->modes[i].mode.height_mm = dcp->height_mm;
-	}
-}
-
-static bool dcpep_process_chunks(struct apple_dcp *dcp,
-				 struct dcp_set_dcpav_prop_end_req *req)
-{
-	struct dcp_parse_ctx ctx;
-	int ret;
-
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "ignoring spurious end\n");
-		return false;
-	}
-
-	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
-
-	if (ret) {
-		dev_warn(dcp->dev, "bad header on dcpav props\n");
-		return false;
-	}
-
-	if (!strcmp(req->key, "TimingElements")) {
-		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
-					     dcp->width_mm, dcp->height_mm);
-
-		if (IS_ERR(dcp->modes)) {
-			dev_warn(dcp->dev, "failed to parse modes\n");
-			dcp->modes = NULL;
-			dcp->nr_modes = 0;
-			return false;
-		}
-	} else if (!strcmp(req->key, "DisplayAttributes")) {
-		ret = parse_display_attributes(&ctx, &dcp->width_mm,
-					       &dcp->height_mm);
-
-		if (ret) {
-			dev_warn(dcp->dev, "failed to parse display attribs\n");
-			return false;
-		}
-
-		dcp_set_dimensions(dcp);
-	}
-
-	return true;
-}
-
-static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
-			    struct dcp_set_dcpav_prop_end_req *req)
-{
-	u8 resp = dcpep_process_chunks(dcp, req);
-
-	/* Reset for the next transfer */
-	devm_kfree(dcp->dev, dcp->chunks.data);
-	dcp->chunks.data = NULL;
-
-	return resp;
-}
-
-/* Boot sequence */
-static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_cb_channel *ch = &dcp->ch_cb;
-	u8 *succ = ch->output[ch->depth - 1];
-	dev_dbg(dcp->dev, "boot done");
-
-	*succ = true;
-	dcp_ack(dcp, DCP_CONTEXT_CB);
-}
-
-static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
-}
-
-static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_late_init_signal(dcp, false, boot_5, NULL);
-}
-
-static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	u32 v_true = true;
-
-	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
-}
-
-static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_setup_video_limits(dcp, false, boot_3, NULL);
-}
-
-static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_create_default_fb(dcp, false, boot_2, NULL);
-}
-
-/* Use special function signature to defer the ACK */
-static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
-{
-	dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__);
-	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
-	return false;
-}
-
-static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
-{
-	if (dcp->disp_registers[5] && dcp->disp_registers[6])
-		return (struct dcp_rt_bandwidth) {
-			.reg_scratch = dcp->disp_registers[5]->start + REG_SCRATCH,
-			.reg_doorbell = dcp->disp_registers[6]->start + REG_DOORBELL,
-			.doorbell_bit = REG_DOORBELL_BIT,
-
-			.padding[3] = 0x4, // XXX: required by 11.x firmware
-		};
-	else if (dcp->disp_registers[4])
-		return (struct dcp_rt_bandwidth) {
-			.reg_scratch = dcp->disp_registers[4]->start + REG_SCRATCH_T600X,
-			.reg_doorbell = 0,
-			.doorbell_bit = 0,
-		};
-	else
-		return (struct dcp_rt_bandwidth) {
-			.reg_scratch = 0,
-			.reg_doorbell = 0,
-			.doorbell_bit = 0,
-		};
-}
-
-/* Callback to get the current time as milliseconds since the UNIX epoch */
-static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
-{
-	return ktime_to_ms(ktime_get_real());
-}
-
-struct dcp_swap_cookie {
-	struct completion done;
-	atomic_t refcount;
-	u32 swap_id;
-};
-
-static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_submit_resp *resp = data;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (cookie) {
-		struct dcp_swap_cookie *info = cookie;
-		complete(&info->done);
-		if (atomic_dec_and_test(&info->refcount))
-			kfree(info);
-	}
-
-	if (resp->ret) {
-		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
-		dcp_drm_crtc_vblank(dcp->crtc);
-		return;
-	}
-
-	while (!list_empty(&dcp->swapped_out_fbs)) {
-		struct dcp_fb_reference *entry;
-		entry = list_first_entry(&dcp->swapped_out_fbs,
-					 struct dcp_fb_reference, head);
-		if (entry->fb)
-			drm_framebuffer_put(entry->fb);
-		list_del(&entry->head);
-		kfree(entry);
-	}
-}
-
-static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
-				   void *cookie)
-{
-	struct dcp_swap_start_resp *resp = data;
-	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
-	dcp->swap.swap.swap_id = resp->swap_id;
-
-	if (cookie) {
-		struct dcp_swap_cookie *info = cookie;
-		info->swap_id = resp->swap_id;
-	}
-
-	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie);
-}
-
-static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (wait) {
-		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
-	}
-}
+#define APPLE_DCP_COPROC_CPU_CONTROL	 0x44
+#define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4)
 
-static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_set_parameter_dcp param = {
-		.param = 14,
-		.value = { 0 },
-		.count = 1,
-	};
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_final, cookie);
-}
+#define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000)
 
-void dcp_poweron(struct platform_device *pdev)
+/* HACK: moved here to avoid circular dependency between apple_drv and dcp */
+void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 {
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	struct dcp_wait_cookie * cookie;
-	struct dcp_set_power_state_req req = {
-		.unklong = 1,
-	};
-	int ret;
-	u32 handle;
-	dev_dbg(dcp->dev, "%s", __func__);
+	unsigned long flags;
 
-	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-	if (!cookie)
+	if (crtc->vsync_disabled)
 		return;
 
-	init_completion(&cookie->done);
-	atomic_set(&cookie->refcount, 2);
-
-	if (dcp->main_display) {
-		handle = 0;
-		dcp_set_display_device(dcp, false, &handle, dcp_on_final, cookie);
-	} else {
-		handle = 2;
-		dcp_set_display_device(dcp, false, &handle, dcp_on_set_parameter, cookie);
-	}
-	dcp_set_power_state(dcp, true, &req, NULL, NULL);
-
-	ret = wait_for_completion_timeout(&cookie->done,  msecs_to_jiffies(500));
-
-	if (ret == 0)
-		dev_warn(dcp->dev, "wait for power timed out");
-
-	if (atomic_dec_and_test(&cookie->refcount))
-		kfree(cookie);
-}
-EXPORT_SYMBOL(dcp_poweron);
-
-static void complete_set_powerstate(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
+	drm_crtc_handle_vblank(&crtc->base);
 
-	if (wait) {
-		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
+	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
+	if (crtc->event) {
+		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
+		drm_crtc_vblank_put(&crtc->base);
+		crtc->event = NULL;
 	}
+	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
 }
 
-void dcp_poweroff(struct platform_device *pdev)
+void dcp_set_dimensions(struct apple_dcp *dcp)
 {
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	int ret, swap_id;
-	struct dcp_set_power_state_req power_req = {
-		.unklong = 0,
-	};
-	struct dcp_swap_cookie *cookie;
-	struct dcp_wait_cookie *poff_cookie;
-	struct dcp_swap_start_req swap_req= { 0 };
-
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-	if (!cookie)
-		return;
-	init_completion(&cookie->done);
-	atomic_set(&cookie->refcount, 2);
-
-	// clear surfaces
-	memset(&dcp->swap, 0, sizeof(dcp->swap));
-
-	dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7;
-	dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7;
-	dcp->swap.swap.unk_10c = 0xFF000000;
-
-	for (int l = 0; l < SWAP_SURFACES; l++)
-		dcp->swap.surf_null[l] = true;
+	int i;
 
-	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
+	/* Set the connector info */
+	if (dcp->connector) {
+		struct drm_connector *connector = &dcp->connector->base;
 
-	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
-	swap_id = cookie->swap_id;
-	if (atomic_dec_and_test(&cookie->refcount))
-		kfree(cookie);
-	if (ret <= 0) {
-		dcp->crashed = true;
-		return;
+		mutex_lock(&connector->dev->mode_config.mutex);
+		connector->display_info.width_mm = dcp->width_mm;
+		connector->display_info.height_mm = dcp->height_mm;
+		mutex_unlock(&connector->dev->mode_config.mutex);
 	}
 
-	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
-
-	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
-	if (!poff_cookie)
-		return;
-	init_completion(&poff_cookie->done);
-	atomic_set(&poff_cookie->refcount, 2);
-
-	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, poff_cookie);
-	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(1000));
-
-	if (ret == 0)
-		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
-	else if (ret > 0)
-		dev_dbg(dcp->dev, "setPowerState(0) finished with %d ms to spare",
-			jiffies_to_msecs(ret));
-
-	if (atomic_dec_and_test(&poff_cookie->refcount))
-		kfree(poff_cookie);
-	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
-}
-EXPORT_SYMBOL(dcp_poweroff);
-
-/*
- * Helper to send a DRM hotplug event. The DCP is accessed from a single
- * (RTKit) thread. To handle hotplug callbacks, we need to call
- * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and
- * waits for vblank (a DCP callback). That means we deadlock if we call from
- * the RTKit thread! Instead, move the call to another thread via a workqueue.
- */
-void dcp_hotplug(struct work_struct *work)
-{
-	struct apple_connector *connector;
-	struct drm_device *dev;
-	struct apple_dcp *dcp;
-
-	connector = container_of(work, struct apple_connector, hotplug_wq);
-	dev = connector->base.dev;
-
-	dcp = platform_get_drvdata(connector->dcp);
-	dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected);
-
 	/*
-	 * DCP defers link training until we set a display mode. But we set
-	 * display modes from atomic_flush, so userspace needs to trigger a
-	 * flush, or the CRTC gets no signal.
+	 * Fix up any probed modes. Modes are created when parsing
+	 * TimingElements, dimensions are calculated when parsing
+	 * DisplayAttributes, and TimingElements may be sent first
 	 */
-	if (!dcp->valid_mode && connector->connected) {
-		drm_connector_set_link_status_property(
-			&connector->base, DRM_MODE_LINK_STATUS_BAD);
-	}
-
-	if (dev && dev->registered)
-		drm_kms_helper_hotplug_event(dev);
-}
-EXPORT_SYMBOL_GPL(dcp_hotplug);
-
-static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
-{
-	struct apple_connector *connector = dcp->connector;
-
-	/* Hotplug invalidates mode. DRM doesn't always handle this. */
-	if (!(*connected)) {
-		dcp->valid_mode = false;
-		/* after unplug swap will not complete until the next
-		 * set_digital_out_mode */
-		schedule_work(&dcp->vblank_wq);
-	}
-
-	if (connector && connector->connected != !!(*connected)) {
-		connector->connected = !!(*connected);
-		dcp->valid_mode = false;
-		schedule_work(&connector->hotplug_wq);
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		dcp->modes[i].mode.width_mm = dcp->width_mm;
+		dcp->modes[i].mode.height_mm = dcp->height_mm;
 	}
 }
 
-static void
-dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
-				    struct dcp_swap_complete_intent_gated *info)
-{
-	dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id,
-		info->width, info->height);
-}
-
 /*
  * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp
  * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks
@@ -1132,597 +86,16 @@ static void dcp_delayed_vblank(struct work_struct *work)
 	dcp_drm_crtc_vblank(dcp->crtc);
 }
 
-
-#define DCPEP_MAX_CB (1000)
-
-/*
- * Define type-safe trampolines. Define typedefs to enforce type-safety on the
- * input data (so if the types don't match, gcc errors out).
- */
-
-#define TRAMPOLINE_VOID(func, handler)                                         \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
-	{                                                                      \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
-		handler(dcp);                                                  \
-		return true;                                                   \
-	}
-
-#define TRAMPOLINE_IN(func, handler, T_in)                                     \
-	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);        \
-                                                                               \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
-	{                                                                      \
-		callback_##handler cb = handler;                               \
-                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
-		cb(dcp, in);                                                   \
-		return true;                                                   \
-	}
-
-#define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                           \
-	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);       \
-                                                                               \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
-	{                                                                      \
-		T_out *typed_out = out;                                        \
-		callback_##handler cb = handler;                               \
-                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
-		*typed_out = cb(dcp, in);                                      \
-		return true;                                                   \
-	}
-
-#define TRAMPOLINE_OUT(func, handler, T_out)                                   \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in)  \
-	{                                                                      \
-		T_out *typed_out = out;                                        \
-                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);       \
-		*typed_out = handler(dcp);                                     \
-		return true;                                                   \
-	}
-
-TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
-TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
-TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
-TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
-TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
-	      struct dc_swap_complete_resp);
-TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
-		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
-TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
-		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
-TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
-	      struct dcp_unmap_buf_resp);
-TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
-		 struct dcp_allocate_buffer_req,
-		 struct dcp_allocate_buffer_resp);
-TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
-		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
-TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc,
-		 u32, u8);
-TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
-		 struct dcp_map_reg_resp);
-TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
-		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
-TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
-TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
-		 struct dcp_set_dcpav_prop_chunk_req, u8);
-TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
-		 struct dcp_set_dcpav_prop_end_req, u8);
-TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
-	       struct dcp_rt_bandwidth);
-TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
-TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
-TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
-TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
-	      dcpep_cb_swap_complete_intent_gated,
-	      struct dcp_swap_complete_intent_gated);
-
-bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *, void *) = {
-	[0] = trampoline_true, /* did_boot_signal */
-	[1] = trampoline_true, /* did_power_on_signal */
-	[2] = trampoline_nop, /* will_power_off_signal */
-	[3] = trampoline_rt_bandwidth,
-	[100] = trampoline_nop, /* match_pmu_service */
-	[101] = trampoline_zero, /* get_display_default_stride */
-	[103] = trampoline_nop, /* set_boolean_property */
-	[106] = trampoline_nop, /* remove_property */
-	[107] = trampoline_true, /* create_provider_service */
-	[108] = trampoline_true, /* create_product_service */
-	[109] = trampoline_true, /* create_pmu_service */
-	[110] = trampoline_true, /* create_iomfb_service */
-	[111] = trampoline_false, /* create_backlight_service */
-	[116] = dcpep_cb_boot_1,
-	[117] = trampoline_false, /* is_dark_boot */
-	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
-	[120] = trampoline_read_edt_data,
-	[122] = trampoline_prop_start,
-	[123] = trampoline_prop_chunk,
-	[124] = trampoline_prop_end,
-	[201] = trampoline_map_piodma,
-	[202] = trampoline_unmap_piodma,
-	[206] = trampoline_true, /* match_pmu_service_2 */
-	[207] = trampoline_true, /* match_backlight_service */
-	[208] = trampoline_get_time,
-	[211] = trampoline_nop, /* update_backlight_factor_prop */
-	[300] = trampoline_nop, /* pr_publish */
-	[401] = trampoline_get_uint_prop,
-	[404] = trampoline_nop, /* sr_set_uint_prop */
-	[406] = trampoline_nop, /* set_fx_prop */
-	[408] = trampoline_get_frequency,
-	[411] = trampoline_map_reg,
-	[413] = trampoline_true, /* sr_set_property_dict */
-	[414] = trampoline_true, /* sr_set_property_int */
-	[415] = trampoline_true, /* sr_set_property_bool */
-	[451] = trampoline_allocate_buffer,
-	[452] = trampoline_map_physical,
-	[456] = trampoline_release_mem_desc,
-	[552] = trampoline_true, /* set_property_dict_0 */
-	[561] = trampoline_true, /* set_property_dict */
-	[563] = trampoline_true, /* set_property_int */
-	[565] = trampoline_true, /* set_property_bool */
-	[567] = trampoline_true, /* set_property_str */
-	[574] = trampoline_zero, /* power_up_dart */
-	[576] = trampoline_hotplug,
-	[577] = trampoline_nop, /* powerstate_notify */
-	[582] = trampoline_true, /* create_default_fb_surface */
-	[589] = trampoline_swap_complete,
-	[591] = trampoline_swap_complete_intent_gated,
-	[593] = trampoline_nop, /* enable_backlight_message_ap_gated */
-	[598] = trampoline_nop, /* find_swap_function_gated */
-};
-
-static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
-			    void *data, u32 length)
-{
-	struct device *dev = dcp->dev;
-	struct dcp_packet_header *hdr = data;
-	void *in, *out;
-	int tag = dcp_parse_tag(hdr->tag);
-	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
-	u8 depth;
-
-	if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) {
-		dev_warn(dev, "received unknown callback %c%c%c%c\n",
-			 hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
-		return;
-	}
-
-	in = data + sizeof(*hdr);
-	out = in + hdr->in_len;
-
-	// TODO: verify that in_len and out_len match our prototypes
-	// for now just clear the out data to have at least consistant results
-	if (hdr->out_len)
-		memset(out, 0, hdr->out_len);
-
-	depth = dcp_push_depth(&ch->depth);
-	ch->output[depth] = out;
-
-	if (dcpep_cb_handlers[tag](dcp, tag, out, in))
-		dcp_ack(dcp, context);
-}
-
-static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
-			     void *data, u32 length)
-{
-	struct dcp_packet_header *header = data;
-	struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context);
-	void *cookie;
-	dcp_callback_t cb;
-
-	if (!ch) {
-		dev_warn(dcp->dev, "ignoring ack on context %X\n", context);
-		return;
-	}
-
-	dcp_pop_depth(&ch->depth);
-
-	cb = ch->callbacks[ch->depth];
-	cookie = ch->cookies[ch->depth];
-
-	ch->callbacks[ch->depth] = NULL;
-	ch->cookies[ch->depth] = NULL;
-
-	if (cb)
-		cb(dcp, data + sizeof(*header) + header->in_len, cookie);
-}
-
-static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
-{
-	enum dcp_context_id ctx_id;
-	u16 offset;
-	u32 length;
-	int channel_offset;
-	void *data;
-
-	ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT;
-	offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT;
-	length = (message >> DCPEP_LENGTH_SHIFT);
-
-	channel_offset = dcp_channel_offset(ctx_id);
-
-	if (channel_offset < 0) {
-		dev_warn(dcp->dev, "invalid context received %u", ctx_id);
-		return;
-	}
-
-	data = dcp->shmem + channel_offset + offset;
-
-	if (message & DCPEP_ACK)
-		dcpep_handle_ack(dcp, ctx_id, data, length);
-	else
-		dcpep_handle_cb(dcp, ctx_id, data, length);
-}
-
-/*
- * Callback for swap requests. If a swap failed, we'll never get a swap
- * complete event so we need to fake a vblank event early to avoid a hang.
- */
-
-static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_submit_resp *resp = data;
-
-	if (resp->ret) {
-		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
-		dcp_drm_crtc_vblank(dcp->crtc);
-		return;
-	}
-
-	while (!list_empty(&dcp->swapped_out_fbs)) {
-		struct dcp_fb_reference *entry;
-		entry = list_first_entry(&dcp->swapped_out_fbs,
-					 struct dcp_fb_reference, head);
-		if (entry->fb)
-			drm_framebuffer_put(entry->fb);
-		list_del(&entry->head);
-		kfree(entry);
-	}
-}
-
-static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_start_resp *resp = data;
-
-	dcp->swap.swap.swap_id = resp->swap_id;
-
-	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL);
-}
-
-/*
- * DRM specifies rectangles as start and end coordinates.  DCP specifies
- * rectangles as a start coordinate and a width/height. Convert a DRM rectangle
- * to a DCP rectangle.
- */
-static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
-{
-	return (struct dcp_rect) {
-		.x = rect->x1,
-		.y = rect->y1,
-		.w = drm_rect_width(rect),
-		.h = drm_rect_height(rect)
-	};
-}
-
-static u32 drm_format_to_dcp(u32 drm)
-{
-	switch (drm) {
-	case DRM_FORMAT_XRGB8888:
-	case DRM_FORMAT_ARGB8888:
-		return fourcc_code('A', 'R', 'G', 'B');
-
-	case DRM_FORMAT_XBGR8888:
-	case DRM_FORMAT_ABGR8888:
-		return fourcc_code('A', 'B', 'G', 'R');
-	}
-
-	pr_warn("DRM format %X not supported in DCP\n", drm);
-	return 0;
-}
-
-int dcp_get_modes(struct drm_connector *connector)
-{
-	struct apple_connector *apple_connector = to_apple_connector(connector);
-	struct platform_device *pdev = apple_connector->dcp;
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-
-	struct drm_device *dev = connector->dev;
-	struct drm_display_mode *mode;
-	int i;
-
-	for (i = 0; i < dcp->nr_modes; ++i) {
-		mode = drm_mode_duplicate(dev, &dcp->modes[i].mode);
-
-		if (!mode) {
-			dev_err(dev->dev, "Failed to duplicate display mode\n");
-			return 0;
-		}
-
-		drm_mode_probed_add(connector, mode);
-	}
-
-	return dcp->nr_modes;
-}
-EXPORT_SYMBOL_GPL(dcp_get_modes);
-
-/* The user may own drm_display_mode, so we need to search for our copy */
-static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
-					    struct drm_display_mode *mode)
-{
-	int i;
-
-	for (i = 0; i < dcp->nr_modes; ++i) {
-		if (drm_mode_match(mode, &dcp->modes[i].mode,
-				   DRM_MODE_MATCH_TIMINGS |
-				   DRM_MODE_MATCH_CLOCK))
-			return &dcp->modes[i];
-	}
-
-	return NULL;
-}
-
-int dcp_mode_valid(struct drm_connector *connector,
-		   struct drm_display_mode *mode)
-{
-	struct apple_connector *apple_connector = to_apple_connector(connector);
-	struct platform_device *pdev = apple_connector->dcp;
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-
-	return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD;
-}
-EXPORT_SYMBOL_GPL(dcp_mode_valid);
-
-/* Helpers to modeset and swap, used to flush */
-static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_start_req start_req = { 0 };
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (dcp->connector && dcp->connector->connected)
-		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
-	else
-		dcp_drm_crtc_vblank(dcp->crtc);
-}
-
-static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
-					  void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	dcp->ignore_swap_complete = false;
-
-	if (wait) {
-		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
-	}
-}
-
-void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
-{
-	struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	struct drm_plane *plane;
-	struct drm_plane_state *new_state, *old_state;
-	struct drm_crtc_state *crtc_state;
-	struct dcp_swap_submit_req *req = &dcp->swap;
-	int l;
-	int has_surface = 0;
-	bool modeset;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
-
-	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
-
-	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
-	    WARN(!modeset && !dcp->connector->connected, "can't flush if disconnected")) {
-		/* HACK: issue a delayed vblank event to avoid timeouts in
-		 * drm_atomic_helper_wait_for_vblanks().
-		 */
-		schedule_work(&dcp->vblank_wq);
-		return;
-	}
-
-	/* Reset to defaults */
-	memset(req, 0, sizeof(*req));
-	for (l = 0; l < SWAP_SURFACES; l++)
-		req->surf_null[l] = true;
-
-	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) {
-		struct drm_framebuffer *fb = new_state->fb;
-		struct drm_rect src_rect;
-
-		WARN_ON(l >= SWAP_SURFACES);
-
-		req->swap.swap_enabled |= BIT(l);
-
-		if (old_state->fb && fb != old_state->fb) {
-			/*
-			 * Race condition between a framebuffer unbind getting
-			 * swapped out and GEM unreferencing a framebuffer. If
-			 * we lose the race, the display gets IOVA faults and
-			 * the DCP crashes. We need to extend the lifetime of
-			 * the drm_framebuffer (and hence the GEM object) until
-			 * after we get a swap complete for the swap unbinding
-			 * it.
-			 */
-			struct dcp_fb_reference *entry = kzalloc(sizeof(*entry), GFP_KERNEL);
-			if (entry) {
-				entry->fb = old_state->fb;
-				list_add_tail(&entry->head, &dcp->swapped_out_fbs);
-			}
-			drm_framebuffer_get(old_state->fb);
-		}
-
-		if (!new_state->fb) {
-			if (old_state->fb)
-				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
-
-			continue;
-		}
-		req->surf_null[l] = false;
-		has_surface = 1;
-
-
-		drm_rect_fp_to_int(&src_rect, &new_state->src);
-
-		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
-		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
-
-		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
-
-		req->surf[l] = (struct dcp_surface) {
-			.format = drm_format_to_dcp(fb->format->format),
-			.xfer_func = 13,
-			.colorspace = 1,
-			.stride = fb->pitches[0],
-			.width = fb->width,
-			.height = fb->height,
-			.buf_size = fb->height * fb->pitches[0],
-			.surface_id = req->swap.surf_ids[l],
-
-			/* Only used for compressed or multiplanar surfaces */
-			.pix_size = 1,
-			.pel_w = 1,
-			.pel_h = 1,
-			.has_comp = 1,
-			.has_planes = 1,
-		};
-	}
-
-	/* These fields should be set together */
-	req->swap.swap_completed = req->swap.swap_enabled;
-
-	if (modeset) {
-		struct dcp_display_mode *mode;
-		struct dcp_wait_cookie *cookie;
-		int ret;
-
-		mode = lookup_mode(dcp, &crtc_state->mode);
-		if (!mode) {
-			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
-				 DRM_MODE_ARG(&crtc_state->mode));
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
-			 mode->color_mode_id, mode->timing_mode_id);
-		dcp->mode = (struct dcp_set_digital_out_mode_req) {
-			.color_mode_id = mode->color_mode_id,
-			.timing_mode_id = mode->timing_mode_id
-		};
-
-		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-		if (!cookie) {
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		init_completion(&cookie->done);
-		atomic_set(&cookie->refcount, 2);
-
-		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
-					 complete_set_digital_out_mode, cookie);
-
-		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
-		ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
-
-		if (atomic_dec_and_test(&cookie->refcount))
-			kfree(cookie);
-
-		if (ret == 0) {
-			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-		else if (ret > 0) {
-			dev_dbg(dcp->dev, "set_digital_out_mode finished with %d to spare",
-				jiffies_to_msecs(ret));
-		}
-
-		dcp->valid_mode = true;
-	}
-
-	if (!has_surface) {
-		if (crtc_state->enable && crtc_state->active && !crtc_state->planes_changed) {
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		req->clear = 1;
-	}
-	do_swap(dcp, NULL, NULL);
-}
-EXPORT_SYMBOL_GPL(dcp_flush);
-
-bool dcp_is_initialized(struct platform_device *pdev)
-{
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-
-	return dcp->active;
-}
-EXPORT_SYMBOL_GPL(dcp_is_initialized);
-
-
-static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct apple_connector *connector;
-	int result = *(int *)out;
-	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
-
-	dcp->main_display = result != 0;
-
-	dcp->active = true;
-
-	connector = dcp->connector;
-	if (connector) {
-		connector->connected = dcp->nr_modes > 0;
-		schedule_work(&connector->hotplug_wq);
-	}
-}
-
-static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
-}
-
-static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_first_client_open(dcp, false, init_3, NULL);
-}
-
-static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	u32 val = 0;
-	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
-}
-
-static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	dev_info(dcp->dev, "DCP booted\n");
-
-	init_1(dcp, data, cookie);
-}
-
-static void dcp_got_msg(void *cookie, u8 endpoint, u64 message)
+static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 {
 	struct apple_dcp *dcp = cookie;
-	enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK;
-
-	WARN_ON(endpoint != DCP_ENDPOINT);
 
-	if (type == DCPEP_TYPE_INITIALIZED)
-		dcp_start_signal(dcp, false, dcp_started, NULL);
-	else if (type == DCPEP_TYPE_MESSAGE)
-		dcpep_got_msg(dcp, message);
-	else
-		dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
+	switch (endpoint) {
+	case IOMFB_ENDPOINT:
+		return iomfb_recv_msg(dcp, message);
+	default:
+		WARN(endpoint, "unknown DCP endpoint %hhu", endpoint);
+	}
 }
 
 static void dcp_rtk_crashed(void *cookie)
@@ -1738,14 +111,16 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 	struct apple_dcp *dcp = cookie;
 
 	if (bfr->iova) {
-		struct iommu_domain *domain = iommu_get_domain_for_dev(dcp->dev);
+		struct iommu_domain *domain =
+			iommu_get_domain_for_dev(dcp->dev);
 		phys_addr_t phy_addr;
 
 		if (!domain)
 			return -ENOMEM;
 
 		// TODO: get map from device-tree
-		phy_addr = iommu_iova_to_phys(domain, bfr->iova & ~dcp->asc_dram_mask);
+		phy_addr = iommu_iova_to_phys(domain,
+					      bfr->iova & ~dcp->asc_dram_mask);
 		if (!phy_addr)
 			return -ENOMEM;
 
@@ -1755,10 +130,13 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 			return -ENOMEM;
 
 		bfr->is_mapped = true;
-		dev_info(dcp->dev, "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx",
-			(uintptr_t)bfr->iova, (uintptr_t)phy_addr, (uintptr_t)bfr->buffer);
+		dev_info(dcp->dev,
+			 "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx",
+			 (uintptr_t)bfr->iova, (uintptr_t)phy_addr,
+			 (uintptr_t)bfr->buffer);
 	} else {
-		bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size, &bfr->iova, GFP_KERNEL);
+		bfr->buffer = dma_alloc_coherent(dcp->dev, bfr->size,
+						 &bfr->iova, GFP_KERNEL);
 		if (!bfr->buffer)
 			return -ENOMEM;
 
@@ -1778,12 +156,13 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
 	if (bfr->is_mapped)
 		memunmap(bfr->buffer);
 	else
-		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova & ~dcp->asc_dram_mask);
+		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer,
+				  bfr->iova & ~dcp->asc_dram_mask);
 }
 
 static struct apple_rtkit_ops rtkit_ops = {
 	.crashed = dcp_rtk_crashed,
-	.recv_message = dcp_got_msg,
+	.recv_message = dcp_recv_msg,
 	.shmem_setup = dcp_rtk_shmem_setup,
 	.shmem_destroy = dcp_rtk_shmem_destroy,
 };
@@ -1839,7 +218,6 @@ static int dcp_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct apple_dcp *dcp;
-	dma_addr_t shmem_iova;
 	u32 cpu_ctrl;
 	int ret;
 
@@ -1884,7 +262,8 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	dcp->clk = devm_clk_get(dev, NULL);
 	if (IS_ERR(dcp->clk))
-		return dev_err_probe(dev, PTR_ERR(dcp->clk), "Unable to find clock\n");
+		return dev_err_probe(dev, PTR_ERR(dcp->clk),
+				     "Unable to find clock\n");
 
 	ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask",
 				   &dcp->asc_dram_mask);
@@ -1898,9 +277,11 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	INIT_WORK(&dcp->vblank_wq, dcp_delayed_vblank);
 
-	dcp->swapped_out_fbs = (struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
+	dcp->swapped_out_fbs =
+		(struct list_head)LIST_HEAD_INIT(dcp->swapped_out_fbs);
 
-	cpu_ctrl = readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
+	cpu_ctrl =
+		readl_relaxed(dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
 	writel_relaxed(cpu_ctrl | APPLE_DCP_COPROC_CPU_CONTROL_RUN,
 		       dcp->coproc_reg + APPLE_DCP_COPROC_CPU_CONTROL);
 
@@ -1914,14 +295,11 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, ret,
 				     "Failed to boot RTKit: %d", ret);
 
-	apple_rtkit_start_ep(dcp->rtk, DCP_ENDPOINT);
-
-	dcp->shmem = dma_alloc_coherent(dev, DCP_SHMEM_SIZE, &shmem_iova,
-					GFP_KERNEL);
-
-	shmem_iova |= dcp->asc_dram_mask;
-	apple_rtkit_send_message(dcp->rtk, DCP_ENDPOINT,
-				 dcpep_set_shmem(shmem_iova), NULL, false);
+	/* start RTKit endpoints */
+	ret = iomfb_start_rtkit(dcp);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				     "Failed to start IOMFB endpoint: %d", ret);
 
 	return ret;
 }
@@ -1934,15 +312,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
 
-	struct dcp_set_power_state_req req = {
-		/* defaults are ok */
-	};
-
-	/* We're going down */
-	dcp->active = false;
-	dcp->valid_mode = false;
-
-	dcp_set_power_state(dcp, false, &req, NULL, NULL);
+	iomfb_shutdown(dcp);
 }
 
 static const struct of_device_id of_match[] = {
@@ -1965,9 +335,7 @@ static int dcp_suspend(struct device *dev)
 
 static int dcp_resume(struct device *dev)
 {
-	struct apple_dcp *dcp = platform_get_drvdata(to_platform_device(dev));
-
-	dcp_start_signal(dcp, false, dcp_started, NULL);
+	dcp_poweron(to_platform_device(dev));
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 9e3e3738a39377..18d71afaf6b72e 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -5,6 +5,9 @@
 #define __APPLE_DCP_H__
 
 #include <drm/drm_atomic.h>
+#include <drm/drm_fourcc.h>
+
+#include "dcp-internal.h"
 #include "parser.h"
 
 struct apple_crtc {
@@ -39,8 +42,16 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
 bool dcp_is_initialized(struct platform_device *pdev);
 void apple_crtc_vblank(struct apple_crtc *apple);
+void dcp_drm_crtc_vblank(struct apple_crtc *crtc);
 int dcp_get_modes(struct drm_connector *connector);
 int dcp_mode_valid(struct drm_connector *connector,
 		   struct drm_display_mode *mode);
+void dcp_set_dimensions(struct apple_dcp *dcp);
+
+
+int iomfb_start_rtkit(struct apple_dcp *dcp);
+void iomfb_shutdown(struct apple_dcp *dcp);
+/* rtkit message handler for IOMFB messages */
+void iomfb_recv_msg(struct apple_dcp *dcp, u64 message);
 
 #endif
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
new file mode 100644
index 00000000000000..0da3de1aa27e78
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -0,0 +1,1626 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#include <linux/bitmap.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/align.h>
+#include <linux/apple-mailbox.h>
+#include <linux/soc/apple/rtkit.h>
+#include <linux/completion.h>
+
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "dcp.h"
+#include "dcp-internal.h"
+#include "iomfb.h"
+#include "parser.h"
+
+/* Register defines used in bandwidth setup structure */
+#define REG_SCRATCH (0x14)
+#define REG_SCRATCH_T600X (0x988)
+#define REG_DOORBELL (0x0)
+#define REG_DOORBELL_BIT (2)
+
+struct dcp_wait_cookie {
+	struct completion done;
+	atomic_t refcount;
+};
+
+static int dcp_tx_offset(enum dcp_context_id id)
+{
+	switch (id) {
+	case DCP_CONTEXT_CB:
+	case DCP_CONTEXT_CMD:
+		return 0x00000;
+	case DCP_CONTEXT_OOBCB:
+	case DCP_CONTEXT_OOBCMD:
+		return 0x08000;
+	default:
+		return -EINVAL;
+	}
+}
+
+static int dcp_channel_offset(enum dcp_context_id id)
+{
+	switch (id) {
+	case DCP_CONTEXT_ASYNC:
+		return 0x40000;
+	case DCP_CONTEXT_CB:
+		return 0x60000;
+	case DCP_CONTEXT_OOBCB:
+		return 0x68000;
+	default:
+		return dcp_tx_offset(id);
+	}
+}
+
+static inline u64 dcpep_set_shmem(u64 dart_va)
+{
+	return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) |
+	       (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) |
+	       (dart_va << DCPEP_DVA_SHIFT);
+}
+
+static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
+{
+	return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) |
+	       ((u64)id << DCPEP_CONTEXT_SHIFT) |
+	       ((u64)offset << DCPEP_OFFSET_SHIFT) |
+	       ((u64)length << DCPEP_LENGTH_SHIFT);
+}
+
+static inline u64 dcpep_ack(enum dcp_context_id id)
+{
+	return dcpep_msg(id, 0, 0) | DCPEP_ACK;
+}
+
+/*
+ * A channel is busy if we have sent a message that has yet to be
+ * acked. The driver must not sent a message to a busy channel.
+ */
+static bool dcp_channel_busy(struct dcp_call_channel *ch)
+{
+	return (ch->depth != 0);
+}
+
+/* Get a call channel for a context */
+static struct dcp_call_channel *
+dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context)
+{
+	switch (context) {
+	case DCP_CONTEXT_CMD:
+	case DCP_CONTEXT_CB:
+		return &dcp->ch_cmd;
+	case DCP_CONTEXT_OOBCMD:
+	case DCP_CONTEXT_OOBCB:
+		return &dcp->ch_oobcmd;
+	default:
+		return NULL;
+	}
+}
+
+/*
+ * Get the context ID passed to the DCP for a command we push. The rule is
+ * simple: callback contexts are used when replying to the DCP, command
+ * contexts are used otherwise. That corresponds to a non/zero call stack
+ * depth. This rule frees the caller from tracking the call context manually.
+ */
+static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
+{
+	u8 depth = oob ? dcp->ch_oobcmd.depth : dcp->ch_cmd.depth;
+
+	if (depth)
+		return oob ? DCP_CONTEXT_OOBCB : DCP_CONTEXT_CB;
+	else
+		return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
+}
+
+/* Get a callback channel for a context */
+static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp,
+						 enum dcp_context_id context)
+{
+	switch (context) {
+	case DCP_CONTEXT_CB:
+		return &dcp->ch_cb;
+	case DCP_CONTEXT_OOBCB:
+		return &dcp->ch_oobcb;
+	case DCP_CONTEXT_ASYNC:
+		return &dcp->ch_async;
+	default:
+		return NULL;
+	}
+}
+
+/* Get the start of a packet: after the end of the previous packet */
+static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth)
+{
+	if (depth > 0)
+		return ch->end[depth - 1];
+	else
+		return 0;
+}
+
+/* Pushes and pops the depth of the call stack with safety checks */
+static u8 dcp_push_depth(u8 *depth)
+{
+	u8 ret = (*depth)++;
+
+	WARN_ON(ret >= DCP_MAX_CALL_DEPTH);
+	return ret;
+}
+
+static u8 dcp_pop_depth(u8 *depth)
+{
+	WARN_ON((*depth) == 0);
+
+	return --(*depth);
+}
+
+#define DCP_METHOD(tag, name) [name] = { #name, tag }
+
+const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
+	DCP_METHOD("A000", dcpep_late_init_signal),
+	DCP_METHOD("A029", dcpep_setup_video_limits),
+	DCP_METHOD("A034", dcpep_update_notify_clients_dcp),
+	DCP_METHOD("A357", dcpep_set_create_dfb),
+	DCP_METHOD("A401", dcpep_start_signal),
+	DCP_METHOD("A407", dcpep_swap_start),
+	DCP_METHOD("A408", dcpep_swap_submit),
+	DCP_METHOD("A410", dcpep_set_display_device),
+	DCP_METHOD("A411", dcpep_is_main_display),
+	DCP_METHOD("A412", dcpep_set_digital_out_mode),
+	DCP_METHOD("A439", dcpep_set_parameter_dcp),
+	DCP_METHOD("A443", dcpep_create_default_fb),
+	DCP_METHOD("A447", dcpep_enable_disable_video_power_savings),
+	DCP_METHOD("A454", dcpep_first_client_open),
+	DCP_METHOD("A460", dcpep_set_display_refresh_properties),
+	DCP_METHOD("A463", dcpep_flush_supports_power),
+	DCP_METHOD("A468", dcpep_set_power_state),
+};
+
+/* Call a DCP function given by a tag */
+static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
+		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
+		     void *cookie)
+{
+	struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd;
+	enum dcp_context_id context = dcp_call_context(dcp, oob);
+
+	struct dcp_packet_header header = {
+		.in_len = in_len,
+		.out_len = out_len,
+
+		/* Tag is reversed due to endianness of the fourcc */
+		.tag[0] = dcp_methods[method].tag[3],
+		.tag[1] = dcp_methods[method].tag[2],
+		.tag[2] = dcp_methods[method].tag[1],
+		.tag[3] = dcp_methods[method].tag[0],
+	};
+
+	u8 depth = dcp_push_depth(&ch->depth);
+	u16 offset = dcp_packet_start(ch, depth);
+
+	void *out = dcp->shmem + dcp_tx_offset(context) + offset;
+	void *out_data = out + sizeof(header);
+	size_t data_len = sizeof(header) + in_len + out_len;
+
+	memcpy(out, &header, sizeof(header));
+
+	if (in_len > 0)
+		memcpy(out_data, data, in_len);
+
+	dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n",
+		dcp_methods[method].name, context, offset, depth);
+
+	ch->callbacks[depth] = cb;
+	ch->cookies[depth] = cookie;
+	ch->output[depth] = out + sizeof(header) + in_len;
+	ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
+
+	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT,
+				 dcpep_msg(context, data_len, offset), NULL,
+				 false);
+}
+
+#define DCP_THUNK_VOID(func, handle)                                         \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
+			 void *cookie)                                       \
+	{                                                                    \
+		dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie);          \
+	}
+
+#define DCP_THUNK_OUT(func, handle, T)                                       \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
+			 void *cookie)                                       \
+	{                                                                    \
+		dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie);  \
+	}
+
+#define DCP_THUNK_IN(func, handle, T)                                       \
+	static void func(struct apple_dcp *dcp, bool oob, T *data,          \
+			 dcp_callback_t cb, void *cookie)                   \
+	{                                                                   \
+		dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \
+	}
+
+#define DCP_THUNK_INOUT(func, handle, T_in, T_out)                            \
+	static void func(struct apple_dcp *dcp, bool oob, T_in *data,         \
+			 dcp_callback_t cb, void *cookie)                     \
+	{                                                                     \
+		dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \
+			 cb, cookie);                                         \
+	}
+
+DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
+		struct dcp_swap_submit_resp);
+
+DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
+		struct dcp_swap_start_resp);
+
+DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
+		struct dcp_set_power_state_req,
+		struct dcp_set_power_state_resp);
+
+DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
+		struct dcp_set_digital_out_mode_req, u32);
+
+DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
+
+DCP_THUNK_OUT(dcp_set_display_refresh_properties,
+	      dcpep_set_display_refresh_properties, u32);
+
+DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
+DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
+DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
+DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
+DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
+DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
+DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
+
+__attribute__((unused))
+DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp,
+	     struct dcp_update_notify_clients_dcp);
+
+DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
+		struct dcp_set_parameter_dcp, u32);
+
+DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
+		dcpep_enable_disable_video_power_savings, u32, int);
+
+DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
+
+/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
+static int dcp_parse_tag(char tag[4])
+{
+	u32 d[3];
+	int i;
+
+	if (tag[3] != 'D')
+		return -EINVAL;
+
+	for (i = 0; i < 3; ++i) {
+		d[i] = (u32)(tag[i] - '0');
+
+		if (d[i] > 9)
+			return -EINVAL;
+	}
+
+	return d[0] + (d[1] * 10) + (d[2] * 100);
+}
+
+/* Ack a callback from the DCP */
+static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
+{
+	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+
+	dcp_pop_depth(&ch->depth);
+	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context),
+				 NULL, false);
+}
+
+/* DCP callback handlers */
+static void dcpep_cb_nop(struct apple_dcp *dcp)
+{
+	/* No operation */
+}
+
+static u8 dcpep_cb_true(struct apple_dcp *dcp)
+{
+	return true;
+}
+
+static u8 dcpep_cb_false(struct apple_dcp *dcp)
+{
+	return false;
+}
+
+static u32 dcpep_cb_zero(struct apple_dcp *dcp)
+{
+	return 0;
+}
+
+static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
+				   struct dc_swap_complete_resp *resp)
+{
+	dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u",
+		resp->swap_id, dcp->ignore_swap_complete);
+
+	if (!dcp->ignore_swap_complete)
+		dcp_drm_crtc_vblank(dcp->crtc);
+}
+
+static struct dcp_get_uint_prop_resp
+dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
+{
+	/* unimplemented for now */
+	return (struct dcp_get_uint_prop_resp){ .value = 0 };
+}
+
+/*
+ * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
+ * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
+ * stream of the display DART, rather than the expected DCP DART.
+ *
+ * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
+ * is a "fundamentally unsafe" operation according to the docs. And yet
+ * everyone does it...
+ */
+static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
+						   struct dcp_map_buf_req *req)
+{
+	struct sg_table *map;
+	int ret;
+
+	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
+		goto reject;
+
+	map = &dcp->memdesc[req->buffer].map;
+
+	if (!map->sgl)
+		goto reject;
+
+	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
+	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+
+	if (ret)
+		goto reject;
+
+	return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) };
+
+reject:
+	dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
+		req->buffer);
+	return (struct dcp_map_buf_resp){ .ret = EINVAL };
+}
+
+static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
+				  struct dcp_unmap_buf_resp *resp)
+{
+	struct sg_table *map;
+	dma_addr_t dma_addr;
+
+	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
+		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
+			 resp->buffer);
+		return;
+	}
+
+	map = &dcp->memdesc[resp->buffer].map;
+
+	if (!map->sgl) {
+		dev_warn(dcp->dev,
+			 "unmap for non-mapped buffer %llu iova:0x%08llx",
+			 resp->buffer, resp->dva);
+		return;
+	}
+
+	dma_addr = sg_dma_address(map->sgl);
+	if (dma_addr != resp->dva) {
+		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
+			 resp->buffer, dma_addr, resp->dva);
+		return;
+	}
+
+	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
+	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+}
+
+/*
+ * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
+ * physically contigiuous, however we should save the sgtable in case the
+ * buffer needs to be later mapped for PIODMA.
+ */
+static struct dcp_allocate_buffer_resp
+dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
+			 struct dcp_allocate_buffer_req *req)
+{
+	struct dcp_allocate_buffer_resp resp = { 0 };
+	struct dcp_mem_descriptor *memdesc;
+	u32 id;
+
+	resp.dva_size = ALIGN(req->size, 4096);
+	resp.mem_desc_id =
+		find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+
+	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
+		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
+		resp.dva_size = 0;
+		resp.mem_desc_id = 0;
+		return resp;
+	}
+	id = resp.mem_desc_id;
+	set_bit(id, dcp->memdesc_map);
+
+	memdesc = &dcp->memdesc[id];
+
+	memdesc->size = resp.dva_size;
+	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size,
+					  &memdesc->dva, GFP_KERNEL);
+
+	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
+			memdesc->size);
+	resp.dva = memdesc->dva;
+
+	return resp;
+}
+
+static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
+{
+	struct dcp_mem_descriptor *memdesc;
+	u32 id = *mem_desc_id;
+
+	if (id >= DCP_MAX_MAPPINGS) {
+		dev_warn(dcp->dev,
+			 "unmap request for out of range mem_desc_id %u", id);
+		return 0;
+	}
+
+	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
+		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
+			 id);
+		return 0;
+	}
+
+	memdesc = &dcp->memdesc[id];
+	if (memdesc->buf) {
+		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
+				  memdesc->dva);
+
+		memdesc->buf = NULL;
+		memset(&memdesc->map, 0, sizeof(memdesc->map));
+	} else {
+		memdesc->reg = 0;
+	}
+
+	memdesc->size = 0;
+
+	return 1;
+}
+
+/* Validate that the specified region is a display register */
+static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
+{
+	int i;
+
+	for (i = 0; i < dcp->nr_disp_registers; ++i) {
+		struct resource *r = dcp->disp_registers[i];
+
+		if ((start >= r->start) && (end <= r->end))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Map contiguous physical memory into the DCP's address space. The firmware
+ * uses this to map the display registers we advertise in
+ * sr_map_device_memory_with_index, so we bounds check against that to guard
+ * safe against malicious coprocessors.
+ */
+static struct dcp_map_physical_resp
+dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
+{
+	int size = ALIGN(req->size, 4096);
+	u32 id;
+
+	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
+		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
+			req->paddr, req->size);
+		return (struct dcp_map_physical_resp){};
+	}
+
+	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+	set_bit(id, dcp->memdesc_map);
+	dcp->memdesc[id].size = size;
+	dcp->memdesc[id].reg = req->paddr;
+
+	return (struct dcp_map_physical_resp){
+		.dva_size = size,
+		.mem_desc_id = id,
+		.dva = dma_map_resource(dcp->dev, req->paddr, size,
+					DMA_BIDIRECTIONAL, 0),
+	};
+}
+
+static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
+{
+	return clk_get_rate(dcp->clk);
+}
+
+static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp,
+						struct dcp_map_reg_req *req)
+{
+	if (req->index >= dcp->nr_disp_registers) {
+		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
+			 req->index);
+
+		return (struct dcp_map_reg_resp){ .ret = 1 };
+	} else {
+		struct resource *rsrc = dcp->disp_registers[req->index];
+
+		return (struct dcp_map_reg_resp){
+			.addr = rsrc->start, .length = resource_size(rsrc)
+		};
+	}
+}
+
+static struct dcp_read_edt_data_resp
+dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
+{
+	return (struct dcp_read_edt_data_resp){
+		.value[0] = req->value[0],
+		.ret = 0,
+	};
+}
+
+/* Chunked data transfer for property dictionaries */
+static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
+{
+	if (dcp->chunks.data != NULL) {
+		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
+		return false;
+	}
+
+	dcp->chunks.length = *length;
+	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "failed to allocate chunks\n");
+		return false;
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
+			      struct dcp_set_dcpav_prop_chunk_req *req)
+{
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious chunk\n");
+		return false;
+	}
+
+	if (req->offset + req->length > dcp->chunks.length) {
+		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
+		return false;
+	}
+
+	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
+	return true;
+}
+
+static bool dcpep_process_chunks(struct apple_dcp *dcp,
+				 struct dcp_set_dcpav_prop_end_req *req)
+{
+	struct dcp_parse_ctx ctx;
+	int ret;
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious end\n");
+		return false;
+	}
+
+	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
+
+	if (ret) {
+		dev_warn(dcp->dev, "bad header on dcpav props\n");
+		return false;
+	}
+
+	if (!strcmp(req->key, "TimingElements")) {
+		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
+					     dcp->width_mm, dcp->height_mm);
+
+		if (IS_ERR(dcp->modes)) {
+			dev_warn(dcp->dev, "failed to parse modes\n");
+			dcp->modes = NULL;
+			dcp->nr_modes = 0;
+			return false;
+		}
+	} else if (!strcmp(req->key, "DisplayAttributes")) {
+		ret = parse_display_attributes(&ctx, &dcp->width_mm,
+					       &dcp->height_mm);
+
+		if (ret) {
+			dev_warn(dcp->dev, "failed to parse display attribs\n");
+			return false;
+		}
+
+		dcp_set_dimensions(dcp);
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
+			    struct dcp_set_dcpav_prop_end_req *req)
+{
+	u8 resp = dcpep_process_chunks(dcp, req);
+
+	/* Reset for the next transfer */
+	devm_kfree(dcp->dev, dcp->chunks.data);
+	dcp->chunks.data = NULL;
+
+	return resp;
+}
+
+/* Boot sequence */
+static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_cb_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+	dev_dbg(dcp->dev, "boot done");
+
+	*succ = true;
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
+}
+
+static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_late_init_signal(dcp, false, boot_5, NULL);
+}
+
+static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 v_true = true;
+
+	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
+}
+
+static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_setup_video_limits(dcp, false, boot_3, NULL);
+}
+
+static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_create_default_fb(dcp, false, boot_2, NULL);
+}
+
+/* Use special function signature to defer the ACK */
+static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__);
+	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
+	return false;
+}
+
+static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
+{
+	if (dcp->disp_registers[5] && dcp->disp_registers[6])
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch =
+				dcp->disp_registers[5]->start + REG_SCRATCH,
+			.reg_doorbell =
+				dcp->disp_registers[6]->start + REG_DOORBELL,
+			.doorbell_bit = REG_DOORBELL_BIT,
+
+			.padding[3] = 0x4, // XXX: required by 11.x firmware
+		};
+	else if (dcp->disp_registers[4])
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch = dcp->disp_registers[4]->start +
+				       REG_SCRATCH_T600X,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
+	else
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch = 0,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
+}
+
+/* Callback to get the current time as milliseconds since the UNIX epoch */
+static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
+{
+	return ktime_to_ms(ktime_get_real());
+}
+
+struct dcp_swap_cookie {
+	struct completion done;
+	atomic_t refcount;
+	u32 swap_id;
+};
+
+static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_submit_resp *resp = data;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		complete(&info->done);
+		if (atomic_dec_and_test(&info->refcount))
+			kfree(info);
+	}
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
+		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
+	}
+}
+
+static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
+				   void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
+	dcp->swap.swap.swap_id = resp->swap_id;
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		info->swap_id = resp->swap_id;
+	}
+
+	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie);
+}
+
+static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
+}
+
+static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_parameter_dcp param = {
+		.param = 14,
+		.value = { 0 },
+		.count = 1,
+	};
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_final, cookie);
+}
+
+void dcp_poweron(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct dcp_wait_cookie *cookie;
+	struct dcp_set_power_state_req req = {
+		.unklong = 1,
+	};
+	int ret;
+	u32 handle;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+
+	init_completion(&cookie->done);
+	atomic_set(&cookie->refcount, 2);
+
+	if (dcp->main_display) {
+		handle = 0;
+		dcp_set_display_device(dcp, false, &handle, dcp_on_final,
+				       cookie);
+	} else {
+		handle = 2;
+		dcp_set_display_device(dcp, false, &handle,
+				       dcp_on_set_parameter, cookie);
+	}
+	dcp_set_power_state(dcp, true, &req, NULL, NULL);
+
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "wait for power timed out");
+
+	if (atomic_dec_and_test(&cookie->refcount))
+		kfree(cookie);
+}
+EXPORT_SYMBOL(dcp_poweron);
+
+static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
+				    void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
+}
+
+void dcp_poweroff(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	int ret, swap_id;
+	struct dcp_set_power_state_req power_req = {
+		.unklong = 0,
+	};
+	struct dcp_swap_cookie *cookie;
+	struct dcp_wait_cookie *poff_cookie;
+	struct dcp_swap_start_req swap_req = { 0 };
+
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+	init_completion(&cookie->done);
+	atomic_set(&cookie->refcount, 2);
+
+	// clear surfaces
+	memset(&dcp->swap, 0, sizeof(dcp->swap));
+
+	dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7;
+	dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7;
+	dcp->swap.swap.unk_10c = 0xFF000000;
+
+	for (int l = 0; l < SWAP_SURFACES; l++)
+		dcp->swap.surf_null[l] = true;
+
+	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
+
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
+	swap_id = cookie->swap_id;
+	if (atomic_dec_and_test(&cookie->refcount))
+		kfree(cookie);
+	if (ret <= 0) {
+		dcp->crashed = true;
+		return;
+	}
+
+	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
+
+	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
+	if (!poff_cookie)
+		return;
+	init_completion(&poff_cookie->done);
+	atomic_set(&poff_cookie->refcount, 2);
+
+	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
+			    poff_cookie);
+	ret = wait_for_completion_timeout(&poff_cookie->done,
+					  msecs_to_jiffies(1000));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
+	else if (ret > 0)
+		dev_dbg(dcp->dev,
+			"setPowerState(0) finished with %d ms to spare",
+			jiffies_to_msecs(ret));
+
+	if (atomic_dec_and_test(&poff_cookie->refcount))
+		kfree(poff_cookie);
+	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
+}
+EXPORT_SYMBOL(dcp_poweroff);
+
+/*
+ * Helper to send a DRM hotplug event. The DCP is accessed from a single
+ * (RTKit) thread. To handle hotplug callbacks, we need to call
+ * drm_kms_helper_hotplug_event, which does an atomic commit (via DCP) and
+ * waits for vblank (a DCP callback). That means we deadlock if we call from
+ * the RTKit thread! Instead, move the call to another thread via a workqueue.
+ */
+void dcp_hotplug(struct work_struct *work)
+{
+	struct apple_connector *connector;
+	struct drm_device *dev;
+	struct apple_dcp *dcp;
+
+	connector = container_of(work, struct apple_connector, hotplug_wq);
+	dev = connector->base.dev;
+
+	dcp = platform_get_drvdata(connector->dcp);
+	dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected);
+
+	/*
+	 * DCP defers link training until we set a display mode. But we set
+	 * display modes from atomic_flush, so userspace needs to trigger a
+	 * flush, or the CRTC gets no signal.
+	 */
+	if (!dcp->valid_mode && connector->connected) {
+		drm_connector_set_link_status_property(
+			&connector->base, DRM_MODE_LINK_STATUS_BAD);
+	}
+
+	if (dev && dev->registered)
+		drm_kms_helper_hotplug_event(dev);
+}
+EXPORT_SYMBOL_GPL(dcp_hotplug);
+
+static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
+{
+	struct apple_connector *connector = dcp->connector;
+
+	/* Hotplug invalidates mode. DRM doesn't always handle this. */
+	if (!(*connected)) {
+		dcp->valid_mode = false;
+		/* after unplug swap will not complete until the next
+		 * set_digital_out_mode */
+		schedule_work(&dcp->vblank_wq);
+	}
+
+	if (connector && connector->connected != !!(*connected)) {
+		connector->connected = !!(*connected);
+		dcp->valid_mode = false;
+		schedule_work(&connector->hotplug_wq);
+	}
+}
+
+static void
+dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
+				    struct dcp_swap_complete_intent_gated *info)
+{
+	dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id,
+		info->width, info->height);
+}
+
+#define DCPEP_MAX_CB (1000)
+
+/*
+ * Define type-safe trampolines. Define typedefs to enforce type-safety on the
+ * input data (so if the types don't match, gcc errors out).
+ */
+
+#define TRAMPOLINE_VOID(func, handler)                                        \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		handler(dcp);                                                 \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_IN(func, handler, T_in)                                    \
+	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);       \
+                                                                              \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		callback_##handler cb = handler;                              \
+                                                                              \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		cb(dcp, in);                                                  \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                          \
+	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);      \
+                                                                              \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		T_out *typed_out = out;                                       \
+		callback_##handler cb = handler;                              \
+                                                                              \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		*typed_out = cb(dcp, in);                                     \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_OUT(func, handler, T_out)                                  \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		T_out *typed_out = out;                                       \
+                                                                              \
+		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		*typed_out = handler(dcp);                                    \
+		return true;                                                  \
+	}
+
+TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
+TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
+TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
+TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
+TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
+	      struct dc_swap_complete_resp);
+TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
+		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
+TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
+		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
+TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
+	      struct dcp_unmap_buf_resp);
+TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
+		 struct dcp_allocate_buffer_req,
+		 struct dcp_allocate_buffer_resp);
+TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
+		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
+TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32,
+		 u8);
+TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
+		 struct dcp_map_reg_resp);
+TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
+		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
+TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
+TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
+		 struct dcp_set_dcpav_prop_chunk_req, u8);
+TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
+		 struct dcp_set_dcpav_prop_end_req, u8);
+TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
+	       struct dcp_rt_bandwidth);
+TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
+TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
+TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
+TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
+	      dcpep_cb_swap_complete_intent_gated,
+	      struct dcp_swap_complete_intent_gated);
+
+bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
+					      void *) = {
+	[0] = trampoline_true, /* did_boot_signal */
+	[1] = trampoline_true, /* did_power_on_signal */
+	[2] = trampoline_nop, /* will_power_off_signal */
+	[3] = trampoline_rt_bandwidth,
+	[100] = trampoline_nop, /* match_pmu_service */
+	[101] = trampoline_zero, /* get_display_default_stride */
+	[103] = trampoline_nop, /* set_boolean_property */
+	[106] = trampoline_nop, /* remove_property */
+	[107] = trampoline_true, /* create_provider_service */
+	[108] = trampoline_true, /* create_product_service */
+	[109] = trampoline_true, /* create_pmu_service */
+	[110] = trampoline_true, /* create_iomfb_service */
+	[111] = trampoline_false, /* create_backlight_service */
+	[116] = dcpep_cb_boot_1,
+	[117] = trampoline_false, /* is_dark_boot */
+	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
+	[120] = trampoline_read_edt_data,
+	[122] = trampoline_prop_start,
+	[123] = trampoline_prop_chunk,
+	[124] = trampoline_prop_end,
+	[201] = trampoline_map_piodma,
+	[202] = trampoline_unmap_piodma,
+	[206] = trampoline_true, /* match_pmu_service_2 */
+	[207] = trampoline_true, /* match_backlight_service */
+	[208] = trampoline_get_time,
+	[211] = trampoline_nop, /* update_backlight_factor_prop */
+	[300] = trampoline_nop, /* pr_publish */
+	[401] = trampoline_get_uint_prop,
+	[404] = trampoline_nop, /* sr_set_uint_prop */
+	[406] = trampoline_nop, /* set_fx_prop */
+	[408] = trampoline_get_frequency,
+	[411] = trampoline_map_reg,
+	[413] = trampoline_true, /* sr_set_property_dict */
+	[414] = trampoline_true, /* sr_set_property_int */
+	[415] = trampoline_true, /* sr_set_property_bool */
+	[451] = trampoline_allocate_buffer,
+	[452] = trampoline_map_physical,
+	[456] = trampoline_release_mem_desc,
+	[552] = trampoline_true, /* set_property_dict_0 */
+	[561] = trampoline_true, /* set_property_dict */
+	[563] = trampoline_true, /* set_property_int */
+	[565] = trampoline_true, /* set_property_bool */
+	[567] = trampoline_true, /* set_property_str */
+	[574] = trampoline_zero, /* power_up_dart */
+	[576] = trampoline_hotplug,
+	[577] = trampoline_nop, /* powerstate_notify */
+	[582] = trampoline_true, /* create_default_fb_surface */
+	[589] = trampoline_swap_complete,
+	[591] = trampoline_swap_complete_intent_gated,
+	[593] = trampoline_nop, /* enable_backlight_message_ap_gated */
+	[598] = trampoline_nop, /* find_swap_function_gated */
+};
+
+static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
+			    void *data, u32 length)
+{
+	struct device *dev = dcp->dev;
+	struct dcp_packet_header *hdr = data;
+	void *in, *out;
+	int tag = dcp_parse_tag(hdr->tag);
+	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+	u8 depth;
+
+	if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) {
+		dev_warn(dev, "received unknown callback %c%c%c%c\n",
+			 hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
+		return;
+	}
+
+	in = data + sizeof(*hdr);
+	out = in + hdr->in_len;
+
+	// TODO: verify that in_len and out_len match our prototypes
+	// for now just clear the out data to have at least consistant results
+	if (hdr->out_len)
+		memset(out, 0, hdr->out_len);
+
+	depth = dcp_push_depth(&ch->depth);
+	ch->output[depth] = out;
+
+	if (dcpep_cb_handlers[tag](dcp, tag, out, in))
+		dcp_ack(dcp, context);
+}
+
+static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
+			     void *data, u32 length)
+{
+	struct dcp_packet_header *header = data;
+	struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context);
+	void *cookie;
+	dcp_callback_t cb;
+
+	if (!ch) {
+		dev_warn(dcp->dev, "ignoring ack on context %X\n", context);
+		return;
+	}
+
+	dcp_pop_depth(&ch->depth);
+
+	cb = ch->callbacks[ch->depth];
+	cookie = ch->cookies[ch->depth];
+
+	ch->callbacks[ch->depth] = NULL;
+	ch->cookies[ch->depth] = NULL;
+
+	if (cb)
+		cb(dcp, data + sizeof(*header) + header->in_len, cookie);
+}
+
+static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
+{
+	enum dcp_context_id ctx_id;
+	u16 offset;
+	u32 length;
+	int channel_offset;
+	void *data;
+
+	ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT;
+	offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT;
+	length = (message >> DCPEP_LENGTH_SHIFT);
+
+	channel_offset = dcp_channel_offset(ctx_id);
+
+	if (channel_offset < 0) {
+		dev_warn(dcp->dev, "invalid context received %u", ctx_id);
+		return;
+	}
+
+	data = dcp->shmem + channel_offset + offset;
+
+	if (message & DCPEP_ACK)
+		dcpep_handle_ack(dcp, ctx_id, data, length);
+	else
+		dcpep_handle_cb(dcp, ctx_id, data, length);
+}
+
+/*
+ * Callback for swap requests. If a swap failed, we'll never get a swap
+ * complete event so we need to fake a vblank event early to avoid a hang.
+ */
+
+static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_submit_resp *resp = data;
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
+		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
+	}
+}
+
+static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+
+	dcp->swap.swap.swap_id = resp->swap_id;
+
+	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL);
+}
+
+/*
+ * DRM specifies rectangles as start and end coordinates.  DCP specifies
+ * rectangles as a start coordinate and a width/height. Convert a DRM rectangle
+ * to a DCP rectangle.
+ */
+static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
+{
+	return (struct dcp_rect){ .x = rect->x1,
+				  .y = rect->y1,
+				  .w = drm_rect_width(rect),
+				  .h = drm_rect_height(rect) };
+}
+
+static u32 drm_format_to_dcp(u32 drm)
+{
+	switch (drm) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+		return fourcc_code('A', 'R', 'G', 'B');
+
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return fourcc_code('A', 'B', 'G', 'R');
+	}
+
+	pr_warn("DRM format %X not supported in DCP\n", drm);
+	return 0;
+}
+
+int dcp_get_modes(struct drm_connector *connector)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+	struct platform_device *pdev = apple_connector->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	struct drm_device *dev = connector->dev;
+	struct drm_display_mode *mode;
+	int i;
+
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		mode = drm_mode_duplicate(dev, &dcp->modes[i].mode);
+
+		if (!mode) {
+			dev_err(dev->dev, "Failed to duplicate display mode\n");
+			return 0;
+		}
+
+		drm_mode_probed_add(connector, mode);
+	}
+
+	return dcp->nr_modes;
+}
+EXPORT_SYMBOL_GPL(dcp_get_modes);
+
+/* The user may own drm_display_mode, so we need to search for our copy */
+static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
+					    struct drm_display_mode *mode)
+{
+	int i;
+
+	for (i = 0; i < dcp->nr_modes; ++i) {
+		if (drm_mode_match(mode, &dcp->modes[i].mode,
+				   DRM_MODE_MATCH_TIMINGS |
+					   DRM_MODE_MATCH_CLOCK))
+			return &dcp->modes[i];
+	}
+
+	return NULL;
+}
+
+int dcp_mode_valid(struct drm_connector *connector,
+		   struct drm_display_mode *mode)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+	struct platform_device *pdev = apple_connector->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	return lookup_mode(dcp, mode) ? MODE_OK : MODE_BAD;
+}
+EXPORT_SYMBOL_GPL(dcp_mode_valid);
+
+/* Helpers to modeset and swap, used to flush */
+static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_req start_req = { 0 };
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (dcp->connector && dcp->connector->connected)
+		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
+	else
+		dcp_drm_crtc_vblank(dcp->crtc);
+}
+
+static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
+					  void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	dcp->ignore_swap_complete = false;
+
+	if (wait) {
+		complete(&wait->done);
+		if (atomic_dec_and_test(&wait->refcount))
+			kfree(wait);
+	}
+}
+
+void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct drm_plane *plane;
+	struct drm_plane_state *new_state, *old_state;
+	struct drm_crtc_state *crtc_state;
+	struct dcp_swap_submit_req *req = &dcp->swap;
+	int l;
+	int has_surface = 0;
+	bool modeset;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
+
+	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
+	    WARN(!modeset && !dcp->connector->connected,
+		 "can't flush if disconnected")) {
+		/* HACK: issue a delayed vblank event to avoid timeouts in
+		 * drm_atomic_helper_wait_for_vblanks().
+		 */
+		schedule_work(&dcp->vblank_wq);
+		return;
+	}
+
+	/* Reset to defaults */
+	memset(req, 0, sizeof(*req));
+	for (l = 0; l < SWAP_SURFACES; l++)
+		req->surf_null[l] = true;
+
+	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) {
+		struct drm_framebuffer *fb = new_state->fb;
+		struct drm_rect src_rect;
+
+		WARN_ON(l >= SWAP_SURFACES);
+
+		req->swap.swap_enabled |= BIT(l);
+
+		if (old_state->fb && fb != old_state->fb) {
+			/*
+			 * Race condition between a framebuffer unbind getting
+			 * swapped out and GEM unreferencing a framebuffer. If
+			 * we lose the race, the display gets IOVA faults and
+			 * the DCP crashes. We need to extend the lifetime of
+			 * the drm_framebuffer (and hence the GEM object) until
+			 * after we get a swap complete for the swap unbinding
+			 * it.
+			 */
+			struct dcp_fb_reference *entry =
+				kzalloc(sizeof(*entry), GFP_KERNEL);
+			if (entry) {
+				entry->fb = old_state->fb;
+				list_add_tail(&entry->head,
+					      &dcp->swapped_out_fbs);
+			}
+			drm_framebuffer_get(old_state->fb);
+		}
+
+		if (!new_state->fb) {
+			if (old_state->fb)
+				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
+
+			continue;
+		}
+		req->surf_null[l] = false;
+		has_surface = 1;
+
+		drm_rect_fp_to_int(&src_rect, &new_state->src);
+
+		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
+		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
+
+		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
+
+		req->surf[l] = (struct dcp_surface){
+			.format = drm_format_to_dcp(fb->format->format),
+			.xfer_func = 13,
+			.colorspace = 1,
+			.stride = fb->pitches[0],
+			.width = fb->width,
+			.height = fb->height,
+			.buf_size = fb->height * fb->pitches[0],
+			.surface_id = req->swap.surf_ids[l],
+
+			/* Only used for compressed or multiplanar surfaces */
+			.pix_size = 1,
+			.pel_w = 1,
+			.pel_h = 1,
+			.has_comp = 1,
+			.has_planes = 1,
+		};
+	}
+
+	/* These fields should be set together */
+	req->swap.swap_completed = req->swap.swap_enabled;
+
+	if (modeset) {
+		struct dcp_display_mode *mode;
+		struct dcp_wait_cookie *cookie;
+		int ret;
+
+		mode = lookup_mode(dcp, &crtc_state->mode);
+		if (!mode) {
+			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
+				 DRM_MODE_ARG(&crtc_state->mode));
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
+			 mode->color_mode_id, mode->timing_mode_id);
+		dcp->mode = (struct dcp_set_digital_out_mode_req){
+			.color_mode_id = mode->color_mode_id,
+			.timing_mode_id = mode->timing_mode_id
+		};
+
+		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+		if (!cookie) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		init_completion(&cookie->done);
+		atomic_set(&cookie->refcount, 2);
+
+		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
+					 complete_set_digital_out_mode, cookie);
+
+		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
+		ret = wait_for_completion_timeout(&cookie->done,
+						  msecs_to_jiffies(500));
+
+		if (atomic_dec_and_test(&cookie->refcount))
+			kfree(cookie);
+
+		if (ret == 0) {
+			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
+			schedule_work(&dcp->vblank_wq);
+			return;
+		} else if (ret > 0) {
+			dev_dbg(dcp->dev,
+				"set_digital_out_mode finished with %d to spare",
+				jiffies_to_msecs(ret));
+		}
+
+		dcp->valid_mode = true;
+	}
+
+	if (!has_surface) {
+		if (crtc_state->enable && crtc_state->active &&
+		    !crtc_state->planes_changed) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		req->clear = 1;
+	}
+	do_swap(dcp, NULL, NULL);
+}
+EXPORT_SYMBOL_GPL(dcp_flush);
+
+bool dcp_is_initialized(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	return dcp->active;
+}
+EXPORT_SYMBOL_GPL(dcp_is_initialized);
+
+static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct apple_connector *connector;
+	int result = *(int *)out;
+	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
+
+	dcp->main_display = result != 0;
+
+	dcp->active = true;
+
+	connector = dcp->connector;
+	if (connector) {
+		connector->connected = dcp->nr_modes > 0;
+		schedule_work(&connector->hotplug_wq);
+	}
+}
+
+static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
+}
+
+static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_first_client_open(dcp, false, init_3, NULL);
+}
+
+static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 val = 0;
+	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
+}
+
+static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	dev_info(dcp->dev, "DCP booted\n");
+
+	init_1(dcp, data, cookie);
+}
+
+void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
+{
+	enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK;
+
+	if (type == DCPEP_TYPE_INITIALIZED)
+		dcp_start_signal(dcp, false, dcp_started, NULL);
+	else if (type == DCPEP_TYPE_MESSAGE)
+		dcpep_got_msg(dcp, message);
+	else
+		dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
+}
+
+int iomfb_start_rtkit(struct apple_dcp *dcp)
+{
+	dma_addr_t shmem_iova;
+	apple_rtkit_start_ep(dcp->rtk, IOMFB_ENDPOINT);
+
+	dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova,
+					GFP_KERNEL);
+
+	shmem_iova |= dcp->asc_dram_mask;
+	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT,
+				 dcpep_set_shmem(shmem_iova), NULL, false);
+
+	return 0;
+}
+
+void iomfb_shutdown(struct apple_dcp *dcp)
+{
+	struct dcp_set_power_state_req req = {
+		/* defaults are ok */
+	};
+
+	/* We're going down */
+	dcp->active = false;
+	dcp->valid_mode = false;
+
+	dcp_set_power_state(dcp, false, &req, NULL, NULL);
+}
diff --git a/drivers/gpu/drm/apple/dcpep.h b/drivers/gpu/drm/apple/iomfb.h
similarity index 86%
rename from drivers/gpu/drm/apple/dcpep.h
rename to drivers/gpu/drm/apple/iomfb.h
index a796b16bd55ad7..96dfd170b8e330 100644
--- a/drivers/gpu/drm/apple/dcpep.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -4,8 +4,10 @@
 #ifndef __APPLE_DCPEP_H__
 #define __APPLE_DCPEP_H__
 
+#include <linux/types.h>
+
 /* Endpoint for general DCP traffic (dcpep in macOS) */
-#define DCP_ENDPOINT 0x37
+#define IOMFB_ENDPOINT 0x37
 
 /* Fixed size of shared memory between DCP and AP */
 #define DCP_SHMEM_SIZE 0x100000
@@ -30,27 +32,6 @@ enum dcp_context_id {
 	DCP_NUM_CONTEXTS
 };
 
-static int dcp_tx_offset(enum dcp_context_id id)
-{
-	switch (id) {
-	case DCP_CONTEXT_CB:
-	case DCP_CONTEXT_CMD:    return 0x00000;
-	case DCP_CONTEXT_OOBCB:
-	case DCP_CONTEXT_OOBCMD: return 0x08000;
-	default:		 return -EINVAL;
-	}
-}
-
-static int dcp_channel_offset(enum dcp_context_id id)
-{
-	switch (id) {
-	case DCP_CONTEXT_ASYNC:  return 0x40000;
-	case DCP_CONTEXT_CB:     return 0x60000;
-	case DCP_CONTEXT_OOBCB:  return 0x68000;
-	default:		 return dcp_tx_offset(id);
-	}
-}
-
 /* RTKit endpoint message types */
 enum dcpep_type {
 	/* Set shared memory */
@@ -87,29 +68,6 @@ struct dcp_packet_header {
 #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0)
 #define DCP_PACKET_ALIGNMENT (0x40)
 
-static inline u64
-dcpep_set_shmem(u64 dart_va)
-{
-	return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) |
-	       (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) |
-	       (dart_va << DCPEP_DVA_SHIFT);
-}
-
-static inline u64
-dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
-{
-	return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) |
-	       ((u64) id << DCPEP_CONTEXT_SHIFT) |
-	       ((u64) offset << DCPEP_OFFSET_SHIFT) |
-	       ((u64) length << DCPEP_LENGTH_SHIFT);
-}
-
-static inline u64
-dcpep_ack(enum dcp_context_id id)
-{
-	return dcpep_msg(id, 0, 0) | DCPEP_ACK;
-}
-
 /* Structures used in v12.0 firmware */
 
 #define SWAP_SURFACES 4

From fb4f1431641ea5c783886e0144c85f48c836e66f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 3 Oct 2022 10:43:02 +0200
Subject: [PATCH 0522/1027] WIP: add header test target copied from i915

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/.gitignore |  1 +
 drivers/gpu/drm/apple/Makefile   | 15 +++++++++++++++
 2 files changed, 16 insertions(+)
 create mode 100644 drivers/gpu/drm/apple/.gitignore

diff --git a/drivers/gpu/drm/apple/.gitignore b/drivers/gpu/drm/apple/.gitignore
new file mode 100644
index 00000000000000..d9a77f3b59b21a
--- /dev/null
+++ b/drivers/gpu/drm/apple/.gitignore
@@ -0,0 +1 @@
+*.hdrtest
diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index d1f909792229e5..e6c517bca2b07d 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -7,3 +7,18 @@ apple_piodma-y := dummy-piodma.o
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
 obj-$(CONFIG_DRM_APPLE) += apple_dcp.o
 obj-$(CONFIG_DRM_APPLE) += apple_piodma.o
+
+# header test
+
+# exclude some broken headers from the test coverage
+no-header-test := \
+
+always-y += \
+	$(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \
+		$(shell cd $(src) && find * -name '*.h')))
+
+quiet_cmd_hdrtest = HDRTEST $(patsubst %.hdrtest,%.h,$@)
+      cmd_hdrtest = $(CC) $(filter-out $(CFLAGS_GCOV), $(c_flags)) -S -o /dev/null -x c /dev/null -include $<; touch $@
+
+$(obj)/%.hdrtest: $(src)/%.h FORCE
+	$(call if_changed_dep,hdrtest)

From 2e8c4894c68caa147443b8b6e65c4778421db5b2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 22 Oct 2022 09:27:51 +0200
Subject: [PATCH 0523/1027] gpu: drm: apple: Use connector types from
 devicetree

Needs to be re-done for upstream submission but the exisiting port,
bridge, connector and display bindings are a bad fit since DCP manages
everything.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 13 ++++++++++++-
 1 file changed, 12 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 16f6459dadb171..4ef2e10a404b13 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -24,6 +24,7 @@
 #include <drm/drm_gem_dma_helper.h>
 #include <drm/drm_gem_framebuffer_helper.h>
 #include <drm/drm_simple_kms_helper.h>
+#include <drm/drm_mode.h>
 #include <drm/drm_modeset_helper.h>
 #include <drm/drm_of.h>
 #include <drm/drm_probe_helper.h>
@@ -299,6 +300,7 @@ static int apple_probe_per_dcp(struct device *dev,
 	struct apple_connector *connector;
 	struct drm_encoder *encoder;
 	struct drm_plane *primary;
+	int con_type;
 	int ret;
 
 	primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
@@ -324,8 +326,17 @@ static int apple_probe_per_dcp(struct device *dev,
 	drm_connector_helper_add(&connector->base,
 				 &apple_connector_helper_funcs);
 
+	if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0)
+		con_type = DRM_MODE_CONNECTOR_eDP;
+	else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0)
+		con_type = DRM_MODE_CONNECTOR_HDMIA;
+	else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0)
+		con_type = DRM_MODE_CONNECTOR_USB;
+	else
+		con_type = DRM_MODE_CONNECTOR_Unknown;
+
 	ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs,
-				 DRM_MODE_CONNECTOR_HDMIA);
+				 con_type);
 	if (ret)
 		return ret;
 

From fbe6643e0c2fb1af6fd07dbd40b62b562ac44a05 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 5 Oct 2022 22:30:58 +0200
Subject: [PATCH 0524/1027] drm: apple: Fix connector state on devices with
 integrated display

DCP issues hotplug_gated callbacks after SetPowerState() calls on devices
with display (macbooks, imacs). This must not result in connector state
changes on DRM side. Weston will not re-enable the CRTC after DPMS off
if the connector is not in connected state.
DCP provides with dcp_is_main_display() a call to query if the device has
an integrated display.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 0da3de1aa27e78..3c930209382685 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -983,6 +983,16 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 {
 	struct apple_connector *connector = dcp->connector;
 
+	/* DCP issues hotplug_gated callbacks after SetPowerState() calls on
+	 * devices with display (macbooks, imacs). This must not result in
+	 * connector state changes on DRM side. Some applications won't enable
+	 * a CRTC with a connector in disconnected state. Weston after DPMS off
+	 * is one example. dcp_is_main_display() returns true on devices with
+	 * integrated display. Ignore the hotplug_gated() callbacks there.
+	 */
+	if (dcp->main_display)
+		return;
+
 	/* Hotplug invalidates mode. DRM doesn't always handle this. */
 	if (!(*connected)) {
 		dcp->valid_mode = false;

From 2083f981af84dcb4fb0c831f97fd5f5d636401c3 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 5 Oct 2022 22:59:54 +0200
Subject: [PATCH 0525/1027] drm: apple: Replace atomic refcount with kref

fixup! drm/apple: Add t600x support

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 61 ++++++++++++++++++++++-------------
 1 file changed, 39 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 3c930209382685..68ca1c2aa8354e 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -9,6 +9,7 @@
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/iommu.h>
+#include <linux/kref.h>
 #include <linux/align.h>
 #include <linux/apple-mailbox.h>
 #include <linux/soc/apple/rtkit.h>
@@ -32,10 +33,18 @@
 #define REG_DOORBELL_BIT (2)
 
 struct dcp_wait_cookie {
+	struct kref refcount;
 	struct completion done;
-	atomic_t refcount;
 };
 
+static void release_wait_cookie(struct kref *ref)
+{
+	struct dcp_wait_cookie *cookie;
+	cookie = container_of(ref, struct dcp_wait_cookie, refcount);
+
+        kfree(cookie);
+}
+
 static int dcp_tx_offset(enum dcp_context_id id)
 {
 	switch (id) {
@@ -755,11 +764,19 @@ static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
 }
 
 struct dcp_swap_cookie {
+	struct kref refcount;
 	struct completion done;
-	atomic_t refcount;
 	u32 swap_id;
 };
 
+static void release_swap_cookie(struct kref *ref)
+{
+	struct dcp_swap_cookie *cookie;
+	cookie = container_of(ref, struct dcp_swap_cookie, refcount);
+
+        kfree(cookie);
+}
+
 static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct dcp_swap_submit_resp *resp = data;
@@ -768,8 +785,7 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
 	if (cookie) {
 		struct dcp_swap_cookie *info = cookie;
 		complete(&info->done);
-		if (atomic_dec_and_test(&info->refcount))
-			kfree(info);
+		kref_put(&info->refcount, release_swap_cookie);
 	}
 
 	if (resp->ret) {
@@ -811,8 +827,7 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
 
 	if (wait) {
 		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
+		kref_put(&wait->refcount, release_wait_cookie);
 	}
 }
 
@@ -844,7 +859,9 @@ void dcp_poweron(struct platform_device *pdev)
 		return;
 
 	init_completion(&cookie->done);
-	atomic_set(&cookie->refcount, 2);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
 
 	if (dcp->main_display) {
 		handle = 0;
@@ -862,8 +879,7 @@ void dcp_poweron(struct platform_device *pdev)
 	if (ret == 0)
 		dev_warn(dcp->dev, "wait for power timed out");
 
-	if (atomic_dec_and_test(&cookie->refcount))
-		kfree(cookie);
+	kref_put(&cookie->refcount, release_wait_cookie);;
 }
 EXPORT_SYMBOL(dcp_poweron);
 
@@ -874,8 +890,7 @@ static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
 
 	if (wait) {
 		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
+		kref_put(&wait->refcount, release_wait_cookie);
 	}
 }
 
@@ -896,7 +911,9 @@ void dcp_poweroff(struct platform_device *pdev)
 	if (!cookie)
 		return;
 	init_completion(&cookie->done);
-	atomic_set(&cookie->refcount, 2);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
 
 	// clear surfaces
 	memset(&dcp->swap, 0, sizeof(dcp->swap));
@@ -912,8 +929,7 @@ void dcp_poweroff(struct platform_device *pdev)
 
 	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
 	swap_id = cookie->swap_id;
-	if (atomic_dec_and_test(&cookie->refcount))
-		kfree(cookie);
+	kref_put(&cookie->refcount, release_swap_cookie);
 	if (ret <= 0) {
 		dcp->crashed = true;
 		return;
@@ -925,7 +941,9 @@ void dcp_poweroff(struct platform_device *pdev)
 	if (!poff_cookie)
 		return;
 	init_completion(&poff_cookie->done);
-	atomic_set(&poff_cookie->refcount, 2);
+	kref_init(&poff_cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&poff_cookie->refcount);
 
 	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
 			    poff_cookie);
@@ -939,8 +957,7 @@ void dcp_poweroff(struct platform_device *pdev)
 			"setPowerState(0) finished with %d ms to spare",
 			jiffies_to_msecs(ret));
 
-	if (atomic_dec_and_test(&poff_cookie->refcount))
-		kfree(poff_cookie);
+	kref_put(&poff_cookie->refcount, release_wait_cookie);
 	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
 }
 EXPORT_SYMBOL(dcp_poweroff);
@@ -1379,8 +1396,7 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
 
 	if (wait) {
 		complete(&wait->done);
-		if (atomic_dec_and_test(&wait->refcount))
-			kfree(wait);
+		kref_put(&wait->refcount, release_wait_cookie);
 	}
 }
 
@@ -1509,7 +1525,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		}
 
 		init_completion(&cookie->done);
-		atomic_set(&cookie->refcount, 2);
+		kref_init(&cookie->refcount);
+		/* increase refcount to ensure the receiver has a reference */
+		kref_get(&cookie->refcount);
 
 		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
 					 complete_set_digital_out_mode, cookie);
@@ -1518,8 +1536,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		ret = wait_for_completion_timeout(&cookie->done,
 						  msecs_to_jiffies(500));
 
-		if (atomic_dec_and_test(&cookie->refcount))
-			kfree(cookie);
+		kref_put(&cookie->refcount, release_wait_cookie);
 
 		if (ret == 0) {
 			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");

From 63045ce7f3dcb86649e4cabcc641694c2926fdab Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 8 Oct 2022 18:30:41 +0200
Subject: [PATCH 0526/1027] gpu: drm: apple: Start using tracepoints

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile       |   5 +
 drivers/gpu/drm/apple/dcp-internal.h |  11 ++
 drivers/gpu/drm/apple/dcp.c          |  10 ++
 drivers/gpu/drm/apple/dcp.h          |   2 +-
 drivers/gpu/drm/apple/iomfb.c        |  32 +++---
 drivers/gpu/drm/apple/iomfb.h        |   3 -
 drivers/gpu/drm/apple/trace.c        |   9 ++
 drivers/gpu/drm/apple/trace.h        | 166 +++++++++++++++++++++++++++
 8 files changed, 217 insertions(+), 21 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/trace.c
 create mode 100644 drivers/gpu/drm/apple/trace.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index e6c517bca2b07d..082c8c58bb87fe 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -1,7 +1,12 @@
 # SPDX-License-Identifier: GPL-2.0-only OR MIT
 
+CFLAGS_trace.o = -I$(src)
+
 appledrm-y := apple_drv.o
+
 apple_dcp-y := dcp.o iomfb.o parser.o
+apple_dcp-$(CONFIG_TRACING) += trace.o
+
 apple_piodma-y := dummy-piodma.o
 
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 648d543b9cd91a..fd7c3aac603e35 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -12,6 +12,17 @@
 
 struct apple_dcp;
 
+enum {
+	SYSTEM_ENDPOINT = 0x20,
+	TEST_ENDPOINT = 0x21,
+	DCP_EXPERT_ENDPOINT = 0x22,
+	DISP0_ENDPOINT = 0x23,
+	DPTX_ENDPOINT = 0x2a,
+	HDCP_ENDPOINT = 0x2b,
+	REMOTE_ALLOC_ENDPOINT = 0x2d,
+	IOMFB_ENDPOINT = 0x37,
+};
+
 /* Temporary backing for a chunked transfer via setDCPAVPropStart/Chunk/End */
 struct dcp_chunks {
 	size_t length;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6ba5b6e93bddf2..10edc9e6377d09 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -22,6 +22,7 @@
 #include "dcp.h"
 #include "dcp-internal.h"
 #include "parser.h"
+#include "trace.h"
 
 #define APPLE_DCP_COPROC_CPU_CONTROL	 0x44
 #define APPLE_DCP_COPROC_CPU_CONTROL_RUN BIT(4)
@@ -90,6 +91,8 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 {
 	struct apple_dcp *dcp = cookie;
 
+	trace_dcp_recv_msg(dcp, endpoint, message);
+
 	switch (endpoint) {
 	case IOMFB_ENDPOINT:
 		return iomfb_recv_msg(dcp, message);
@@ -167,6 +170,13 @@ static struct apple_rtkit_ops rtkit_ops = {
 	.shmem_destroy = dcp_rtk_shmem_destroy,
 };
 
+void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message)
+{
+	trace_dcp_send_msg(dcp, endpoint, message);
+	apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL,
+				 false);
+}
+
 void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 	      struct apple_connector *connector)
 {
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 18d71afaf6b72e..047c1b3a160457 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -47,7 +47,7 @@ int dcp_get_modes(struct drm_connector *connector);
 int dcp_mode_valid(struct drm_connector *connector,
 		   struct drm_display_mode *mode);
 void dcp_set_dimensions(struct apple_dcp *dcp);
-
+void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message);
 
 int iomfb_start_rtkit(struct apple_dcp *dcp);
 void iomfb_shutdown(struct apple_dcp *dcp);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 68ca1c2aa8354e..5bab8825df5a60 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -25,6 +25,7 @@
 #include "dcp-internal.h"
 #include "iomfb.h"
 #include "parser.h"
+#include "trace.h"
 
 /* Register defines used in bandwidth setup structure */
 #define REG_SCRATCH (0x14)
@@ -228,17 +229,15 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 	if (in_len > 0)
 		memcpy(out_data, data, in_len);
 
-	dev_dbg(dcp->dev, "---> %s: context %u, offset %u, depth %u\n",
-		dcp_methods[method].name, context, offset, depth);
+	trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth);
 
 	ch->callbacks[depth] = cb;
 	ch->cookies[depth] = cookie;
 	ch->output[depth] = out + sizeof(header) + in_len;
 	ch->end[depth] = offset + ALIGN(data_len, DCP_PACKET_ALIGNMENT);
 
-	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT,
-				 dcpep_msg(context, data_len, offset), NULL,
-				 false);
+	dcp_send_message(dcp, IOMFB_ENDPOINT,
+			 dcpep_msg(context, data_len, offset));
 }
 
 #define DCP_THUNK_VOID(func, handle)                                         \
@@ -333,8 +332,8 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
 
 	dcp_pop_depth(&ch->depth);
-	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT, dcpep_ack(context),
-				 NULL, false);
+	dcp_send_message(dcp, IOMFB_ENDPOINT,
+			 dcpep_ack(context));
 }
 
 /* DCP callback handlers */
@@ -361,8 +360,7 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp)
 static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 				   struct dc_swap_complete_resp *resp)
 {
-	dev_dbg(dcp->dev, "swap complete for swap_id: %u vblank: %u",
-		resp->swap_id, dcp->ignore_swap_complete);
+	trace_iomfb_swap_complete(dcp, resp->swap_id);
 
 	if (!dcp->ignore_swap_complete)
 		dcp_drm_crtc_vblank(dcp->crtc);
@@ -725,7 +723,7 @@ static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
 /* Use special function signature to defer the ACK */
 static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
 {
-	dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, __func__);
+	trace_iomfb_callback(dcp, tag, __func__);
 	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
 	return false;
 }
@@ -1029,7 +1027,7 @@ static void
 dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 				    struct dcp_swap_complete_intent_gated *info)
 {
-	dev_dbg(dcp->dev, "swap_id:%u width:%u height:%u", info->swap_id,
+	trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id,
 		info->width, info->height);
 }
 
@@ -1043,7 +1041,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 #define TRAMPOLINE_VOID(func, handler)                                        \
 	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
 	{                                                                     \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
 		handler(dcp);                                                 \
 		return true;                                                  \
 	}
@@ -1055,7 +1053,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 	{                                                                     \
 		callback_##handler cb = handler;                              \
                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
 		cb(dcp, in);                                                  \
 		return true;                                                  \
 	}
@@ -1068,7 +1066,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 		T_out *typed_out = out;                                       \
 		callback_##handler cb = handler;                              \
                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
 		*typed_out = cb(dcp, in);                                     \
 		return true;                                                  \
 	}
@@ -1078,7 +1076,7 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 	{                                                                     \
 		T_out *typed_out = out;                                       \
                                                                               \
-		dev_dbg(dcp->dev, "Callback D%03d %s\n", tag, #handler);      \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
 		*typed_out = handler(dcp);                                    \
 		return true;                                                  \
 	}
@@ -1290,6 +1288,7 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
 
 	dcp->swap.swap.swap_id = resp->swap_id;
 
+	trace_iomfb_swap_submit(dcp, resp->swap_id);
 	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL);
 }
 
@@ -1633,8 +1632,7 @@ int iomfb_start_rtkit(struct apple_dcp *dcp)
 					GFP_KERNEL);
 
 	shmem_iova |= dcp->asc_dram_mask;
-	apple_rtkit_send_message(dcp->rtk, IOMFB_ENDPOINT,
-				 dcpep_set_shmem(shmem_iova), NULL, false);
+	dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova));
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 96dfd170b8e330..5b1f4ba789bccf 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -6,9 +6,6 @@
 
 #include <linux/types.h>
 
-/* Endpoint for general DCP traffic (dcpep in macOS) */
-#define IOMFB_ENDPOINT 0x37
-
 /* Fixed size of shared memory between DCP and AP */
 #define DCP_SHMEM_SIZE 0x100000
 
diff --git a/drivers/gpu/drm/apple/trace.c b/drivers/gpu/drm/apple/trace.c
new file mode 100644
index 00000000000000..6f40d5a583df01
--- /dev/null
+++ b/drivers/gpu/drm/apple/trace.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Tracepoints for Apple DCP driver
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#define CREATE_TRACE_POINTS
+#include "trace.h"
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
new file mode 100644
index 00000000000000..25a23c1586e259
--- /dev/null
+++ b/drivers/gpu/drm/apple/trace.h
@@ -0,0 +1,166 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright (C) The Asahi Linux Contributors */
+
+#undef TRACE_SYSTEM
+#define TRACE_SYSTEM dcp
+
+#if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ)
+#define _TRACE_DCP_H
+
+#include "dcp-internal.h"
+
+#include <linux/stringify.h>
+#include <linux/types.h>
+#include <linux/tracepoint.h>
+
+#define show_dcp_endpoint(ep)                                      \
+	__print_symbolic(ep, { SYSTEM_ENDPOINT, "system" },        \
+			 { TEST_ENDPOINT, "test" },                \
+			 { DCP_EXPERT_ENDPOINT, "dcpexpert" },     \
+			 { DISP0_ENDPOINT, "disp0" },              \
+			 { DPTX_ENDPOINT, "dptxport" },            \
+			 { HDCP_ENDPOINT, "hdcp" },                \
+			 { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \
+			 { IOMFB_ENDPOINT, "iomfb" })
+
+TRACE_EVENT(dcp_recv_msg,
+	    TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
+	    TP_ARGS(dcp, endpoint, message),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
+			     __field(u8, endpoint)
+			     __field(u64, message)),
+
+	    TP_fast_assign(__assign_str(devname);
+			   __entry->endpoint = endpoint;
+			   __entry->message = message;),
+
+	    TP_printk("%s: endpoint 0x%x (%s): received message 0x%016llx",
+		      __get_str(devname), __entry->endpoint,
+		      show_dcp_endpoint(__entry->endpoint), __entry->message));
+
+TRACE_EVENT(dcp_send_msg,
+	    TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
+	    TP_ARGS(dcp, endpoint, message),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
+			     __field(u8, endpoint)
+			     __field(u64, message)),
+
+	    TP_fast_assign(__assign_str(devname);
+			   __entry->endpoint = endpoint;
+			   __entry->message = message;),
+
+	    TP_printk("%s: endpoint 0x%x (%s): will send message 0x%016llx",
+		      __get_str(devname), __entry->endpoint,
+		      show_dcp_endpoint(__entry->endpoint), __entry->message));
+
+TRACE_EVENT(iomfb_callback,
+	    TP_PROTO(struct apple_dcp *dcp, int tag, const char *name),
+	    TP_ARGS(dcp, tag, name),
+
+	    TP_STRUCT__entry(
+				__string(devname, dev_name(dcp->dev))
+				__field(int, tag)
+				__field(const char *, name)
+			),
+
+	    TP_fast_assign(
+				__assign_str(devname);
+				__entry->tag = tag; __entry->name = name;
+			),
+
+	    TP_printk("%s: Callback D%03d %s", __get_str(devname), __entry->tag,
+		      __entry->name));
+
+TRACE_EVENT(iomfb_push,
+	    TP_PROTO(struct apple_dcp *dcp,
+		     const struct dcp_method_entry *method, int context,
+		     int offset, int depth),
+	    TP_ARGS(dcp, method, context, offset, depth),
+
+	    TP_STRUCT__entry(
+				__string(devname, dev_name(dcp->dev))
+				__string(name, method->name)
+				__field(int, context)
+				__field(int, offset)
+				__field(int, depth)),
+
+	    TP_fast_assign(
+				__assign_str(devname);
+				__assign_str(name);
+				__entry->context = context; __entry->offset = offset;
+				__entry->depth = depth;
+			),
+
+	    TP_printk("%s: Method %s: context %u, offset %u, depth %u",
+		      __get_str(devname), __get_str(name), __entry->context,
+		      __entry->offset, __entry->depth));
+
+TRACE_EVENT(iomfb_swap_submit,
+	    TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
+	    TP_ARGS(dcp, swap_id),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, swap_id)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->swap_id = swap_id;
+	    ),
+	    TP_printk("dcp=%llx, swap_id=%d",
+		      __entry->dcp,
+		      __entry->swap_id)
+);
+
+TRACE_EVENT(iomfb_swap_complete,
+	    TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
+	    TP_ARGS(dcp, swap_id),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, swap_id)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->swap_id = swap_id;
+	    ),
+	    TP_printk("dcp=%llx, swap_id=%d",
+		      __entry->dcp,
+		      __entry->swap_id
+	    )
+);
+
+TRACE_EVENT(iomfb_swap_complete_intent_gated,
+	    TP_PROTO(struct apple_dcp *dcp, u32 swap_id, u32 width, u32 height),
+	    TP_ARGS(dcp, swap_id, width, height),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, swap_id)
+			     __field(u32, width)
+			     __field(u32, height)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->swap_id = swap_id;
+			   __entry->height = height;
+			   __entry->width = width;
+	    ),
+	    TP_printk("dcp=%llx, swap_id=%u %ux%u",
+		      __entry->dcp,
+		      __entry->swap_id,
+		      __entry->width,
+		      __entry->height
+	    )
+);
+
+#endif /* _TRACE_DCP_H */
+
+/* This part must be outside protection */
+
+#undef TRACE_INCLUDE_FILE
+#define TRACE_INCLUDE_FILE trace
+
+#undef TRACE_INCLUDE_PATH
+#define TRACE_INCLUDE_PATH .
+
+#include <trace/define_trace.h>

From a1a854ddde494a4bc0587f08bba2f8849297e9ea Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 11 Oct 2022 08:34:03 +0200
Subject: [PATCH 0527/1027] gpu: drm: apple: Unbreak multiple DCP plane <->
 crtc matching

Still untested so this changes the status from definitively broken to
probably broken.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 11 +++++++----
 drivers/gpu/drm/apple/iomfb.c     | 12 ++++++++++--
 2 files changed, 17 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 4ef2e10a404b13..76875cda8bf983 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -135,6 +135,7 @@ u64 apple_format_modifiers[] = {
 };
 
 static struct drm_plane *apple_plane_init(struct drm_device *dev,
+					  unsigned long possible_crtcs,
 					  enum drm_plane_type type)
 {
 	int ret;
@@ -142,7 +143,8 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev,
 
 	plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
 
-	ret = drm_universal_plane_init(dev, plane, 0x1, &apple_plane_funcs,
+	ret = drm_universal_plane_init(dev, plane, possible_crtcs,
+				       &apple_plane_funcs,
 				       dcp_formats, ARRAY_SIZE(dcp_formats),
 				       apple_format_modifiers, type, NULL);
 	if (ret)
@@ -294,7 +296,8 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
 
 static int apple_probe_per_dcp(struct device *dev,
 			       struct drm_device *drm,
-			       struct platform_device *dcp)
+			       struct platform_device *dcp,
+			       int num)
 {
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
@@ -303,7 +306,7 @@ static int apple_probe_per_dcp(struct device *dev,
 	int con_type;
 	int ret;
 
-	primary = apple_plane_init(drm, DRM_PLANE_TYPE_PRIMARY);
+	primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY);
 
 	if (IS_ERR(primary))
 		return PTR_ERR(primary);
@@ -420,7 +423,7 @@ static int apple_platform_probe(struct platform_device *pdev)
 	apple->drm.mode_config.helper_private = &apple_mode_config_helpers;
 
 	for (i = 0; i < nr_dcp; ++i) {
-		ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i]);
+		ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i);
 
 		if (ret)
 			goto err_unload;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 5bab8825df5a60..d4b3dc8d3e6dd5 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1407,7 +1407,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	struct drm_plane_state *new_state, *old_state;
 	struct drm_crtc_state *crtc_state;
 	struct dcp_swap_submit_req *req = &dcp->swap;
-	int l;
+	int plane_idx, l;
 	int has_surface = 0;
 	bool modeset;
 	dev_dbg(dcp->dev, "%s", __func__);
@@ -1431,10 +1431,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	for (l = 0; l < SWAP_SURFACES; l++)
 		req->surf_null[l] = true;
 
-	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, l) {
+	l = 0;
+	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
 		struct drm_framebuffer *fb = new_state->fb;
 		struct drm_rect src_rect;
 
+		/* skip planes not for this crtc */
+		if (old_state->crtc != crtc && new_state->crtc != crtc)
+			continue;
+
 		WARN_ON(l >= SWAP_SURFACES);
 
 		req->swap.swap_enabled |= BIT(l);
@@ -1463,6 +1468,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			if (old_state->fb)
 				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
 
+			l += 1;
 			continue;
 		}
 		req->surf_null[l] = false;
@@ -1492,6 +1498,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			.has_comp = 1,
 			.has_planes = 1,
 		};
+
+		l += 1;
 	}
 
 	/* These fields should be set together */

From 067a85049b713730a37155d0bcfa7b289be059aa Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 11 Oct 2022 08:38:32 +0200
Subject: [PATCH 0528/1027] gpu: drm: apple: Add support for
 DRM_FORMAT_XRGB2101010

iboot uses DRM_FORMAT_XRGB2101010 at boot announce preference by listing
it as first format.

Disabled for now since it results in oversaturated colors. Might be
fixed by using "IOMFB::UPPipe2::set_matrix()".

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  2 ++
 drivers/gpu/drm/apple/iomfb.c     | 23 ++++++++++++++++++++++-
 2 files changed, 24 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 76875cda8bf983..bd74ac438deb7b 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -123,6 +123,8 @@ static const struct drm_plane_funcs apple_plane_funcs = {
  * advertise formats without alpha.
  */
 static const u32 dcp_formats[] = {
+	// DRM_FORMAT_XRGB2101010,
+	// DRM_FORMAT_ARGB2101010,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XBGR8888,
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index d4b3dc8d3e6dd5..56622c9e463173 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1315,12 +1315,33 @@ static u32 drm_format_to_dcp(u32 drm)
 	case DRM_FORMAT_XBGR8888:
 	case DRM_FORMAT_ABGR8888:
 		return fourcc_code('A', 'B', 'G', 'R');
+
+	case DRM_FORMAT_ARGB2101010:
+	case DRM_FORMAT_XRGB2101010:
+		return fourcc_code('r', '0', '3', 'w');
 	}
 
 	pr_warn("DRM format %X not supported in DCP\n", drm);
 	return 0;
 }
 
+static u8 drm_format_to_colorspace(u32 drm)
+{
+	switch (drm) {
+	case DRM_FORMAT_XRGB8888:
+	case DRM_FORMAT_ARGB8888:
+	case DRM_FORMAT_XBGR8888:
+	case DRM_FORMAT_ABGR8888:
+		return 1;
+
+	case DRM_FORMAT_ARGB2101010:
+	case DRM_FORMAT_XRGB2101010:
+		return 2;
+	}
+
+	return 1;
+}
+
 int dcp_get_modes(struct drm_connector *connector)
 {
 	struct apple_connector *apple_connector = to_apple_connector(connector);
@@ -1484,7 +1505,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->surf[l] = (struct dcp_surface){
 			.format = drm_format_to_dcp(fb->format->format),
 			.xfer_func = 13,
-			.colorspace = 1,
+			.colorspace = drm_format_to_colorspace(fb->format->format),
 			.stride = fb->pitches[0],
 			.width = fb->width,
 			.height = fb->height,

From 50dd8af28914ecc8c30a8de24ecda5578c925f35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 11 Oct 2022 08:40:46 +0200
Subject: [PATCH 0529/1027] gpu: drm: apple: Add apple_drm_gem_dumb_create()

DCP needs a 64-byte aligned pitch, override drm_gem_dma_dumb_create()
to align the pitch as necessary and use
drm_gem_dma_dumb_create_internal() to allocate the GEM object.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index bd74ac438deb7b..918f1042247005 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -46,8 +46,18 @@ struct apple_drm_private {
 
 DEFINE_DRM_GEM_DMA_FOPS(apple_fops);
 
+static int apple_drm_gem_dumb_create(struct drm_file *file_priv,
+                            struct drm_device *drm,
+                            struct drm_mode_create_dumb *args)
+{
+        args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64);
+        args->size = args->pitch * args->height;
+
+	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
+}
+
 static const struct drm_driver apple_drm_driver = {
-	DRM_GEM_DMA_DRIVER_OPS,
+	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create),
 	.name			= DRIVER_NAME,
 	.desc			= DRIVER_DESC,
 	.date			= "20210901",

From c326f1f7837c4032149fe423df355d6c7dbfcac2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 12 Oct 2022 23:12:07 +0200
Subject: [PATCH 0530/1027] gpu: drm: apple: Reject modes without valid color
 mode

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index f0cd38c2a81048..bb7d57f272ddb9 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -339,6 +339,12 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 			return ret;
 	}
 
+	/*
+	 * Reject modes without valid color mode.
+	 */
+	if (best_color_mode < 0)
+		return -EINVAL;
+
 	/*
 	 * We need to skip virtual modes. In some cases, virtual modes are "too
 	 * big" for the monitor and can cause breakage. It is unclear why the

From 403db2a11623d0a3c0d9fc6af6040ef255ba1533 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 12 Oct 2022 23:25:49 +0200
Subject: [PATCH 0531/1027] gpu: drm: apple: Convert 2 non-assert WARN()s to
 dev_err()

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++---
 1 file changed, 12 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 56622c9e463173..3893c0f86ef296 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1437,9 +1437,18 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
 
-	if (WARN(dcp_channel_busy(&dcp->ch_cmd), "unexpected busy channel") ||
-	    WARN(!modeset && !dcp->connector->connected,
-		 "can't flush if disconnected")) {
+	if (dcp_channel_busy(&dcp->ch_cmd))
+	{
+		dev_err(dcp->dev, "unexpected busy command channel");
+		/* HACK: issue a delayed vblank event to avoid timeouts in
+		 * drm_atomic_helper_wait_for_vblanks().
+		 */
+		schedule_work(&dcp->vblank_wq);
+		return;
+	}
+	if (!modeset && !dcp->connector->connected)
+	{
+		dev_err(dcp->dev, "dcp_flush while disconnected");
 		/* HACK: issue a delayed vblank event to avoid timeouts in
 		 * drm_atomic_helper_wait_for_vblanks().
 		 */

From e8b8acb62721c842ae58fe69c77aa4b8a54dea9b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 20 Oct 2022 20:42:09 +0200
Subject: [PATCH 0532/1027] gpu: drm: apple: Send an disconnected hotplug event
 on ASC crash

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 10edc9e6377d09..cfed9e53844c57 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -107,6 +107,10 @@ static void dcp_rtk_crashed(void *cookie)
 
 	dcp->crashed = true;
 	dev_err(dcp->dev, "DCP has crashed");
+	if (dcp->connector) {
+		dcp->connector->connected = 0;
+		schedule_work(&dcp->connector->hotplug_wq);
+	}
 }
 
 static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)

From a7b51f6fddda998e8df208ea2ec2f706a805966b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 20 Oct 2022 23:02:50 +0200
Subject: [PATCH 0533/1027] gpu: drm: apple: Add dcp_crtc_atomic_check

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c    |  1 +
 drivers/gpu/drm/apple/dcp-internal.h |  2 ++
 drivers/gpu/drm/apple/dcp.c          | 38 ++++++++++++++++++++++++++++
 drivers/gpu/drm/apple/dcp.h          |  1 +
 drivers/gpu/drm/apple/iomfb.c        |  9 -------
 5 files changed, 42 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 918f1042247005..d8d7dfd9bea40a 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -301,6 +301,7 @@ static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
 
 static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
 	.atomic_begin		= apple_crtc_atomic_begin,
+	.atomic_check		= dcp_crtc_atomic_check,
 	.atomic_flush		= dcp_flush,
 	.atomic_enable		= apple_crtc_atomic_enable,
 	.atomic_disable		= apple_crtc_atomic_disable,
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index fd7c3aac603e35..c7a7b9563a156f 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -10,6 +10,8 @@
 
 #include "iomfb.h"
 
+#define DCP_MAX_PLANES 2
+
 struct apple_dcp;
 
 enum {
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index cfed9e53844c57..40f374a2ad680a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -181,6 +181,44 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message)
 				 false);
 }
 
+int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct drm_plane_state *new_state;
+	struct drm_plane *plane;
+	struct drm_crtc_state *crtc_state;
+	int plane_idx, plane_count = 0;
+	bool needs_modeset;
+
+	if (dcp->crashed)
+		return -EINVAL;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
+	if (!needs_modeset && !dcp->connector->connected) {
+		dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset");
+		return -EINVAL;
+	}
+
+	for_each_new_plane_in_state(state, plane, new_state, plane_idx) {
+		/* skip planes not for this crtc */
+		if (new_state->crtc != crtc)
+			continue;
+
+		plane_count += 1;
+	}
+
+	if (plane_count > DCP_MAX_PLANES) {
+		dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check);
+
 void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 	      struct apple_connector *connector)
 {
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 047c1b3a160457..72ac1315372e5c 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -37,6 +37,7 @@ struct apple_connector {
 
 void dcp_poweroff(struct platform_device *pdev);
 void dcp_poweron(struct platform_device *pdev);
+int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);
 void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 	      struct apple_connector *connector);
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 3893c0f86ef296..da6d34b6d7d146 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1446,15 +1446,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		schedule_work(&dcp->vblank_wq);
 		return;
 	}
-	if (!modeset && !dcp->connector->connected)
-	{
-		dev_err(dcp->dev, "dcp_flush while disconnected");
-		/* HACK: issue a delayed vblank event to avoid timeouts in
-		 * drm_atomic_helper_wait_for_vblanks().
-		 */
-		schedule_work(&dcp->vblank_wq);
-		return;
-	}
 
 	/* Reset to defaults */
 	memset(req, 0, sizeof(*req));

From 73343e6e1b2f3af3d99c871e9fdaec7706b703ec Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 21 Oct 2022 22:31:42 +0200
Subject: [PATCH 0534/1027] gpu: drm: apple: Fix DCP run time PM

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 40f374a2ad680a..62d3c9f6ec9812 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -381,7 +381,7 @@ MODULE_DEVICE_TABLE(of, of_match);
  */
 static int dcp_suspend(struct device *dev)
 {
-	dcp_platform_shutdown(to_platform_device(dev));
+	dcp_poweroff(to_platform_device(dev));
 	return 0;
 }
 

From df6612d66bd82fd9bb4dc0a2c17c8e61d694253c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 21 Oct 2022 22:34:23 +0200
Subject: [PATCH 0535/1027] gpu: drm: apple: Fix DCP initialisation

Try to avoid races between DRM driver and DCP(ext) probing and
initialisation:
- do not start RTKit endpoints in dcp probe, iomfb excepts DRM objects
  to be already initialized on startup
- ensure in apple_drv that all DCPs are probe via device links
- initialize DRM planes, connectors, encoders
- start DCP RTKit endpoints synchronously

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 26 ++++++++++++++++++--------
 drivers/gpu/drm/apple/dcp.c       | 24 +++++++++++++-----------
 drivers/gpu/drm/apple/dcp.h       |  1 +
 3 files changed, 32 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index d8d7dfd9bea40a..31b844bcb2f43e 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -370,14 +370,16 @@ static int apple_probe_per_dcp(struct device *dev,
 
 static int apple_platform_probe(struct platform_device *pdev)
 {
+	struct device *dev = &pdev->dev;
 	struct apple_drm_private *apple;
 	struct platform_device *dcp[MAX_COPROCESSORS];
 	int ret, nr_dcp, i;
 
 	for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) {
 		struct device_node *np;
+		struct device_link *dcp_link;
 
-		np = of_parse_phandle(pdev->dev.of_node, "apple,coprocessors",
+		np = of_parse_phandle(dev->of_node, "apple,coprocessors",
 				      nr_dcp);
 
 		if (!np)
@@ -388,11 +390,14 @@ static int apple_platform_probe(struct platform_device *pdev)
 		if (!dcp[nr_dcp])
 			return -ENODEV;
 
-		/* DCP needs to be initialized before KMS can come online */
-		if (!platform_get_drvdata(dcp[nr_dcp]))
-			return -EPROBE_DEFER;
+		dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev,
+					   DL_FLAG_AUTOREMOVE_CONSUMER);
+		if (!dcp_link) {
+			dev_err(dev, "Failed to link to DCP %d device", nr_dcp);
+			return -EINVAL;
+		}
 
-		if (!dcp_is_initialized(dcp[nr_dcp]))
+		if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
 			return -EPROBE_DEFER;
 	}
 
@@ -400,11 +405,11 @@ static int apple_platform_probe(struct platform_device *pdev)
 	if (nr_dcp < 1)
 		return -ENODEV;
 
-	ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
 	if (ret)
 		return ret;
 
-	apple = devm_drm_dev_alloc(&pdev->dev, &apple_drm_driver,
+	apple = devm_drm_dev_alloc(dev, &apple_drm_driver,
 				   struct apple_drm_private, drm);
 	if (IS_ERR(apple))
 		return PTR_ERR(apple);
@@ -436,7 +441,12 @@ static int apple_platform_probe(struct platform_device *pdev)
 	apple->drm.mode_config.helper_private = &apple_mode_config_helpers;
 
 	for (i = 0; i < nr_dcp; ++i) {
-		ret = apple_probe_per_dcp(&pdev->dev, &apple->drm, dcp[i], i);
+		ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i);
+
+		if (ret)
+			goto err_unload;
+
+		ret = dcp_start(dcp[i]);
 
 		if (ret)
 			goto err_unload;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 62d3c9f6ec9812..83782b8fda74ab 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -226,14 +226,22 @@ void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 
 	dcp->crtc = crtc;
 	dcp->connector = connector;
+}
+EXPORT_SYMBOL_GPL(dcp_link);
+
+int dcp_start(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	int ret;
 
-	/* init connector status by modes offered by dcp */
-	connector->connected = dcp->nr_modes > 0;
+	/* start RTKit endpoints */
+	ret = iomfb_start_rtkit(dcp);
+	if (ret)
+		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret);
 
-	/* Dimensions might already be parsed */
-	dcp_set_dimensions(dcp);
+	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_link);
+EXPORT_SYMBOL(dcp_start);
 
 static struct platform_device *dcp_get_dev(struct device *dev, const char *name)
 {
@@ -347,12 +355,6 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		return dev_err_probe(dev, ret,
 				     "Failed to boot RTKit: %d", ret);
 
-	/* start RTKit endpoints */
-	ret = iomfb_start_rtkit(dcp);
-	if (ret)
-		return dev_err_probe(dev, ret,
-				     "Failed to start IOMFB endpoint: %d", ret);
-
 	return ret;
 }
 
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 72ac1315372e5c..60e9bcfa4714e0 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -40,6 +40,7 @@ void dcp_poweron(struct platform_device *pdev);
 int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);
 void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 	      struct apple_connector *connector);
+int dcp_start(struct platform_device *pdev);
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
 bool dcp_is_initialized(struct platform_device *pdev);
 void apple_crtc_vblank(struct apple_crtc *apple);

From 1679769226da3dbfb500f4249d200fa19f867f60 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 21 Oct 2022 23:43:57 +0200
Subject: [PATCH 0536/1027] gpu: drm: apple: Specify correct number of DCP*s
 for drm_vblank_init

Unbreaks dcpext a little further.

fixup! WIP: drm/apple: Add DCP display driver

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 31b844bcb2f43e..57599add7b2c0e 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -414,7 +414,7 @@ static int apple_platform_probe(struct platform_device *pdev)
 	if (IS_ERR(apple))
 		return PTR_ERR(apple);
 
-	ret = drm_vblank_init(&apple->drm, 1);
+	ret = drm_vblank_init(&apple->drm, nr_dcp);
 	if (ret)
 		return ret;
 

From 1047be47aad80d7b42b5c822c566259d040fc0c1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 25 Oct 2022 08:17:10 +0200
Subject: [PATCH 0537/1027] gpu: drm: apple: Remove other framebuffers before
 DRM setup

Allows taking over the device node from simpledrm and appaers to be the
common pattern in DRM drivers replacing boot framebuffers.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 57599add7b2c0e..51a66f53c1e377 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -454,6 +454,7 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 	drm_mode_config_reset(&apple->drm);
 
+	// remove before registering our DRM device
 	ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver);
 	if (ret)
 		return ret;

From 5fa2bb0e8c86898235e5b611fad1368dca42b7c7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 27 Oct 2022 02:09:45 +0200
Subject: [PATCH 0538/1027] gpu: drm: apple: Support opaque pixel formats

Opaque pixel formats such as XRGB8888 or YUV formats require flag in
struct dcp_surface to be set to be displayed properly.
Fixes display issues with fbcon after the handover from simpledrm on
j314c with 16:10 modes (notch hiding).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 5 +++++
 drivers/gpu/drm/apple/iomfb.h | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index da6d34b6d7d146..2358f17ab509bf 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1456,6 +1456,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
 		struct drm_framebuffer *fb = new_state->fb;
 		struct drm_rect src_rect;
+		bool opaque = false;
 
 		/* skip planes not for this crtc */
 		if (old_state->crtc != crtc && new_state->crtc != crtc)
@@ -1495,6 +1496,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->surf_null[l] = false;
 		has_surface = 1;
 
+		if (!fb->format->has_alpha ||
+		    new_state->plane->type == DRM_PLANE_TYPE_PRIMARY)
+		    opaque = true;
 		drm_rect_fp_to_int(&src_rect, &new_state->src);
 
 		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
@@ -1503,6 +1507,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
 
 		req->surf[l] = (struct dcp_surface){
+			.opaque = opaque,
 			.format = drm_format_to_dcp(fb->format->format),
 			.xfer_func = 13,
 			.colorspace = drm_format_to_colorspace(fb->format->format),
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 5b1f4ba789bccf..f9ead84c21f255 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -142,7 +142,7 @@ struct dcp_component_types {
 struct dcp_surface {
 	u8 is_tiled;
 	u8 unk_1;
-	u8 unk_2;
+	u8 opaque; /** ignore alpha, also required YUV overlays */
 	u32 plane_cnt;
 	u32 plane_cnt2;
 	u32 format; /* DCP fourcc */

From d29028443ec11d52112f4c2dd22b8aa1dd82a814 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 25 Oct 2022 08:24:01 +0200
Subject: [PATCH 0539/1027] gpu: drm: apple: Provide notch-less modes

If the device tree caries a "apple,notch-height" property subtract it
from all modes and render all framebuffers offsetted by it.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 4 ++++
 drivers/gpu/drm/apple/dcp.c          | 7 +++++++
 drivers/gpu/drm/apple/iomfb.c        | 6 +++++-
 drivers/gpu/drm/apple/parser.c       | 9 ++++++---
 drivers/gpu/drm/apple/parser.h       | 2 +-
 5 files changed, 23 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index c7a7b9563a156f..6624672109c33e 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -67,6 +67,8 @@ struct dcp_fb_reference {
 	struct drm_framebuffer *fb;
 };
 
+#define MAX_NOTCH_HEIGHT 160
+
 /* TODO: move IOMFB members to its own struct */
 struct apple_dcp {
 	struct device *dev;
@@ -134,6 +136,8 @@ struct apple_dcp {
 	/* Attributes of the connected display */
 	int width_mm, height_mm;
 
+	unsigned notch_height;
+
 	/* Workqueue for sending vblank events when a dcp swap is not possible */
 	struct work_struct vblank_wq;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 83782b8fda74ab..fb7c0df2a85573 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -331,6 +331,13 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret);
 	dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask);
 
+	ret = of_property_read_u32(dev->of_node, "apple,notch-height",
+				   &dcp->notch_height);
+	if (dcp->notch_height > MAX_NOTCH_HEIGHT)
+		dcp->notch_height = MAX_NOTCH_HEIGHT;
+	if (dcp->notch_height > 0)
+		dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height);
+
 	bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS);
 	// TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry
 	set_bit(0, dcp->memdesc_map);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 2358f17ab509bf..404f7638cfb2c0 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -647,7 +647,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp,
 
 	if (!strcmp(req->key, "TimingElements")) {
 		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
-					     dcp->width_mm, dcp->height_mm);
+					     dcp->width_mm, dcp->height_mm,
+					     dcp->notch_height);
 
 		if (IS_ERR(dcp->modes)) {
 			dev_warn(dcp->dev, "failed to parse modes\n");
@@ -1504,6 +1505,9 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
 		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
 
+		if (dcp->notch_height > 0)
+			req->swap.dst_rect[l].y += dcp->notch_height;
+
 		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
 
 		req->surf[l] = (struct dcp_surface){
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index bb7d57f272ddb9..fc52c26490ec80 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -305,7 +305,7 @@ static u32 calculate_clock(struct dimension *horiz, struct dimension *vert)
 
 static int parse_mode(struct dcp_parse_ctx *handle,
 		      struct dcp_display_mode *out, s64 *score, int width_mm,
-		      int height_mm)
+		      int height_mm, unsigned notch_height)
 {
 	int ret = 0;
 	struct iterator it;
@@ -353,6 +353,9 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 	if (is_virtual)
 		return -EINVAL;
 
+	vert.active -= notch_height;
+	vert.sync_width += notch_height;
+
 	/* From here we must succeed. Start filling out the mode. */
 	*mode = (struct drm_display_mode) {
 		.type = DRM_MODE_TYPE_DRIVER,
@@ -383,7 +386,7 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 
 struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 					 unsigned int *count, int width_mm,
-					 int height_mm)
+					 int height_mm, unsigned notch_height)
 {
 	struct iterator it;
 	int ret;
@@ -405,7 +408,7 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 
 	for (; it.idx < it.len; ++it.idx) {
 		mode = &modes[*count];
-		ret = parse_mode(it.handle, mode, &score, width_mm, height_mm);
+		ret = parse_mode(it.handle, mode, &score, width_mm, height_mm, notch_height);
 
 		/* Errors for a single mode are recoverable -- just skip it. */
 		if (ret)
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index 66a675079dc164..a2d479258ed0eb 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -25,7 +25,7 @@ struct dcp_display_mode {
 int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx);
 struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 					 unsigned int *count, int width_mm,
-					 int height_mm);
+					 int height_mm, unsigned notch_height);
 int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 			     int *height_mm);
 

From 6f3ef5a2bda38d6f4dbaca1c91770e075de46281 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 6 Nov 2022 10:39:05 +0100
Subject: [PATCH 0540/1027] gpu: drm: apple: Fix shutdown of partially probed
 dcp

No need to shut the co-processor down if it wasn't booted to begin with.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index fb7c0df2a85573..f4161faad6baf0 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -373,7 +373,8 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
 
-	iomfb_shutdown(dcp);
+	if (dcp->shmem)
+		iomfb_shutdown(dcp);
 }
 
 static const struct of_device_id of_match[] = {

From 2d0900b37777449faacafd8eb83964d06f08c1d2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 6 Nov 2022 19:23:08 +0100
Subject: [PATCH 0541/1027] gpu: drm: apple: Set maximal framebuffer size
 correctly

DCP reports this in the IOMFBMaxSrcPixels dictionary. Use that instead
of the hardcoded values from M1 Max.
Fixes multiscreen X11 setups.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 51a66f53c1e377..3a0d410a7c8f19 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -429,13 +429,15 @@ static int apple_platform_probe(struct platform_device *pdev)
 	apple->drm.mode_config.min_width = 32;
 	apple->drm.mode_config.min_height = 32;
 
-	/* Unknown maximum, use the iMac (24-inch, 2021) display resolution as
-	 * maximum.
-	 * TODO: this is the max framebuffer size not the maximal supported output
-	 * resolution. DCP reports the maximal framebuffer size take it from there.
+	/*
+	 * TODO: this is the max framebuffer size not the maximal supported
+	 * output resolution. DCP reports the maximal framebuffer size take it
+	 * from there.
+	 * Hardcode it for now to the M1 Max DCP reported 'MaxSrcBufferWidth'
+	 * and 'MaxSrcBufferHeight' of 16384.
 	 */
-	apple->drm.mode_config.max_width = 4480;
-	apple->drm.mode_config.max_height = 2520;
+	apple->drm.mode_config.max_width = 16384;
+	apple->drm.mode_config.max_height = 16384;
 
 	apple->drm.mode_config.funcs = &apple_mode_config_funcs;
 	apple->drm.mode_config.helper_private = &apple_mode_config_helpers;

From 15406902669481946f07020512e7621e845f6356 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 6 Nov 2022 20:35:41 +0100
Subject: [PATCH 0542/1027] gpu: drm: apple: Prevent NULL pointer in
 dcp_hotplug

A bit hackish, probably missing something in the setup or simply calling
this too early.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 404f7638cfb2c0..ee00db453da00b 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -985,7 +985,7 @@ void dcp_hotplug(struct work_struct *work)
 	 * display modes from atomic_flush, so userspace needs to trigger a
 	 * flush, or the CRTC gets no signal.
 	 */
-	if (!dcp->valid_mode && connector->connected) {
+	if (connector->base.state && !dcp->valid_mode && connector->connected) {
 		drm_connector_set_link_status_property(
 			&connector->base, DRM_MODE_LINK_STATUS_BAD);
 	}

From 68f13204fb4e356aa44b0c4b0d3384737febf606 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 6 Nov 2022 23:02:54 +0100
Subject: [PATCH 0543/1027] gpu: drm: apple: Update date last update

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 3a0d410a7c8f19..14ac96bdab942d 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -60,7 +60,7 @@ static const struct drm_driver apple_drm_driver = {
 	DRM_GEM_DMA_DRIVER_OPS_WITH_DUMB_CREATE(apple_drm_gem_dumb_create),
 	.name			= DRIVER_NAME,
 	.desc			= DRIVER_DESC,
-	.date			= "20210901",
+	.date			= "20221106",
 	.major			= 1,
 	.minor			= 0,
 	.driver_features	= DRIVER_MODESET | DRIVER_GEM | DRIVER_ATOMIC,

From a94ad6aa7de2324304c905b74bb386cf9b54e83e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Oct 2022 13:11:06 +0100
Subject: [PATCH 0544/1027] gpu: drm: apple: iomfb: Use FIELD_{GET,PREP}

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 31 ++++++++++++++++---------------
 drivers/gpu/drm/apple/iomfb.h | 26 ++++++++++++--------------
 2 files changed, 28 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index ee00db453da00b..72924983eefa0b 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1,6 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <linux/bitfield.h>
 #include <linux/bitmap.h>
 #include <linux/clk.h>
 #include <linux/module.h>
@@ -76,22 +77,22 @@ static int dcp_channel_offset(enum dcp_context_id id)
 
 static inline u64 dcpep_set_shmem(u64 dart_va)
 {
-	return (DCPEP_TYPE_SET_SHMEM << DCPEP_TYPE_SHIFT) |
-	       (DCPEP_FLAG_VALUE << DCPEP_FLAG_SHIFT) |
-	       (dart_va << DCPEP_DVA_SHIFT);
+	return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_SET_SHMEM) |
+	       FIELD_PREP(IOMFB_SHMEM_FLAG, IOMFB_SHMEM_FLAG_VALUE) |
+	       FIELD_PREP(IOMFB_SHMEM_DVA, dart_va);
 }
 
 static inline u64 dcpep_msg(enum dcp_context_id id, u32 length, u16 offset)
 {
-	return (DCPEP_TYPE_MESSAGE << DCPEP_TYPE_SHIFT) |
-	       ((u64)id << DCPEP_CONTEXT_SHIFT) |
-	       ((u64)offset << DCPEP_OFFSET_SHIFT) |
-	       ((u64)length << DCPEP_LENGTH_SHIFT);
+	return FIELD_PREP(IOMFB_MESSAGE_TYPE, IOMFB_MESSAGE_TYPE_MSG) |
+		FIELD_PREP(IOMFB_MSG_CONTEXT, id) |
+		FIELD_PREP(IOMFB_MSG_OFFSET, offset) |
+		FIELD_PREP(IOMFB_MSG_LENGTH, length);
 }
 
 static inline u64 dcpep_ack(enum dcp_context_id id)
 {
-	return dcpep_msg(id, 0, 0) | DCPEP_ACK;
+	return dcpep_msg(id, 0, 0) | IOMFB_MSG_ACK;
 }
 
 /*
@@ -1238,9 +1239,9 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
 	int channel_offset;
 	void *data;
 
-	ctx_id = (message & DCPEP_CONTEXT_MASK) >> DCPEP_CONTEXT_SHIFT;
-	offset = (message & DCPEP_OFFSET_MASK) >> DCPEP_OFFSET_SHIFT;
-	length = (message >> DCPEP_LENGTH_SHIFT);
+	ctx_id = FIELD_GET(IOMFB_MSG_CONTEXT, message);
+	offset = FIELD_GET(IOMFB_MSG_OFFSET, message);
+	length = FIELD_GET(IOMFB_MSG_LENGTH, message);
 
 	channel_offset = dcp_channel_offset(ctx_id);
 
@@ -1251,7 +1252,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
 
 	data = dcp->shmem + channel_offset + offset;
 
-	if (message & DCPEP_ACK)
+	if (FIELD_GET(IOMFB_MSG_ACK, message))
 		dcpep_handle_ack(dcp, ctx_id, data, length);
 	else
 		dcpep_handle_cb(dcp, ctx_id, data, length);
@@ -1651,11 +1652,11 @@ static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
 
 void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
 {
-	enum dcpep_type type = (message >> DCPEP_TYPE_SHIFT) & DCPEP_TYPE_MASK;
+	enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message);
 
-	if (type == DCPEP_TYPE_INITIALIZED)
+	if (type == IOMFB_MESSAGE_TYPE_INITIALIZED)
 		dcp_start_signal(dcp, false, dcp_started, NULL);
-	else if (type == DCPEP_TYPE_MESSAGE)
+	else if (type == IOMFB_MESSAGE_TYPE_MSG)
 		dcpep_got_msg(dcp, message);
 	else
 		dev_warn(dcp->dev, "Ignoring unknown message %llx\n", message);
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index f9ead84c21f255..a82d960512bfd1 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -32,29 +32,27 @@ enum dcp_context_id {
 /* RTKit endpoint message types */
 enum dcpep_type {
 	/* Set shared memory */
-	DCPEP_TYPE_SET_SHMEM = 0,
+	IOMFB_MESSAGE_TYPE_SET_SHMEM = 0,
 
 	/* DCP is initialized */
-	DCPEP_TYPE_INITIALIZED = 1,
+	IOMFB_MESSAGE_TYPE_INITIALIZED = 1,
 
 	/* Remote procedure call */
-	DCPEP_TYPE_MESSAGE = 2,
+	IOMFB_MESSAGE_TYPE_MSG = 2,
 };
 
+#define IOMFB_MESSAGE_TYPE	GENMASK_ULL( 3,  0)
+
 /* Message */
-#define DCPEP_TYPE_SHIFT (0)
-#define DCPEP_TYPE_MASK GENMASK(1, 0)
-#define DCPEP_ACK BIT_ULL(6)
-#define DCPEP_CONTEXT_SHIFT (8)
-#define DCPEP_CONTEXT_MASK GENMASK(11, 8)
-#define DCPEP_OFFSET_SHIFT (16)
-#define DCPEP_OFFSET_MASK GENMASK(31, 16)
-#define DCPEP_LENGTH_SHIFT (32)
+#define IOMFB_MSG_LENGTH	GENMASK_ULL(63, 32)
+#define IOMFB_MSG_OFFSET	GENMASK_ULL(31, 16)
+#define IOMFB_MSG_CONTEXT	GENMASK_ULL(11,  8)
+#define IOMFB_MSG_ACK		BIT_ULL(6)
 
 /* Set shmem */
-#define DCPEP_DVA_SHIFT (16)
-#define DCPEP_FLAG_SHIFT (4)
-#define DCPEP_FLAG_VALUE (4)
+#define IOMFB_SHMEM_DVA		GENMASK_ULL(63, 16)
+#define IOMFB_SHMEM_FLAG	GENMASK_ULL( 7,  4)
+#define IOMFB_SHMEM_FLAG_VALUE	4
 
 struct dcp_packet_header {
 	char tag[4];

From 6a37496e9e1f21706a07431f4c60fc812db21c35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Oct 2022 13:17:28 +0100
Subject: [PATCH 0545/1027] gpu: drm: apple: iomfb: Unify call and callback
 channels

AP calls initiated inside callbacks are using the callback channel and
context.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 11 ++-----
 drivers/gpu/drm/apple/iomfb.c        | 45 +++++++++++-----------------
 2 files changed, 20 insertions(+), 36 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 6624672109c33e..8c12382e281b42 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -47,7 +47,7 @@ struct dcp_mem_descriptor {
 
 typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
 
-struct dcp_call_channel {
+struct dcp_channel {
 	dcp_callback_t callbacks[DCP_MAX_CALL_DEPTH];
 	void *cookies[DCP_MAX_CALL_DEPTH];
 	void *output[DCP_MAX_CALL_DEPTH];
@@ -57,11 +57,6 @@ struct dcp_call_channel {
 	u8 depth;
 };
 
-struct dcp_cb_channel {
-	u8 depth;
-	void *output[DCP_MAX_CALL_DEPTH];
-};
-
 struct dcp_fb_reference {
 	struct list_head head;
 	struct drm_framebuffer *fb;
@@ -108,8 +103,8 @@ struct apple_dcp {
 	/* Indexed table of memory descriptors */
 	struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
 
-	struct dcp_call_channel ch_cmd, ch_oobcmd;
-	struct dcp_cb_channel ch_cb, ch_oobcb, ch_async;
+	struct dcp_channel ch_cmd, ch_oobcmd;
+	struct dcp_channel ch_cb, ch_oobcb, ch_async;
 
 	/* Active chunked transfer. There can only be one at a time. */
 	struct dcp_chunks chunks;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 72924983eefa0b..aae242eecee726 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -99,27 +99,11 @@ static inline u64 dcpep_ack(enum dcp_context_id id)
  * A channel is busy if we have sent a message that has yet to be
  * acked. The driver must not sent a message to a busy channel.
  */
-static bool dcp_channel_busy(struct dcp_call_channel *ch)
+static bool dcp_channel_busy(struct dcp_channel *ch)
 {
 	return (ch->depth != 0);
 }
 
-/* Get a call channel for a context */
-static struct dcp_call_channel *
-dcp_get_call_channel(struct apple_dcp *dcp, enum dcp_context_id context)
-{
-	switch (context) {
-	case DCP_CONTEXT_CMD:
-	case DCP_CONTEXT_CB:
-		return &dcp->ch_cmd;
-	case DCP_CONTEXT_OOBCMD:
-	case DCP_CONTEXT_OOBCB:
-		return &dcp->ch_oobcmd;
-	default:
-		return NULL;
-	}
-}
-
 /*
  * Get the context ID passed to the DCP for a command we push. The rule is
  * simple: callback contexts are used when replying to the DCP, command
@@ -136,15 +120,19 @@ static enum dcp_context_id dcp_call_context(struct apple_dcp *dcp, bool oob)
 		return oob ? DCP_CONTEXT_OOBCMD : DCP_CONTEXT_CMD;
 }
 
-/* Get a callback channel for a context */
-static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp,
-						 enum dcp_context_id context)
+/* Get a channel for a context */
+static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp,
+					   enum dcp_context_id context)
 {
 	switch (context) {
 	case DCP_CONTEXT_CB:
 		return &dcp->ch_cb;
+	case DCP_CONTEXT_CMD:
+		return &dcp->ch_cmd;
 	case DCP_CONTEXT_OOBCB:
 		return &dcp->ch_oobcb;
+	case DCP_CONTEXT_OOBCMD:
+		return &dcp->ch_oobcmd;
 	case DCP_CONTEXT_ASYNC:
 		return &dcp->ch_async;
 	default:
@@ -153,7 +141,7 @@ static struct dcp_cb_channel *dcp_get_cb_channel(struct apple_dcp *dcp,
 }
 
 /* Get the start of a packet: after the end of the previous packet */
-static u16 dcp_packet_start(struct dcp_call_channel *ch, u8 depth)
+static u16 dcp_packet_start(struct dcp_channel *ch, u8 depth)
 {
 	if (depth > 0)
 		return ch->end[depth - 1];
@@ -204,8 +192,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
 		     void *cookie)
 {
-	struct dcp_call_channel *ch = oob ? &dcp->ch_oobcmd : &dcp->ch_cmd;
 	enum dcp_context_id context = dcp_call_context(dcp, oob);
+	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 
 	struct dcp_packet_header header = {
 		.in_len = in_len,
@@ -330,7 +318,7 @@ static int dcp_parse_tag(char tag[4])
 /* Ack a callback from the DCP */
 static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 {
-	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 
 	dcp_pop_depth(&ch->depth);
 	dcp_send_message(dcp, IOMFB_ENDPOINT,
@@ -687,7 +675,7 @@ static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
 /* Boot sequence */
 static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
 {
-	struct dcp_cb_channel *ch = &dcp->ch_cb;
+	struct dcp_channel *ch = &dcp->ch_cb;
 	u8 *succ = ch->output[ch->depth - 1];
 	dev_dbg(dcp->dev, "boot done");
 
@@ -1176,13 +1164,13 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 };
 
 static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
-			    void *data, u32 length)
+			    void *data, u32 length, u16 offset)
 {
 	struct device *dev = dcp->dev;
 	struct dcp_packet_header *hdr = data;
 	void *in, *out;
 	int tag = dcp_parse_tag(hdr->tag);
-	struct dcp_cb_channel *ch = dcp_get_cb_channel(dcp, context);
+	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 	u8 depth;
 
 	if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) {
@@ -1201,6 +1189,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 
 	depth = dcp_push_depth(&ch->depth);
 	ch->output[depth] = out;
+	ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT);
 
 	if (dcpep_cb_handlers[tag](dcp, tag, out, in))
 		dcp_ack(dcp, context);
@@ -1210,7 +1199,7 @@ static void dcpep_handle_ack(struct apple_dcp *dcp, enum dcp_context_id context,
 			     void *data, u32 length)
 {
 	struct dcp_packet_header *header = data;
-	struct dcp_call_channel *ch = dcp_get_call_channel(dcp, context);
+	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 	void *cookie;
 	dcp_callback_t cb;
 
@@ -1255,7 +1244,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
 	if (FIELD_GET(IOMFB_MSG_ACK, message))
 		dcpep_handle_ack(dcp, ctx_id, data, length);
 	else
-		dcpep_handle_cb(dcp, ctx_id, data, length);
+		dcpep_handle_cb(dcp, ctx_id, data, length, offset);
 }
 
 /*

From efb55f1827b3467429e6a8798fff0cdc71103892 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Oct 2022 13:29:23 +0100
Subject: [PATCH 0546/1027] gpu: drm: apple: "match" PMU/backlight services on
 init

Verify that this still works on HDMI/USB-C.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 103 +++++++++++++++++++++++++++++++---
 drivers/gpu/drm/apple/iomfb.h |   9 +++
 2 files changed, 105 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index aae242eecee726..34a08fa86edf34 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -171,7 +171,10 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	DCP_METHOD("A000", dcpep_late_init_signal),
 	DCP_METHOD("A029", dcpep_setup_video_limits),
 	DCP_METHOD("A034", dcpep_update_notify_clients_dcp),
+	DCP_METHOD("A131", iomfbep_a131_pmu_service_matched),
+	DCP_METHOD("A132", iomfbep_a132_backlight_service_matched),
 	DCP_METHOD("A357", dcpep_set_create_dfb),
+	DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint),
 	DCP_METHOD("A401", dcpep_start_signal),
 	DCP_METHOD("A407", dcpep_swap_start),
 	DCP_METHOD("A408", dcpep_swap_submit),
@@ -258,6 +261,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 			 cb, cookie);                                         \
 	}
 
+DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
+DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
+DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
+
 DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
 		struct dcp_swap_submit_resp);
 
@@ -355,11 +362,91 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 		dcp_drm_crtc_vblank(dcp->crtc);
 }
 
+/* special */
+static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	// ack D100 cb_match_pmu_service
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+	iomfb_a358_vi_set_temperature_hint(dcp, false,
+					   complete_vi_set_temperature_hint,
+					   NULL);
+
+	// return false for deferred ACK
+	return false;
+}
+
+static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+
+	*succ = true;
+
+	// ack D206 cb_match_pmu_service_2
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+
+	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
+				       out);
+
+	// return false for deferred ACK
+	return false;
+}
+
+static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+
+	*succ = true;
+
+	// ack D206 cb_match_backlight_service
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+
+	iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
+
+	// return false for deferred ACK
+	return false;
+}
+
 static struct dcp_get_uint_prop_resp
 dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
 {
-	/* unimplemented for now */
-	return (struct dcp_get_uint_prop_resp){ .value = 0 };
+	struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){
+	    .value = 0
+	};
+
+	if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
+	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
+		/*
+		 * TODO: value from j314c, find out if it is temperature in
+		 *       centigrade C and which temperature sensor reports it
+		 */
+		resp.value = 3029;
+		resp.ret = true;
+	    }
+	}
+
+	return resp;
+}
+
+static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
+{
+    // TODO: trace this, see if there properties which needs to used later
 }
 
 /*
@@ -1079,6 +1166,8 @@ TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
 	      struct dc_swap_complete_resp);
 TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
 		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
+TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop,
+	      struct iomfb_set_fx_prop_req)
 TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
 		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
 TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
@@ -1114,7 +1203,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[1] = trampoline_true, /* did_power_on_signal */
 	[2] = trampoline_nop, /* will_power_off_signal */
 	[3] = trampoline_rt_bandwidth,
-	[100] = trampoline_nop, /* match_pmu_service */
+	[100] = iomfbep_cb_match_pmu_service,
 	[101] = trampoline_zero, /* get_display_default_stride */
 	[103] = trampoline_nop, /* set_boolean_property */
 	[106] = trampoline_nop, /* remove_property */
@@ -1122,7 +1211,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[108] = trampoline_true, /* create_product_service */
 	[109] = trampoline_true, /* create_pmu_service */
 	[110] = trampoline_true, /* create_iomfb_service */
-	[111] = trampoline_false, /* create_backlight_service */
+	[111] = trampoline_true, /* create_backlight_service */
 	[116] = dcpep_cb_boot_1,
 	[117] = trampoline_false, /* is_dark_boot */
 	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
@@ -1132,14 +1221,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[124] = trampoline_prop_end,
 	[201] = trampoline_map_piodma,
 	[202] = trampoline_unmap_piodma,
-	[206] = trampoline_true, /* match_pmu_service_2 */
-	[207] = trampoline_true, /* match_backlight_service */
+	[206] = iomfbep_cb_match_pmu_service_2,
+	[207] = iomfbep_cb_match_backlight_service,
 	[208] = trampoline_get_time,
 	[211] = trampoline_nop, /* update_backlight_factor_prop */
 	[300] = trampoline_nop, /* pr_publish */
 	[401] = trampoline_get_uint_prop,
 	[404] = trampoline_nop, /* sr_set_uint_prop */
-	[406] = trampoline_nop, /* set_fx_prop */
+	[406] = trampoline_set_fx_prop,
 	[408] = trampoline_get_frequency,
 	[411] = trampoline_map_reg,
 	[413] = trampoline_true, /* sr_set_property_dict */
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index a82d960512bfd1..68fdc654d597f5 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -197,6 +197,9 @@ enum dcpep_method {
 	dcpep_set_parameter_dcp,
 	dcpep_enable_disable_video_power_savings,
 	dcpep_is_main_display,
+	iomfbep_a131_pmu_service_matched,
+	iomfbep_a132_backlight_service_matched,
+	iomfbep_a358_vi_set_temperature_hint,
 	dcpep_num_methods
 };
 
@@ -337,6 +340,12 @@ struct dcp_get_uint_prop_resp {
 	u8 padding[3];
 } __packed;
 
+struct iomfb_set_fx_prop_req {
+	char obj[4];
+	char key[0x40];
+	u32 value;
+} __packed;
+
 struct dcp_set_power_state_req {
 	u64 unklong;
 	u8 unkbool;

From d13188ed122a2d5667b18f4da059e352831665e7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 31 Oct 2022 01:11:36 +0100
Subject: [PATCH 0547/1027] gpu: drm: apple: Brightness control via atomic
 commits

This abuses color_mgnt_change in drm_crtc_state and will be changed once
phase 2 of the "drm/kms: control display brightness through drm_connector
properties" RfC (linked below) is implemented.

The lookup of DAC values from brightness (nits) is not fully understood.
Since IOMFB reports te brightness back the easiest solution would be to
create our own lookup table or find a approximation which works.

DCP appears to report the brightness in nits by
"PropRelay::pr_publish(prop_id=15, value=...)" (scaled by
"Brightness_scale").

Link: https://lore.kernel.org/dri-devel/b61d3eeb-6213-afac-2e70-7b9791c86d2e@redhat.com/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile        |   2 +-
 drivers/gpu/drm/apple/apple_drv.c     |  12 +-
 drivers/gpu/drm/apple/dcp-internal.h  |  15 ++
 drivers/gpu/drm/apple/dcp.c           |  31 ++++
 drivers/gpu/drm/apple/dcp.h           |   1 +
 drivers/gpu/drm/apple/dcp_backlight.c | 232 ++++++++++++++++++++++++++
 drivers/gpu/drm/apple/iomfb.c         |  47 +++++-
 drivers/gpu/drm/apple/iomfb.h         |  25 ++-
 8 files changed, 349 insertions(+), 16 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/dcp_backlight.c

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 082c8c58bb87fe..a61024ddce4c38 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src)
 
 appledrm-y := apple_drv.o
 
-apple_dcp-y := dcp.o iomfb.o parser.o
+apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
 apple_piodma-y := dummy-piodma.o
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 14ac96bdab942d..51e2d03f225b49 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -316,7 +316,6 @@ static int apple_probe_per_dcp(struct device *dev,
 	struct apple_connector *connector;
 	struct drm_encoder *encoder;
 	struct drm_plane *primary;
-	int con_type;
 	int ret;
 
 	primary = apple_plane_init(drm, 1U << num, DRM_PLANE_TYPE_PRIMARY);
@@ -342,17 +341,8 @@ static int apple_probe_per_dcp(struct device *dev,
 	drm_connector_helper_add(&connector->base,
 				 &apple_connector_helper_funcs);
 
-	if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "eDP") >= 0)
-		con_type = DRM_MODE_CONNECTOR_eDP;
-	else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "HDMI-A") >= 0)
-		con_type = DRM_MODE_CONNECTOR_HDMIA;
-	else if (of_property_match_string(dcp->dev.of_node, "apple,connector-type", "USB-C") >= 0)
-		con_type = DRM_MODE_CONNECTOR_USB;
-	else
-		con_type = DRM_MODE_CONNECTOR_Unknown;
-
 	ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs,
-				 con_type);
+				 dcp_get_connector_type(dcp));
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 8c12382e281b42..c2fa002b45d5de 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -64,6 +64,14 @@ struct dcp_fb_reference {
 
 #define MAX_NOTCH_HEIGHT 160
 
+struct dcp_brightness {
+	u32 dac;
+	int nits;
+	int set;
+	int scale;
+	bool update;
+};
+
 /* TODO: move IOMFB members to its own struct */
 struct apple_dcp {
 	struct device *dev;
@@ -128,6 +136,9 @@ struct apple_dcp {
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
 
+	/* Attributes of the connector */
+	int connector_type;
+
 	/* Attributes of the connected display */
 	int width_mm, height_mm;
 
@@ -140,6 +151,10 @@ struct apple_dcp {
 	 * on the next successfully completed swap.
 	 */
 	struct list_head swapped_out_fbs;
+
+	struct dcp_brightness brightness;
 };
 
+int dcp_backlight_register(struct apple_dcp *dcp);
+
 #endif /* __APPLE_DCP_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index f4161faad6baf0..d0d6f5be3f3dbf 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -21,6 +21,7 @@
 
 #include "dcp.h"
 #include "dcp-internal.h"
+#include "iomfb.h"
 #include "parser.h"
 #include "trace.h"
 
@@ -219,6 +220,14 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
 }
 EXPORT_SYMBOL_GPL(dcp_crtc_atomic_check);
 
+int dcp_get_connector_type(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	return (dcp->connector_type);
+}
+EXPORT_SYMBOL_GPL(dcp_get_connector_type);
+
 void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 	      struct apple_connector *connector)
 {
@@ -277,6 +286,7 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp)
 static int dcp_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
+	struct device_node *panel_np;
 	struct apple_dcp *dcp;
 	u32 cpu_ctrl;
 	int ret;
@@ -298,6 +308,27 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	of_platform_default_populate(dev->of_node, NULL, dev);
 
+	/* intialize brightness scale to a sensible default to avoid divide by 0*/
+	dcp->brightness.scale = 65536;
+	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
+	if (panel_np) {
+		of_node_put(panel_np);
+		dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
+
+		/* try to register backlight device, */
+		ret = dcp_backlight_register(dcp);
+		if (ret)
+			return dev_err_probe(dev, ret,
+						"Unable to register backlight device\n");
+	} else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0)
+		dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA;
+	else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0)
+		dcp->connector_type = DRM_MODE_CONNECTOR_DisplayPort;
+	else if (of_property_match_string(dev->of_node, "apple,connector-type", "USB-C") >= 0)
+		dcp->connector_type = DRM_MODE_CONNECTOR_USB;
+	else
+		dcp->connector_type = DRM_MODE_CONNECTOR_Unknown;
+
 	dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper");
 	if (!dcp->piodma) {
 		dev_err(dev, "failed to find piodma\n");
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 60e9bcfa4714e0..dfe014f3f5d1da 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -38,6 +38,7 @@ struct apple_connector {
 void dcp_poweroff(struct platform_device *pdev);
 void dcp_poweron(struct platform_device *pdev);
 int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);
+int dcp_get_connector_type(struct platform_device *pdev);
 void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 	      struct apple_connector *connector);
 int dcp_start(struct platform_device *pdev);
diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
new file mode 100644
index 00000000000000..c695e3bad7db91
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -0,0 +1,232 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright (C) The Asahi Linux Contributors */
+
+#include <drm/drm_atomic.h>
+#include <drm/drm_crtc.h>
+#include <drm/drm_drv.h>
+#include <drm/drm_modeset_lock.h>
+
+#include <linux/backlight.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include "linux/jiffies.h"
+
+#include "dcp.h"
+#include "dcp-internal.h"
+
+#define MIN_BRIGHTNESS_PART1	2U
+#define MAX_BRIGHTNESS_PART1	99U
+#define MIN_BRIGHTNESS_PART2	103U
+#define MAX_BRIGHTNESS_PART2	510U
+
+/*
+ * lookup for display brightness 2 to 99 nits
+ * */
+static u32 brightness_part1[] = {
+	0x0000000, 0x0810038, 0x0f000bd, 0x143011c,
+	0x1850165, 0x1bc01a1, 0x1eb01d4, 0x2140200,
+	0x2380227, 0x2590249, 0x2770269, 0x2930285,
+	0x2ac02a0, 0x2c402b8, 0x2d902cf, 0x2ee02e4,
+	0x30102f8, 0x314030b, 0x325031c, 0x335032d,
+	0x345033d, 0x354034d, 0x362035b, 0x3700369,
+	0x37d0377, 0x38a0384, 0x3960390, 0x3a2039c,
+	0x3ad03a7, 0x3b803b3, 0x3c303bd, 0x3cd03c8,
+	0x3d703d2, 0x3e103dc, 0x3ea03e5, 0x3f303ef,
+	0x3fc03f8, 0x4050400, 0x40d0409, 0x4150411,
+	0x41d0419, 0x4250421, 0x42d0429, 0x4340431,
+	0x43c0438, 0x443043f, 0x44a0446, 0x451044d,
+	0x4570454, 0x45e045b, 0x4640461, 0x46b0468,
+	0x471046e, 0x4770474, 0x47d047a, 0x4830480,
+	0x4890486, 0x48e048b, 0x4940491, 0x4990497,
+	0x49f049c, 0x4a404a1, 0x4a904a7, 0x4ae04ac,
+	0x4b304b1, 0x4b804b6, 0x4bd04bb, 0x4c204c0,
+	0x4c704c5, 0x4cc04c9, 0x4d004ce, 0x4d504d3,
+	0x4d904d7, 0x4de04dc, 0x4e204e0, 0x4e704e4,
+	0x4eb04e9, 0x4ef04ed, 0x4f304f1, 0x4f704f5,
+	0x4fb04f9, 0x4ff04fd, 0x5030501, 0x5070505,
+	0x50b0509, 0x50f050d, 0x5130511, 0x5160515,
+	0x51a0518, 0x51e051c, 0x5210520, 0x5250523,
+	0x5290527, 0x52c052a, 0x52f052e, 0x5330531,
+	0x5360535, 0x53a0538, 0x53d053b, 0x540053f,
+	0x5440542, 0x5470545, 0x54a0548, 0x54d054c,
+	0x550054f, 0x5530552, 0x5560555, 0x5590558,
+	0x55c055b, 0x55f055e, 0x5620561, 0x5650564,
+	0x5680567, 0x56b056a, 0x56e056d, 0x571056f,
+	0x5740572, 0x5760575, 0x5790578, 0x57c057b,
+	0x57f057d, 0x5810580, 0x5840583, 0x5870585,
+	0x5890588, 0x58c058b, 0x58f058d
+};
+
+static u32 brightness_part12[] = { 0x58f058d, 0x59d058f };
+
+/*
+ * lookup table for display brightness 103.3 to 510 nits
+ * */
+static u32 brightness_part2[] = {
+	0x59d058f, 0x5b805ab, 0x5d105c5, 0x5e805dd,
+	0x5fe05f3, 0x6120608, 0x625061c, 0x637062e,
+	0x6480640, 0x6580650, 0x6680660, 0x677066f,
+	0x685067e, 0x693068c, 0x6a00699, 0x6ac06a6,
+	0x6b806b2, 0x6c406be, 0x6cf06ca, 0x6da06d5,
+	0x6e506df, 0x6ef06ea, 0x6f906f4, 0x70206fe,
+	0x70c0707, 0x7150710, 0x71e0719, 0x7260722,
+	0x72f072a, 0x7370733, 0x73f073b, 0x7470743,
+	0x74e074a, 0x7560752, 0x75d0759, 0x7640760,
+	0x76b0768, 0x772076e, 0x7780775, 0x77f077c,
+	0x7850782, 0x78c0789, 0x792078f, 0x7980795,
+	0x79e079b, 0x7a407a1, 0x7aa07a7, 0x7af07ac,
+	0x7b507b2, 0x7ba07b8, 0x7c007bd, 0x7c507c2,
+	0x7ca07c8, 0x7cf07cd, 0x7d407d2, 0x7d907d7,
+	0x7de07dc, 0x7e307e1, 0x7e807e5, 0x7ec07ea,
+	0x7f107ef, 0x7f607f3, 0x7fa07f8, 0x7fe07fc
+};
+
+
+static int dcp_get_brightness(struct backlight_device *bd)
+{
+	struct apple_dcp *dcp = bl_get_data(bd);
+
+	return dcp->brightness.nits;
+}
+
+#define SCALE_FACTOR (1 << 10)
+
+static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size)
+{
+	u32 frac;
+	u64 low, high;
+	u32 interpolated = (tbl_size - 1) * ((val - min) * SCALE_FACTOR) / (max - min);
+
+	size_t index = interpolated / SCALE_FACTOR;
+
+	if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val))
+		return tbl[tbl_size / 2];
+
+	frac = interpolated & (SCALE_FACTOR - 1);
+	low = tbl[index];
+	high = tbl[index + 1];
+
+	return ((frac * high) + ((SCALE_FACTOR - frac) * low)) / SCALE_FACTOR;
+}
+
+static u32 calculate_dac(struct apple_dcp *dcp, int val)
+{
+	u32 dac;
+
+	if (val <= MIN_BRIGHTNESS_PART1)
+		return 16 * brightness_part1[0];
+	else if (val == MAX_BRIGHTNESS_PART1)
+		return 16 * brightness_part1[ARRAY_SIZE(brightness_part1) - 1];
+	else if (val == MIN_BRIGHTNESS_PART2)
+		return 16 * brightness_part2[0];
+	else if (val >= MAX_BRIGHTNESS_PART2)
+		return brightness_part2[ARRAY_SIZE(brightness_part2) - 1];
+
+	if (val < MAX_BRIGHTNESS_PART1) {
+		dac = interpolate(val, MIN_BRIGHTNESS_PART1, MAX_BRIGHTNESS_PART1,
+				  brightness_part1, ARRAY_SIZE(brightness_part1));
+	} else if (val > MIN_BRIGHTNESS_PART2) {
+		dac = interpolate(val, MIN_BRIGHTNESS_PART2, MAX_BRIGHTNESS_PART2,
+				  brightness_part2, ARRAY_SIZE(brightness_part2));
+	} else {
+		dac = interpolate(val, MAX_BRIGHTNESS_PART1, MIN_BRIGHTNESS_PART2,
+				  brightness_part12, ARRAY_SIZE(brightness_part12));
+	}
+
+	return 16 * dac;
+}
+
+static int drm_crtc_set_brightness(struct drm_crtc *crtc,
+				   struct drm_modeset_acquire_ctx *ctx)
+{
+	struct drm_atomic_state *state;
+	struct drm_crtc_state *crtc_state;
+	int ret = 0;
+
+	state = drm_atomic_state_alloc(crtc->dev);
+	if (!state)
+		return -ENOMEM;
+
+	state->acquire_ctx = ctx;
+	crtc_state = drm_atomic_get_crtc_state(state, crtc);
+	if (IS_ERR(crtc_state)) {
+		ret = PTR_ERR(crtc_state);
+		goto fail;
+	}
+
+	crtc_state->color_mgmt_changed |= true;
+
+	ret = drm_atomic_commit(state);
+
+fail:
+	drm_atomic_state_put(state);
+	return ret;
+}
+
+static int dcp_set_brightness(struct backlight_device *bd)
+{
+	int ret = 0;
+	struct apple_dcp *dcp = bl_get_data(bd);
+
+	bd->props.power = FB_BLANK_UNBLANK;
+	if (dcp->brightness.set != bd->props.brightness) {
+	       dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness);
+	       dcp->brightness.set = bd->props.brightness;
+	       dcp->brightness.update = true;
+	}
+
+	if (dcp->brightness.update && dcp->crtc) {
+		struct drm_modeset_acquire_ctx ctx;
+		struct drm_device *drm_dev = dcp->crtc->base.dev;
+
+		DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret);
+		ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx);
+		DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret);
+	}
+
+	return ret;
+}
+
+static const struct backlight_ops dcp_backlight_ops = {
+	.get_brightness = dcp_get_brightness,
+	.update_status = dcp_set_brightness,
+};
+
+int dcp_backlight_register(struct apple_dcp *dcp)
+{
+	struct device *dev = dcp->dev;
+	struct backlight_device *bd;
+	struct device_node *panel_np;
+	struct backlight_properties props = {
+		.type = BACKLIGHT_PLATFORM,
+		.brightness = dcp->brightness.nits,
+		.max_brightness = 0,
+		.scale = BACKLIGHT_SCALE_LINEAR,
+	};
+	u32 max_brightness;
+	int ret = 0;
+
+	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
+	if (!panel_np)
+		return 0;
+
+	if (!of_device_is_available(panel_np))
+		goto out_put;
+
+	ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness);
+	if (ret) {
+		dev_err(dev, "Missing property 'apple,max-brightness'\n");
+		goto out_put;
+	}
+	props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1);
+
+	bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp,
+					    &dcp_backlight_ops, &props);
+	if (IS_ERR(bd))
+		ret = PTR_ERR(bd);
+
+out_put:
+	of_node_put(panel_np);
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 34a08fa86edf34..ca29658cfc8e99 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -423,6 +423,21 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v
 	return false;
 }
 
+static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop)
+{
+	switch (prop->id) {
+	case IOMFB_PROPERTY_NITS:
+		dcp->brightness.nits = prop->value / dcp->brightness.scale;
+		/* temporary for user debugging during tesing */
+		dev_info(dcp->dev, "Backlight updated to %u nits\n",
+			 dcp->brightness.nits);
+		dcp->brightness.update = false;
+		break;
+	default:
+		dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
+	}
+}
+
 static struct dcp_get_uint_prop_resp
 dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
 {
@@ -444,6 +459,19 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
 	return resp;
 }
 
+static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp,
+					 struct iomfb_sr_set_property_int_req *req)
+{
+	if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */
+		if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) {
+			if (!req->value_null)
+				dcp->brightness.scale = req->value;
+		}
+	}
+
+	return 1;
+}
+
 static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
 {
     // TODO: trace this, see if there properties which needs to used later
@@ -1172,6 +1200,8 @@ TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
 		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
 TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
 	      struct dcp_unmap_buf_resp);
+TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int,
+		 struct iomfb_sr_set_property_int_req, u8);
 TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
 		 struct dcp_allocate_buffer_req,
 		 struct dcp_allocate_buffer_resp);
@@ -1196,6 +1226,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
 TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
 	      dcpep_cb_swap_complete_intent_gated,
 	      struct dcp_swap_complete_intent_gated);
+TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
+	      struct iomfb_property);
 
 bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 					      void *) = {
@@ -1205,6 +1237,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[3] = trampoline_rt_bandwidth,
 	[100] = iomfbep_cb_match_pmu_service,
 	[101] = trampoline_zero, /* get_display_default_stride */
+	[102] = trampoline_nop, /* set_number_property */
 	[103] = trampoline_nop, /* set_boolean_property */
 	[106] = trampoline_nop, /* remove_property */
 	[107] = trampoline_true, /* create_provider_service */
@@ -1225,14 +1258,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[207] = iomfbep_cb_match_backlight_service,
 	[208] = trampoline_get_time,
 	[211] = trampoline_nop, /* update_backlight_factor_prop */
-	[300] = trampoline_nop, /* pr_publish */
+	[300] = trampoline_pr_publish,
 	[401] = trampoline_get_uint_prop,
 	[404] = trampoline_nop, /* sr_set_uint_prop */
 	[406] = trampoline_set_fx_prop,
 	[408] = trampoline_get_frequency,
 	[411] = trampoline_map_reg,
 	[413] = trampoline_true, /* sr_set_property_dict */
-	[414] = trampoline_true, /* sr_set_property_int */
+	[414] = trampoline_sr_set_property_int,
 	[415] = trampoline_true, /* sr_set_property_bool */
 	[451] = trampoline_allocate_buffer,
 	[452] = trampoline_map_physical,
@@ -1614,6 +1647,14 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	/* These fields should be set together */
 	req->swap.swap_completed = req->swap.swap_enabled;
 
+	/* update brightness if changed */
+	if (dcp->brightness.update) {
+		req->swap.bl_unk = 1;
+		req->swap.bl_value = dcp->brightness.dac;
+		req->swap.bl_power = 0x40;
+		dcp->brightness.update = false;
+	}
+
 	if (modeset) {
 		struct dcp_display_mode *mode;
 		struct dcp_wait_cookie *cookie;
@@ -1667,7 +1708,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		dcp->valid_mode = true;
 	}
 
-	if (!has_surface) {
+	if (!has_surface && !crtc_state->color_mgmt_changed) {
 		if (crtc_state->enable && crtc_state->active &&
 		    !crtc_state->planes_changed) {
 			schedule_work(&dcp->vblank_wq);
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 68fdc654d597f5..386a84cfcc5cd1 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -63,6 +63,12 @@ struct dcp_packet_header {
 #define DCP_IS_NULL(ptr) ((ptr) ? 1 : 0)
 #define DCP_PACKET_ALIGNMENT (0x40)
 
+enum iomfb_property_id {
+    IOMFB_PROPERTY_NITS = 15, // divide by Brightness_Scale
+};
+
+#define IOMFB_BRIGHTNESS_MIN 0x10000000
+
 /* Structures used in v12.0 firmware */
 
 #define SWAP_SURFACES 4
@@ -114,7 +120,11 @@ struct dcp_swap {
 	u32 unk_2c8;
 	u8 unk_2cc[0x14];
 	u32 unk_2e0;
-	u8 unk_2e4[0x3c];
+	u16 unk_2e2;
+	u64 bl_unk;
+	u32 bl_value; // min value is 0x10000000
+	u8  bl_power; // constant 0x40 for on
+	u8 unk_2f3[0x2d];
 } __packed;
 
 /* Information describing a plane of a planar compressed surface */
@@ -340,6 +350,14 @@ struct dcp_get_uint_prop_resp {
 	u8 padding[3];
 } __packed;
 
+struct iomfb_sr_set_property_int_req {
+	char obj[4];
+	char key[0x40];
+	u64 value;
+	u8 value_null;
+	u8 padding[3];
+} __packed;
+
 struct iomfb_set_fx_prop_req {
 	char obj[4];
 	char key[0x40];
@@ -410,4 +428,9 @@ struct dcp_read_edt_data_resp {
 	u8 ret;
 } __packed;
 
+struct iomfb_property {
+	u32 id;
+	u32 value;
+} __packed;
+
 #endif

From 416006796b2c8efc3498413341b6a72409ca9d36 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 16 Nov 2022 00:10:31 +0100
Subject: [PATCH 0548/1027] HACK: gpu: drm: apple: j314/j316: Ignore 120 Hz
 mode for integrated display

It's currently not useful as DCP limits the swap rate to 60 Hz anyway
and marcan reported pointer choppiness with 120 Hz.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index fc52c26490ec80..31aecd4b2fc195 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -353,6 +353,19 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 	if (is_virtual)
 		return -EINVAL;
 
+	/*
+	 * HACK:
+	 * Ignore the 120 Hz mode on j314/j316 (identified by resolution).
+	 * DCP limits normal swaps to 60 Hz anyway and the 120 Hz mode might
+	 * cause choppiness with X11.
+	 * Just downscoring it and thus making the 60 Hz mode the preferred mode
+	 * seems not enough for some user space.
+	 */
+	if (vert.precise_sync_rate >> 16 == 120 &&
+	    ((horiz.active == 3024 && vert.active == 1964) ||
+	     (horiz.active == 3456 && vert.active == 2234)))
+		return -EINVAL;
+
 	vert.active -= notch_height;
 	vert.sync_width += notch_height;
 

From c974fbece25d9cb8d20f71be79940ba28a3716ad Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 17 Nov 2022 21:51:09 +0900
Subject: [PATCH 0549/1027] drm/apple: Fix suspend/resume handling

Use the drm_modeset helpers for suspend/resume at the subsystem level,
which actually do the proper save/restore dance and work, instead of
open-coding calls to dcp_poweroff/dcp_poweron which is clearly wrong and
doesn't restore properly (nor would it be correct if the display was
already off when suspended).

Also fix apple_platform_remove while I'm here, since drvdata wasn't
getting set so that would never work.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/apple_drv.c | 31 +++++++++++++++++++++++++++++--
 drivers/gpu/drm/apple/dcp.c       | 27 ---------------------------
 2 files changed, 29 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 51e2d03f225b49..21248f39368a26 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -404,6 +404,8 @@ static int apple_platform_probe(struct platform_device *pdev)
 	if (IS_ERR(apple))
 		return PTR_ERR(apple);
 
+	dev_set_drvdata(dev, apple);
+
 	ret = drm_vblank_init(&apple->drm, nr_dcp);
 	if (ret)
 		return ret;
@@ -466,9 +468,9 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 static int apple_platform_remove(struct platform_device *pdev)
 {
-	struct drm_device *drm = platform_get_drvdata(pdev);
+	struct apple_drm_private *apple = platform_get_drvdata(pdev);
 
-	drm_dev_unregister(drm);
+	drm_dev_unregister(&apple->drm);
 
 	return 0;
 }
@@ -479,10 +481,35 @@ static const struct of_device_id of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, of_match);
 
+#ifdef CONFIG_PM_SLEEP
+static int apple_platform_suspend(struct device *dev)
+{
+	struct apple_drm_private *apple = dev_get_drvdata(dev);
+
+	return drm_mode_config_helper_suspend(&apple->drm);
+}
+
+static int apple_platform_resume(struct device *dev)
+{
+	struct apple_drm_private *apple = dev_get_drvdata(dev);
+
+	drm_mode_config_helper_resume(&apple->drm);
+	return 0;
+}
+
+static const struct dev_pm_ops apple_platform_pm_ops = {
+	.suspend	= apple_platform_suspend,
+	.resume		= apple_platform_resume,
+};
+#endif
+
 static struct platform_driver apple_platform_driver = {
 	.driver	= {
 		.name = "apple-drm",
 		.of_match_table	= of_match,
+#ifdef CONFIG_PM_SLEEP
+		.pm = &apple_platform_pm_ops,
+#endif
 	},
 	.probe		= apple_platform_probe,
 	.remove		= apple_platform_remove,
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index d0d6f5be3f3dbf..f4523ccbbce453 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -414,39 +414,12 @@ static const struct of_device_id of_match[] = {
 };
 MODULE_DEVICE_TABLE(of, of_match);
 
-#ifdef CONFIG_PM_SLEEP
-/*
- * We don't hold any useful persistent state, so for suspend/resume it suffices
- * to power off/on the entire DCP. The firmware will sort out the details for
- * us.
- */
-static int dcp_suspend(struct device *dev)
-{
-	dcp_poweroff(to_platform_device(dev));
-	return 0;
-}
-
-static int dcp_resume(struct device *dev)
-{
-	dcp_poweron(to_platform_device(dev));
-	return 0;
-}
-
-static const struct dev_pm_ops dcp_pm_ops = {
-	.suspend	= dcp_suspend,
-	.resume		= dcp_resume,
-};
-#endif
-
 static struct platform_driver apple_platform_driver = {
 	.probe		= dcp_platform_probe,
 	.shutdown	= dcp_platform_shutdown,
 	.driver	= {
 		.name = "apple-dcp",
 		.of_match_table	= of_match,
-#ifdef CONFIG_PM_SLEEP
-		.pm = &dcp_pm_ops,
-#endif
 	},
 };
 

From 38d13f3b24693213de319ceb6ed0d719afbadfdb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 20 Nov 2022 09:20:14 +0100
Subject: [PATCH 0550/1027] gpu: drm: apple: Avoid drm_fb_dma_get_gem_addr

It adjust the address by the source position duplicating setting the
source postion in IOMFB's swap_submit struct. Prefer the later since it
is more explicit.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index ca29658cfc8e99..0b31b8a0ee7bd7 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -19,6 +19,7 @@
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_dma_helper.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
 
@@ -1568,6 +1569,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	l = 0;
 	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
 		struct drm_framebuffer *fb = new_state->fb;
+		struct drm_gem_dma_object *obj;
 		struct drm_rect src_rect;
 		bool opaque = false;
 
@@ -1620,7 +1622,13 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		if (dcp->notch_height > 0)
 			req->swap.dst_rect[l].y += dcp->notch_height;
 
-		req->surf_iova[l] = drm_fb_dma_get_gem_addr(fb, new_state, 0);
+		/* the obvious helper call drm_fb_dma_get_gem_addr() adjusts
+		 * the address for source x/y offsets. Since IOMFB has a direct
+		 * support source position prefer that.
+		 */
+		obj = drm_fb_dma_get_gem_obj(fb, 0);
+		if (obj)
+			req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
 
 		req->surf[l] = (struct dcp_surface){
 			.opaque = opaque,

From b50cd3b3b972e171230c48d19f456c7c8bf605e7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 23 Nov 2022 00:19:11 +0100
Subject: [PATCH 0551/1027] drm/apple: register backlight device after IOMFB
 start

This allows us to specify the boot display brightness as initial
brightness of the baclight device.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h  |  8 +++-
 drivers/gpu/drm/apple/dcp.c           | 37 +++++++++++++---
 drivers/gpu/drm/apple/dcp_backlight.c | 61 +++++++++------------------
 drivers/gpu/drm/apple/iomfb.c         |  9 ++--
 4 files changed, 64 insertions(+), 51 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index c2fa002b45d5de..9f32fd9d0182ed 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -4,7 +4,9 @@
 #ifndef __APPLE_DCP_INTERNAL_H__
 #define __APPLE_DCP_INTERNAL_H__
 
+#include <linux/backlight.h>
 #include <linux/device.h>
+#include <linux/mutex.h>
 #include <linux/platform_device.h>
 #include <linux/scatterlist.h>
 
@@ -65,9 +67,10 @@ struct dcp_fb_reference {
 #define MAX_NOTCH_HEIGHT 160
 
 struct dcp_brightness {
+	struct backlight_device *bl_dev;
+	u32 maximum;
 	u32 dac;
 	int nits;
-	int set;
 	int scale;
 	bool update;
 };
@@ -153,6 +156,9 @@ struct apple_dcp {
 	struct list_head swapped_out_fbs;
 
 	struct dcp_brightness brightness;
+	/* Workqueue for updating the initial initial brightness */
+	struct work_struct bl_register_wq;
+	struct mutex bl_register_mutex;
 };
 
 int dcp_backlight_register(struct apple_dcp *dcp);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index f4523ccbbce453..29aa6b018ba23e 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -12,6 +12,7 @@
 #include <linux/align.h>
 #include <linux/soc/apple/rtkit.h>
 #include <linux/completion.h>
+#include "linux/workqueue.h"
 
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
@@ -252,6 +253,28 @@ int dcp_start(struct platform_device *pdev)
 }
 EXPORT_SYMBOL(dcp_start);
 
+static void dcp_work_register_backlight(struct work_struct *work)
+{
+	int ret;
+	struct apple_dcp *dcp;
+
+	dcp = container_of(work, struct apple_dcp, bl_register_wq);
+
+	mutex_lock(&dcp->bl_register_mutex);
+	if (dcp->brightness.bl_dev)
+		goto out_unlock;
+
+	/* try to register backlight device, */
+	ret = dcp_backlight_register(dcp);
+	if (ret) {
+		dev_err(dcp->dev, "Unable to register backlight device\n");
+		dcp->brightness.maximum = 0;
+	}
+
+out_unlock:
+	mutex_unlock(&dcp->bl_register_mutex);
+}
+
 static struct platform_device *dcp_get_dev(struct device *dev, const char *name)
 {
 	struct platform_device *pdev;
@@ -312,14 +335,16 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	dcp->brightness.scale = 65536;
 	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
 	if (panel_np) {
+		if (of_device_is_available(panel_np)) {
+			ret = of_property_read_u32(panel_np, "apple,max-brightness",
+						   &dcp->brightness.maximum);
+			if (ret)
+				dev_err(dev, "Missing property 'apple,max-brightness'\n");
+		}
 		of_node_put(panel_np);
 		dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
-
-		/* try to register backlight device, */
-		ret = dcp_backlight_register(dcp);
-		if (ret)
-			return dev_err_probe(dev, ret,
-						"Unable to register backlight device\n");
+		INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight);
+		mutex_init(&dcp->bl_register_mutex);
 	} else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0)
 		dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA;
 	else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0)
diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index c695e3bad7db91..5b4a41c53ca21b 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -165,29 +165,28 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc,
 
 static int dcp_set_brightness(struct backlight_device *bd)
 {
-	int ret = 0;
+	int ret;
 	struct apple_dcp *dcp = bl_get_data(bd);
+	struct drm_modeset_acquire_ctx ctx;
 
-	bd->props.power = FB_BLANK_UNBLANK;
-	if (dcp->brightness.set != bd->props.brightness) {
-	       dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness);
-	       dcp->brightness.set = bd->props.brightness;
-	       dcp->brightness.update = true;
-	}
+	if (bd->props.state & BL_CORE_SUSPENDED)
+		return 0;
 
-	if (dcp->brightness.update && dcp->crtc) {
-		struct drm_modeset_acquire_ctx ctx;
-		struct drm_device *drm_dev = dcp->crtc->base.dev;
+	if (!dcp->crtc)
+		return -EAGAIN;
 
-		DRM_MODESET_LOCK_ALL_BEGIN(drm_dev, ctx, 0, ret);
-		ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx);
-		DRM_MODESET_LOCK_ALL_END(drm_dev, ctx, ret);
-	}
+	dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness);
+	dcp->brightness.update = true;
+
+	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
+	ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx);
+	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
 
 	return ret;
 }
 
 static const struct backlight_ops dcp_backlight_ops = {
+	.options = BL_CORE_SUSPENDRESUME,
 	.get_brightness = dcp_get_brightness,
 	.update_status = dcp_set_brightness,
 };
@@ -195,38 +194,20 @@ static const struct backlight_ops dcp_backlight_ops = {
 int dcp_backlight_register(struct apple_dcp *dcp)
 {
 	struct device *dev = dcp->dev;
-	struct backlight_device *bd;
-	struct device_node *panel_np;
+	struct backlight_device *bl_dev;
 	struct backlight_properties props = {
 		.type = BACKLIGHT_PLATFORM,
 		.brightness = dcp->brightness.nits,
-		.max_brightness = 0,
 		.scale = BACKLIGHT_SCALE_LINEAR,
 	};
-	u32 max_brightness;
-	int ret = 0;
+	props.max_brightness = min(dcp->brightness.maximum, MAX_BRIGHTNESS_PART2 - 1);
 
-	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
-	if (!panel_np)
-		return 0;
-
-	if (!of_device_is_available(panel_np))
-		goto out_put;
-
-	ret = of_property_read_u32(panel_np, "apple,max-brightness", &max_brightness);
-	if (ret) {
-		dev_err(dev, "Missing property 'apple,max-brightness'\n");
-		goto out_put;
-	}
-	props.max_brightness = min(max_brightness, MAX_BRIGHTNESS_PART2 - 1);
-
-	bd = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp,
-					    &dcp_backlight_ops, &props);
-	if (IS_ERR(bd))
-		ret = PTR_ERR(bd);
+	bl_dev = devm_backlight_device_register(dev, "apple-panel-bl", dev, dcp,
+						&dcp_backlight_ops, &props);
+	if (IS_ERR(bl_dev))
+		return PTR_ERR(bl_dev);
 
-out_put:
-	of_node_put(panel_np);
+	dcp->brightness.bl_dev = bl_dev;
 
-	return ret;
+	return 0;
 }
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 0b31b8a0ee7bd7..735addf784fd8a 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -428,12 +428,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr
 {
 	switch (prop->id) {
 	case IOMFB_PROPERTY_NITS:
+	{
 		dcp->brightness.nits = prop->value / dcp->brightness.scale;
-		/* temporary for user debugging during tesing */
-		dev_info(dcp->dev, "Backlight updated to %u nits\n",
-			 dcp->brightness.nits);
-		dcp->brightness.update = false;
+		/* notify backlight device of the initial brightness */
+		if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
+			schedule_work(&dcp->bl_register_wq);
 		break;
+	}
 	default:
 		dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
 	}

From 16f4bd5fd82a4c0d1888dbd86ea9bccfb5f26f1a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 23 Nov 2022 00:27:38 +0100
Subject: [PATCH 0552/1027] drm/apple: Add trace point for display brightness

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c |  1 +
 drivers/gpu/drm/apple/trace.h | 18 ++++++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 735addf784fd8a..9effd013729676 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -433,6 +433,7 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr
 		/* notify backlight device of the initial brightness */
 		if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
 			schedule_work(&dcp->bl_register_wq);
+		trace_iomfb_brightness(dcp, prop->value);
 		break;
 	}
 	default:
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index 25a23c1586e259..c7b5dee11ed07d 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -153,6 +153,24 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated,
 	    )
 );
 
+TRACE_EVENT(iomfb_brightness,
+	    TP_PROTO(struct apple_dcp *dcp, u32 nits),
+	    TP_ARGS(dcp, nits),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, nits)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->nits = nits;
+	    ),
+	    TP_printk("dcp=%llx, nits=%u (raw=0x%05x)",
+		      __entry->dcp,
+		      __entry->nits >> 16,
+		      __entry->nits
+	    )
+);
+
 #endif /* _TRACE_DCP_H */
 
 /* This part must be outside protection */

From a08ed8baf2928a9a9a1fb5f90b61a4536b7ab1bb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 23 Nov 2022 23:21:54 +0100
Subject: [PATCH 0553/1027] drm/apple: Implement
 drm_crtc_helper_funcs.mode_fixup

Prevents KDE for now to set modes not reported by IOMFB. Seen on j493
with the common display resolution of 2560x1600.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  1 +
 drivers/gpu/drm/apple/dcp.h       |  3 +++
 drivers/gpu/drm/apple/iomfb.c     | 15 ++++++++++++++-
 3 files changed, 18 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 21248f39368a26..7f865a681f492c 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -305,6 +305,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
 	.atomic_flush		= dcp_flush,
 	.atomic_enable		= apple_crtc_atomic_enable,
 	.atomic_disable		= apple_crtc_atomic_disable,
+	.mode_fixup		= dcp_crtc_mode_fixup,
 };
 
 static int apple_probe_per_dcp(struct device *dev,
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index dfe014f3f5d1da..fb4397e7b390fe 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -49,6 +49,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc);
 int dcp_get_modes(struct drm_connector *connector);
 int dcp_mode_valid(struct drm_connector *connector,
 		   struct drm_display_mode *mode);
+bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
+			 const struct drm_display_mode *mode,
+			 struct drm_display_mode *adjusted_mode);
 void dcp_set_dimensions(struct apple_dcp *dcp);
 void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message);
 
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 9effd013729676..37ea896122ccd8 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1485,7 +1485,7 @@ EXPORT_SYMBOL_GPL(dcp_get_modes);
 
 /* The user may own drm_display_mode, so we need to search for our copy */
 static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
-					    struct drm_display_mode *mode)
+					    const struct drm_display_mode *mode)
 {
 	int i;
 
@@ -1510,6 +1510,19 @@ int dcp_mode_valid(struct drm_connector *connector,
 }
 EXPORT_SYMBOL_GPL(dcp_mode_valid);
 
+bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
+			 const struct drm_display_mode *mode,
+			 struct drm_display_mode *adjusted_mode)
+{
+	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+	struct platform_device *pdev = apple_crtc->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	/* TODO: support synthesized modes through scaling */
+	return lookup_mode(dcp, mode) != NULL;
+}
+EXPORT_SYMBOL(dcp_crtc_mode_fixup);
+
 /* Helpers to modeset and swap, used to flush */
 static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
 {

From efb0e2cb98bf4947b8f53669bf394c5540712621 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 24 Nov 2022 21:44:36 +0100
Subject: [PATCH 0554/1027] drm/apple: Read display dimensions from devicetree

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 22 +++++++++++++++-------
 drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++-----
 2 files changed, 25 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 29aa6b018ba23e..66d53328f09acb 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -331,16 +331,31 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	of_platform_default_populate(dev->of_node, NULL, dev);
 
+	ret = of_property_read_u32(dev->of_node, "apple,notch-height",
+				   &dcp->notch_height);
+	if (dcp->notch_height > MAX_NOTCH_HEIGHT)
+		dcp->notch_height = MAX_NOTCH_HEIGHT;
+	if (dcp->notch_height > 0)
+		dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height);
+
 	/* intialize brightness scale to a sensible default to avoid divide by 0*/
 	dcp->brightness.scale = 65536;
 	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
 	if (panel_np) {
+		const char height_prop[2][16] = { "adj-height-mm", "height-mm" };
+
 		if (of_device_is_available(panel_np)) {
 			ret = of_property_read_u32(panel_np, "apple,max-brightness",
 						   &dcp->brightness.maximum);
 			if (ret)
 				dev_err(dev, "Missing property 'apple,max-brightness'\n");
 		}
+
+		of_property_read_u32(panel_np, "width-mm", &dcp->width_mm);
+		/* use adjusted height as long as the notch is hidden */
+		of_property_read_u32(panel_np, height_prop[!dcp->notch_height],
+				     &dcp->height_mm);
+
 		of_node_put(panel_np);
 		dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
 		INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight);
@@ -387,13 +402,6 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret);
 	dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask);
 
-	ret = of_property_read_u32(dev->of_node, "apple,notch-height",
-				   &dcp->notch_height);
-	if (dcp->notch_height > MAX_NOTCH_HEIGHT)
-		dcp->notch_height = MAX_NOTCH_HEIGHT;
-	if (dcp->notch_height > 0)
-		dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height);
-
 	bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS);
 	// TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry
 	set_bit(0, dcp->memdesc_map);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 37ea896122ccd8..1892dc55ae4281 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -764,12 +764,17 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp,
 			return false;
 		}
 	} else if (!strcmp(req->key, "DisplayAttributes")) {
-		ret = parse_display_attributes(&ctx, &dcp->width_mm,
-					       &dcp->height_mm);
+		/* DisplayAttributes are empty for integrated displays, use
+		 * display dimensions read from the devicetree
+		 */
+		if (dcp->main_display) {
+			ret = parse_display_attributes(&ctx, &dcp->width_mm,
+						&dcp->height_mm);
 
-		if (ret) {
-			dev_warn(dcp->dev, "failed to parse display attribs\n");
-			return false;
+			if (ret) {
+				dev_warn(dcp->dev, "failed to parse display attribs\n");
+				return false;
+			}
 		}
 
 		dcp_set_dimensions(dcp);

From ef6633e239810c821fee6c97da69170f66e9a541 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Mon, 28 Nov 2022 00:42:25 +0900
Subject: [PATCH 0555/1027] drm/apple: Wait for power on request to complete
 synchronously

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++-------
 1 file changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 1892dc55ae4281..361c597b36f966 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -942,6 +942,16 @@ static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
 	}
 }
 
+static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_power_state_req req = {
+		.unklong = 1,
+	};
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
+}
+
 static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
 {
 	struct dcp_set_parameter_dcp param = {
@@ -951,16 +961,13 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
 	};
 	dev_dbg(dcp->dev, "%s", __func__);
 
-	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_final, cookie);
+	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
 }
 
 void dcp_poweron(struct platform_device *pdev)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
 	struct dcp_wait_cookie *cookie;
-	struct dcp_set_power_state_req req = {
-		.unklong = 1,
-	};
 	int ret;
 	u32 handle;
 	dev_dbg(dcp->dev, "%s", __func__);
@@ -976,15 +983,13 @@ void dcp_poweron(struct platform_device *pdev)
 
 	if (dcp->main_display) {
 		handle = 0;
-		dcp_set_display_device(dcp, false, &handle, dcp_on_final,
+		dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state,
 				       cookie);
 	} else {
 		handle = 2;
 		dcp_set_display_device(dcp, false, &handle,
 				       dcp_on_set_parameter, cookie);
 	}
-	dcp_set_power_state(dcp, true, &req, NULL, NULL);
-
 	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
 
 	if (ret == 0)

From ec845e88c40367e006ea78c9c1f5641eda53b8e3 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Mon, 28 Nov 2022 00:44:41 +0900
Subject: [PATCH 0556/1027] drm/apple: Remove obsolete ignore_swap_complete

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 2 --
 drivers/gpu/drm/apple/iomfb.c        | 5 +----
 2 files changed, 1 insertion(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 9f32fd9d0182ed..24db9f39d78e2a 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -133,8 +133,6 @@ struct apple_dcp {
 	/* eDP display without DP-HDMI conversion */
 	bool main_display;
 
-	bool ignore_swap_complete;
-
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 361c597b36f966..e0687117e898d4 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -359,8 +359,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 {
 	trace_iomfb_swap_complete(dcp, resp->swap_id);
 
-	if (!dcp->ignore_swap_complete)
-		dcp_drm_crtc_vblank(dcp->crtc);
+	dcp_drm_crtc_vblank(dcp->crtc);
 }
 
 /* special */
@@ -1551,8 +1550,6 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
 	struct dcp_wait_cookie *wait = cookie;
 	dev_dbg(dcp->dev, "%s", __func__);
 
-	dcp->ignore_swap_complete = false;
-
 	if (wait) {
 		complete(&wait->done);
 		kref_put(&wait->refcount, release_wait_cookie);

From cbbb60b34798e98b1c813dc7a183d2f3640d5a5b Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Mon, 28 Nov 2022 01:02:24 +0900
Subject: [PATCH 0557/1027] drm/asahi: Fix backlight restores on non-microLED
 devices

Apparently what happens here is that the DCP's idea of backlight
brightness is desynced with the real brightness across power cycles.
This means that even if we just force an update after a power cycle, it
doesn't work since it considers it unchanged. To fix this, we need to
both force an update on poweron and also explicitly turn the backlight
off on poweroff, which makes DCP listen to us and actually update the
backlight state properly.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/dcp_backlight.c |  1 +
 drivers/gpu/drm/apple/iomfb.c         | 31 ++++++++++++++++++++-------
 2 files changed, 24 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index 5b4a41c53ca21b..42b1097eaa0180 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -208,6 +208,7 @@ int dcp_backlight_register(struct apple_dcp *dcp)
 		return PTR_ERR(bl_dev);
 
 	dcp->brightness.bl_dev = bl_dev;
+	dcp->brightness.dac = calculate_dac(dcp, dcp->brightness.nits);
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index e0687117e898d4..2f2fb3e4b5d26b 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -995,6 +995,9 @@ void dcp_poweron(struct platform_device *pdev)
 		dev_warn(dcp->dev, "wait for power timed out");
 
 	kref_put(&cookie->refcount, release_wait_cookie);;
+
+	/* Force a brightness update after poweron, to restore the brightness */
+	dcp->brightness.update = true;
 }
 EXPORT_SYMBOL(dcp_poweron);
 
@@ -1037,6 +1040,17 @@ void dcp_poweroff(struct platform_device *pdev)
 	dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7;
 	dcp->swap.swap.unk_10c = 0xFF000000;
 
+	/*
+	 * Turn off the backlight. This matters because the DCP's idea of
+	 * backlight brightness gets desynced after a power change, and it
+	 * needs to be told it's going to turn off so it will consider the
+	 * subsequent update on poweron an actual change and restore the
+	 * brightness.
+	 */
+	dcp->swap.swap.bl_unk = 1;
+	dcp->swap.swap.bl_value = 0;
+	dcp->swap.swap.bl_power = 0;
+
 	for (int l = 0; l < SWAP_SURFACES; l++)
 		dcp->swap.surf_null[l] = true;
 
@@ -1677,14 +1691,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	/* These fields should be set together */
 	req->swap.swap_completed = req->swap.swap_enabled;
 
-	/* update brightness if changed */
-	if (dcp->brightness.update) {
-		req->swap.bl_unk = 1;
-		req->swap.bl_value = dcp->brightness.dac;
-		req->swap.bl_power = 0x40;
-		dcp->brightness.update = false;
-	}
-
 	if (modeset) {
 		struct dcp_display_mode *mode;
 		struct dcp_wait_cookie *cookie;
@@ -1747,6 +1753,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 		req->clear = 1;
 	}
+
+	/* update brightness if changed */
+	if (dcp->brightness.update) {
+		req->swap.bl_unk = 1;
+		req->swap.bl_value = dcp->brightness.dac;
+		req->swap.bl_power = 0x40;
+		dcp->brightness.update = false;
+	}
+
 	do_swap(dcp, NULL, NULL);
 }
 EXPORT_SYMBOL_GPL(dcp_flush);

From e7029ae875b2fee400eb3cb6108bafb68cb91c6e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 27 Nov 2022 21:48:44 +0100
Subject: [PATCH 0558/1027] drm/apple: Schedule backlight update on
 enable_backlight_message_ap_gated

On non mini-LED displays the backlight comes out of power-off (DPMS)
with minimal backlight brightness. This seems to be a DCP firmware
issue. It logs "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of
range" to syslog although the brightness in the swap_submit call is
valid.

This fixes the issue only for clients using swap. For other clients an
atomic backlight update has to be scheduled via a work queue.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 2f2fb3e4b5d26b..2275afb2ada926 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -697,6 +697,17 @@ dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
 	};
 }
 
+static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
+							 u8 *enabled)
+{
+	/*
+	 * update backlight brightness on next swap, on non mini-LED displays
+	 * DCP seems to set an invalid iDAC value after coming out of DPMS.
+	 * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
+	 */
+	dcp->brightness.update = true;
+}
+
 /* Chunked data transfer for property dictionaries */
 static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
 {
@@ -1252,6 +1263,8 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
 TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
 	      dcpep_cb_swap_complete_intent_gated,
 	      struct dcp_swap_complete_intent_gated);
+TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
+	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
 TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
 	      struct iomfb_property);
 
@@ -1307,7 +1320,7 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[582] = trampoline_true, /* create_default_fb_surface */
 	[589] = trampoline_swap_complete,
 	[591] = trampoline_swap_complete_intent_gated,
-	[593] = trampoline_nop, /* enable_backlight_message_ap_gated */
+	[593] = trampoline_enable_backlight_message_ap_gated,
 	[598] = trampoline_nop, /* find_swap_function_gated */
 };
 

From c504647cce457bfeac194ca6aa6e96d9b2948955 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 27 Nov 2022 22:45:22 +0100
Subject: [PATCH 0559/1027] drm/apple: Report "PMUS.Temperature" only for
 mini-LED backlights

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 3 +++
 drivers/gpu/drm/apple/dcp.c          | 7 ++++++-
 drivers/gpu/drm/apple/iomfb.c        | 3 ++-
 3 files changed, 11 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 24db9f39d78e2a..7fe6490509f754 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -133,6 +133,9 @@ struct apple_dcp {
 	/* eDP display without DP-HDMI conversion */
 	bool main_display;
 
+	/* panel has a mini-LED backllight */
+	bool has_mini_led;
+	
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 66d53328f09acb..1c5f5d6c55322d 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -340,7 +340,12 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	/* intialize brightness scale to a sensible default to avoid divide by 0*/
 	dcp->brightness.scale = 65536;
-	panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
+	panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led");
+	if (panel_np)
+		dcp->has_mini_led = true;
+	else
+		panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
+
 	if (panel_np) {
 		const char height_prop[2][16] = { "adj-height-mm", "height-mm" };
 
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 2275afb2ada926..f7da26c391d532 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -447,7 +447,8 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
 	    .value = 0
 	};
 
-	if (memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
+	if (dcp->has_mini_led &&
+	    memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
 	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
 		/*
 		 * TODO: value from j314c, find out if it is temperature in

From 2d8b935100f3a23bc9fa4fcd2517a309edb9f5dc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 4 Dec 2022 11:36:05 +0100
Subject: [PATCH 0560/1027] drm/apple: Check if DCP firmware is supported

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h |  8 +++
 drivers/gpu/drm/apple/dcp.c          | 77 ++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 7fe6490509f754..18969ab18e8319 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -16,6 +16,11 @@
 
 struct apple_dcp;
 
+enum dcp_firmware_version {
+	DCP_FIRMWARE_UNKNOWN,
+	DCP_FIRMWARE_V_12_3,
+};
+
 enum {
 	SYSTEM_ENDPOINT = 0x20,
 	TEST_ENDPOINT = 0x21,
@@ -84,6 +89,9 @@ struct apple_dcp {
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
 
+	/* firmware version and compatible firmware version */
+	enum dcp_firmware_version fw_compat;
+
 	/* Coprocessor control register */
 	void __iomem *coproc_reg;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 1c5f5d6c55322d..a19c124396ed87 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -3,8 +3,10 @@
 
 #include <linux/bitmap.h>
 #include <linux/clk.h>
+#include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/slab.h>
+#include <linux/string.h>
 #include <linux/of_device.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
@@ -306,18 +308,93 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp)
 	return 0;
 }
 
+#define DCP_FW_VERSION_MIN_LEN	3
+#define DCP_FW_VERSION_MAX_LEN	5
+#define DCP_FW_VERSION_STR_LEN	(DCP_FW_VERSION_MAX_LEN * 4)
+
+static int dcp_read_fw_version(struct device *dev, const char *name,
+			       char *version_str)
+{
+	u32 ver[DCP_FW_VERSION_MAX_LEN];
+	int len_str;
+	int len;
+
+	len = of_property_read_variable_u32_array(dev->of_node, name, ver,
+						  DCP_FW_VERSION_MIN_LEN,
+						  DCP_FW_VERSION_MAX_LEN);
+
+	switch (len) {
+	case 3:
+		len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
+				    "%d.%d.%d", ver[0], ver[1], ver[2]);
+		break;
+	case 4:
+		len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
+				    "%d.%d.%d.%d", ver[0], ver[1], ver[2],
+				    ver[3]);
+		break;
+	case 5:
+		len_str = scnprintf(version_str, DCP_FW_VERSION_STR_LEN,
+				    "%d.%d.%d.%d.%d", ver[0], ver[1], ver[2],
+				    ver[3], ver[4]);
+		break;
+	default:
+		len_str = strscpy(version_str, "UNKNOWN",
+				  DCP_FW_VERSION_STR_LEN);
+		if (len >= 0)
+			len = -EOVERFLOW;
+		break;
+	}
+
+	if (len_str >= DCP_FW_VERSION_STR_LEN)
+		dev_warn(dev, "'%s' truncated: '%s'\n", name, version_str);
+
+	return len;
+}
+
+static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
+{
+	char compat_str[DCP_FW_VERSION_STR_LEN];
+	char fw_str[DCP_FW_VERSION_STR_LEN];
+	int ret;
+
+	/* firmware version is just informative */
+	dcp_read_fw_version(dev, "apple,firmware-version", fw_str);
+
+	ret = dcp_read_fw_version(dev, "apple,firmware-compat", compat_str);
+	if (ret < 0) {
+		dev_err(dev, "Could not read 'apple,firmware-compat': %d\n", ret);
+		return DCP_FIRMWARE_UNKNOWN;
+	}
+
+	if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0)
+		return DCP_FIRMWARE_V_12_3;
+
+	dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n",
+		compat_str, fw_str);
+
+	return DCP_FIRMWARE_UNKNOWN;
+}
+
 static int dcp_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct device_node *panel_np;
 	struct apple_dcp *dcp;
+	enum dcp_firmware_version fw_compat;
 	u32 cpu_ctrl;
 	int ret;
 
+	fw_compat = dcp_check_firmware_version(dev);
+	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
+		return -ENODEV;
+
 	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
 	if (!dcp)
 		return -ENOMEM;
 
+	dcp->fw_compat = fw_compat;
+
 	platform_set_drvdata(pdev, dcp);
 	dcp->dev = dev;
 

From f21ab25efdd3580ff643792c82412dce109576ca Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 27 Nov 2022 19:32:55 +0900
Subject: [PATCH 0561/1027] drm/apple: Disable fake vblank IRQ machinery

The hardware does not have a vblank IRQ and drm already knows how to
deal with that appropriately, so don't pretend it does.

Fixes Xorg.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 23 -----------------------
 drivers/gpu/drm/apple/dcp.c       |  6 ------
 2 files changed, 29 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 7f865a681f492c..9d4d45d98cfbe3 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -167,18 +167,6 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev,
 	return plane;
 }
 
-static int apple_enable_vblank(struct drm_crtc *crtc)
-{
-	to_apple_crtc(crtc)->vsync_disabled = false;
-
-	return 0;
-}
-
-static void apple_disable_vblank(struct drm_crtc *crtc)
-{
-	to_apple_crtc(crtc)->vsync_disabled = true;
-}
-
 static enum drm_connector_status
 apple_connector_detect(struct drm_connector *connector, bool force)
 {
@@ -200,7 +188,6 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 		dcp_poweron(apple_crtc->dcp);
 		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
 	}
-	drm_crtc_vblank_on(crtc);
 }
 
 static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
@@ -209,8 +196,6 @@ static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
 	struct drm_crtc_state *crtc_state;
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
-	drm_crtc_vblank_off(crtc);
-
 	if (crtc_state->active_changed && !crtc_state->active) {
 		struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
 		dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
@@ -234,8 +219,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
 	unsigned long flags;
 
 	if (crtc->state->event) {
-		WARN_ON(drm_crtc_vblank_get(crtc) != 0);
-
 		spin_lock_irqsave(&crtc->dev->event_lock, flags);
 		apple_crtc->event = crtc->state->event;
 		spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
@@ -271,8 +254,6 @@ static const struct drm_crtc_funcs apple_crtc_funcs = {
 	.page_flip		= drm_atomic_helper_page_flip,
 	.reset			= drm_atomic_helper_crtc_reset,
 	.set_config             = drm_atomic_helper_set_config,
-	.enable_vblank		= apple_enable_vblank,
-	.disable_vblank		= apple_disable_vblank,
 };
 
 static const struct drm_mode_config_funcs apple_mode_config_funcs = {
@@ -407,10 +388,6 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 	dev_set_drvdata(dev, apple);
 
-	ret = drm_vblank_init(&apple->drm, nr_dcp);
-	if (ret)
-		return ret;
-
 	ret = drmm_mode_config_init(&apple->drm);
 	if (ret)
 		goto err_unload;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index a19c124396ed87..c62b814045f5a4 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -38,15 +38,9 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 {
 	unsigned long flags;
 
-	if (crtc->vsync_disabled)
-		return;
-
-	drm_crtc_handle_vblank(&crtc->base);
-
 	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
 	if (crtc->event) {
 		drm_crtc_send_vblank_event(&crtc->base, crtc->event);
-		drm_crtc_vblank_put(&crtc->base);
 		crtc->event = NULL;
 	}
 	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);

From 7db59caa35f9f455f006840e931f372375d77531 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 13:42:49 +0100
Subject: [PATCH 0562/1027] gpu: drm: apple: Parse color modes completely

Selecting the mode with the highest score may result in HDR mode for
some displays. Since HDR is not support on driver side this produces
an unexpected color representation.
Full parsing allows us to reject virtual color modes (should already be
invalid due to missing "Score").
Preparation for color and timing mode tracing.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 41 ++++++++++++++++++++++++++--------
 1 file changed, 32 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 31aecd4b2fc195..17060bd5e87ba6 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -248,6 +248,16 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim)
 	return 0;
 }
 
+struct color_mode {
+	s64 colorimetry;
+	s64 depth;
+	s64 dynamic_range;
+	s64 eotf;
+	s64 id;
+	s64 pixel_encoding;
+	s64 score;
+};
+
 static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
 {
 	struct iterator outer_it;
@@ -258,17 +268,30 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
 
 	dcp_parse_foreach_in_array(handle, outer_it) {
 		struct iterator it;
-		s64 score = -1, id = -1;
+		bool is_virtual = true;
+		struct color_mode cmode;
 
 		dcp_parse_foreach_in_dict(handle, it) {
 			char *key = parse_string(it.handle);
 
 			if (IS_ERR(key))
 				ret = PTR_ERR(key);
-			else if (!strcmp(key, "Score"))
-				ret = parse_int(it.handle, &score);
+			else if (!strcmp(key, "Colorimetry"))
+				ret = parse_int(it.handle, &cmode.colorimetry);
+			else if (!strcmp(key, "Depth"))
+				ret = parse_int(it.handle, &cmode.depth);
+			else if (!strcmp(key, "DynamicRange"))
+				ret = parse_int(it.handle, &cmode.dynamic_range);
+			else if (!strcmp(key, "EOTF"))
+				ret = parse_int(it.handle, &cmode.eotf);
 			else if (!strcmp(key, "ID"))
-				ret = parse_int(it.handle, &id);
+				ret = parse_int(it.handle, &cmode.id);
+			else if (!strcmp(key, "IsVirtual"))
+				ret = parse_bool(it.handle, &is_virtual);
+			else if (!strcmp(key, "PixelEncoding"))
+				ret = parse_int(it.handle, &cmode.pixel_encoding);
+			else if (!strcmp(key, "Score"))
+				ret = parse_int(it.handle, &cmode.score);
 			else
 				skip(it.handle);
 
@@ -276,13 +299,13 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
 				return ret;
 		}
 
-		/* Skip partial entries */
-		if (score < 0 || id < 0)
+		/* Skip virtual or partial entries */
+		if (is_virtual || cmode.score < 0 || cmode.id < 0)
 			continue;
 
-		if (score > best_score) {
-			best_score = score;
-			*best_id = id;
+		if (cmode.score > best_score) {
+			best_score = cmode.score;
+			*best_id = cmode.id;
 		}
 	}
 

From 8ef5c8973ab6aaac977c43d107af8339f664f482 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 14:11:30 +0100
Subject: [PATCH 0563/1027] gpu: drm: apple: Skip parsing elements of virtual
 timing modes

Prevents trace points of unused color modes.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 17060bd5e87ba6..d84c77be5fd78b 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -343,6 +343,8 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 
 		if (IS_ERR(key))
 			ret = PTR_ERR(key);
+		else if (is_virtual)
+			skip(it.handle);
 		else if (!strcmp(key, "HorizontalAttributes"))
 			ret = parse_dimension(it.handle, &horiz);
 		else if (!strcmp(key, "VerticalAttributes"))

From cf10acc1f880b60f4c3494c6dfbad44df3f930ee Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 13:58:53 +0100
Subject: [PATCH 0564/1027] gpu: drm: apple: Add tracing for color and timing
 modes

Restrict symbol mapping for EOTF, pixel encoding and colorimetry to
tracing due to low confidence that it is correct. Main concern is the
colorimetry mapping.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c  |   3 +
 drivers/gpu/drm/apple/parser.c |  11 +++
 drivers/gpu/drm/apple/parser.h |   3 +
 drivers/gpu/drm/apple/trace.h  | 120 +++++++++++++++++++++++++++++++++
 4 files changed, 137 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index f7da26c391d532..533835de89756c 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -756,6 +756,9 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp,
 		return false;
 	}
 
+	/* used just as opaque pointer for tracing */
+	ctx.dcp = dcp;
+
 	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
 
 	if (ret) {
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index d84c77be5fd78b..a253ec62640ba5 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -6,7 +6,9 @@
 #include <linux/math.h>
 #include <linux/string.h>
 #include <linux/slab.h>
+
 #include "parser.h"
+#include "trace.h"
 
 #define DCP_PARSE_HEADER 0xd3
 
@@ -303,6 +305,11 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
 		if (is_virtual || cmode.score < 0 || cmode.id < 0)
 			continue;
 
+		trace_iomfb_color_mode(handle->dcp, cmode.id, cmode.score,
+				       cmode.depth, cmode.colorimetry,
+				       cmode.eotf, cmode.dynamic_range,
+				       cmode.pixel_encoding);
+
 		if (cmode.score > best_score) {
 			best_score = cmode.score;
 			*best_id = cmode.id;
@@ -419,6 +426,10 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 	out->timing_mode_id = id;
 	out->color_mode_id = best_color_mode;
 
+	trace_iomfb_timing_mode(handle->dcp, id, *score, horiz.active,
+				vert.active, vert.precise_sync_rate,
+				best_color_mode);
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index a2d479258ed0eb..92fe9473d56718 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -7,7 +7,10 @@
 /* For mode parsing */
 #include <drm/drm_modes.h>
 
+struct apple_dcp;
+
 struct dcp_parse_ctx {
+	struct apple_dcp *dcp;
 	void *blob;
 	u32 pos, len;
 };
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index c7b5dee11ed07d..127bda420592a0 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -171,6 +171,126 @@ TRACE_EVENT(iomfb_brightness,
 	    )
 );
 
+#define show_eotf(eotf)					\
+	__print_symbolic(eotf, { 0, "SDR gamma"},	\
+			       { 1, "HDR gamma"},	\
+			       { 2, "ST 2084 (PQ)"},	\
+			       { 3, "BT.2100 (HLG)"},	\
+			       { 4, "unexpected"})
+
+#define show_encoding(enc)							\
+	__print_symbolic(enc, { 0, "RGB"},					\
+			      { 1, "YUV 4:2:0"},				\
+			      { 3, "YUV 4:2:2"},				\
+			      { 2, "YUV 4:4:4"},				\
+			      { 4, "DolbyVision (native)"},			\
+			      { 5, "DolbyVision (HDMI)"},			\
+			      { 6, "YCbCr 4:2:2 (DP tunnel)"},			\
+			      { 7, "YCbCr 4:2:2 (HDMI tunnel)"},		\
+			      { 8, "DolbyVision LL YCbCr 4:2:2"},		\
+			      { 9, "DolbyVision LL YCbCr 4:2:2 (DP)"},		\
+			      {10, "DolbyVision LL YCbCr 4:2:2 (HDMI)"},	\
+			      {11, "DolbyVision LL YCbCr 4:4:4"},		\
+			      {12, "DolbyVision LL RGB 4:2:2"},			\
+			      {13, "GRGB as YCbCr422 (Even line blue)"},	\
+			      {14, "GRGB as YCbCr422 (Even line red)"},		\
+			      {15, "unexpected"})
+
+#define show_colorimetry(col)					\
+	__print_symbolic(col, { 0, "SMPTE 170M/BT.601"},	\
+			      { 1, "BT.701"},			\
+			      { 2, "xvYCC601"},			\
+			      { 3, "xvYCC709"},			\
+			      { 4, "sYCC601"},			\
+			      { 5, "AdobeYCC601"},		\
+			      { 6, "BT.2020 (c)"},		\
+			      { 7, "BT.2020 (nc)"},		\
+			      { 8, "DolbyVision VSVDB"},	\
+			      { 9, "BT.2020 (RGB)"},		\
+			      {10, "sRGB"},			\
+			      {11, "scRGB"},			\
+			      {12, "scRGBfixed"},		\
+			      {13, "AdobeRGB"},			\
+			      {14, "DCI-P3 (D65)"},		\
+			      {15, "DCI-P3 (Theater)"},		\
+			      {16, "Default RGB"},		\
+			      {17, "unexpected"})
+
+#define show_range(range)				\
+	__print_symbolic(range, { 0, "Full"},		\
+				{ 1, "Limited"},	\
+				{ 2, "unexpected"})
+
+TRACE_EVENT(iomfb_color_mode,
+	    TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 depth,
+		     u32 colorimetry, u32 eotf, u32 range, u32 pixel_enc),
+	    TP_ARGS(dcp, id, score, depth, colorimetry, eotf, range, pixel_enc),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, id)
+			     __field(u32, score)
+			     __field(u32, depth)
+			     __field(u32, colorimetry)
+			     __field(u32, eotf)
+			     __field(u32, range)
+			     __field(u32, pixel_enc)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->id = id;
+			   __entry->score = score;
+			   __entry->depth = depth;
+			   __entry->colorimetry = min_t(u32, colorimetry, 17U);
+			   __entry->eotf = min_t(u32, eotf, 4U);
+			   __entry->range = min_t(u32, range, 2U);
+			   __entry->pixel_enc = min_t(u32, pixel_enc, 15U);
+	    ),
+	    TP_printk("dcp=%llx, id=%u, score=%u,  depth=%u, colorimetry=%s, eotf=%s, range=%s, pixel_enc=%s",
+		      __entry->dcp,
+		      __entry->id,
+		      __entry->score,
+		      __entry->depth,
+		      show_colorimetry(__entry->colorimetry),
+		      show_eotf(__entry->eotf),
+		      show_range(__entry->range),
+		      show_encoding(__entry->pixel_enc)
+	    )
+);
+
+TRACE_EVENT(iomfb_timing_mode,
+	    TP_PROTO(struct apple_dcp *dcp, u32 id, u32 score, u32 width,
+		     u32 height, u32 clock, u32 color_mode),
+	    TP_ARGS(dcp, id, score, width, height, clock, color_mode),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, id)
+			     __field(u32, score)
+			     __field(u32, width)
+			     __field(u32, height)
+			     __field(u32, clock)
+			     __field(u32, color_mode)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->id = id;
+			   __entry->score = score;
+			   __entry->width = width;
+			   __entry->height = height;
+			   __entry->clock = clock;
+			   __entry->color_mode = color_mode;
+	    ),
+	    TP_printk("dcp=%llx, id=%u, score=%u,  %ux%u@%u.%u, color_mode=%u",
+		      __entry->dcp,
+		      __entry->id,
+		      __entry->score,
+		      __entry->width,
+		      __entry->height,
+		      __entry->clock >> 16,
+		      ((__entry->clock & 0xffff) * 1000) >> 16,
+		      __entry->color_mode
+	    )
+);
+
 #endif /* _TRACE_DCP_H */
 
 /* This part must be outside protection */

From 51addfa7af596e938e0815665d8fc18c5aca17c4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 14:27:13 +0100
Subject: [PATCH 0565/1027] gpu: drm: apple: Prefer SDR color modes

DCP best scored color mode might result in an HDR mode. As long as the
driver (and DRM) is not ready for HDR try to avoid such modes.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index a253ec62640ba5..678c0e42e10682 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -260,13 +260,14 @@ struct color_mode {
 	s64 score;
 };
 
-static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
+static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id)
 {
 	struct iterator outer_it;
 	int ret = 0;
-	s64 best_score = -1;
+	s64 best_score = -1, best_score_sdr = -1;
+	s64 best_id = -1, best_id_sdr = -1;
 
-	*best_id = -1;
+	*preferred_id = -1;
 
 	dcp_parse_foreach_in_array(handle, outer_it) {
 		struct iterator it;
@@ -310,12 +311,25 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *best_id)
 				       cmode.eotf, cmode.dynamic_range,
 				       cmode.pixel_encoding);
 
-		if (cmode.score > best_score) {
-			best_score = cmode.score;
-			*best_id = cmode.id;
+		if (cmode.eotf == 0) {
+			if (cmode.score > best_score_sdr) {
+				best_score_sdr = cmode.score;
+				best_id_sdr = cmode.id;
+			}
+		} else {
+			if (cmode.score > best_score) {
+				best_score = cmode.score;
+				best_id = cmode.id;
+			}
 		}
 	}
 
+	/* prefer SDR color modes as long as HDR is not supported */
+	if (best_score_sdr >= 0)
+		*preferred_id = best_id_sdr;
+	else if (best_score >= 0)
+		*preferred_id = best_id;
+
 	return 0;
 }
 

From 9cb752b2a507a20d0dc8203379b71c355c2ef163 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 17:54:40 +0100
Subject: [PATCH 0566/1027] gpu: drm: apple: Add
 IOMobileFramebufferAP::get_color_remap_mode

Probably not important but avoids an unnecessary difference compred to
macOS.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 19 ++++++++++++++++++-
 drivers/gpu/drm/apple/iomfb.h | 12 ++++++++++++
 2 files changed, 30 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 533835de89756c..258eeeecb23a4f 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -182,6 +182,7 @@ const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	DCP_METHOD("A410", dcpep_set_display_device),
 	DCP_METHOD("A411", dcpep_is_main_display),
 	DCP_METHOD("A412", dcpep_set_digital_out_mode),
+	DCP_METHOD("A426", iomfbep_get_color_remap_mode),
 	DCP_METHOD("A439", dcpep_set_parameter_dcp),
 	DCP_METHOD("A443", dcpep_create_default_fb),
 	DCP_METHOD("A447", dcpep_enable_disable_video_power_savings),
@@ -262,10 +263,21 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 			 cb, cookie);                                         \
 	}
 
+#define IOMFB_THUNK_INOUT(name, T_in, T_out)                                      \
+	static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data,   \
+			 dcp_callback_t cb, void *cookie)                         \
+	{                                                                         \
+		dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \
+			 data,  cb, cookie);                                      \
+	}
+
 DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
 DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
 DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
 
+IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req,
+		struct iomfb_get_color_remap_mode_resp);
+
 DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
 		struct dcp_swap_submit_resp);
 
@@ -1826,9 +1838,14 @@ static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
 
 static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
 {
+	struct iomfb_get_color_remap_mode_req color_remap =
+		(struct iomfb_get_color_remap_mode_req){
+			.mode = 6,
+		};
+
 	dev_info(dcp->dev, "DCP booted\n");
 
-	init_1(dcp, data, cookie);
+	iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie);
 }
 
 void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 386a84cfcc5cd1..083ebd4e0be33f 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -210,6 +210,7 @@ enum dcpep_method {
 	iomfbep_a131_pmu_service_matched,
 	iomfbep_a132_backlight_service_matched,
 	iomfbep_a358_vi_set_temperature_hint,
+	iomfbep_get_color_remap_mode,
 	dcpep_num_methods
 };
 
@@ -433,4 +434,15 @@ struct iomfb_property {
 	u32 value;
 } __packed;
 
+struct iomfb_get_color_remap_mode_req {
+	u32 mode;
+	u8 mode_null;
+	u8 padding[3];
+} __packed;
+
+struct iomfb_get_color_remap_mode_resp {
+	u32 mode;
+	u32 ret;
+} __packed;
+
 #endif

From af384fcda3ce7a0b3cd7384366dced90091f67d3 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 14:39:02 +0100
Subject: [PATCH 0567/1027] gpu: drm: apple: reenable support for
 {A,X}RGB2101010

Seems to work now with 'surface.colorspace = 12'.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 4 ++--
 drivers/gpu/drm/apple/iomfb.c     | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 9d4d45d98cfbe3..c18910e2d38346 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -133,8 +133,8 @@ static const struct drm_plane_funcs apple_plane_funcs = {
  * advertise formats without alpha.
  */
 static const u32 dcp_formats[] = {
-	// DRM_FORMAT_XRGB2101010,
-	// DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_XRGB2101010,
+	DRM_FORMAT_ARGB2101010,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XBGR8888,
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 258eeeecb23a4f..abe02d4e1e10bc 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1504,7 +1504,7 @@ static u8 drm_format_to_colorspace(u32 drm)
 
 	case DRM_FORMAT_ARGB2101010:
 	case DRM_FORMAT_XRGB2101010:
-		return 2;
+		return 12;
 	}
 
 	return 1;

From 19729ef387f415b012840321a698200361bbb7a5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 23:48:37 +0100
Subject: [PATCH 0568/1027] gpu: drm: apple: Add show_notch module parameter

Can be used on devices with camera notch to use the full display height
and thus show the notch.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index c62b814045f5a4..9fca0a79ac147e 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -5,6 +5,7 @@
 #include <linux/clk.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
+#include <linux/moduleparam.h>
 #include <linux/slab.h>
 #include <linux/string.h>
 #include <linux/of_device.h>
@@ -33,6 +34,10 @@
 
 #define DCP_BOOT_TIMEOUT msecs_to_jiffies(1000)
 
+static bool show_notch;
+module_param(show_notch, bool, 0644);
+MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch");
+
 /* HACK: moved here to avoid circular dependency between apple_drv and dcp */
 void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 {
@@ -402,8 +407,10 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	of_platform_default_populate(dev->of_node, NULL, dev);
 
-	ret = of_property_read_u32(dev->of_node, "apple,notch-height",
-				   &dcp->notch_height);
+	if (!show_notch)
+		ret = of_property_read_u32(dev->of_node, "apple,notch-height",
+					   &dcp->notch_height);
+
 	if (dcp->notch_height > MAX_NOTCH_HEIGHT)
 		dcp->notch_height = MAX_NOTCH_HEIGHT;
 	if (dcp->notch_height > 0)

From 00b312fa57dd02886dd0f6201064ddedb3ce2fe2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 13 Dec 2022 00:38:56 +0100
Subject: [PATCH 0569/1027] Revert "gpu: drm: apple: reenable support for
 {A,X}RGB2101010"

This reverts commit d39542179a8f5a5d2e80eebf77bc739857a1051c.

'w30r' is a wide gammut mode. As long as the display is SDR DCP will end
up displaying picture correctly but on HDR displays like the display in
the Macbook Pro 14"/16" (2021) or external displays with HDR EOTF the
picture has oversaturated / black colors.

The non-wide gammut 10-bit per component pixelformats "l10r" and "R10k"
 are not supported.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 4 ++--
 drivers/gpu/drm/apple/iomfb.c     | 2 +-
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index c18910e2d38346..9d4d45d98cfbe3 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -133,8 +133,8 @@ static const struct drm_plane_funcs apple_plane_funcs = {
  * advertise formats without alpha.
  */
 static const u32 dcp_formats[] = {
-	DRM_FORMAT_XRGB2101010,
-	DRM_FORMAT_ARGB2101010,
+	// DRM_FORMAT_XRGB2101010,
+	// DRM_FORMAT_ARGB2101010,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XBGR8888,
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index abe02d4e1e10bc..258eeeecb23a4f 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1504,7 +1504,7 @@ static u8 drm_format_to_colorspace(u32 drm)
 
 	case DRM_FORMAT_ARGB2101010:
 	case DRM_FORMAT_XRGB2101010:
-		return 12;
+		return 2;
 	}
 
 	return 1;

From 1d2461a7c157e2ed6cbdae8a564318fdb428cda2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 26 Dec 2022 00:26:05 +0900
Subject: [PATCH 0570/1027] drm/apple: Enable 10-bit mode & set colorspace to
 native

This works on both 8-bit and 10-bit modes without any weirdness, and
gives us the native colorspace without any conversion. Color correction
should probably be handled in software anyway.

However, we need to use surface 1 (at least on t600x), since 0 seems
stuck in bg-sRGB mode for some reason...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/apple_drv.c |  4 ++--
 drivers/gpu/drm/apple/iomfb.c     | 24 ++++--------------------
 drivers/gpu/drm/apple/iomfb.h     | 11 +++++++++++
 3 files changed, 17 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 9d4d45d98cfbe3..c18910e2d38346 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -133,8 +133,8 @@ static const struct drm_plane_funcs apple_plane_funcs = {
  * advertise formats without alpha.
  */
 static const u32 dcp_formats[] = {
-	// DRM_FORMAT_XRGB2101010,
-	// DRM_FORMAT_ARGB2101010,
+	DRM_FORMAT_XRGB2101010,
+	DRM_FORMAT_ARGB2101010,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XBGR8888,
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 258eeeecb23a4f..4a7d4809d53415 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1493,23 +1493,6 @@ static u32 drm_format_to_dcp(u32 drm)
 	return 0;
 }
 
-static u8 drm_format_to_colorspace(u32 drm)
-{
-	switch (drm) {
-	case DRM_FORMAT_XRGB8888:
-	case DRM_FORMAT_ARGB8888:
-	case DRM_FORMAT_XBGR8888:
-	case DRM_FORMAT_ABGR8888:
-		return 1;
-
-	case DRM_FORMAT_ARGB2101010:
-	case DRM_FORMAT_XRGB2101010:
-		return 2;
-	}
-
-	return 1;
-}
-
 int dcp_get_modes(struct drm_connector *connector)
 {
 	struct apple_connector *apple_connector = to_apple_connector(connector);
@@ -1631,7 +1614,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	for (l = 0; l < SWAP_SURFACES; l++)
 		req->surf_null[l] = true;
 
-	l = 0;
+	// Surface 0 has limitations at least on t600x.
+	l = 1;
 	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
 		struct drm_framebuffer *fb = new_state->fb;
 		struct drm_gem_dma_object *obj;
@@ -1698,8 +1682,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		req->surf[l] = (struct dcp_surface){
 			.opaque = opaque,
 			.format = drm_format_to_dcp(fb->format->format),
-			.xfer_func = 13,
-			.colorspace = drm_format_to_colorspace(fb->format->format),
+			.xfer_func = DCP_XFER_FUNC_SDR,
+			.colorspace = DCP_COLORSPACE_NATIVE,
 			.stride = fb->pitches[0],
 			.width = fb->width,
 			.height = fb->height,
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 083ebd4e0be33f..90b6668b075000 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -74,6 +74,17 @@ enum iomfb_property_id {
 #define SWAP_SURFACES 4
 #define MAX_PLANES 3
 
+enum dcp_colorspace {
+	DCP_COLORSPACE_BG_SRGB = 0,
+	DCP_COLORSPACE_BG_BT2020 = 9,
+	DCP_COLORSPACE_NATIVE = 12,
+};
+
+enum dcp_xfer_func {
+	DCP_XFER_FUNC_SDR = 13,
+	DCP_XFER_FUNC_HDR = 16,
+};
+
 struct dcp_iouserclient {
 	/* Handle for the IOUserClient. macOS sets this to a kernel VA. */
 	u64 handle;

From 9bb7faf36bb2aaf68dabb759ba399985238cf0d4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 31 Dec 2022 14:53:59 +0100
Subject: [PATCH 0571/1027] gpu: drm: apple: Clear all surfaces on startup

With "drm/apple: Enable 10-bit mode & set colorspace to native" kernel
log messages are shown in an Apple logo shaped region in the middle of
the display when using BGRA.
The field currently identified as "opaque" is mislabeled and has to be
investigated further.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 3 +++
 drivers/gpu/drm/apple/iomfb.c        | 9 +++++++++
 2 files changed, 12 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 18969ab18e8319..8ca2324cb038c5 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -141,6 +141,9 @@ struct apple_dcp {
 	/* eDP display without DP-HDMI conversion */
 	bool main_display;
 
+	/* clear all surfaces on init */
+	bool surfaces_cleared;
+
 	/* panel has a mini-LED backllight */
 	bool has_mini_led;
 	
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 4a7d4809d53415..6898ad4c65e82d 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1614,6 +1614,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	for (l = 0; l < SWAP_SURFACES; l++)
 		req->surf_null[l] = true;
 
+	/*
+	 * Clear all surfaces on startup. The boot framebuffer in surface 0
+	 * sticks around.
+	 */
+	if (!dcp->surfaces_cleared) {
+		req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF;
+		dcp->surfaces_cleared = true;
+	}
+
 	// Surface 0 has limitations at least on t600x.
 	l = 1;
 	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {

From 7799a00140952f783dc5d7cb380b34930e9f0e6b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 2 Jan 2023 23:37:51 +0100
Subject: [PATCH 0572/1027] drm/apple: Update swap handling

- opaque -> is_premultiplied
- swap_enabled BIT(31) seems to be update background with
  dcp_swap.bg_color
- add unused fields is_tearing_allowed, ycbcr_matrix, protection_opts,
  unk_num, unk_denom

Changes: use is_premultiplied only for XRGB8/XBGR8, Update background
only when necessary.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 37 +++++++++++++++++++++--------------
 drivers/gpu/drm/apple/iomfb.h | 23 ++++++++++------------
 2 files changed, 32 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 6898ad4c65e82d..e328769ee49fb4 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1063,9 +1063,9 @@ void dcp_poweroff(struct platform_device *pdev)
 	// clear surfaces
 	memset(&dcp->swap, 0, sizeof(dcp->swap));
 
-	dcp->swap.swap.swap_enabled = DCP_REMOVE_LAYERS | 0x7;
-	dcp->swap.swap.swap_completed = DCP_REMOVE_LAYERS | 0x7;
-	dcp->swap.swap.unk_10c = 0xFF000000;
+	dcp->swap.swap.swap_enabled =
+		dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF;
+	dcp->swap.swap.bg_color = 0xFF000000;
 
 	/*
 	 * Turn off the backlight. This matters because the DCP's idea of
@@ -1619,7 +1619,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	 * sticks around.
 	 */
 	if (!dcp->surfaces_cleared) {
-		req->swap.swap_enabled = DCP_REMOVE_LAYERS | 0xF;
+		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF;
+		req->swap.bg_color = 0xFF000000;
 		dcp->surfaces_cleared = true;
 	}
 
@@ -1629,7 +1630,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		struct drm_framebuffer *fb = new_state->fb;
 		struct drm_gem_dma_object *obj;
 		struct drm_rect src_rect;
-		bool opaque = false;
+		bool is_premultiplied = false;
 
 		/* skip planes not for this crtc */
 		if (old_state->crtc != crtc && new_state->crtc != crtc)
@@ -1660,18 +1661,21 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		}
 
 		if (!new_state->fb) {
-			if (old_state->fb)
-				req->swap.swap_enabled |= DCP_REMOVE_LAYERS;
-
 			l += 1;
 			continue;
 		}
 		req->surf_null[l] = false;
 		has_surface = 1;
 
-		if (!fb->format->has_alpha ||
-		    new_state->plane->type == DRM_PLANE_TYPE_PRIMARY)
-		    opaque = true;
+		/*
+		 * DCP doesn't support XBGR8 / XRGB8 natively. Blending as
+		 * pre-multiplied alpha with a black background can be used as
+		 * workaround for the bottommost plane.
+		 */
+		if (fb->format->format == DRM_FORMAT_XRGB8888 ||
+		    fb->format->format == DRM_FORMAT_XBGR8888)
+		    is_premultiplied = true;
+
 		drm_rect_fp_to_int(&src_rect, &new_state->src);
 
 		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
@@ -1689,7 +1693,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
 
 		req->surf[l] = (struct dcp_surface){
-			.opaque = opaque,
+			.is_premultiplied = is_premultiplied,
 			.format = drm_format_to_dcp(fb->format->format),
 			.xfer_func = DCP_XFER_FUNC_SDR,
 			.colorspace = DCP_COLORSPACE_NATIVE,
@@ -1710,9 +1714,6 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		l += 1;
 	}
 
-	/* These fields should be set together */
-	req->swap.swap_completed = req->swap.swap_enabled;
-
 	if (modeset) {
 		struct dcp_display_mode *mode;
 		struct dcp_wait_cookie *cookie;
@@ -1773,9 +1774,15 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 			return;
 		}
 
+		/* Set black background */
+		req->swap.swap_enabled |= IOMFB_SET_BACKGROUND;
+		req->swap.bg_color = 0xFF000000;
 		req->clear = 1;
 	}
 
+	/* These fields should be set together */
+	req->swap.swap_completed = req->swap.swap_enabled;
+
 	/* update brightness if changed */
 	if (dcp->brightness.update) {
 		req->swap.bl_unk = 1;
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 90b6668b075000..a7e9b62425b2c4 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -102,12 +102,9 @@ struct dcp_rect {
 } __packed;
 
 /*
- * Set in the swap_{enabled,completed} field to remove missing
- * layers. Without this flag, the DCP will assume missing layers have
- * not changed since the previous frame and will preserve their
- * content.
-  */
-#define DCP_REMOVE_LAYERS BIT(31)
+ * Update background color to struct dcp_swap.bg_color
+ */
+#define IOMFB_SET_BACKGROUND	BIT(31)
 
 struct dcp_swap {
 	u64 ts1;
@@ -126,7 +123,7 @@ struct dcp_swap {
 	u32 swap_enabled;
 	u32 swap_completed;
 
-	u32 unk_10c;
+	u32 bg_color;
 	u8 unk_110[0x1b8];
 	u32 unk_2c8;
 	u8 unk_2cc[0x14];
@@ -160,12 +157,12 @@ struct dcp_component_types {
 /* Information describing a surface */
 struct dcp_surface {
 	u8 is_tiled;
-	u8 unk_1;
-	u8 opaque; /** ignore alpha, also required YUV overlays */
+	u8 is_tearing_allowed;
+	u8 is_premultiplied;
 	u32 plane_cnt;
 	u32 plane_cnt2;
 	u32 format; /* DCP fourcc */
-	u32 unk_f;
+	u32 ycbcr_matrix;
 	u8 xfer_func;
 	u8 colorspace;
 	u32 stride;
@@ -176,8 +173,7 @@ struct dcp_surface {
 	u32 width;
 	u32 height;
 	u32 buf_size;
-	u32 unk_2d;
-	u32 unk_31;
+	u64 protection_opts;
 	u32 surface_id;
 	struct dcp_component_types comp_types[MAX_PLANES];
 	u64 has_comp;
@@ -185,7 +181,8 @@ struct dcp_surface {
 	u64 has_planes;
 	u32 compression_info[MAX_PLANES][13];
 	u64 has_compr_info;
-	u64 unk_1f5;
+	u32 unk_num;
+	u32 unk_denom;
 	u8 padding[7];
 } __packed;
 

From 9924139bf694b1a331c594f3249b11a5abbf6c9a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 22 Dec 2022 23:58:44 +0100
Subject: [PATCH 0573/1027] gpu: drm: apple: Use
 drm_aperture_remove_conflicting_framebuffers

This does not seem to be as racy as drm_aperture_remove_framebuffers()
and seems to reliably takes over simpledrm's device node.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 53 ++++++++++++++++++++++++++++---
 1 file changed, 48 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index c18910e2d38346..d067f16d68b56c 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -9,6 +9,7 @@
 
 #include <linux/module.h>
 #include <linux/dma-mapping.h>
+#include <linux/of_address.h>
 #include <linux/of_device.h>
 #include <linux/of_platform.h>
 
@@ -340,10 +341,45 @@ static int apple_probe_per_dcp(struct device *dev,
 	return drm_connector_attach_encoder(&connector->base, encoder);
 }
 
+static int apple_get_fb_resource(struct device *dev, const char *name,
+				 struct resource *fb_r)
+{
+	int idx, ret = -ENODEV;
+	struct device_node *node;
+
+	idx = of_property_match_string(dev->of_node, "memory-region-names", name);
+
+	node = of_parse_phandle(dev->of_node, "memory-region", idx);
+	if (!node) {
+		dev_err(dev, "reserved-memory node '%s' not found\n", name);
+		return -ENODEV;
+	}
+
+	if (!of_device_is_available(node)) {
+		dev_err(dev, "reserved-memory node '%s' is unavailable\n", name);
+		goto err;
+	}
+
+	if (!of_device_is_compatible(node, "framebuffer")) {
+		dev_err(dev, "reserved-memory node '%s' is incompatible\n",
+			node->full_name);
+		goto err;
+	}
+
+	ret = of_address_to_resource(node, 0, fb_r);
+
+err:
+	of_node_put(node);
+	return ret;
+}
+
+
 static int apple_platform_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
 	struct apple_drm_private *apple;
+	struct resource fb_r;
+	resource_size_t fb_size;
 	struct platform_device *dcp[MAX_COPROCESSORS];
 	int ret, nr_dcp, i;
 
@@ -381,6 +417,18 @@ static int apple_platform_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
+	ret = apple_get_fb_resource(dev, "framebuffer", &fb_r);
+	if (ret)
+		return ret;
+
+	fb_size = fb_r.end - fb_r.start + 1;
+	ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
+						false, &apple_drm_driver);
+	if (ret) {
+		dev_err(dev, "Failed remove fb: %d\n", ret);
+		return ret;
+	}
+
 	apple = devm_drm_dev_alloc(dev, &apple_drm_driver,
 				   struct apple_drm_private, drm);
 	if (IS_ERR(apple))
@@ -426,11 +474,6 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 	drm_mode_config_reset(&apple->drm);
 
-	// remove before registering our DRM device
-	ret = drm_aperture_remove_framebuffers(false, &apple_drm_driver);
-	if (ret)
-		return ret;
-
 	ret = drm_dev_register(&apple->drm, 0);
 	if (ret)
 		goto err_unload;

From 0296047946103e03869b2b223cc9118cd9e4c34c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 31 Dec 2022 15:27:07 +0100
Subject: [PATCH 0574/1027] drm/apple: Use drm_module_platform_driver

This check for the "nomodeset" kernel command line parameter in its
register method.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c    | 3 ++-
 drivers/gpu/drm/apple/dcp.c          | 3 ++-
 drivers/gpu/drm/apple/dummy-piodma.c | 4 +++-
 3 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index d067f16d68b56c..245ef70f1d077d 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -27,6 +27,7 @@
 #include <drm/drm_simple_kms_helper.h>
 #include <drm/drm_mode.h>
 #include <drm/drm_modeset_helper.h>
+#include <drm/drm_module.h>
 #include <drm/drm_of.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
@@ -536,7 +537,7 @@ static struct platform_driver apple_platform_driver = {
 	.remove		= apple_platform_remove,
 };
 
-module_platform_driver(apple_platform_driver);
+drm_module_platform_driver(apple_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION(DRIVER_DESC);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9fca0a79ac147e..fbbc79ab1e8c14 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -20,6 +20,7 @@
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_framebuffer.h>
+#include <drm/drm_module.h>
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
 
@@ -539,7 +540,7 @@ static struct platform_driver apple_platform_driver = {
 	},
 };
 
-module_platform_driver(apple_platform_driver);
+drm_module_platform_driver(apple_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION("Apple Display Controller DRM driver");
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
index 3d4454df4a25da..daf4327592a041 100644
--- a/drivers/gpu/drm/apple/dummy-piodma.c
+++ b/drivers/gpu/drm/apple/dummy-piodma.c
@@ -1,6 +1,8 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <drm/drm_module.h>
+
 #include <linux/module.h>
 #include <linux/dma-mapping.h>
 #include <linux/of_device.h>
@@ -24,7 +26,7 @@ static struct platform_driver dcp_piodma_platform_driver = {
 	},
 };
 
-module_platform_driver(dcp_piodma_platform_driver);
+drm_module_platform_driver(dcp_piodma_platform_driver);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim");

From 266101ce3f145e57b1a493a48c0c1bbe820e6920 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 2 Jan 2023 19:32:07 +0100
Subject: [PATCH 0575/1027] drm/apple: Allocate drm objects according to drm's
 expectations

drm's documentaion explicitly tells us not to use devm_kzalloc(). drm
device structures might out live the device when they are in use by
userspace while the device vanishes.
---
 drivers/gpu/drm/apple/apple_drv.c | 43 +++++++++++++++++++++----------
 drivers/gpu/drm/apple/dcp.h       |  7 +++++
 2 files changed, 37 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 245ef70f1d077d..4689ca4c8c8bb7 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -114,10 +114,16 @@ static const struct drm_plane_helper_funcs apple_plane_helper_funcs = {
 	.atomic_update	= apple_plane_atomic_update,
 };
 
+static void apple_plane_cleanup(struct drm_plane *plane)
+{
+	drm_plane_cleanup(plane);
+	kfree(plane);
+}
+
 static const struct drm_plane_funcs apple_plane_funcs = {
 	.update_plane		= drm_atomic_helper_update_plane,
 	.disable_plane		= drm_atomic_helper_disable_plane,
-	.destroy		= drm_plane_cleanup,
+	.destroy		= apple_plane_cleanup,
 	.reset			= drm_atomic_helper_plane_reset,
 	.atomic_duplicate_state = drm_atomic_helper_plane_duplicate_state,
 	.atomic_destroy_state	= drm_atomic_helper_plane_destroy_state,
@@ -155,7 +161,7 @@ static struct drm_plane *apple_plane_init(struct drm_device *dev,
 	int ret;
 	struct drm_plane *plane;
 
-	plane = devm_kzalloc(dev->dev, sizeof(*plane), GFP_KERNEL);
+	plane = kzalloc(sizeof(*plane), GFP_KERNEL);
 
 	ret = drm_universal_plane_init(dev, plane, possible_crtcs,
 				       &apple_plane_funcs,
@@ -248,11 +254,16 @@ static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state)
 	drm_atomic_helper_cleanup_planes(dev, old_state);
 }
 
+static void apple_crtc_cleanup(struct drm_crtc *crtc)
+{
+	drm_crtc_cleanup(crtc);
+	kfree(to_apple_crtc(crtc));
+}
 
 static const struct drm_crtc_funcs apple_crtc_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_crtc_destroy_state,
 	.atomic_duplicate_state = drm_atomic_helper_crtc_duplicate_state,
-	.destroy		= drm_crtc_cleanup,
+	.destroy		= apple_crtc_cleanup,
 	.page_flip		= drm_atomic_helper_page_flip,
 	.reset			= drm_atomic_helper_crtc_reset,
 	.set_config             = drm_atomic_helper_set_config,
@@ -268,9 +279,15 @@ static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = {
 	.atomic_commit_tail	= dcp_atomic_commit_tail,
 };
 
+static void appledrm_connector_cleanup(struct drm_connector *connector)
+{
+	drm_connector_cleanup(connector);
+	kfree(to_apple_connector(connector));
+}
+
 static const struct drm_connector_funcs apple_connector_funcs = {
 	.fill_modes		= drm_helper_probe_single_connector_modes,
-	.destroy		= drm_connector_cleanup,
+	.destroy		= appledrm_connector_cleanup,
 	.reset			= drm_atomic_helper_connector_reset,
 	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
@@ -298,7 +315,7 @@ static int apple_probe_per_dcp(struct device *dev,
 {
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
-	struct drm_encoder *encoder;
+	struct apple_encoder *enc;
 	struct drm_plane *primary;
 	int ret;
 
@@ -307,7 +324,7 @@ static int apple_probe_per_dcp(struct device *dev,
 	if (IS_ERR(primary))
 		return PTR_ERR(primary);
 
-	crtc = devm_kzalloc(dev, sizeof(*crtc), GFP_KERNEL);
+	crtc = kzalloc(sizeof(*crtc), GFP_KERNEL);
 	ret = drm_crtc_init_with_planes(drm, &crtc->base, primary, NULL,
 					&apple_crtc_funcs, NULL);
 	if (ret)
@@ -315,13 +332,13 @@ static int apple_probe_per_dcp(struct device *dev,
 
 	drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs);
 
-	encoder = devm_kzalloc(dev, sizeof(*encoder), GFP_KERNEL);
-	encoder->possible_crtcs = drm_crtc_mask(&crtc->base);
-	ret = drm_simple_encoder_init(drm, encoder, DRM_MODE_ENCODER_TMDS);
-	if (ret)
-		return ret;
+	enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base,
+					DRM_MODE_ENCODER_TMDS);
+	if (IS_ERR(enc))
+                return PTR_ERR(enc);
+	enc->base.possible_crtcs = drm_crtc_mask(&crtc->base);
 
-	connector = devm_kzalloc(dev, sizeof(*connector), GFP_KERNEL);
+	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
 	drm_connector_helper_add(&connector->base,
 				 &apple_connector_helper_funcs);
 
@@ -339,7 +356,7 @@ static int apple_probe_per_dcp(struct device *dev,
 	crtc->dcp = dcp;
 	dcp_link(dcp, crtc, connector);
 
-	return drm_connector_attach_encoder(&connector->base, encoder);
+	return drm_connector_attach_encoder(&connector->base, &enc->base);
 }
 
 static int apple_get_fb_resource(struct device *dev, const char *name,
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index fb4397e7b390fe..f4476f4acaf265 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -5,6 +5,7 @@
 #define __APPLE_DCP_H__
 
 #include <drm/drm_atomic.h>
+#include <drm/drm_encoder.h>
 #include <drm/drm_fourcc.h>
 
 #include "dcp-internal.h"
@@ -35,6 +36,12 @@ struct apple_connector {
 
 #define to_apple_connector(x) container_of(x, struct apple_connector, base)
 
+struct apple_encoder {
+	struct drm_encoder base;
+};
+
+#define to_apple_encoder(x) container_of(x, struct apple_encoder, base)
+
 void dcp_poweroff(struct platform_device *pdev);
 void dcp_poweron(struct platform_device *pdev);
 int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state);

From 94c21561cbbc7419ce0ec481af4317a0056a6bc5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 29 Dec 2022 21:05:40 +0100
Subject: [PATCH 0576/1027] gpu: drm: apple: Use components to avoid deferred
 probing

There was a report of a race between DRM device registration (and
removal of the simpledrm device) and GDM startup.

The component based device binding ensures that all necessary devices
are bind in the probe method of the last missing component.

Technically the piodma-mapper should be a component of dcp but since it
is only used for its iommu it can be a component of the display
subsystem.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c    | 201 +++++++++++++++++++--------
 drivers/gpu/drm/apple/dcp-internal.h |   1 -
 drivers/gpu/drm/apple/dcp.c          | 103 ++++++++------
 drivers/gpu/drm/apple/dummy-piodma.c |  39 +++++-
 4 files changed, 246 insertions(+), 98 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 4689ca4c8c8bb7..ec7cf40328cafe 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -7,8 +7,9 @@
  * Copyright (C) 2014 Endless Mobile
  */
 
-#include <linux/module.h>
+#include <linux/component.h>
 #include <linux/dma-mapping.h>
+#include <linux/module.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
 #include <linux/of_platform.h>
@@ -391,46 +392,55 @@ static int apple_get_fb_resource(struct device *dev, const char *name,
 	return ret;
 }
 
+static const struct of_device_id apple_dcp_id_tbl[] = {
+	{ .compatible = "apple,dcp" },
+	{},
+};
 
-static int apple_platform_probe(struct platform_device *pdev)
+static int apple_drm_init_dcp(struct device *dev)
 {
-	struct device *dev = &pdev->dev;
-	struct apple_drm_private *apple;
-	struct resource fb_r;
-	resource_size_t fb_size;
+	struct apple_drm_private *apple = dev_get_drvdata(dev);
 	struct platform_device *dcp[MAX_COPROCESSORS];
-	int ret, nr_dcp, i;
-
-	for (nr_dcp = 0; nr_dcp < MAX_COPROCESSORS; ++nr_dcp) {
-		struct device_node *np;
-		struct device_link *dcp_link;
+	struct device_node *np;
+	int ret, num_dcp = 0;
 
-		np = of_parse_phandle(dev->of_node, "apple,coprocessors",
-				      nr_dcp);
-
-		if (!np)
-			break;
+	for_each_matching_node(np, apple_dcp_id_tbl) {
+		if (!of_device_is_available(np)) {
+			of_node_put(np);
+			continue;
+		}
 
-		dcp[nr_dcp] = of_find_device_by_node(np);
+		dcp[num_dcp] = of_find_device_by_node(np);
+		of_node_put(np);
+		if (!dcp[num_dcp])
+			continue;
 
-		if (!dcp[nr_dcp])
-			return -ENODEV;
+		ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp],
+					  num_dcp);
+		if (ret)
+			continue;
 
-		dcp_link = device_link_add(dev, &dcp[nr_dcp]->dev,
-					   DL_FLAG_AUTOREMOVE_CONSUMER);
-		if (!dcp_link) {
-			dev_err(dev, "Failed to link to DCP %d device", nr_dcp);
-			return -EINVAL;
-		}
+		ret = dcp_start(dcp[num_dcp]);
+		if (ret)
+			continue;
 
-		if (dcp_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
-			return -EPROBE_DEFER;
+		num_dcp++;
 	}
 
-	/* Need at least 1 DCP for a display subsystem */
-	if (nr_dcp < 1)
+	if (num_dcp < 1)
 		return -ENODEV;
 
+
+	return 0;
+}
+
+static int apple_drm_init(struct device *dev)
+{
+	struct apple_drm_private *apple;
+	struct resource fb_r;
+	resource_size_t fb_size;
+	int ret;
+
 	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
 	if (ret)
 		return ret;
@@ -439,14 +449,6 @@ static int apple_platform_probe(struct platform_device *pdev)
 	if (ret)
 		return ret;
 
-	fb_size = fb_r.end - fb_r.start + 1;
-	ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
-						false, &apple_drm_driver);
-	if (ret) {
-		dev_err(dev, "Failed remove fb: %d\n", ret);
-		return ret;
-	}
-
 	apple = devm_drm_dev_alloc(dev, &apple_drm_driver,
 				   struct apple_drm_private, drm);
 	if (IS_ERR(apple))
@@ -454,9 +456,21 @@ static int apple_platform_probe(struct platform_device *pdev)
 
 	dev_set_drvdata(dev, apple);
 
+	ret = component_bind_all(dev, apple);
+	if (ret)
+		return ret;
+
+	fb_size = fb_r.end - fb_r.start + 1;
+	ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
+						false, &apple_drm_driver);
+	if (ret) {
+		dev_err(dev, "Failed remove fb: %d\n", ret);
+		goto err_unbind;
+	}
+
 	ret = drmm_mode_config_init(&apple->drm);
 	if (ret)
-		goto err_unload;
+		goto err_unbind;
 
 	/*
 	 * IOMFB::UPPipeDCP_H13P::verify_surfaces produces the error "plane
@@ -478,42 +492,112 @@ static int apple_platform_probe(struct platform_device *pdev)
 	apple->drm.mode_config.funcs = &apple_mode_config_funcs;
 	apple->drm.mode_config.helper_private = &apple_mode_config_helpers;
 
-	for (i = 0; i < nr_dcp; ++i) {
-		ret = apple_probe_per_dcp(dev, &apple->drm, dcp[i], i);
-
-		if (ret)
-			goto err_unload;
-
-		ret = dcp_start(dcp[i]);
-
-		if (ret)
-			goto err_unload;
-	}
+	ret = apple_drm_init_dcp(dev);
+	if (ret)
+		goto err_unbind;
 
 	drm_mode_config_reset(&apple->drm);
 
 	ret = drm_dev_register(&apple->drm, 0);
 	if (ret)
-		goto err_unload;
+		goto err_unbind;
 
 	drm_fbdev_dma_setup(&apple->drm, 32);
 
 	return 0;
 
-err_unload:
-	drm_dev_put(&apple->drm);
+err_unbind:
+	component_unbind_all(dev, NULL);
 	return ret;
 }
 
-static int apple_platform_remove(struct platform_device *pdev)
+static void apple_drm_uninit(struct device *dev)
 {
-	struct apple_drm_private *apple = platform_get_drvdata(pdev);
+	struct apple_drm_private *apple = dev_get_drvdata(dev);
 
 	drm_dev_unregister(&apple->drm);
+	drm_atomic_helper_shutdown(&apple->drm);
+
+	component_unbind_all(dev, NULL);
+
+	dev_set_drvdata(dev, NULL);
+}
+
+static int apple_drm_bind(struct device *dev)
+{
+	return apple_drm_init(dev);
+}
+
+static void apple_drm_unbind(struct device *dev)
+{
+	apple_drm_uninit(dev);
+}
+
+const struct component_master_ops apple_drm_ops = {
+	.bind	= apple_drm_bind,
+	.unbind	= apple_drm_unbind,
+};
+
+static const struct of_device_id apple_component_id_tbl[] = {
+	{ .compatible = "apple,dcp-piodma" },
+	{},
+};
+
+static int add_display_components(struct device *dev,
+				  struct component_match **matchptr)
+{
+	struct device_node *np;
+
+	for_each_matching_node(np, apple_component_id_tbl) {
+		if (of_device_is_available(np))
+			drm_of_component_match_add(dev, matchptr,
+						   component_compare_of, np);
+		of_node_put(np);
+	}
 
 	return 0;
 }
 
+static int add_dcp_components(struct device *dev,
+			      struct component_match **matchptr)
+{
+	struct device_node *np;
+	int num = 0;
+
+	for_each_matching_node(np, apple_dcp_id_tbl) {
+		if (of_device_is_available(np)) {
+			drm_of_component_match_add(dev, matchptr,
+						   component_compare_of, np);
+			num++;
+		}
+		of_node_put(np);
+	}
+
+	return num;
+}
+
+static int apple_platform_probe(struct platform_device *pdev)
+{
+	struct device *mdev = &pdev->dev;
+	struct component_match *match = NULL;
+	int num_dcp;
+
+	/* add PIODMA mapper components */
+	add_display_components(mdev, &match);
+
+	/* add DCP components, handle less than 1 as probe error */
+	num_dcp = add_dcp_components(mdev, &match);
+	if (num_dcp < 1)
+		return -ENODEV;
+
+	return component_master_add_with_match(mdev, &apple_drm_ops, match);
+}
+
+static void apple_platform_remove(struct platform_device *pdev)
+{
+	component_master_del(&pdev->dev, &apple_drm_ops);
+}
+
 static const struct of_device_id of_match[] = {
 	{ .compatible = "apple,display-subsystem" },
 	{}
@@ -525,14 +609,19 @@ static int apple_platform_suspend(struct device *dev)
 {
 	struct apple_drm_private *apple = dev_get_drvdata(dev);
 
-	return drm_mode_config_helper_suspend(&apple->drm);
+	if (apple)
+		return drm_mode_config_helper_suspend(&apple->drm);
+
+	return 0;
 }
 
 static int apple_platform_resume(struct device *dev)
 {
 	struct apple_drm_private *apple = dev_get_drvdata(dev);
 
-	drm_mode_config_helper_resume(&apple->drm);
+	if (apple)
+		drm_mode_config_helper_resume(&apple->drm);
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 8ca2324cb038c5..ed11d38f80bb59 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -84,7 +84,6 @@ struct dcp_brightness {
 struct apple_dcp {
 	struct device *dev;
 	struct platform_device *piodma;
-	struct device_link *piodma_link;
 	struct apple_rtkit *rtk;
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index fbbc79ab1e8c14..6ee40c9bb600cc 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1,21 +1,22 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <linux/align.h>
 #include <linux/bitmap.h>
 #include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/component.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
-#include <linux/slab.h>
-#include <linux/string.h>
 #include <linux/of_device.h>
-#include <linux/delay.h>
-#include <linux/dma-mapping.h>
-#include <linux/iommu.h>
-#include <linux/align.h>
+#include <linux/slab.h>
 #include <linux/soc/apple/rtkit.h>
-#include <linux/completion.h>
-#include "linux/workqueue.h"
+#include <linux/string.h>
+#include <linux/workqueue.h>
 
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
@@ -376,33 +377,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
 	return DCP_FIRMWARE_UNKNOWN;
 }
 
-static int dcp_platform_probe(struct platform_device *pdev)
+static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 {
-	struct device *dev = &pdev->dev;
 	struct device_node *panel_np;
-	struct apple_dcp *dcp;
-	enum dcp_firmware_version fw_compat;
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
 	u32 cpu_ctrl;
 	int ret;
 
-	fw_compat = dcp_check_firmware_version(dev);
-	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
-		return -ENODEV;
-
-	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
-	if (!dcp)
-		return -ENOMEM;
-
-	dcp->fw_compat = fw_compat;
-
-	platform_set_drvdata(pdev, dcp);
-	dcp->dev = dev;
-
 	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
 	if (ret)
 		return ret;
 
-	dcp->coproc_reg = devm_platform_ioremap_resource_byname(pdev, "coproc");
+	dcp->coproc_reg = devm_platform_ioremap_resource_byname(to_platform_device(dev), "coproc");
 	if (IS_ERR(dcp->coproc_reg))
 		return PTR_ERR(dcp->coproc_reg);
 
@@ -453,22 +439,17 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	else
 		dcp->connector_type = DRM_MODE_CONNECTOR_Unknown;
 
+	/*
+	 * Components do not ensure the bind order of sub components but
+	 * the piodma device is only used for its iommu. The iommu is fully
+	 * initialized by the time dcp_piodma_probe() calls component_add().
+	 */
 	dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper");
 	if (!dcp->piodma) {
 		dev_err(dev, "failed to find piodma\n");
 		return -ENODEV;
 	}
 
-	dcp->piodma_link = device_link_add(dev, &dcp->piodma->dev,
-					   DL_FLAG_AUTOREMOVE_CONSUMER);
-	if (!dcp->piodma_link) {
-		dev_err(dev, "Failed to link to piodma device");
-		return -EINVAL;
-	}
-
-	if (dcp->piodma_link->supplier->links.status != DL_DEV_DRIVER_BOUND)
-		return -EPROBE_DEFER;
-
 	ret = dcp_get_disp_regs(dcp);
 	if (ret) {
 		dev_err(dev, "failed to find display registers\n");
@@ -517,12 +498,55 @@ static int dcp_platform_probe(struct platform_device *pdev)
  * We need to shutdown DCP before tearing down the display subsystem. Otherwise
  * the DCP will crash and briefly flash a green screen of death.
  */
-static void dcp_platform_shutdown(struct platform_device *pdev)
+static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 {
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
 
-	if (dcp->shmem)
+	if (dcp && dcp->shmem)
 		iomfb_shutdown(dcp);
+
+	platform_device_put(dcp->piodma);
+	dcp->piodma = NULL;
+
+	devm_clk_put(dev, dcp->clk);
+	dcp->clk = NULL;
+}
+
+static const struct component_ops dcp_comp_ops = {
+	.bind	= dcp_comp_bind,
+	.unbind	= dcp_comp_unbind,
+};
+
+static int dcp_platform_probe(struct platform_device *pdev)
+{
+	enum dcp_firmware_version fw_compat;
+	struct device *dev = &pdev->dev;
+	struct apple_dcp *dcp;
+
+	fw_compat = dcp_check_firmware_version(dev);
+	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
+		return -ENODEV;
+
+	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
+	if (!dcp)
+		return -ENOMEM;
+
+	dcp->fw_compat = fw_compat;
+	dcp->dev = dev;
+
+	platform_set_drvdata(pdev, dcp);
+
+	return component_add(&pdev->dev, &dcp_comp_ops);
+}
+
+static void dcp_platform_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcp_comp_ops);
+}
+
+static void dcp_platform_shutdown(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcp_comp_ops);
 }
 
 static const struct of_device_id of_match[] = {
@@ -533,6 +557,7 @@ MODULE_DEVICE_TABLE(of, of_match);
 
 static struct platform_driver apple_platform_driver = {
 	.probe		= dcp_platform_probe,
+	.remove		= dcp_platform_remove,
 	.shutdown	= dcp_platform_shutdown,
 	.driver	= {
 		.name = "apple-dcp",
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
index daf4327592a041..ec5d4fecf356c0 100644
--- a/drivers/gpu/drm/apple/dummy-piodma.c
+++ b/drivers/gpu/drm/apple/dummy-piodma.c
@@ -3,13 +3,46 @@
 
 #include <drm/drm_module.h>
 
-#include <linux/module.h>
+#include <linux/component.h>
 #include <linux/dma-mapping.h>
+#include <linux/module.h>
 #include <linux/of_device.h>
 
+static int dcp_piodma_comp_bind(struct device *dev, struct device *main,
+				void *data)
+{
+	return 0;
+}
+
+static void dcp_piodma_comp_unbind(struct device *dev, struct device *main,
+				   void *data)
+{
+	/* nothing to do */
+}
+
+static const struct component_ops dcp_piodma_comp_ops = {
+	.bind	= dcp_piodma_comp_bind,
+	.unbind	= dcp_piodma_comp_unbind,
+};
 static int dcp_piodma_probe(struct platform_device *pdev)
 {
-	return dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
+	int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
+	if (ret)
+		return ret;
+
+	return component_add(&pdev->dev, &dcp_piodma_comp_ops);
+}
+
+static int dcp_piodma_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcp_piodma_comp_ops);
+
+	return 0;
+}
+
+static void dcp_piodma_shutdown(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcp_piodma_comp_ops);
 }
 
 static const struct of_device_id of_match[] = {
@@ -20,6 +53,8 @@ MODULE_DEVICE_TABLE(of, of_match);
 
 static struct platform_driver dcp_piodma_platform_driver = {
 	.probe		= dcp_piodma_probe,
+	.remove		= dcp_piodma_remove,
+	.shutdown	= dcp_piodma_shutdown,
 	.driver	= {
 		.name = "apple,dcp-piodma",
 		.of_match_table	= of_match,

From 638ca87fb38e0a2c28c3ef1919ce133317ebd81f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 29 Dec 2022 21:38:44 +0100
Subject: [PATCH 0577/1027] gpu: drm: apple: Wait for iomfb initialization

Avoids "[drm] Cannot find any crtc or sizes" during fbdev initialization
if a display is connected.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c    | 19 ++++++++++++++++++-
 drivers/gpu/drm/apple/dcp-internal.h |  3 +++
 drivers/gpu/drm/apple/dcp.c          | 26 ++++++++++++++++++++++++++
 drivers/gpu/drm/apple/dcp.h          |  1 +
 drivers/gpu/drm/apple/iomfb.c        |  5 +++--
 5 files changed, 51 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index ec7cf40328cafe..7227d475cab03e 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -9,6 +9,7 @@
 
 #include <linux/component.h>
 #include <linux/dma-mapping.h>
+#include <linux/jiffies.h>
 #include <linux/module.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
@@ -402,7 +403,8 @@ static int apple_drm_init_dcp(struct device *dev)
 	struct apple_drm_private *apple = dev_get_drvdata(dev);
 	struct platform_device *dcp[MAX_COPROCESSORS];
 	struct device_node *np;
-	int ret, num_dcp = 0;
+	u64 timeout;
+	int i, ret, num_dcp = 0;
 
 	for_each_matching_node(np, apple_dcp_id_tbl) {
 		if (!of_device_is_available(np)) {
@@ -430,6 +432,21 @@ static int apple_drm_init_dcp(struct device *dev)
 	if (num_dcp < 1)
 		return -ENODEV;
 
+	timeout = get_jiffies_64() + msecs_to_jiffies(500);
+
+	for (i = 0; i < num_dcp; ++i) {
+		u64 jiffies = get_jiffies_64();
+		u64 wait = time_after_eq64(jiffies, timeout) ?
+				   0 :
+				   timeout - jiffies;
+		ret = dcp_wait_ready(dcp[i], wait);
+		/* There is nothing we can do if a dcp/dcpext does not boot
+		 * (successfully). Ignoring it should not do any harm now.
+		 * Needs to reevaluated whenn adding dcpext support.
+		 */
+		if (ret)
+			dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret);
+	}
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index ed11d38f80bb59..85e9137bbdf10e 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -134,6 +134,9 @@ struct apple_dcp {
 	bool valid_mode;
 	struct dcp_set_digital_out_mode_req mode;
 
+	/* completion for active turning true */
+	struct completion start_done;
+
 	/* Is the DCP booted? */
 	bool active;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6ee40c9bb600cc..9ad50c9812525c 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -116,6 +116,7 @@ static void dcp_rtk_crashed(void *cookie)
 		dcp->connector->connected = 0;
 		schedule_work(&dcp->connector->hotplug_wq);
 	}
+	complete(&dcp->start_done);
 }
 
 static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
@@ -247,6 +248,8 @@ int dcp_start(struct platform_device *pdev)
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
 	int ret;
 
+	init_completion(&dcp->start_done);
+
 	/* start RTKit endpoints */
 	ret = iomfb_start_rtkit(dcp);
 	if (ret)
@@ -256,6 +259,29 @@ int dcp_start(struct platform_device *pdev)
 }
 EXPORT_SYMBOL(dcp_start);
 
+int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	int ret;
+
+	if (dcp->crashed)
+		return -ENODEV;
+	if (dcp->active)
+		return 0;
+	if (timeout <= 0)
+		return -ETIMEDOUT;
+
+	ret = wait_for_completion_timeout(&dcp->start_done, timeout);
+	if (ret < 0)
+		return ret;
+
+	if (dcp->crashed)
+		return -ENODEV;
+
+	return dcp->active ? 0 : -ETIMEDOUT;
+}
+EXPORT_SYMBOL(dcp_wait_ready);
+
 static void dcp_work_register_backlight(struct work_struct *work)
 {
 	int ret;
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index f4476f4acaf265..2011d27e809d53 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -49,6 +49,7 @@ int dcp_get_connector_type(struct platform_device *pdev);
 void dcp_link(struct platform_device *pdev, struct apple_crtc *apple,
 	      struct apple_connector *connector);
 int dcp_start(struct platform_device *pdev);
+int dcp_wait_ready(struct platform_device *pdev, u64 timeout);
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state);
 bool dcp_is_initialized(struct platform_device *pdev);
 void apple_crtc_vblank(struct apple_crtc *apple);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index e328769ee49fb4..4382fe12b0592b 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1811,13 +1811,14 @@ static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
 
 	dcp->main_display = result != 0;
 
-	dcp->active = true;
-
 	connector = dcp->connector;
 	if (connector) {
 		connector->connected = dcp->nr_modes > 0;
 		schedule_work(&connector->hotplug_wq);
 	}
+
+	dcp->active = true;
+	complete(&dcp->start_done);
 }
 
 static void init_3(struct apple_dcp *dcp, void *out, void *cookie)

From b91c24f5c166f64d16d69057d29a36a22141a638 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 8 Jan 2023 21:24:51 +0100
Subject: [PATCH 0578/1027] drm/apple: simplify IOMFB_THUNK_INOUT

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 18 ++++++++++--------
 1 file changed, 10 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 4382fe12b0592b..640e869386d44a 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -263,20 +263,22 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 			 cb, cookie);                                         \
 	}
 
-#define IOMFB_THUNK_INOUT(name, T_in, T_out)                                      \
-	static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, T_in *data,   \
-			 dcp_callback_t cb, void *cookie)                         \
-	{                                                                         \
-		dcp_push(dcp, oob, iomfbep_ ## name, sizeof(T_in), sizeof(T_out), \
-			 data,  cb, cookie);                                      \
+#define IOMFB_THUNK_INOUT(name)                                     \
+	static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \
+			struct iomfb_ ## name ## _req *data,        \
+			dcp_callback_t cb, void *cookie)            \
+	{                                                           \
+		dcp_push(dcp, oob, iomfbep_ ## name,                \
+			 sizeof(struct iomfb_ ## name ## _req),     \
+			 sizeof(struct iomfb_ ## name ## _resp),    \
+			 data,  cb, cookie);                        \
 	}
 
 DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
 DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
 DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
 
-IOMFB_THUNK_INOUT(get_color_remap_mode, struct iomfb_get_color_remap_mode_req,
-		struct iomfb_get_color_remap_mode_resp);
+IOMFB_THUNK_INOUT(get_color_remap_mode);
 
 DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
 		struct dcp_swap_submit_resp);

From fc345e410d3ff4fa72d97ff363406e78c5135d3d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 15 Feb 2023 20:57:56 +0900
Subject: [PATCH 0579/1027] drm/apple: Fix parse_string() memory leaks

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/parser.c | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 678c0e42e10682..1d85d76ae0e16f 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -243,6 +243,9 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim)
 		else
 			skip(it.handle);
 
+		if (!IS_ERR_OR_NULL(key))
+			kfree(key);
+
 		if (ret)
 			return ret;
 	}
@@ -298,6 +301,9 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id)
 			else
 				skip(it.handle);
 
+			if (!IS_ERR_OR_NULL(key))
+				kfree(key);
+
 			if (ret)
 				return ret;
 		}
@@ -381,6 +387,9 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 		else
 			skip(it.handle);
 
+		if (!IS_ERR_OR_NULL(key))
+			kfree(key);
+
 		if (ret)
 			return ret;
 	}
@@ -511,6 +520,9 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 		else
 			skip(it.handle);
 
+		if (!IS_ERR_OR_NULL(key))
+			kfree(key);
+
 		if (ret)
 			return ret;
 	}

From 24bf7665d5b754be949b3f2422e2ddec93a9c9fa Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 15 Feb 2023 20:57:47 +0900
Subject: [PATCH 0580/1027] drm/apple: Fix bad error return

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/parser.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 1d85d76ae0e16f..5665c2ba19682f 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -229,7 +229,7 @@ static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim)
 		char *key = parse_string(it.handle);
 
 		if (IS_ERR(key))
-			ret = PTR_ERR(handle);
+			ret = PTR_ERR(key);
 		else if (!strcmp(key, "Active"))
 			ret = parse_int(it.handle, &dim->active);
 		else if (!strcmp(key, "Total"))

From 22e7a65a7d73a24cb11dac1b5bc498ed1ace936e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 22 Jan 2023 20:16:28 +0100
Subject: [PATCH 0581/1027] drm/apple: Set backlight level indirectly if no
 mode is set

Fixes following warning when systemd-backlight restores the backlight
level on boot before a mode is set:

Call trace:
  drm_atomic_helper_crtc_duplicate_state+0x58/0x74
  drm_atomic_get_crtc_state+0x84/0x120
  dcp_set_brightness+0xd8/0x21c [apple_dcp]
  backlight_device_set_brightness+0x78/0x130
  ...

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp_backlight.c | 17 +++++++++++++----
 1 file changed, 13 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index 42b1097eaa0180..45827fe6041a27 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -165,21 +165,30 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc,
 
 static int dcp_set_brightness(struct backlight_device *bd)
 {
-	int ret;
+	int ret = 0;
 	struct apple_dcp *dcp = bl_get_data(bd);
 	struct drm_modeset_acquire_ctx ctx;
 
 	if (bd->props.state & BL_CORE_SUSPENDED)
 		return 0;
 
-	if (!dcp->crtc)
-		return -EAGAIN;
+	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
 
 	dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness);
 	dcp->brightness.update = true;
 
-	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
+	/*
+	 * Do not actively try to change brightness if no mode is set.
+	 * TODO: should this be reflected the in backlight's power property?
+	 *       defer this hopefully until it becomes irrelevant due to proper
+	 *       drm integrated backlight handling
+	 */
+	if (!dcp->valid_mode)
+		goto out;
+
 	ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx);
+
+out:
 	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
 
 	return ret;

From 5c13054a61b746ac6db04855354245352b01ce35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 22 Jan 2023 19:43:35 +0100
Subject: [PATCH 0582/1027] drm/apple: Use backlight_get_brightness()

Backlight drivers are expected to use this instead of accessing
backlight properties.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp_backlight.c | 6 ++----
 1 file changed, 2 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index 45827fe6041a27..d063ecd7ad2068 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -168,13 +168,11 @@ static int dcp_set_brightness(struct backlight_device *bd)
 	int ret = 0;
 	struct apple_dcp *dcp = bl_get_data(bd);
 	struct drm_modeset_acquire_ctx ctx;
-
-	if (bd->props.state & BL_CORE_SUSPENDED)
-		return 0;
+	int brightness = backlight_get_brightness(bd);
 
 	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
 
-	dcp->brightness.dac = calculate_dac(dcp, bd->props.brightness);
+	dcp->brightness.dac = calculate_dac(dcp, brightness);
 	dcp->brightness.update = true;
 
 	/*

From e9aac1395e9b54a39f4ba4e96002c497199fd554 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 26 Mar 2023 15:56:03 +0200
Subject: [PATCH 0583/1027] drm/apple: Move panel options to its own sub-struct

Fixes overwriting the panel's physical dimensions on poweroff/sleep.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 16 +++++++++++++---
 drivers/gpu/drm/apple/dcp.c          | 21 ++++++++++++++-------
 drivers/gpu/drm/apple/iomfb.c        |  2 +-
 3 files changed, 28 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 85e9137bbdf10e..b34294816ec1ff 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -80,6 +80,16 @@ struct dcp_brightness {
 	bool update;
 };
 
+/** laptop/AiO integrated panel parameters from DT */
+struct dcp_panel {
+	/// panel width in millimeter
+	int width_mm;
+	/// panel height in millimeter
+	int height_mm;
+	/// panel has a mini-LED backllight
+	bool has_mini_led;
+};
+
 /* TODO: move IOMFB members to its own struct */
 struct apple_dcp {
 	struct device *dev;
@@ -146,9 +156,6 @@ struct apple_dcp {
 	/* clear all surfaces on init */
 	bool surfaces_cleared;
 
-	/* panel has a mini-LED backllight */
-	bool has_mini_led;
-	
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
@@ -173,6 +180,9 @@ struct apple_dcp {
 	/* Workqueue for updating the initial initial brightness */
 	struct work_struct bl_register_wq;
 	struct mutex bl_register_mutex;
+
+	/* integrated panel if present */
+	struct dcp_panel panel;
 };
 
 int dcp_backlight_register(struct apple_dcp *dcp);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9ad50c9812525c..23847752903f90 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -56,14 +56,21 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 void dcp_set_dimensions(struct apple_dcp *dcp)
 {
 	int i;
+	int width_mm = dcp->width_mm;
+	int height_mm = dcp->height_mm;
+
+	if (width_mm == 0 || height_mm == 0) {
+		width_mm = dcp->panel.width_mm;
+		height_mm = dcp->panel.height_mm;
+	}
 
 	/* Set the connector info */
 	if (dcp->connector) {
 		struct drm_connector *connector = &dcp->connector->base;
 
 		mutex_lock(&connector->dev->mode_config.mutex);
-		connector->display_info.width_mm = dcp->width_mm;
-		connector->display_info.height_mm = dcp->height_mm;
+		connector->display_info.width_mm = width_mm;
+		connector->display_info.height_mm = height_mm;
 		mutex_unlock(&connector->dev->mode_config.mutex);
 	}
 
@@ -73,8 +80,8 @@ void dcp_set_dimensions(struct apple_dcp *dcp)
 	 * DisplayAttributes, and TimingElements may be sent first
 	 */
 	for (i = 0; i < dcp->nr_modes; ++i) {
-		dcp->modes[i].mode.width_mm = dcp->width_mm;
-		dcp->modes[i].mode.height_mm = dcp->height_mm;
+		dcp->modes[i].mode.width_mm = width_mm;
+		dcp->modes[i].mode.height_mm = height_mm;
 	}
 }
 
@@ -433,7 +440,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	dcp->brightness.scale = 65536;
 	panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led");
 	if (panel_np)
-		dcp->has_mini_led = true;
+		dcp->panel.has_mini_led = true;
 	else
 		panel_np = of_get_compatible_child(dev->of_node, "apple,panel");
 
@@ -447,10 +454,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 				dev_err(dev, "Missing property 'apple,max-brightness'\n");
 		}
 
-		of_property_read_u32(panel_np, "width-mm", &dcp->width_mm);
+		of_property_read_u32(panel_np, "width-mm", &dcp->panel.width_mm);
 		/* use adjusted height as long as the notch is hidden */
 		of_property_read_u32(panel_np, height_prop[!dcp->notch_height],
-				     &dcp->height_mm);
+				     &dcp->panel.height_mm);
 
 		of_node_put(panel_np);
 		dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 640e869386d44a..ee88261661f318 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -461,7 +461,7 @@ dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
 	    .value = 0
 	};
 
-	if (dcp->has_mini_led &&
+	if (dcp->panel.has_mini_led &&
 	    memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
 	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
 		/*

From 232850f0f1a4878ae86efdf836d4b4f2d303ff8c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Mar 2023 16:09:09 +0900
Subject: [PATCH 0584/1027] drm/apple: Align buffers to 16K page size

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 7227d475cab03e..0b5d6b2d462c6f 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -50,12 +50,14 @@ struct apple_drm_private {
 
 DEFINE_DRM_GEM_DMA_FOPS(apple_fops);
 
+#define DART_PAGE_SIZE 16384
+
 static int apple_drm_gem_dumb_create(struct drm_file *file_priv,
                             struct drm_device *drm,
                             struct drm_mode_create_dumb *args)
 {
         args->pitch = ALIGN(DIV_ROUND_UP(args->width * args->bpp, 8), 64);
-        args->size = args->pitch * args->height;
+        args->size = round_up(args->pitch * args->height, DART_PAGE_SIZE);
 
 	return drm_gem_dma_dumb_create_internal(file_priv, drm, args);
 }

From 99e9b862b66315e3d723e03ba9045f077aaf2cd2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 28 Feb 2023 20:34:03 +0100
Subject: [PATCH 0585/1027] drm/apple: purge unused
 dcp_update_notify_clients_dcp

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c |  5 -----
 drivers/gpu/drm/apple/iomfb.h | 18 ------------------
 2 files changed, 23 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index ee88261661f318..b7a3c8cb454b3f 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -171,7 +171,6 @@ static u8 dcp_pop_depth(u8 *depth)
 const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	DCP_METHOD("A000", dcpep_late_init_signal),
 	DCP_METHOD("A029", dcpep_setup_video_limits),
-	DCP_METHOD("A034", dcpep_update_notify_clients_dcp),
 	DCP_METHOD("A131", iomfbep_a131_pmu_service_matched),
 	DCP_METHOD("A132", iomfbep_a132_backlight_service_matched),
 	DCP_METHOD("A357", dcpep_set_create_dfb),
@@ -306,10 +305,6 @@ DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
 DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
 DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
 
-__attribute__((unused))
-DCP_THUNK_IN(dcp_update_notify_clients_dcp, dcpep_update_notify_clients_dcp,
-	     struct dcp_update_notify_clients_dcp);
-
 DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
 		struct dcp_set_parameter_dcp, u32);
 
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index a7e9b62425b2c4..54b34fbba94894 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -211,7 +211,6 @@ enum dcpep_method {
 	dcpep_flush_supports_power,
 	dcpep_set_power_state,
 	dcpep_first_client_open,
-	dcpep_update_notify_clients_dcp,
 	dcpep_set_parameter_dcp,
 	dcpep_enable_disable_video_power_savings,
 	dcpep_is_main_display,
@@ -395,23 +394,6 @@ struct dcp_set_dcpav_prop_end_req {
 	char key[0x40];
 } __packed;
 
-struct dcp_update_notify_clients_dcp {
-	u32 client_0;
-	u32 client_1;
-	u32 client_2;
-	u32 client_3;
-	u32 client_4;
-	u32 client_5;
-	u32 client_6;
-	u32 client_7;
-	u32 client_8;
-	u32 client_9;
-	u32 client_a;
-	u32 client_b;
-	u32 client_c;
-	u32 client_d;
-} __packed;
-
 struct dcp_set_parameter_dcp {
 	u32 param;
 	u32 value[8];

From 2fb65ab075d48a359dc1afb3bc1cf7e788fff715 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 8 Jan 2023 21:30:22 +0100
Subject: [PATCH 0586/1027] drm/apple: Add callbacks triggered by
 last_client_close_dcp()

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index b7a3c8cb454b3f..3691f7bfa4332d 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1331,9 +1331,14 @@ bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
 	[576] = trampoline_hotplug,
 	[577] = trampoline_nop, /* powerstate_notify */
 	[582] = trampoline_true, /* create_default_fb_surface */
+	[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
+	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
 	[589] = trampoline_swap_complete,
 	[591] = trampoline_swap_complete_intent_gated,
 	[593] = trampoline_enable_backlight_message_ap_gated,
+	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
+	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
+	[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
 	[598] = trampoline_nop, /* find_swap_function_gated */
 };
 

From 3547ab16132c87d27566f02e2ab7cc8e861385e1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 17 Feb 2023 23:17:10 +0100
Subject: [PATCH 0587/1027] drm/apple: Add support for the macOS 13.2 DCP
 firmware

This adds support for multiple incompatible DCP firmware versions.
The approach taken here duplicates more than necessary. Unmodified calls
do not need to be templated. For simplicity and in the expectation that
more calls and callbacks are modified in the future everything is
templated.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile         |    2 +
 drivers/gpu/drm/apple/dcp-internal.h   |   11 +-
 drivers/gpu/drm/apple/dcp.c            |    2 +
 drivers/gpu/drm/apple/iomfb.c          | 1476 ++----------------------
 drivers/gpu/drm/apple/iomfb.h          |  123 +-
 drivers/gpu/drm/apple/iomfb_internal.h |  123 ++
 drivers/gpu/drm/apple/iomfb_template.c | 1344 +++++++++++++++++++++
 drivers/gpu/drm/apple/iomfb_template.h |  181 +++
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  105 ++
 drivers/gpu/drm/apple/iomfb_v12_3.h    |   17 +
 drivers/gpu/drm/apple/iomfb_v13_2.c    |  105 ++
 drivers/gpu/drm/apple/iomfb_v13_2.h    |   17 +
 drivers/gpu/drm/apple/version_utils.h  |   15 +
 13 files changed, 2025 insertions(+), 1496 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/iomfb_internal.h
 create mode 100644 drivers/gpu/drm/apple/iomfb_template.c
 create mode 100644 drivers/gpu/drm/apple/iomfb_template.h
 create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.c
 create mode 100644 drivers/gpu/drm/apple/iomfb_v12_3.h
 create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.c
 create mode 100644 drivers/gpu/drm/apple/iomfb_v13_2.h
 create mode 100644 drivers/gpu/drm/apple/version_utils.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index a61024ddce4c38..3ee61beeb7857b 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -5,6 +5,8 @@ CFLAGS_trace.o = -I$(src)
 appledrm-y := apple_drv.o
 
 apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o
+apple_dcp-y += iomfb_v12_3.o
+apple_dcp-y += iomfb_v13_2.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
 apple_piodma-y := dummy-piodma.o
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index b34294816ec1ff..59777809a7f370 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -11,6 +11,8 @@
 #include <linux/scatterlist.h>
 
 #include "iomfb.h"
+#include "iomfb_v12_3.h"
+#include "iomfb_v13_2.h"
 
 #define DCP_MAX_PLANES 2
 
@@ -19,6 +21,7 @@ struct apple_dcp;
 enum dcp_firmware_version {
 	DCP_FIRMWARE_UNKNOWN,
 	DCP_FIRMWARE_V_12_3,
+	DCP_FIRMWARE_V_13_2,
 };
 
 enum {
@@ -134,11 +137,17 @@ struct apple_dcp {
 	struct dcp_channel ch_cmd, ch_oobcmd;
 	struct dcp_channel ch_cb, ch_oobcb, ch_async;
 
+	/* iomfb EP callback handlers */
+	const iomfb_cb_handler *cb_handlers;
+
 	/* Active chunked transfer. There can only be one at a time. */
 	struct dcp_chunks chunks;
 
 	/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
-	struct dcp_swap_submit_req swap;
+	union {
+		struct dcp_swap_submit_req_v12_3 v12_3;
+		struct dcp_swap_submit_req_v13_2 v13_2;
+	} swap;
 
 	/* Current display mode */
 	bool valid_mode;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 23847752903f90..b49a15be0fb103 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -403,6 +403,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
 
 	if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0)
 		return DCP_FIRMWARE_V_12_3;
+	if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0)
+		return DCP_FIRMWARE_V_13_2;
 
 	dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n",
 		compat_str, fw_str);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 3691f7bfa4332d..447de730af721f 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -1,20 +1,20 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
 
+#include <linux/align.h>
 #include <linux/bitfield.h>
 #include <linux/bitmap.h>
 #include <linux/clk.h>
-#include <linux/module.h>
-#include <linux/slab.h>
-#include <linux/of_device.h>
+#include <linux/completion.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/iommu.h>
 #include <linux/kref.h>
-#include <linux/align.h>
-#include <linux/apple-mailbox.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/ratelimit.h>
+#include <linux/slab.h>
 #include <linux/soc/apple/rtkit.h>
-#include <linux/completion.h>
 
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
@@ -26,28 +26,10 @@
 #include "dcp.h"
 #include "dcp-internal.h"
 #include "iomfb.h"
+#include "iomfb_internal.h"
 #include "parser.h"
 #include "trace.h"
 
-/* Register defines used in bandwidth setup structure */
-#define REG_SCRATCH (0x14)
-#define REG_SCRATCH_T600X (0x988)
-#define REG_DOORBELL (0x0)
-#define REG_DOORBELL_BIT (2)
-
-struct dcp_wait_cookie {
-	struct kref refcount;
-	struct completion done;
-};
-
-static void release_wait_cookie(struct kref *ref)
-{
-	struct dcp_wait_cookie *cookie;
-	cookie = container_of(ref, struct dcp_wait_cookie, refcount);
-
-        kfree(cookie);
-}
-
 static int dcp_tx_offset(enum dcp_context_id id)
 {
 	switch (id) {
@@ -166,33 +148,8 @@ static u8 dcp_pop_depth(u8 *depth)
 	return --(*depth);
 }
 
-#define DCP_METHOD(tag, name) [name] = { #name, tag }
-
-const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
-	DCP_METHOD("A000", dcpep_late_init_signal),
-	DCP_METHOD("A029", dcpep_setup_video_limits),
-	DCP_METHOD("A131", iomfbep_a131_pmu_service_matched),
-	DCP_METHOD("A132", iomfbep_a132_backlight_service_matched),
-	DCP_METHOD("A357", dcpep_set_create_dfb),
-	DCP_METHOD("A358", iomfbep_a358_vi_set_temperature_hint),
-	DCP_METHOD("A401", dcpep_start_signal),
-	DCP_METHOD("A407", dcpep_swap_start),
-	DCP_METHOD("A408", dcpep_swap_submit),
-	DCP_METHOD("A410", dcpep_set_display_device),
-	DCP_METHOD("A411", dcpep_is_main_display),
-	DCP_METHOD("A412", dcpep_set_digital_out_mode),
-	DCP_METHOD("A426", iomfbep_get_color_remap_mode),
-	DCP_METHOD("A439", dcpep_set_parameter_dcp),
-	DCP_METHOD("A443", dcpep_create_default_fb),
-	DCP_METHOD("A447", dcpep_enable_disable_video_power_savings),
-	DCP_METHOD("A454", dcpep_first_client_open),
-	DCP_METHOD("A460", dcpep_set_display_refresh_properties),
-	DCP_METHOD("A463", dcpep_flush_supports_power),
-	DCP_METHOD("A468", dcpep_set_power_state),
-};
-
 /* Call a DCP function given by a tag */
-static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
+void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call,
 		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
 		     void *cookie)
 {
@@ -204,10 +161,10 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 		.out_len = out_len,
 
 		/* Tag is reversed due to endianness of the fourcc */
-		.tag[0] = dcp_methods[method].tag[3],
-		.tag[1] = dcp_methods[method].tag[2],
-		.tag[2] = dcp_methods[method].tag[1],
-		.tag[3] = dcp_methods[method].tag[0],
+		.tag[0] = call->tag[3],
+		.tag[1] = call->tag[2],
+		.tag[2] = call->tag[1],
+		.tag[3] = call->tag[0],
 	};
 
 	u8 depth = dcp_push_depth(&ch->depth);
@@ -222,7 +179,7 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 	if (in_len > 0)
 		memcpy(out_data, data, in_len);
 
-	trace_iomfb_push(dcp, &dcp_methods[method], context, offset, depth);
+	trace_iomfb_push(dcp, call, context, offset, depth);
 
 	ch->callbacks[depth] = cb;
 	ch->cookies[depth] = cookie;
@@ -233,88 +190,8 @@ static void dcp_push(struct apple_dcp *dcp, bool oob, enum dcpep_method method,
 			 dcpep_msg(context, data_len, offset));
 }
 
-#define DCP_THUNK_VOID(func, handle)                                         \
-	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
-			 void *cookie)                                       \
-	{                                                                    \
-		dcp_push(dcp, oob, handle, 0, 0, NULL, cb, cookie);          \
-	}
-
-#define DCP_THUNK_OUT(func, handle, T)                                       \
-	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
-			 void *cookie)                                       \
-	{                                                                    \
-		dcp_push(dcp, oob, handle, 0, sizeof(T), NULL, cb, cookie);  \
-	}
-
-#define DCP_THUNK_IN(func, handle, T)                                       \
-	static void func(struct apple_dcp *dcp, bool oob, T *data,          \
-			 dcp_callback_t cb, void *cookie)                   \
-	{                                                                   \
-		dcp_push(dcp, oob, handle, sizeof(T), 0, data, cb, cookie); \
-	}
-
-#define DCP_THUNK_INOUT(func, handle, T_in, T_out)                            \
-	static void func(struct apple_dcp *dcp, bool oob, T_in *data,         \
-			 dcp_callback_t cb, void *cookie)                     \
-	{                                                                     \
-		dcp_push(dcp, oob, handle, sizeof(T_in), sizeof(T_out), data, \
-			 cb, cookie);                                         \
-	}
-
-#define IOMFB_THUNK_INOUT(name)                                     \
-	static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \
-			struct iomfb_ ## name ## _req *data,        \
-			dcp_callback_t cb, void *cookie)            \
-	{                                                           \
-		dcp_push(dcp, oob, iomfbep_ ## name,                \
-			 sizeof(struct iomfb_ ## name ## _req),     \
-			 sizeof(struct iomfb_ ## name ## _resp),    \
-			 data,  cb, cookie);                        \
-	}
-
-DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
-DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
-DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
-
-IOMFB_THUNK_INOUT(get_color_remap_mode);
-
-DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit, struct dcp_swap_submit_req,
-		struct dcp_swap_submit_resp);
-
-DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
-		struct dcp_swap_start_resp);
-
-DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
-		struct dcp_set_power_state_req,
-		struct dcp_set_power_state_resp);
-
-DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
-		struct dcp_set_digital_out_mode_req, u32);
-
-DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
-
-DCP_THUNK_OUT(dcp_set_display_refresh_properties,
-	      dcpep_set_display_refresh_properties, u32);
-
-DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
-DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
-DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
-DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
-DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
-DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
-DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
-
-DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
-		struct dcp_set_parameter_dcp, u32);
-
-DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
-		dcpep_enable_disable_video_power_savings, u32, int);
-
-DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
-
 /* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
-static int dcp_parse_tag(char tag[4])
+int dcp_parse_tag(char tag[4])
 {
 	u32 d[3];
 	int i;
@@ -333,7 +210,7 @@ static int dcp_parse_tag(char tag[4])
 }
 
 /* Ack a callback from the DCP */
-static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
+void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 {
 	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 
@@ -342,776 +219,54 @@ static void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 			 dcpep_ack(context));
 }
 
-/* DCP callback handlers */
-static void dcpep_cb_nop(struct apple_dcp *dcp)
-{
-	/* No operation */
-}
-
-static u8 dcpep_cb_true(struct apple_dcp *dcp)
-{
-	return true;
-}
-
-static u8 dcpep_cb_false(struct apple_dcp *dcp)
-{
-	return false;
-}
-
-static u32 dcpep_cb_zero(struct apple_dcp *dcp)
-{
-	return 0;
-}
-
-static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
-				   struct dc_swap_complete_resp *resp)
-{
-	trace_iomfb_swap_complete(dcp, resp->swap_id);
-
-	dcp_drm_crtc_vblank(dcp->crtc);
-}
-
-/* special */
-static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	// ack D100 cb_match_pmu_service
-	dcp_ack(dcp, DCP_CONTEXT_CB);
-}
-
-static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
-{
-	trace_iomfb_callback(dcp, tag, __func__);
-	iomfb_a358_vi_set_temperature_hint(dcp, false,
-					   complete_vi_set_temperature_hint,
-					   NULL);
-
-	// return false for deferred ACK
-	return false;
-}
-
-static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
+void dcp_sleep(struct apple_dcp *dcp)
 {
-	struct dcp_channel *ch = &dcp->ch_cb;
-	u8 *succ = ch->output[ch->depth - 1];
-
-	*succ = true;
-
-	// ack D206 cb_match_pmu_service_2
-	dcp_ack(dcp, DCP_CONTEXT_CB);
-}
-
-static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in)
-{
-	trace_iomfb_callback(dcp, tag, __func__);
-
-	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
-				       out);
-
-	// return false for deferred ACK
-	return false;
-}
-
-static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_channel *ch = &dcp->ch_cb;
-	u8 *succ = ch->output[ch->depth - 1];
-
-	*succ = true;
-
-	// ack D206 cb_match_backlight_service
-	dcp_ack(dcp, DCP_CONTEXT_CB);
-}
-
-static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in)
-{
-	trace_iomfb_callback(dcp, tag, __func__);
-
-	iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
-
-	// return false for deferred ACK
-	return false;
-}
-
-static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop)
-{
-	switch (prop->id) {
-	case IOMFB_PROPERTY_NITS:
-	{
-		dcp->brightness.nits = prop->value / dcp->brightness.scale;
-		/* notify backlight device of the initial brightness */
-		if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
-			schedule_work(&dcp->bl_register_wq);
-		trace_iomfb_brightness(dcp, prop->value);
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_sleep_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_sleep_v13_2(dcp);
 		break;
-	}
 	default:
-		dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
-	}
-}
-
-static struct dcp_get_uint_prop_resp
-dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
-{
-	struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){
-	    .value = 0
-	};
-
-	if (dcp->panel.has_mini_led &&
-	    memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
-	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
-		/*
-		 * TODO: value from j314c, find out if it is temperature in
-		 *       centigrade C and which temperature sensor reports it
-		 */
-		resp.value = 3029;
-		resp.ret = true;
-	    }
-	}
-
-	return resp;
-}
-
-static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp,
-					 struct iomfb_sr_set_property_int_req *req)
-{
-	if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */
-		if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) {
-			if (!req->value_null)
-				dcp->brightness.scale = req->value;
-		}
-	}
-
-	return 1;
-}
-
-static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
-{
-    // TODO: trace this, see if there properties which needs to used later
-}
-
-/*
- * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
- * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
- * stream of the display DART, rather than the expected DCP DART.
- *
- * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
- * is a "fundamentally unsafe" operation according to the docs. And yet
- * everyone does it...
- */
-static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
-						   struct dcp_map_buf_req *req)
-{
-	struct sg_table *map;
-	int ret;
-
-	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
-		goto reject;
-
-	map = &dcp->memdesc[req->buffer].map;
-
-	if (!map->sgl)
-		goto reject;
-
-	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
-	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
-
-	if (ret)
-		goto reject;
-
-	return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) };
-
-reject:
-	dev_err(dcp->dev, "denying map of invalid buffer %llx for pidoma\n",
-		req->buffer);
-	return (struct dcp_map_buf_resp){ .ret = EINVAL };
-}
-
-static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
-				  struct dcp_unmap_buf_resp *resp)
-{
-	struct sg_table *map;
-	dma_addr_t dma_addr;
-
-	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
-		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
-			 resp->buffer);
-		return;
-	}
-
-	map = &dcp->memdesc[resp->buffer].map;
-
-	if (!map->sgl) {
-		dev_warn(dcp->dev,
-			 "unmap for non-mapped buffer %llu iova:0x%08llx",
-			 resp->buffer, resp->dva);
-		return;
-	}
-
-	dma_addr = sg_dma_address(map->sgl);
-	if (dma_addr != resp->dva) {
-		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
-			 resp->buffer, dma_addr, resp->dva);
-		return;
-	}
-
-	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
-	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
-}
-
-/*
- * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
- * physically contigiuous, however we should save the sgtable in case the
- * buffer needs to be later mapped for PIODMA.
- */
-static struct dcp_allocate_buffer_resp
-dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
-			 struct dcp_allocate_buffer_req *req)
-{
-	struct dcp_allocate_buffer_resp resp = { 0 };
-	struct dcp_mem_descriptor *memdesc;
-	u32 id;
-
-	resp.dva_size = ALIGN(req->size, 4096);
-	resp.mem_desc_id =
-		find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
-
-	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
-		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
-		resp.dva_size = 0;
-		resp.mem_desc_id = 0;
-		return resp;
-	}
-	id = resp.mem_desc_id;
-	set_bit(id, dcp->memdesc_map);
-
-	memdesc = &dcp->memdesc[id];
-
-	memdesc->size = resp.dva_size;
-	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size,
-					  &memdesc->dva, GFP_KERNEL);
-
-	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
-			memdesc->size);
-	resp.dva = memdesc->dva;
-
-	return resp;
-}
-
-static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
-{
-	struct dcp_mem_descriptor *memdesc;
-	u32 id = *mem_desc_id;
-
-	if (id >= DCP_MAX_MAPPINGS) {
-		dev_warn(dcp->dev,
-			 "unmap request for out of range mem_desc_id %u", id);
-		return 0;
-	}
-
-	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
-		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
-			 id);
-		return 0;
-	}
-
-	memdesc = &dcp->memdesc[id];
-	if (memdesc->buf) {
-		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
-				  memdesc->dva);
-
-		memdesc->buf = NULL;
-		memset(&memdesc->map, 0, sizeof(memdesc->map));
-	} else {
-		memdesc->reg = 0;
-	}
-
-	memdesc->size = 0;
-
-	return 1;
-}
-
-/* Validate that the specified region is a display register */
-static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
-{
-	int i;
-
-	for (i = 0; i < dcp->nr_disp_registers; ++i) {
-		struct resource *r = dcp->disp_registers[i];
-
-		if ((start >= r->start) && (end <= r->end))
-			return true;
-	}
-
-	return false;
-}
-
-/*
- * Map contiguous physical memory into the DCP's address space. The firmware
- * uses this to map the display registers we advertise in
- * sr_map_device_memory_with_index, so we bounds check against that to guard
- * safe against malicious coprocessors.
- */
-static struct dcp_map_physical_resp
-dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
-{
-	int size = ALIGN(req->size, 4096);
-	u32 id;
-
-	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
-		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
-			req->paddr, req->size);
-		return (struct dcp_map_physical_resp){};
-	}
-
-	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
-	set_bit(id, dcp->memdesc_map);
-	dcp->memdesc[id].size = size;
-	dcp->memdesc[id].reg = req->paddr;
-
-	return (struct dcp_map_physical_resp){
-		.dva_size = size,
-		.mem_desc_id = id,
-		.dva = dma_map_resource(dcp->dev, req->paddr, size,
-					DMA_BIDIRECTIONAL, 0),
-	};
-}
-
-static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
-{
-	return clk_get_rate(dcp->clk);
-}
-
-static struct dcp_map_reg_resp dcpep_cb_map_reg(struct apple_dcp *dcp,
-						struct dcp_map_reg_req *req)
-{
-	if (req->index >= dcp->nr_disp_registers) {
-		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
-			 req->index);
-
-		return (struct dcp_map_reg_resp){ .ret = 1 };
-	} else {
-		struct resource *rsrc = dcp->disp_registers[req->index];
-
-		return (struct dcp_map_reg_resp){
-			.addr = rsrc->start, .length = resource_size(rsrc)
-		};
-	}
-}
-
-static struct dcp_read_edt_data_resp
-dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
-{
-	return (struct dcp_read_edt_data_resp){
-		.value[0] = req->value[0],
-		.ret = 0,
-	};
-}
-
-static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
-							 u8 *enabled)
-{
-	/*
-	 * update backlight brightness on next swap, on non mini-LED displays
-	 * DCP seems to set an invalid iDAC value after coming out of DPMS.
-	 * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
-	 */
-	dcp->brightness.update = true;
-}
-
-/* Chunked data transfer for property dictionaries */
-static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
-{
-	if (dcp->chunks.data != NULL) {
-		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
-		return false;
-	}
-
-	dcp->chunks.length = *length;
-	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
-
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "failed to allocate chunks\n");
-		return false;
-	}
-
-	return true;
-}
-
-static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
-			      struct dcp_set_dcpav_prop_chunk_req *req)
-{
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "ignoring spurious chunk\n");
-		return false;
-	}
-
-	if (req->offset + req->length > dcp->chunks.length) {
-		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
-		return false;
-	}
-
-	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
-	return true;
-}
-
-static bool dcpep_process_chunks(struct apple_dcp *dcp,
-				 struct dcp_set_dcpav_prop_end_req *req)
-{
-	struct dcp_parse_ctx ctx;
-	int ret;
-
-	if (!dcp->chunks.data) {
-		dev_warn(dcp->dev, "ignoring spurious end\n");
-		return false;
-	}
-
-	/* used just as opaque pointer for tracing */
-	ctx.dcp = dcp;
-
-	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
-
-	if (ret) {
-		dev_warn(dcp->dev, "bad header on dcpav props\n");
-		return false;
-	}
-
-	if (!strcmp(req->key, "TimingElements")) {
-		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
-					     dcp->width_mm, dcp->height_mm,
-					     dcp->notch_height);
-
-		if (IS_ERR(dcp->modes)) {
-			dev_warn(dcp->dev, "failed to parse modes\n");
-			dcp->modes = NULL;
-			dcp->nr_modes = 0;
-			return false;
-		}
-	} else if (!strcmp(req->key, "DisplayAttributes")) {
-		/* DisplayAttributes are empty for integrated displays, use
-		 * display dimensions read from the devicetree
-		 */
-		if (dcp->main_display) {
-			ret = parse_display_attributes(&ctx, &dcp->width_mm,
-						&dcp->height_mm);
-
-			if (ret) {
-				dev_warn(dcp->dev, "failed to parse display attribs\n");
-				return false;
-			}
-		}
-
-		dcp_set_dimensions(dcp);
-	}
-
-	return true;
-}
-
-static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
-			    struct dcp_set_dcpav_prop_end_req *req)
-{
-	u8 resp = dcpep_process_chunks(dcp, req);
-
-	/* Reset for the next transfer */
-	devm_kfree(dcp->dev, dcp->chunks.data);
-	dcp->chunks.data = NULL;
-
-	return resp;
-}
-
-/* Boot sequence */
-static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_channel *ch = &dcp->ch_cb;
-	u8 *succ = ch->output[ch->depth - 1];
-	dev_dbg(dcp->dev, "boot done");
-
-	*succ = true;
-	dcp_ack(dcp, DCP_CONTEXT_CB);
-}
-
-static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
-}
-
-static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_late_init_signal(dcp, false, boot_5, NULL);
-}
-
-static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	u32 v_true = true;
-
-	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
-}
-
-static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_setup_video_limits(dcp, false, boot_3, NULL);
-}
-
-static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_create_default_fb(dcp, false, boot_2, NULL);
-}
-
-/* Use special function signature to defer the ACK */
-static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
-{
-	trace_iomfb_callback(dcp, tag, __func__);
-	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
-	return false;
-}
-
-static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
-{
-	if (dcp->disp_registers[5] && dcp->disp_registers[6])
-		return (struct dcp_rt_bandwidth){
-			.reg_scratch =
-				dcp->disp_registers[5]->start + REG_SCRATCH,
-			.reg_doorbell =
-				dcp->disp_registers[6]->start + REG_DOORBELL,
-			.doorbell_bit = REG_DOORBELL_BIT,
-
-			.padding[3] = 0x4, // XXX: required by 11.x firmware
-		};
-	else if (dcp->disp_registers[4])
-		return (struct dcp_rt_bandwidth){
-			.reg_scratch = dcp->disp_registers[4]->start +
-				       REG_SCRATCH_T600X,
-			.reg_doorbell = 0,
-			.doorbell_bit = 0,
-		};
-	else
-		return (struct dcp_rt_bandwidth){
-			.reg_scratch = 0,
-			.reg_doorbell = 0,
-			.doorbell_bit = 0,
-		};
-}
-
-/* Callback to get the current time as milliseconds since the UNIX epoch */
-static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
-{
-	return ktime_to_ms(ktime_get_real());
-}
-
-struct dcp_swap_cookie {
-	struct kref refcount;
-	struct completion done;
-	u32 swap_id;
-};
-
-static void release_swap_cookie(struct kref *ref)
-{
-	struct dcp_swap_cookie *cookie;
-	cookie = container_of(ref, struct dcp_swap_cookie, refcount);
-
-        kfree(cookie);
-}
-
-static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_submit_resp *resp = data;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (cookie) {
-		struct dcp_swap_cookie *info = cookie;
-		complete(&info->done);
-		kref_put(&info->refcount, release_swap_cookie);
-	}
-
-	if (resp->ret) {
-		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
-		dcp_drm_crtc_vblank(dcp->crtc);
-		return;
-	}
-
-	while (!list_empty(&dcp->swapped_out_fbs)) {
-		struct dcp_fb_reference *entry;
-		entry = list_first_entry(&dcp->swapped_out_fbs,
-					 struct dcp_fb_reference, head);
-		if (entry->fb)
-			drm_framebuffer_put(entry->fb);
-		list_del(&entry->head);
-		kfree(entry);
-	}
-}
-
-static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
-				   void *cookie)
-{
-	struct dcp_swap_start_resp *resp = data;
-	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
-	dcp->swap.swap.swap_id = resp->swap_id;
-
-	if (cookie) {
-		struct dcp_swap_cookie *info = cookie;
-		info->swap_id = resp->swap_id;
-	}
-
-	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swap_cleared, cookie);
-}
-
-static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (wait) {
-		complete(&wait->done);
-		kref_put(&wait->refcount, release_wait_cookie);
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
 	}
 }
 
-static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_set_power_state_req req = {
-		.unklong = 1,
-	};
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
-}
-
-static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct dcp_set_parameter_dcp param = {
-		.param = 14,
-		.value = { 0 },
-		.count = 1,
-	};
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
-}
-
 void dcp_poweron(struct platform_device *pdev)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	struct dcp_wait_cookie *cookie;
-	int ret;
-	u32 handle;
-	dev_dbg(dcp->dev, "%s", __func__);
 
-	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-	if (!cookie)
-		return;
-
-	init_completion(&cookie->done);
-	kref_init(&cookie->refcount);
-	/* increase refcount to ensure the receiver has a reference */
-	kref_get(&cookie->refcount);
-
-	if (dcp->main_display) {
-		handle = 0;
-		dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state,
-				       cookie);
-	} else {
-		handle = 2;
-		dcp_set_display_device(dcp, false, &handle,
-				       dcp_on_set_parameter, cookie);
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_poweron_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_poweron_v13_2(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
 	}
-	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
-
-	if (ret == 0)
-		dev_warn(dcp->dev, "wait for power timed out");
-
-	kref_put(&cookie->refcount, release_wait_cookie);;
-
-	/* Force a brightness update after poweron, to restore the brightness */
-	dcp->brightness.update = true;
 }
 EXPORT_SYMBOL(dcp_poweron);
 
-static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
-				    void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
-
-	if (wait) {
-		complete(&wait->done);
-		kref_put(&wait->refcount, release_wait_cookie);
-	}
-}
-
 void dcp_poweroff(struct platform_device *pdev)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	int ret, swap_id;
-	struct dcp_set_power_state_req power_req = {
-		.unklong = 0,
-	};
-	struct dcp_swap_cookie *cookie;
-	struct dcp_wait_cookie *poff_cookie;
-	struct dcp_swap_start_req swap_req = { 0 };
-
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-	if (!cookie)
-		return;
-	init_completion(&cookie->done);
-	kref_init(&cookie->refcount);
-	/* increase refcount to ensure the receiver has a reference */
-	kref_get(&cookie->refcount);
 
-	// clear surfaces
-	memset(&dcp->swap, 0, sizeof(dcp->swap));
-
-	dcp->swap.swap.swap_enabled =
-		dcp->swap.swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF;
-	dcp->swap.swap.bg_color = 0xFF000000;
-
-	/*
-	 * Turn off the backlight. This matters because the DCP's idea of
-	 * backlight brightness gets desynced after a power change, and it
-	 * needs to be told it's going to turn off so it will consider the
-	 * subsequent update on poweron an actual change and restore the
-	 * brightness.
-	 */
-	dcp->swap.swap.bl_unk = 1;
-	dcp->swap.swap.bl_value = 0;
-	dcp->swap.swap.bl_power = 0;
-
-	for (int l = 0; l < SWAP_SURFACES; l++)
-		dcp->swap.surf_null[l] = true;
-
-	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
-
-	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
-	swap_id = cookie->swap_id;
-	kref_put(&cookie->refcount, release_swap_cookie);
-	if (ret <= 0) {
-		dcp->crashed = true;
-		return;
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_poweroff_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_poweroff_v13_2(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
 	}
-
-	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
-
-	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
-	if (!poff_cookie)
-		return;
-	init_completion(&poff_cookie->done);
-	kref_init(&poff_cookie->refcount);
-	/* increase refcount to ensure the receiver has a reference */
-	kref_get(&poff_cookie->refcount);
-
-	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
-			    poff_cookie);
-	ret = wait_for_completion_timeout(&poff_cookie->done,
-					  msecs_to_jiffies(1000));
-
-	if (ret == 0)
-		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
-	else if (ret > 0)
-		dev_dbg(dcp->dev,
-			"setPowerState(0) finished with %d ms to spare",
-			jiffies_to_msecs(ret));
-
-	kref_put(&poff_cookie->refcount, release_wait_cookie);
-	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
 }
 EXPORT_SYMBOL(dcp_poweroff);
 
@@ -1149,199 +304,6 @@ void dcp_hotplug(struct work_struct *work)
 }
 EXPORT_SYMBOL_GPL(dcp_hotplug);
 
-static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
-{
-	struct apple_connector *connector = dcp->connector;
-
-	/* DCP issues hotplug_gated callbacks after SetPowerState() calls on
-	 * devices with display (macbooks, imacs). This must not result in
-	 * connector state changes on DRM side. Some applications won't enable
-	 * a CRTC with a connector in disconnected state. Weston after DPMS off
-	 * is one example. dcp_is_main_display() returns true on devices with
-	 * integrated display. Ignore the hotplug_gated() callbacks there.
-	 */
-	if (dcp->main_display)
-		return;
-
-	/* Hotplug invalidates mode. DRM doesn't always handle this. */
-	if (!(*connected)) {
-		dcp->valid_mode = false;
-		/* after unplug swap will not complete until the next
-		 * set_digital_out_mode */
-		schedule_work(&dcp->vblank_wq);
-	}
-
-	if (connector && connector->connected != !!(*connected)) {
-		connector->connected = !!(*connected);
-		dcp->valid_mode = false;
-		schedule_work(&connector->hotplug_wq);
-	}
-}
-
-static void
-dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
-				    struct dcp_swap_complete_intent_gated *info)
-{
-	trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id,
-		info->width, info->height);
-}
-
-#define DCPEP_MAX_CB (1000)
-
-/*
- * Define type-safe trampolines. Define typedefs to enforce type-safety on the
- * input data (so if the types don't match, gcc errors out).
- */
-
-#define TRAMPOLINE_VOID(func, handler)                                        \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
-	{                                                                     \
-		trace_iomfb_callback(dcp, tag, #handler);                     \
-		handler(dcp);                                                 \
-		return true;                                                  \
-	}
-
-#define TRAMPOLINE_IN(func, handler, T_in)                                    \
-	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);       \
-                                                                              \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
-	{                                                                     \
-		callback_##handler cb = handler;                              \
-                                                                              \
-		trace_iomfb_callback(dcp, tag, #handler);                     \
-		cb(dcp, in);                                                  \
-		return true;                                                  \
-	}
-
-#define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                          \
-	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);      \
-                                                                              \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
-	{                                                                     \
-		T_out *typed_out = out;                                       \
-		callback_##handler cb = handler;                              \
-                                                                              \
-		trace_iomfb_callback(dcp, tag, #handler);                     \
-		*typed_out = cb(dcp, in);                                     \
-		return true;                                                  \
-	}
-
-#define TRAMPOLINE_OUT(func, handler, T_out)                                  \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
-	{                                                                     \
-		T_out *typed_out = out;                                       \
-                                                                              \
-		trace_iomfb_callback(dcp, tag, #handler);                     \
-		*typed_out = handler(dcp);                                    \
-		return true;                                                  \
-	}
-
-TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
-TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
-TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
-TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
-TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
-	      struct dc_swap_complete_resp);
-TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
-		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
-TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop,
-	      struct iomfb_set_fx_prop_req)
-TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
-		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
-TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
-	      struct dcp_unmap_buf_resp);
-TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int,
-		 struct iomfb_sr_set_property_int_req, u8);
-TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
-		 struct dcp_allocate_buffer_req,
-		 struct dcp_allocate_buffer_resp);
-TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
-		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
-TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32,
-		 u8);
-TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg, struct dcp_map_reg_req,
-		 struct dcp_map_reg_resp);
-TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
-		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
-TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
-TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
-		 struct dcp_set_dcpav_prop_chunk_req, u8);
-TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
-		 struct dcp_set_dcpav_prop_end_req, u8);
-TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
-	       struct dcp_rt_bandwidth);
-TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
-TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
-TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
-TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
-	      dcpep_cb_swap_complete_intent_gated,
-	      struct dcp_swap_complete_intent_gated);
-TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
-	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
-TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
-	      struct iomfb_property);
-
-bool (*const dcpep_cb_handlers[DCPEP_MAX_CB])(struct apple_dcp *, int, void *,
-					      void *) = {
-	[0] = trampoline_true, /* did_boot_signal */
-	[1] = trampoline_true, /* did_power_on_signal */
-	[2] = trampoline_nop, /* will_power_off_signal */
-	[3] = trampoline_rt_bandwidth,
-	[100] = iomfbep_cb_match_pmu_service,
-	[101] = trampoline_zero, /* get_display_default_stride */
-	[102] = trampoline_nop, /* set_number_property */
-	[103] = trampoline_nop, /* set_boolean_property */
-	[106] = trampoline_nop, /* remove_property */
-	[107] = trampoline_true, /* create_provider_service */
-	[108] = trampoline_true, /* create_product_service */
-	[109] = trampoline_true, /* create_pmu_service */
-	[110] = trampoline_true, /* create_iomfb_service */
-	[111] = trampoline_true, /* create_backlight_service */
-	[116] = dcpep_cb_boot_1,
-	[117] = trampoline_false, /* is_dark_boot */
-	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
-	[120] = trampoline_read_edt_data,
-	[122] = trampoline_prop_start,
-	[123] = trampoline_prop_chunk,
-	[124] = trampoline_prop_end,
-	[201] = trampoline_map_piodma,
-	[202] = trampoline_unmap_piodma,
-	[206] = iomfbep_cb_match_pmu_service_2,
-	[207] = iomfbep_cb_match_backlight_service,
-	[208] = trampoline_get_time,
-	[211] = trampoline_nop, /* update_backlight_factor_prop */
-	[300] = trampoline_pr_publish,
-	[401] = trampoline_get_uint_prop,
-	[404] = trampoline_nop, /* sr_set_uint_prop */
-	[406] = trampoline_set_fx_prop,
-	[408] = trampoline_get_frequency,
-	[411] = trampoline_map_reg,
-	[413] = trampoline_true, /* sr_set_property_dict */
-	[414] = trampoline_sr_set_property_int,
-	[415] = trampoline_true, /* sr_set_property_bool */
-	[451] = trampoline_allocate_buffer,
-	[452] = trampoline_map_physical,
-	[456] = trampoline_release_mem_desc,
-	[552] = trampoline_true, /* set_property_dict_0 */
-	[561] = trampoline_true, /* set_property_dict */
-	[563] = trampoline_true, /* set_property_int */
-	[565] = trampoline_true, /* set_property_bool */
-	[567] = trampoline_true, /* set_property_str */
-	[574] = trampoline_zero, /* power_up_dart */
-	[576] = trampoline_hotplug,
-	[577] = trampoline_nop, /* powerstate_notify */
-	[582] = trampoline_true, /* create_default_fb_surface */
-	[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
-	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
-	[589] = trampoline_swap_complete,
-	[591] = trampoline_swap_complete_intent_gated,
-	[593] = trampoline_enable_backlight_message_ap_gated,
-	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
-	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
-	[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
-	[598] = trampoline_nop, /* find_swap_function_gated */
-};
-
 static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 			    void *data, u32 length, u16 offset)
 {
@@ -1352,7 +314,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 	struct dcp_channel *ch = dcp_get_channel(dcp, context);
 	u8 depth;
 
-	if (tag < 0 || tag >= DCPEP_MAX_CB || !dcpep_cb_handlers[tag]) {
+	if (tag < 0 || tag >= IOMFB_MAX_CB || !dcp->cb_handlers || !dcp->cb_handlers[tag]) {
 		dev_warn(dev, "received unknown callback %c%c%c%c\n",
 			 hdr->tag[3], hdr->tag[2], hdr->tag[1], hdr->tag[0]);
 		return;
@@ -1370,7 +332,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 	ch->output[depth] = out;
 	ch->end[depth] = offset + ALIGN(length, DCP_PACKET_ALIGNMENT);
 
-	if (dcpep_cb_handlers[tag](dcp, tag, out, in))
+	if (dcp->cb_handlers[tag](dcp, tag, out, in))
 		dcp_ack(dcp, context);
 }
 
@@ -1426,48 +388,12 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
 		dcpep_handle_cb(dcp, ctx_id, data, length, offset);
 }
 
-/*
- * Callback for swap requests. If a swap failed, we'll never get a swap
- * complete event so we need to fake a vblank event early to avoid a hang.
- */
-
-static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_submit_resp *resp = data;
-
-	if (resp->ret) {
-		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
-		dcp_drm_crtc_vblank(dcp->crtc);
-		return;
-	}
-
-	while (!list_empty(&dcp->swapped_out_fbs)) {
-		struct dcp_fb_reference *entry;
-		entry = list_first_entry(&dcp->swapped_out_fbs,
-					 struct dcp_fb_reference, head);
-		if (entry->fb)
-			drm_framebuffer_put(entry->fb);
-		list_del(&entry->head);
-		kfree(entry);
-	}
-}
-
-static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_start_resp *resp = data;
-
-	dcp->swap.swap.swap_id = resp->swap_id;
-
-	trace_iomfb_swap_submit(dcp, resp->swap_id);
-	dcp_swap_submit(dcp, false, &dcp->swap, dcp_swapped, NULL);
-}
-
 /*
  * DRM specifies rectangles as start and end coordinates.  DCP specifies
  * rectangles as a start coordinate and a width/height. Convert a DRM rectangle
  * to a DCP rectangle.
  */
-static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
+struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
 {
 	return (struct dcp_rect){ .x = rect->x1,
 				  .y = rect->y1,
@@ -1475,7 +401,7 @@ static struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect)
 				  .h = drm_rect_height(rect) };
 }
 
-static u32 drm_format_to_dcp(u32 drm)
+u32 drm_format_to_dcp(u32 drm)
 {
 	switch (drm) {
 	case DRM_FORMAT_XRGB8888:
@@ -1521,7 +447,7 @@ int dcp_get_modes(struct drm_connector *connector)
 EXPORT_SYMBOL_GPL(dcp_get_modes);
 
 /* The user may own drm_display_mode, so we need to search for our copy */
-static struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
+struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
 					    const struct drm_display_mode *mode)
 {
 	int i;
@@ -1560,46 +486,11 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 }
 EXPORT_SYMBOL(dcp_crtc_mode_fixup);
 
-/* Helpers to modeset and swap, used to flush */
-static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct dcp_swap_start_req start_req = { 0 };
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (dcp->connector && dcp->connector->connected)
-		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
-	else
-		dcp_drm_crtc_vblank(dcp->crtc);
-}
-
-static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
-					  void *cookie)
-{
-	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	if (wait) {
-		complete(&wait->done);
-		kref_put(&wait->refcount, release_wait_cookie);
-	}
-}
 
 void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 {
 	struct platform_device *pdev = to_apple_crtc(crtc)->dcp;
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	struct drm_plane *plane;
-	struct drm_plane_state *new_state, *old_state;
-	struct drm_crtc_state *crtc_state;
-	struct dcp_swap_submit_req *req = &dcp->swap;
-	int plane_idx, l;
-	int has_surface = 0;
-	bool modeset;
-	dev_dbg(dcp->dev, "%s", __func__);
-
-	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
-
-	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
 
 	if (dcp_channel_busy(&dcp->ch_cmd))
 	{
@@ -1611,191 +502,34 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 		return;
 	}
 
-	/* Reset to defaults */
-	memset(req, 0, sizeof(*req));
-	for (l = 0; l < SWAP_SURFACES; l++)
-		req->surf_null[l] = true;
-
-	/*
-	 * Clear all surfaces on startup. The boot framebuffer in surface 0
-	 * sticks around.
-	 */
-	if (!dcp->surfaces_cleared) {
-		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF;
-		req->swap.bg_color = 0xFF000000;
-		dcp->surfaces_cleared = true;
-	}
-
-	// Surface 0 has limitations at least on t600x.
-	l = 1;
-	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
-		struct drm_framebuffer *fb = new_state->fb;
-		struct drm_gem_dma_object *obj;
-		struct drm_rect src_rect;
-		bool is_premultiplied = false;
-
-		/* skip planes not for this crtc */
-		if (old_state->crtc != crtc && new_state->crtc != crtc)
-			continue;
-
-		WARN_ON(l >= SWAP_SURFACES);
-
-		req->swap.swap_enabled |= BIT(l);
-
-		if (old_state->fb && fb != old_state->fb) {
-			/*
-			 * Race condition between a framebuffer unbind getting
-			 * swapped out and GEM unreferencing a framebuffer. If
-			 * we lose the race, the display gets IOVA faults and
-			 * the DCP crashes. We need to extend the lifetime of
-			 * the drm_framebuffer (and hence the GEM object) until
-			 * after we get a swap complete for the swap unbinding
-			 * it.
-			 */
-			struct dcp_fb_reference *entry =
-				kzalloc(sizeof(*entry), GFP_KERNEL);
-			if (entry) {
-				entry->fb = old_state->fb;
-				list_add_tail(&entry->head,
-					      &dcp->swapped_out_fbs);
-			}
-			drm_framebuffer_get(old_state->fb);
-		}
-
-		if (!new_state->fb) {
-			l += 1;
-			continue;
-		}
-		req->surf_null[l] = false;
-		has_surface = 1;
-
-		/*
-		 * DCP doesn't support XBGR8 / XRGB8 natively. Blending as
-		 * pre-multiplied alpha with a black background can be used as
-		 * workaround for the bottommost plane.
-		 */
-		if (fb->format->format == DRM_FORMAT_XRGB8888 ||
-		    fb->format->format == DRM_FORMAT_XBGR8888)
-		    is_premultiplied = true;
-
-		drm_rect_fp_to_int(&src_rect, &new_state->src);
-
-		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
-		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
-
-		if (dcp->notch_height > 0)
-			req->swap.dst_rect[l].y += dcp->notch_height;
-
-		/* the obvious helper call drm_fb_dma_get_gem_addr() adjusts
-		 * the address for source x/y offsets. Since IOMFB has a direct
-		 * support source position prefer that.
-		 */
-		obj = drm_fb_dma_get_gem_obj(fb, 0);
-		if (obj)
-			req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
-
-		req->surf[l] = (struct dcp_surface){
-			.is_premultiplied = is_premultiplied,
-			.format = drm_format_to_dcp(fb->format->format),
-			.xfer_func = DCP_XFER_FUNC_SDR,
-			.colorspace = DCP_COLORSPACE_NATIVE,
-			.stride = fb->pitches[0],
-			.width = fb->width,
-			.height = fb->height,
-			.buf_size = fb->height * fb->pitches[0],
-			.surface_id = req->swap.surf_ids[l],
-
-			/* Only used for compressed or multiplanar surfaces */
-			.pix_size = 1,
-			.pel_w = 1,
-			.pel_h = 1,
-			.has_comp = 1,
-			.has_planes = 1,
-		};
-
-		l += 1;
-	}
-
-	if (modeset) {
-		struct dcp_display_mode *mode;
-		struct dcp_wait_cookie *cookie;
-		int ret;
-
-		mode = lookup_mode(dcp, &crtc_state->mode);
-		if (!mode) {
-			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
-				 DRM_MODE_ARG(&crtc_state->mode));
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
-			 mode->color_mode_id, mode->timing_mode_id);
-		dcp->mode = (struct dcp_set_digital_out_mode_req){
-			.color_mode_id = mode->color_mode_id,
-			.timing_mode_id = mode->timing_mode_id
-		};
-
-		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-		if (!cookie) {
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		init_completion(&cookie->done);
-		kref_init(&cookie->refcount);
-		/* increase refcount to ensure the receiver has a reference */
-		kref_get(&cookie->refcount);
-
-		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
-					 complete_set_digital_out_mode, cookie);
-
-		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
-		ret = wait_for_completion_timeout(&cookie->done,
-						  msecs_to_jiffies(500));
-
-		kref_put(&cookie->refcount, release_wait_cookie);
-
-		if (ret == 0) {
-			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
-			schedule_work(&dcp->vblank_wq);
-			return;
-		} else if (ret > 0) {
-			dev_dbg(dcp->dev,
-				"set_digital_out_mode finished with %d to spare",
-				jiffies_to_msecs(ret));
-		}
-
-		dcp->valid_mode = true;
-	}
-
-	if (!has_surface && !crtc_state->color_mgmt_changed) {
-		if (crtc_state->enable && crtc_state->active &&
-		    !crtc_state->planes_changed) {
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		/* Set black background */
-		req->swap.swap_enabled |= IOMFB_SET_BACKGROUND;
-		req->swap.bg_color = 0xFF000000;
-		req->clear = 1;
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_flush_v12_3(dcp, crtc, state);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_flush_v13_2(dcp, crtc, state);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
 	}
+}
+EXPORT_SYMBOL_GPL(dcp_flush);
 
-	/* These fields should be set together */
-	req->swap.swap_completed = req->swap.swap_enabled;
-
-	/* update brightness if changed */
-	if (dcp->brightness.update) {
-		req->swap.bl_unk = 1;
-		req->swap.bl_value = dcp->brightness.dac;
-		req->swap.bl_power = 0x40;
-		dcp->brightness.update = false;
+static void iomfb_start(struct apple_dcp *dcp)
+{
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_start_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_start_v13_2(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
 	}
-
-	do_swap(dcp, NULL, NULL);
 }
-EXPORT_SYMBOL_GPL(dcp_flush);
 
 bool dcp_is_initialized(struct platform_device *pdev)
 {
@@ -1805,58 +539,12 @@ bool dcp_is_initialized(struct platform_device *pdev)
 }
 EXPORT_SYMBOL_GPL(dcp_is_initialized);
 
-static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	struct apple_connector *connector;
-	int result = *(int *)out;
-	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
-
-	dcp->main_display = result != 0;
-
-	connector = dcp->connector;
-	if (connector) {
-		connector->connected = dcp->nr_modes > 0;
-		schedule_work(&connector->hotplug_wq);
-	}
-
-	dcp->active = true;
-	complete(&dcp->start_done);
-}
-
-static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
-}
-
-static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	dcp_first_client_open(dcp, false, init_3, NULL);
-}
-
-static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
-{
-	u32 val = 0;
-	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
-}
-
-static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
-{
-	struct iomfb_get_color_remap_mode_req color_remap =
-		(struct iomfb_get_color_remap_mode_req){
-			.mode = 6,
-		};
-
-	dev_info(dcp->dev, "DCP booted\n");
-
-	iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie);
-}
-
 void iomfb_recv_msg(struct apple_dcp *dcp, u64 message)
 {
 	enum dcpep_type type = FIELD_GET(IOMFB_MESSAGE_TYPE, message);
 
 	if (type == IOMFB_MESSAGE_TYPE_INITIALIZED)
-		dcp_start_signal(dcp, false, dcp_started, NULL);
+		iomfb_start(dcp);
 	else if (type == IOMFB_MESSAGE_TYPE_MSG)
 		dcpep_got_msg(dcp, message);
 	else
@@ -1879,13 +567,19 @@ int iomfb_start_rtkit(struct apple_dcp *dcp)
 
 void iomfb_shutdown(struct apple_dcp *dcp)
 {
-	struct dcp_set_power_state_req req = {
-		/* defaults are ok */
-	};
-
 	/* We're going down */
 	dcp->active = false;
 	dcp->valid_mode = false;
 
-	dcp_set_power_state(dcp, false, &req, NULL, NULL);
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_shutdown_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_2:
+		iomfb_shutdown_v13_2(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
+	}
 }
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 54b34fbba94894..0c8061bb96e3e0 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -6,6 +6,8 @@
 
 #include <linux/types.h>
 
+#include "version_utils.h"
+
 /* Fixed size of shared memory between DCP and AP */
 #define DCP_SHMEM_SIZE 0x100000
 
@@ -106,35 +108,6 @@ struct dcp_rect {
  */
 #define IOMFB_SET_BACKGROUND	BIT(31)
 
-struct dcp_swap {
-	u64 ts1;
-	u64 ts2;
-	u64 unk_10[6];
-	u64 flags1;
-	u64 flags2;
-
-	u32 swap_id;
-
-	u32 surf_ids[SWAP_SURFACES];
-	struct dcp_rect src_rect[SWAP_SURFACES];
-	u32 surf_flags[SWAP_SURFACES];
-	u32 surf_unk[SWAP_SURFACES];
-	struct dcp_rect dst_rect[SWAP_SURFACES];
-	u32 swap_enabled;
-	u32 swap_completed;
-
-	u32 bg_color;
-	u8 unk_110[0x1b8];
-	u32 unk_2c8;
-	u8 unk_2cc[0x14];
-	u32 unk_2e0;
-	u16 unk_2e2;
-	u64 bl_unk;
-	u32 bl_value; // min value is 0x10000000
-	u8  bl_power; // constant 0x40 for on
-	u8 unk_2f3[0x2d];
-} __packed;
-
 /* Information describing a plane of a planar compressed surface */
 struct dcp_plane_info {
 	u32 width;
@@ -154,38 +127,6 @@ struct dcp_component_types {
 	u8 types[7];
 } __packed;
 
-/* Information describing a surface */
-struct dcp_surface {
-	u8 is_tiled;
-	u8 is_tearing_allowed;
-	u8 is_premultiplied;
-	u32 plane_cnt;
-	u32 plane_cnt2;
-	u32 format; /* DCP fourcc */
-	u32 ycbcr_matrix;
-	u8 xfer_func;
-	u8 colorspace;
-	u32 stride;
-	u16 pix_size;
-	u8 pel_w;
-	u8 pel_h;
-	u32 offset;
-	u32 width;
-	u32 height;
-	u32 buf_size;
-	u64 protection_opts;
-	u32 surface_id;
-	struct dcp_component_types comp_types[MAX_PLANES];
-	u64 has_comp;
-	struct dcp_plane_info planes[MAX_PLANES];
-	u64 has_planes;
-	u32 compression_info[MAX_PLANES][13];
-	u64 has_compr_info;
-	u32 unk_num;
-	u32 unk_denom;
-	u8 padding[7];
-} __packed;
-
 struct dcp_rt_bandwidth {
 	u64 unk1;
 	u64 reg_scratch;
@@ -218,14 +159,22 @@ enum dcpep_method {
 	iomfbep_a132_backlight_service_matched,
 	iomfbep_a358_vi_set_temperature_hint,
 	iomfbep_get_color_remap_mode,
+	iomfbep_last_client_close,
 	dcpep_num_methods
 };
 
+#define IOMFB_METHOD(tag, name) [name] = { #name, tag }
+
 struct dcp_method_entry {
 	const char *name;
 	char tag[4];
 };
 
+#define IOMFB_MAX_CB (1000)
+struct apple_dcp;
+
+typedef bool (*iomfb_cb_handler)(struct apple_dcp *, int, void *, void *);
+
 /* Prototypes */
 
 struct dcp_set_digital_out_mode_req {
@@ -287,21 +236,6 @@ struct dcp_map_physical_resp {
 	u32 mem_desc_id;
 } __packed;
 
-struct dcp_map_reg_req {
-	char obj[4];
-	u32 index;
-	u32 flags;
-	u8 addr_null;
-	u8 length_null;
-	u8 padding[2];
-} __packed;
-
-struct dcp_map_reg_resp {
-	u64 addr;
-	u64 length;
-	u32 ret;
-} __packed;
-
 struct dcp_swap_start_req {
 	u32 swap_id;
 	struct dcp_iouserclient client;
@@ -316,34 +250,6 @@ struct dcp_swap_start_resp {
 	u32 ret;
 } __packed;
 
-struct dcp_swap_submit_req {
-	struct dcp_swap swap;
-	struct dcp_surface surf[SWAP_SURFACES];
-	u64 surf_iova[SWAP_SURFACES];
-	u8 unkbool;
-	u64 unkdouble;
-	u32 clear; // or maybe switch to default fb?
-	u8 swap_null;
-	u8 surf_null[SWAP_SURFACES];
-	u8 unkoutbool_null;
-	u8 padding[1];
-} __packed;
-
-struct dcp_swap_submit_resp {
-	u8 unkoutbool;
-	u32 ret;
-	u8 padding[3];
-} __packed;
-
-struct dc_swap_complete_resp {
-	u32 swap_id;
-	u8 unkbool;
-	u64 swap_data;
-	u8 swap_info[0x6c4];
-	u32 unkint;
-	u8 swap_info_null;
-} __packed;
-
 struct dcp_get_uint_prop_req {
 	char obj[4];
 	char key[0x40];
@@ -435,4 +341,13 @@ struct iomfb_get_color_remap_mode_resp {
 	u32 ret;
 } __packed;
 
+struct iomfb_last_client_close_req {
+	u8 unkint_null;
+	u8 padding[3];
+} __packed;
+
+struct iomfb_last_client_close_resp {
+	u32 unkint;
+} __packed;
+
 #endif
diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h
new file mode 100644
index 00000000000000..401b6ec32848d3
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_internal.h
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#include <drm/drm_modes.h>
+#include <drm/drm_rect.h>
+
+#include "dcp-internal.h"
+
+struct apple_dcp;
+
+typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
+
+
+#define DCP_THUNK_VOID(func, handle)                                         \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
+			 void *cookie)                                       \
+	{                                                                    \
+		dcp_push(dcp, oob, &dcp_methods[handle], 0, 0, NULL, cb, cookie);          \
+	}
+
+#define DCP_THUNK_OUT(func, handle, T)                                       \
+	static void func(struct apple_dcp *dcp, bool oob, dcp_callback_t cb, \
+			 void *cookie)                                       \
+	{                                                                    \
+		dcp_push(dcp, oob, &dcp_methods[handle], 0, sizeof(T), NULL, cb, cookie);  \
+	}
+
+#define DCP_THUNK_IN(func, handle, T)                                       \
+	static void func(struct apple_dcp *dcp, bool oob, T *data,          \
+			 dcp_callback_t cb, void *cookie)                   \
+	{                                                                   \
+		dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T), 0, data, cb, cookie); \
+	}
+
+#define DCP_THUNK_INOUT(func, handle, T_in, T_out)                            \
+	static void func(struct apple_dcp *dcp, bool oob, T_in *data,         \
+			 dcp_callback_t cb, void *cookie)                     \
+	{                                                                     \
+		dcp_push(dcp, oob, &dcp_methods[handle], sizeof(T_in), sizeof(T_out), data, \
+			 cb, cookie);                                         \
+	}
+
+#define IOMFB_THUNK_INOUT(name)                                     \
+	static void iomfb_ ## name(struct apple_dcp *dcp, bool oob, \
+			struct iomfb_ ## name ## _req *data,        \
+			dcp_callback_t cb, void *cookie)            \
+	{                                                           \
+		dcp_push(dcp, oob, &dcp_methods[iomfbep_ ## name],                \
+			 sizeof(struct iomfb_ ## name ## _req),     \
+			 sizeof(struct iomfb_ ## name ## _resp),    \
+			 data,  cb, cookie);                        \
+	}
+
+/*
+ * Define type-safe trampolines. Define typedefs to enforce type-safety on the
+ * input data (so if the types don't match, gcc errors out).
+ */
+
+#define TRAMPOLINE_VOID(func, handler)                                        \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
+		handler(dcp);                                                 \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_IN(func, handler, T_in)                                    \
+	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);       \
+                                                                              \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		callback_##handler cb = handler;                              \
+                                                                              \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
+		cb(dcp, in);                                                  \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                          \
+	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);      \
+                                                                              \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		T_out *typed_out = out;                                       \
+		callback_##handler cb = handler;                              \
+                                                                              \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
+		*typed_out = cb(dcp, in);                                     \
+		return true;                                                  \
+	}
+
+#define TRAMPOLINE_OUT(func, handler, T_out)                                  \
+	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	{                                                                     \
+		T_out *typed_out = out;                                       \
+                                                                              \
+		trace_iomfb_callback(dcp, tag, #handler);                     \
+		*typed_out = handler(dcp);                                    \
+		return true;                                                  \
+	}
+
+/* Call a DCP function given by a tag */
+void dcp_push(struct apple_dcp *dcp, bool oob, const struct dcp_method_entry *call,
+		     u32 in_len, u32 out_len, void *data, dcp_callback_t cb,
+		     void *cookie);
+
+/* Parse a callback tag "D123" into the ID 123. Returns -EINVAL on failure. */
+int dcp_parse_tag(char tag[4]);
+
+void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context);
+
+/*
+ * DRM specifies rectangles as start and end coordinates.  DCP specifies
+ * rectangles as a start coordinate and a width/height. Convert a DRM rectangle
+ * to a DCP rectangle.
+ */
+struct dcp_rect drm_to_dcp_rect(struct drm_rect *rect);
+
+u32 drm_format_to_dcp(u32 drm);
+
+/* The user may own drm_display_mode, so we need to search for our copy */
+struct dcp_display_mode *lookup_mode(struct apple_dcp *dcp,
+					    const struct drm_display_mode *mode);
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
new file mode 100644
index 00000000000000..25ec66a2f24f53
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -0,0 +1,1344 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io>
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/align.h>
+#include <linux/bitmap.h>
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/delay.h>
+#include <linux/dma-mapping.h>
+#include <linux/iommu.h>
+#include <linux/kref.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/slab.h>
+
+#include <drm/drm_fb_dma_helper.h>
+#include <drm/drm_fourcc.h>
+#include <drm/drm_framebuffer.h>
+#include <drm/drm_gem_dma_helper.h>
+#include <drm/drm_probe_helper.h>
+#include <drm/drm_vblank.h>
+
+#include "dcp.h"
+#include "dcp-internal.h"
+#include "iomfb.h"
+#include "iomfb_internal.h"
+#include "parser.h"
+#include "trace.h"
+#include "version_utils.h"
+
+/* Register defines used in bandwidth setup structure */
+#define REG_SCRATCH (0x14)
+#define REG_SCRATCH_T600X (0x988)
+#define REG_DOORBELL (0x0)
+#define REG_DOORBELL_BIT (2)
+
+struct dcp_wait_cookie {
+	struct kref refcount;
+	struct completion done;
+};
+
+static void release_wait_cookie(struct kref *ref)
+{
+	struct dcp_wait_cookie *cookie;
+	cookie = container_of(ref, struct dcp_wait_cookie, refcount);
+
+        kfree(cookie);
+}
+
+DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched, u32);
+DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
+DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
+
+IOMFB_THUNK_INOUT(get_color_remap_mode);
+IOMFB_THUNK_INOUT(last_client_close);
+
+DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit,
+		struct DCP_FW_NAME(dcp_swap_submit_req),
+		struct DCP_FW_NAME(dcp_swap_submit_resp));
+
+DCP_THUNK_INOUT(dcp_swap_start, dcpep_swap_start, struct dcp_swap_start_req,
+		struct dcp_swap_start_resp);
+
+DCP_THUNK_INOUT(dcp_set_power_state, dcpep_set_power_state,
+		struct dcp_set_power_state_req,
+		struct dcp_set_power_state_resp);
+
+DCP_THUNK_INOUT(dcp_set_digital_out_mode, dcpep_set_digital_out_mode,
+		struct dcp_set_digital_out_mode_req, u32);
+
+DCP_THUNK_INOUT(dcp_set_display_device, dcpep_set_display_device, u32, u32);
+
+DCP_THUNK_OUT(dcp_set_display_refresh_properties,
+	      dcpep_set_display_refresh_properties, u32);
+
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+DCP_THUNK_INOUT(dcp_late_init_signal, dcpep_late_init_signal, u32, u32);
+#else
+DCP_THUNK_OUT(dcp_late_init_signal, dcpep_late_init_signal, u32);
+#endif
+DCP_THUNK_IN(dcp_flush_supports_power, dcpep_flush_supports_power, u32);
+DCP_THUNK_OUT(dcp_create_default_fb, dcpep_create_default_fb, u32);
+DCP_THUNK_OUT(dcp_start_signal, dcpep_start_signal, u32);
+DCP_THUNK_VOID(dcp_setup_video_limits, dcpep_setup_video_limits);
+DCP_THUNK_VOID(dcp_set_create_dfb, dcpep_set_create_dfb);
+DCP_THUNK_VOID(dcp_first_client_open, dcpep_first_client_open);
+
+DCP_THUNK_INOUT(dcp_set_parameter_dcp, dcpep_set_parameter_dcp,
+		struct dcp_set_parameter_dcp, u32);
+
+DCP_THUNK_INOUT(dcp_enable_disable_video_power_savings,
+		dcpep_enable_disable_video_power_savings, u32, int);
+
+DCP_THUNK_OUT(dcp_is_main_display, dcpep_is_main_display, u32);
+
+/* DCP callback handlers */
+static void dcpep_cb_nop(struct apple_dcp *dcp)
+{
+	/* No operation */
+}
+
+static u8 dcpep_cb_true(struct apple_dcp *dcp)
+{
+	return true;
+}
+
+static u8 dcpep_cb_false(struct apple_dcp *dcp)
+{
+	return false;
+}
+
+static u32 dcpep_cb_zero(struct apple_dcp *dcp)
+{
+	return 0;
+}
+
+static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
+				   struct DCP_FW_NAME(dc_swap_complete_resp) *resp)
+{
+	trace_iomfb_swap_complete(dcp, resp->swap_id);
+
+	dcp_drm_crtc_vblank(dcp->crtc);
+}
+
+/* special */
+static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	// ack D100 cb_match_pmu_service
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+	iomfb_a358_vi_set_temperature_hint(dcp, false,
+					   complete_vi_set_temperature_hint,
+					   NULL);
+
+	// return false for deferred ACK
+	return false;
+}
+
+static void complete_pmu_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+
+	*succ = true;
+
+	// ack D206 cb_match_pmu_service_2
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+
+	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
+				       out);
+
+	// return false for deferred ACK
+	return false;
+}
+
+static void complete_backlight_service_matched(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+
+	*succ = true;
+
+	// ack D206 cb_match_backlight_service
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+
+	iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
+
+	// return false for deferred ACK
+	return false;
+}
+
+static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *prop)
+{
+	switch (prop->id) {
+	case IOMFB_PROPERTY_NITS:
+	{
+		dcp->brightness.nits = prop->value / dcp->brightness.scale;
+		/* notify backlight device of the initial brightness */
+		if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
+			schedule_work(&dcp->bl_register_wq);
+		trace_iomfb_brightness(dcp, prop->value);
+		break;
+	}
+	default:
+		dev_dbg(dcp->dev, "pr_publish: id: %d = %u\n", prop->id, prop->value);
+	}
+}
+
+static struct dcp_get_uint_prop_resp
+dcpep_cb_get_uint_prop(struct apple_dcp *dcp, struct dcp_get_uint_prop_req *req)
+{
+	struct dcp_get_uint_prop_resp resp = (struct dcp_get_uint_prop_resp){
+	    .value = 0
+	};
+
+	if (dcp->panel.has_mini_led &&
+	    memcmp(req->obj, "SUMP", sizeof(req->obj)) == 0) { /* "PMUS */
+	    if (strncmp(req->key, "Temperature", sizeof(req->key)) == 0) {
+		/*
+		 * TODO: value from j314c, find out if it is temperature in
+		 *       centigrade C and which temperature sensor reports it
+		 */
+		resp.value = 3029;
+		resp.ret = true;
+	    }
+	}
+
+	return resp;
+}
+
+static u8 iomfbep_cb_sr_set_property_int(struct apple_dcp *dcp,
+					 struct iomfb_sr_set_property_int_req *req)
+{
+	if (memcmp(req->obj, "FMOI", sizeof(req->obj)) == 0) { /* "IOMF */
+		if (strncmp(req->key, "Brightness_Scale", sizeof(req->key)) == 0) {
+			if (!req->value_null)
+				dcp->brightness.scale = req->value;
+		}
+	}
+
+	return 1;
+}
+
+static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_prop_req *req)
+{
+    // TODO: trace this, see if there properties which needs to used later
+}
+
+/*
+ * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
+ * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
+ * stream of the display DART, rather than the expected DCP DART.
+ *
+ * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
+ * is a "fundamentally unsafe" operation according to the docs. And yet
+ * everyone does it...
+ */
+static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
+						   struct dcp_map_buf_req *req)
+{
+	struct sg_table *map;
+	int ret;
+
+	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
+		goto reject;
+
+	map = &dcp->memdesc[req->buffer].map;
+
+	if (!map->sgl)
+		goto reject;
+
+	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
+	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+
+	if (ret)
+		goto reject;
+
+	return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) };
+
+reject:
+	dev_err(dcp->dev, "denying map of invalid buffer %llx for piodma\n",
+		req->buffer);
+	return (struct dcp_map_buf_resp){ .ret = EINVAL };
+}
+
+static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
+				  struct dcp_unmap_buf_resp *resp)
+{
+	struct sg_table *map;
+	dma_addr_t dma_addr;
+
+	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
+		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
+			 resp->buffer);
+		return;
+	}
+
+	map = &dcp->memdesc[resp->buffer].map;
+
+	if (!map->sgl) {
+		dev_warn(dcp->dev,
+			 "unmap for non-mapped buffer %llu iova:0x%08llx",
+			 resp->buffer, resp->dva);
+		return;
+	}
+
+	dma_addr = sg_dma_address(map->sgl);
+	if (dma_addr != resp->dva) {
+		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
+			 resp->buffer, dma_addr, resp->dva);
+		return;
+	}
+
+	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
+	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+}
+
+/*
+ * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
+ * physically contigiuous, however we should save the sgtable in case the
+ * buffer needs to be later mapped for PIODMA.
+ */
+static struct dcp_allocate_buffer_resp
+dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
+			 struct dcp_allocate_buffer_req *req)
+{
+	struct dcp_allocate_buffer_resp resp = { 0 };
+	struct dcp_mem_descriptor *memdesc;
+	u32 id;
+
+	resp.dva_size = ALIGN(req->size, 4096);
+	resp.mem_desc_id =
+		find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+
+	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
+		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
+		resp.dva_size = 0;
+		resp.mem_desc_id = 0;
+		return resp;
+	}
+	id = resp.mem_desc_id;
+	set_bit(id, dcp->memdesc_map);
+
+	memdesc = &dcp->memdesc[id];
+
+	memdesc->size = resp.dva_size;
+	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size,
+					  &memdesc->dva, GFP_KERNEL);
+
+	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
+			memdesc->size);
+	resp.dva = memdesc->dva;
+
+	return resp;
+}
+
+static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
+{
+	struct dcp_mem_descriptor *memdesc;
+	u32 id = *mem_desc_id;
+
+	if (id >= DCP_MAX_MAPPINGS) {
+		dev_warn(dcp->dev,
+			 "unmap request for out of range mem_desc_id %u", id);
+		return 0;
+	}
+
+	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
+		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
+			 id);
+		return 0;
+	}
+
+	memdesc = &dcp->memdesc[id];
+	if (memdesc->buf) {
+		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
+				  memdesc->dva);
+
+		memdesc->buf = NULL;
+		memset(&memdesc->map, 0, sizeof(memdesc->map));
+	} else {
+		memdesc->reg = 0;
+	}
+
+	memdesc->size = 0;
+
+	return 1;
+}
+
+/* Validate that the specified region is a display register */
+static bool is_disp_register(struct apple_dcp *dcp, u64 start, u64 end)
+{
+	int i;
+
+	for (i = 0; i < dcp->nr_disp_registers; ++i) {
+		struct resource *r = dcp->disp_registers[i];
+
+		if ((start >= r->start) && (end <= r->end))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Map contiguous physical memory into the DCP's address space. The firmware
+ * uses this to map the display registers we advertise in
+ * sr_map_device_memory_with_index, so we bounds check against that to guard
+ * safe against malicious coprocessors.
+ */
+static struct dcp_map_physical_resp
+dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
+{
+	int size = ALIGN(req->size, 4096);
+	u32 id;
+
+	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
+		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
+			req->paddr, req->size);
+		return (struct dcp_map_physical_resp){};
+	}
+
+	id = find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
+	set_bit(id, dcp->memdesc_map);
+	dcp->memdesc[id].size = size;
+	dcp->memdesc[id].reg = req->paddr;
+
+	return (struct dcp_map_physical_resp){
+		.dva_size = size,
+		.mem_desc_id = id,
+		.dva = dma_map_resource(dcp->dev, req->paddr, size,
+					DMA_BIDIRECTIONAL, 0),
+	};
+}
+
+static u64 dcpep_cb_get_frequency(struct apple_dcp *dcp)
+{
+	return clk_get_rate(dcp->clk);
+}
+
+static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *dcp,
+						struct DCP_FW_NAME(dcp_map_reg_req) *req)
+{
+	if (req->index >= dcp->nr_disp_registers) {
+		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
+			 req->index);
+
+		return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 };
+	} else {
+		struct resource *rsrc = dcp->disp_registers[req->index];
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+		dma_addr_t dva = dma_map_resource(dcp->dev, rsrc->start, resource_size(rsrc),
+						  DMA_BIDIRECTIONAL, 0);
+		WARN_ON(dva == DMA_MAPPING_ERROR);
+#endif
+
+		return (struct DCP_FW_NAME(dcp_map_reg_resp)){
+			.addr = rsrc->start,
+			.length = resource_size(rsrc),
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+			.dva = dva,
+#endif
+		};
+	}
+}
+
+static struct dcp_read_edt_data_resp
+dcpep_cb_read_edt_data(struct apple_dcp *dcp, struct dcp_read_edt_data_req *req)
+{
+	return (struct dcp_read_edt_data_resp){
+		.value[0] = req->value[0],
+		.ret = 0,
+	};
+}
+
+static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
+							 u8 *enabled)
+{
+	/*
+	 * update backlight brightness on next swap, on non mini-LED displays
+	 * DCP seems to set an invalid iDAC value after coming out of DPMS.
+	 * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
+	 */
+	dcp->brightness.update = true;
+}
+
+/* Chunked data transfer for property dictionaries */
+static u8 dcpep_cb_prop_start(struct apple_dcp *dcp, u32 *length)
+{
+	if (dcp->chunks.data != NULL) {
+		dev_warn(dcp->dev, "ignoring spurious transfer start\n");
+		return false;
+	}
+
+	dcp->chunks.length = *length;
+	dcp->chunks.data = devm_kzalloc(dcp->dev, *length, GFP_KERNEL);
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "failed to allocate chunks\n");
+		return false;
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_chunk(struct apple_dcp *dcp,
+			      struct dcp_set_dcpav_prop_chunk_req *req)
+{
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious chunk\n");
+		return false;
+	}
+
+	if (req->offset + req->length > dcp->chunks.length) {
+		dev_warn(dcp->dev, "ignoring overflowing chunk\n");
+		return false;
+	}
+
+	memcpy(dcp->chunks.data + req->offset, req->data, req->length);
+	return true;
+}
+
+static bool dcpep_process_chunks(struct apple_dcp *dcp,
+				 struct dcp_set_dcpav_prop_end_req *req)
+{
+	struct dcp_parse_ctx ctx;
+	int ret;
+
+	if (!dcp->chunks.data) {
+		dev_warn(dcp->dev, "ignoring spurious end\n");
+		return false;
+	}
+
+	/* used just as opaque pointer for tracing */
+	ctx.dcp = dcp;
+
+	ret = parse(dcp->chunks.data, dcp->chunks.length, &ctx);
+
+	if (ret) {
+		dev_warn(dcp->dev, "bad header on dcpav props\n");
+		return false;
+	}
+
+	if (!strcmp(req->key, "TimingElements")) {
+		dcp->modes = enumerate_modes(&ctx, &dcp->nr_modes,
+					     dcp->width_mm, dcp->height_mm,
+					     dcp->notch_height);
+
+		if (IS_ERR(dcp->modes)) {
+			dev_warn(dcp->dev, "failed to parse modes\n");
+			dcp->modes = NULL;
+			dcp->nr_modes = 0;
+			return false;
+		}
+	} else if (!strcmp(req->key, "DisplayAttributes")) {
+		/* DisplayAttributes are empty for integrated displays, use
+		 * display dimensions read from the devicetree
+		 */
+		if (dcp->main_display) {
+			ret = parse_display_attributes(&ctx, &dcp->width_mm,
+						&dcp->height_mm);
+
+			if (ret) {
+				dev_warn(dcp->dev, "failed to parse display attribs\n");
+				return false;
+			}
+		}
+
+		dcp_set_dimensions(dcp);
+	}
+
+	return true;
+}
+
+static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
+			    struct dcp_set_dcpav_prop_end_req *req)
+{
+	u8 resp = dcpep_process_chunks(dcp, req);
+
+	/* Reset for the next transfer */
+	devm_kfree(dcp->dev, dcp->chunks.data);
+	dcp->chunks.data = NULL;
+
+	return resp;
+}
+
+/* Boot sequence */
+static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_channel *ch = &dcp->ch_cb;
+	u8 *succ = ch->output[ch->depth - 1];
+	dev_dbg(dcp->dev, "boot done");
+
+	*succ = true;
+	dcp_ack(dcp, DCP_CONTEXT_CB);
+}
+
+static void boot_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_set_display_refresh_properties(dcp, false, boot_done, NULL);
+}
+
+static void boot_4(struct apple_dcp *dcp, void *out, void *cookie)
+{
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u32 v_true = 1;
+	dcp_late_init_signal(dcp, false, &v_true, boot_5, NULL);
+#else
+	dcp_late_init_signal(dcp, false, boot_5, NULL);
+#endif
+}
+
+static void boot_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 v_true = true;
+
+	dcp_flush_supports_power(dcp, false, &v_true, boot_4, NULL);
+}
+
+static void boot_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_setup_video_limits(dcp, false, boot_3, NULL);
+}
+
+static void boot_1_5(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_create_default_fb(dcp, false, boot_2, NULL);
+}
+
+/* Use special function signature to defer the ACK */
+static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
+{
+	trace_iomfb_callback(dcp, tag, __func__);
+	dcp_set_create_dfb(dcp, false, boot_1_5, NULL);
+	return false;
+}
+
+static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
+{
+	if (dcp->disp_registers[5] && dcp->disp_registers[6])
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch =
+				dcp->disp_registers[5]->start + REG_SCRATCH,
+			.reg_doorbell =
+				dcp->disp_registers[6]->start + REG_DOORBELL,
+			.doorbell_bit = REG_DOORBELL_BIT,
+
+			.padding[3] = 0x4, // XXX: required by 11.x firmware
+		};
+	else if (dcp->disp_registers[4])
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch = dcp->disp_registers[4]->start +
+				       REG_SCRATCH_T600X,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
+	else
+		return (struct dcp_rt_bandwidth){
+			.reg_scratch = 0,
+			.reg_doorbell = 0,
+			.doorbell_bit = 0,
+		};
+}
+
+/* Callback to get the current time as milliseconds since the UNIX epoch */
+static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
+{
+	return ktime_to_ms(ktime_get_real());
+}
+
+struct dcp_swap_cookie {
+	struct kref refcount;
+	struct completion done;
+	u32 swap_id;
+};
+
+static void release_swap_cookie(struct kref *ref)
+{
+	struct dcp_swap_cookie *cookie;
+	cookie = container_of(ref, struct dcp_swap_cookie, refcount);
+
+        kfree(cookie);
+}
+
+static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		complete(&info->done);
+		kref_put(&info->refcount, release_swap_cookie);
+	}
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap_clear failed! status %u\n", resp->ret);
+		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
+	}
+}
+
+static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
+				   void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
+	DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
+
+	if (cookie) {
+		struct dcp_swap_cookie *info = cookie;
+		info->swap_id = resp->swap_id;
+	}
+
+	dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swap_cleared, cookie);
+}
+
+static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (wait) {
+		complete(&wait->done);
+		kref_put(&wait->refcount, release_wait_cookie);
+	}
+}
+
+static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_power_state_req req = {
+		.unklong = 1,
+	};
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
+}
+
+static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_parameter_dcp param = {
+		.param = 14,
+		.value = { 0 },
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+		.count = 3,
+#else
+		.count = 1,
+#endif
+	};
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
+}
+
+void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp)
+{
+	struct dcp_wait_cookie *cookie;
+	int ret;
+	u32 handle;
+	dev_info(dcp->dev, "dcp_poweron() starting\n");
+
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+
+	init_completion(&cookie->done);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
+
+	if (dcp->main_display) {
+		handle = 0;
+		dcp_set_display_device(dcp, false, &handle, dcp_on_set_power_state,
+				       cookie);
+	} else {
+		handle = 2;
+		dcp_set_display_device(dcp, false, &handle,
+				       dcp_on_set_parameter, cookie);
+	}
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "wait for power timed out");
+
+	kref_put(&cookie->refcount, release_wait_cookie);;
+
+	/* Force a brightness update after poweron, to restore the brightness */
+	dcp->brightness.update = true;
+}
+
+static void complete_set_powerstate(struct apple_dcp *dcp, void *out,
+				    void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+
+	if (wait) {
+		complete(&wait->done);
+		kref_put(&wait->refcount, release_wait_cookie);
+	}
+}
+
+static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_power_state_req power_req = {
+		.unklong = 0,
+	};
+	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate,
+			    cookie);
+}
+
+void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
+{
+	int ret, swap_id;
+	struct iomfb_last_client_close_req last_client_req = {};
+	struct dcp_swap_cookie *cookie;
+	struct dcp_wait_cookie *poff_cookie;
+	struct dcp_swap_start_req swap_req = { 0 };
+	struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap);
+
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+	init_completion(&cookie->done);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
+
+	// clear surfaces
+	memset(swap, 0, sizeof(*swap));
+
+	swap->swap.swap_enabled =
+		swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF;
+	swap->swap.bg_color = 0xFF000000;
+
+	/*
+	 * Turn off the backlight. This matters because the DCP's idea of
+	 * backlight brightness gets desynced after a power change, and it
+	 * needs to be told it's going to turn off so it will consider the
+	 * subsequent update on poweron an actual change and restore the
+	 * brightness.
+	 */
+	swap->swap.bl_unk = 1;
+	swap->swap.bl_value = 0;
+	swap->swap.bl_power = 0;
+
+	for (int l = 0; l < SWAP_SURFACES; l++)
+		swap->surf_null[l] = true;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	for (int l = 0; l < 5; l++)
+		swap->surf2_null[l] = true;
+	swap->unkU32Ptr_null = true;
+	swap->unkU32out_null = true;
+#endif
+
+	dcp_swap_start(dcp, false, &swap_req, dcp_swap_clear_started, cookie);
+
+	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(50));
+	swap_id = cookie->swap_id;
+	kref_put(&cookie->refcount, release_swap_cookie);
+	if (ret <= 0) {
+		dcp->crashed = true;
+		return;
+	}
+
+	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
+
+	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
+	if (!poff_cookie)
+		return;
+	init_completion(&poff_cookie->done);
+	kref_init(&poff_cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&poff_cookie->refcount);
+
+	iomfb_last_client_close(dcp, false, &last_client_req,
+				last_client_closed_poff, poff_cookie);
+	ret = wait_for_completion_timeout(&poff_cookie->done,
+					  msecs_to_jiffies(1000));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
+	else if (ret > 0)
+		dev_dbg(dcp->dev,
+			"setPowerState(0) finished with %d ms to spare",
+			jiffies_to_msecs(ret));
+
+	kref_put(&poff_cookie->refcount, release_wait_cookie);
+	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
+
+	dev_info(dcp->dev, "dcp_poweroff() done\n");
+}
+
+static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct dcp_set_power_state_req power_req = {
+		.unklong = 0,
+	};
+	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie);
+}
+
+void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
+{
+	int ret;
+	struct iomfb_last_client_close_req req = {};
+
+	struct dcp_wait_cookie *cookie;
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie)
+		return;
+	init_completion(&cookie->done);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
+
+	iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep,
+				cookie);
+	ret = wait_for_completion_timeout(&cookie->done,
+					  msecs_to_jiffies(1000));
+
+	if (ret == 0)
+		dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms", 1000);
+
+	kref_put(&cookie->refcount, release_wait_cookie);
+	dev_dbg(dcp->dev, "%s: setDCPPower(0) done", __func__);
+
+	dev_info(dcp->dev, "dcp_sleep() done\n");
+}
+
+static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
+{
+	struct apple_connector *connector = dcp->connector;
+
+	/* DCP issues hotplug_gated callbacks after SetPowerState() calls on
+	 * devices with display (macbooks, imacs). This must not result in
+	 * connector state changes on DRM side. Some applications won't enable
+	 * a CRTC with a connector in disconnected state. Weston after DPMS off
+	 * is one example. dcp_is_main_display() returns true on devices with
+	 * integrated display. Ignore the hotplug_gated() callbacks there.
+	 */
+	if (dcp->main_display)
+		return;
+
+	/* Hotplug invalidates mode. DRM doesn't always handle this. */
+	if (!(*connected)) {
+		dcp->valid_mode = false;
+		/* after unplug swap will not complete until the next
+		 * set_digital_out_mode */
+		schedule_work(&dcp->vblank_wq);
+	}
+
+	if (connector && connector->connected != !!(*connected)) {
+		connector->connected = !!(*connected);
+		dcp->valid_mode = false;
+		schedule_work(&connector->hotplug_wq);
+	}
+}
+
+static void
+dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
+				    struct dcp_swap_complete_intent_gated *info)
+{
+	trace_iomfb_swap_complete_intent_gated(dcp, info->swap_id,
+		info->width, info->height);
+}
+
+TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
+TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
+TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
+TRAMPOLINE_OUT(trampoline_zero, dcpep_cb_zero, u32);
+TRAMPOLINE_IN(trampoline_swap_complete, dcpep_cb_swap_complete,
+	      struct DCP_FW_NAME(dc_swap_complete_resp));
+TRAMPOLINE_INOUT(trampoline_get_uint_prop, dcpep_cb_get_uint_prop,
+		 struct dcp_get_uint_prop_req, struct dcp_get_uint_prop_resp);
+TRAMPOLINE_IN(trampoline_set_fx_prop, iomfbep_cb_set_fx_prop,
+	      struct iomfb_set_fx_prop_req)
+TRAMPOLINE_INOUT(trampoline_map_piodma, dcpep_cb_map_piodma,
+		 struct dcp_map_buf_req, struct dcp_map_buf_resp);
+TRAMPOLINE_IN(trampoline_unmap_piodma, dcpep_cb_unmap_piodma,
+	      struct dcp_unmap_buf_resp);
+TRAMPOLINE_INOUT(trampoline_sr_set_property_int, iomfbep_cb_sr_set_property_int,
+		 struct iomfb_sr_set_property_int_req, u8);
+TRAMPOLINE_INOUT(trampoline_allocate_buffer, dcpep_cb_allocate_buffer,
+		 struct dcp_allocate_buffer_req,
+		 struct dcp_allocate_buffer_resp);
+TRAMPOLINE_INOUT(trampoline_map_physical, dcpep_cb_map_physical,
+		 struct dcp_map_physical_req, struct dcp_map_physical_resp);
+TRAMPOLINE_INOUT(trampoline_release_mem_desc, dcpep_cb_release_mem_desc, u32,
+		 u8);
+TRAMPOLINE_INOUT(trampoline_map_reg, dcpep_cb_map_reg,
+		 struct DCP_FW_NAME(dcp_map_reg_req),
+		 struct DCP_FW_NAME(dcp_map_reg_resp));
+TRAMPOLINE_INOUT(trampoline_read_edt_data, dcpep_cb_read_edt_data,
+		 struct dcp_read_edt_data_req, struct dcp_read_edt_data_resp);
+TRAMPOLINE_INOUT(trampoline_prop_start, dcpep_cb_prop_start, u32, u8);
+TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
+		 struct dcp_set_dcpav_prop_chunk_req, u8);
+TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
+		 struct dcp_set_dcpav_prop_end_req, u8);
+TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
+	       struct dcp_rt_bandwidth);
+TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
+TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
+TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
+TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
+	      dcpep_cb_swap_complete_intent_gated,
+	      struct dcp_swap_complete_intent_gated);
+TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
+	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
+TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
+	      struct iomfb_property);
+
+/*
+ * Callback for swap requests. If a swap failed, we'll never get a swap
+ * complete event so we need to fake a vblank event early to avoid a hang.
+ */
+
+static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
+
+	if (resp->ret) {
+		dev_err(dcp->dev, "swap failed! status %u\n", resp->ret);
+		dcp_drm_crtc_vblank(dcp->crtc);
+		return;
+	}
+
+	while (!list_empty(&dcp->swapped_out_fbs)) {
+		struct dcp_fb_reference *entry;
+		entry = list_first_entry(&dcp->swapped_out_fbs,
+					 struct dcp_fb_reference, head);
+		if (entry->fb)
+			drm_framebuffer_put(entry->fb);
+		list_del(&entry->head);
+		kfree(entry);
+	}
+}
+
+static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_resp *resp = data;
+
+	DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
+
+	trace_iomfb_swap_submit(dcp, resp->swap_id);
+	dcp_swap_submit(dcp, false, &DCP_FW_UNION(dcp->swap), dcp_swapped, NULL);
+}
+
+/* Helpers to modeset and swap, used to flush */
+static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct dcp_swap_start_req start_req = { 0 };
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (dcp->connector && dcp->connector->connected)
+		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
+	else
+		dcp_drm_crtc_vblank(dcp->crtc);
+}
+
+static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
+					  void *cookie)
+{
+	struct dcp_wait_cookie *wait = cookie;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	if (wait) {
+		complete(&wait->done);
+		kref_put(&wait->refcount, release_wait_cookie);
+	}
+}
+
+void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state)
+{
+	struct drm_plane *plane;
+	struct drm_plane_state *new_state, *old_state;
+	struct drm_crtc_state *crtc_state;
+	struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap);
+	int plane_idx, l;
+	int has_surface = 0;
+	bool modeset;
+	dev_dbg(dcp->dev, "%s", __func__);
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+
+	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
+
+	/* Reset to defaults */
+	memset(req, 0, sizeof(*req));
+	for (l = 0; l < SWAP_SURFACES; l++)
+		req->surf_null[l] = true;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	for (l = 0; l < 5; l++)
+		req->surf2_null[l] = true;
+	req->unkU32Ptr_null = true;
+	req->unkU32out_null = true;
+#endif
+
+	/*
+	 * Clear all surfaces on startup. The boot framebuffer in surface 0
+	 * sticks around.
+	 */
+	if (!dcp->surfaces_cleared) {
+		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF;
+		req->swap.bg_color = 0xFF000000;
+		dcp->surfaces_cleared = true;
+	}
+
+	// Surface 0 has limitations at least on t600x.
+	l = 1;
+	for_each_oldnew_plane_in_state(state, plane, old_state, new_state, plane_idx) {
+		struct drm_framebuffer *fb = new_state->fb;
+		struct drm_gem_dma_object *obj;
+		struct drm_rect src_rect;
+		bool is_premultiplied = false;
+
+		/* skip planes not for this crtc */
+		if (old_state->crtc != crtc && new_state->crtc != crtc)
+			continue;
+
+		WARN_ON(l >= SWAP_SURFACES);
+
+		req->swap.swap_enabled |= BIT(l);
+
+		if (old_state->fb && fb != old_state->fb) {
+			/*
+			 * Race condition between a framebuffer unbind getting
+			 * swapped out and GEM unreferencing a framebuffer. If
+			 * we lose the race, the display gets IOVA faults and
+			 * the DCP crashes. We need to extend the lifetime of
+			 * the drm_framebuffer (and hence the GEM object) until
+			 * after we get a swap complete for the swap unbinding
+			 * it.
+			 */
+			struct dcp_fb_reference *entry =
+				kzalloc(sizeof(*entry), GFP_KERNEL);
+			if (entry) {
+				entry->fb = old_state->fb;
+				list_add_tail(&entry->head,
+					      &dcp->swapped_out_fbs);
+			}
+			drm_framebuffer_get(old_state->fb);
+		}
+
+		if (!new_state->fb) {
+			l += 1;
+			continue;
+		}
+		req->surf_null[l] = false;
+		has_surface = 1;
+
+		/*
+		 * DCP doesn't support XBGR8 / XRGB8 natively. Blending as
+		 * pre-multiplied alpha with a black background can be used as
+		 * workaround for the bottommost plane.
+		 */
+		if (fb->format->format == DRM_FORMAT_XRGB8888 ||
+		    fb->format->format == DRM_FORMAT_XBGR8888)
+		    is_premultiplied = true;
+
+		drm_rect_fp_to_int(&src_rect, &new_state->src);
+
+		req->swap.src_rect[l] = drm_to_dcp_rect(&src_rect);
+		req->swap.dst_rect[l] = drm_to_dcp_rect(&new_state->dst);
+
+		if (dcp->notch_height > 0)
+			req->swap.dst_rect[l].y += dcp->notch_height;
+
+		/* the obvious helper call drm_fb_dma_get_gem_addr() adjusts
+		 * the address for source x/y offsets. Since IOMFB has a direct
+		 * support source position prefer that.
+		 */
+		obj = drm_fb_dma_get_gem_obj(fb, 0);
+		if (obj)
+			req->surf_iova[l] = obj->dma_addr + fb->offsets[0];
+
+		req->surf[l] = (struct DCP_FW_NAME(dcp_surface)){
+			.is_premultiplied = is_premultiplied,
+			.format = drm_format_to_dcp(fb->format->format),
+			.xfer_func = DCP_XFER_FUNC_SDR,
+			.colorspace = DCP_COLORSPACE_NATIVE,
+			.stride = fb->pitches[0],
+			.width = fb->width,
+			.height = fb->height,
+			.buf_size = fb->height * fb->pitches[0],
+			.surface_id = req->swap.surf_ids[l],
+
+			/* Only used for compressed or multiplanar surfaces */
+			.pix_size = 1,
+			.pel_w = 1,
+			.pel_h = 1,
+			.has_comp = 1,
+			.has_planes = 1,
+		};
+
+		l += 1;
+	}
+
+	if (modeset) {
+		struct dcp_display_mode *mode;
+		struct dcp_wait_cookie *cookie;
+		int ret;
+
+		mode = lookup_mode(dcp, &crtc_state->mode);
+		if (!mode) {
+			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
+				 DRM_MODE_ARG(&crtc_state->mode));
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
+			 mode->color_mode_id, mode->timing_mode_id);
+		dcp->mode = (struct dcp_set_digital_out_mode_req){
+			.color_mode_id = mode->color_mode_id,
+			.timing_mode_id = mode->timing_mode_id
+		};
+
+		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+		if (!cookie) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		init_completion(&cookie->done);
+		kref_init(&cookie->refcount);
+		/* increase refcount to ensure the receiver has a reference */
+		kref_get(&cookie->refcount);
+
+		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
+					 complete_set_digital_out_mode, cookie);
+
+		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
+		ret = wait_for_completion_timeout(&cookie->done,
+						  msecs_to_jiffies(500));
+
+		kref_put(&cookie->refcount, release_wait_cookie);
+
+		if (ret == 0) {
+			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
+			schedule_work(&dcp->vblank_wq);
+			return;
+		} else if (ret > 0) {
+			dev_dbg(dcp->dev,
+				"set_digital_out_mode finished with %d to spare",
+				jiffies_to_msecs(ret));
+		}
+
+		dcp->valid_mode = true;
+	}
+
+	if (!has_surface && !crtc_state->color_mgmt_changed) {
+		if (crtc_state->enable && crtc_state->active &&
+		    !crtc_state->planes_changed) {
+			schedule_work(&dcp->vblank_wq);
+			return;
+		}
+
+		/* Set black background */
+		req->swap.swap_enabled |= IOMFB_SET_BACKGROUND;
+		req->swap.bg_color = 0xFF000000;
+		req->clear = 1;
+	}
+
+	/* These fields should be set together */
+	req->swap.swap_completed = req->swap.swap_enabled;
+
+	/* update brightness if changed */
+	if (dcp->brightness.update) {
+		req->swap.bl_unk = 1;
+		req->swap.bl_value = dcp->brightness.dac;
+		req->swap.bl_power = 0x40;
+		dcp->brightness.update = false;
+	}
+
+	do_swap(dcp, NULL, NULL);
+}
+
+static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct apple_connector *connector;
+	int result = *(int *)out;
+	dev_info(dcp->dev, "DCP is_main_display: %d\n", result);
+
+	dcp->main_display = result != 0;
+
+	connector = dcp->connector;
+	if (connector) {
+		connector->connected = dcp->nr_modes > 0;
+		schedule_work(&connector->hotplug_wq);
+	}
+
+	dcp->active = true;
+	complete(&dcp->start_done);
+}
+
+static void init_3(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_is_main_display(dcp, false, res_is_main_display, NULL);
+}
+
+static void init_2(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	dcp_first_client_open(dcp, false, init_3, NULL);
+}
+
+static void init_1(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	u32 val = 0;
+	dcp_enable_disable_video_power_savings(dcp, false, &val, init_2, NULL);
+}
+
+static void dcp_started(struct apple_dcp *dcp, void *data, void *cookie)
+{
+	struct iomfb_get_color_remap_mode_req color_remap =
+		(struct iomfb_get_color_remap_mode_req){
+			.mode = 6,
+		};
+
+	dev_info(dcp->dev, "DCP booted\n");
+
+	iomfb_get_color_remap_mode(dcp, false, &color_remap, init_1, cookie);
+}
+
+void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp)
+{
+	struct dcp_set_power_state_req req = {
+		/* defaults are ok */
+	};
+
+	dcp_set_power_state(dcp, false, &req, NULL, NULL);
+}
diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h
new file mode 100644
index 00000000000000..539ec65e5825f4
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_template.h
@@ -0,0 +1,181 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+/*
+ * This file is intended to be included multiple times with IOMFB_VER
+ * defined to declare DCP firmware version dependent structs.
+ */
+
+#ifdef DCP_FW_VER
+
+#include <drm/drm_crtc.h>
+
+#include <linux/types.h>
+
+#include "iomfb.h"
+#include "version_utils.h"
+
+struct DCP_FW_NAME(dcp_swap) {
+	u64 ts1;
+	u64 ts2;
+	u64 unk_10[6];
+	u64 flags1;
+	u64 flags2;
+
+	u32 swap_id;
+
+	u32 surf_ids[SWAP_SURFACES];
+	struct dcp_rect src_rect[SWAP_SURFACES];
+	u32 surf_flags[SWAP_SURFACES];
+	u32 surf_unk[SWAP_SURFACES];
+	struct dcp_rect dst_rect[SWAP_SURFACES];
+	u32 swap_enabled;
+	u32 swap_completed;
+
+	u32 bg_color;
+	u8 unk_110[0x1b8];
+	u32 unk_2c8;
+	u8 unk_2cc[0x14];
+	u32 unk_2e0;
+#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
+	u16 unk_2e2;
+#else
+	u8 unk_2e2[3];
+#endif
+	u64 bl_unk;
+	u32 bl_value; // min value is 0x10000000
+	u8  bl_power; // constant 0x40 for on
+	u8 unk_2f3[0x2d];
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u8 unk_320[0x13f];
+#endif
+} __packed;
+
+/* Information describing a surface */
+struct DCP_FW_NAME(dcp_surface) {
+	u8 is_tiled;
+	u8 is_tearing_allowed;
+	u8 is_premultiplied;
+	u32 plane_cnt;
+	u32 plane_cnt2;
+	u32 format; /* DCP fourcc */
+	u32 ycbcr_matrix;
+	u8 xfer_func;
+	u8 colorspace;
+	u32 stride;
+	u16 pix_size;
+	u8 pel_w;
+	u8 pel_h;
+	u32 offset;
+	u32 width;
+	u32 height;
+	u32 buf_size;
+	u64 protection_opts;
+	u32 surface_id;
+	struct dcp_component_types comp_types[MAX_PLANES];
+	u64 has_comp;
+	struct dcp_plane_info planes[MAX_PLANES];
+	u64 has_planes;
+	u32 compression_info[MAX_PLANES][13];
+	u64 has_compr_info;
+	u32 unk_num;
+	u32 unk_denom;
+#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
+	u8 padding[7];
+#else
+	u8 padding[47];
+#endif
+} __packed;
+
+/* Prototypes */
+
+struct DCP_FW_NAME(dcp_swap_submit_req) {
+	struct DCP_FW_NAME(dcp_swap) swap;
+	struct DCP_FW_NAME(dcp_surface) surf[SWAP_SURFACES];
+	u64 surf_iova[SWAP_SURFACES];
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u64 unk_u64_a[SWAP_SURFACES];
+	struct DCP_FW_NAME(dcp_surface) surf2[5];
+	u64 surf2_iova[5];
+#endif
+	u8 unkbool;
+	u64 unkdouble;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u64 unkU64;
+	u8 unkbool2;
+#endif
+	u32 clear; // or maybe switch to default fb?
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u32 unkU32Ptr;
+#endif
+	u8 swap_null;
+	u8 surf_null[SWAP_SURFACES];
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u8 surf2_null[5];
+#endif
+	u8 unkoutbool_null;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u8 unkU32Ptr_null;
+	u8 unkU32out_null;
+#endif
+	u8 padding[1];
+} __packed;
+
+struct DCP_FW_NAME(dcp_swap_submit_resp) {
+	u8 unkoutbool;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u32 unkU32out;
+#endif
+	u32 ret;
+	u8 padding[3];
+} __packed;
+
+struct DCP_FW_NAME(dc_swap_complete_resp) {
+	u32 swap_id;
+	u8 unkbool;
+	u64 swap_data;
+#if DCP_FW_VER < DCP_FW_VERSION(13, 2, 0)
+	u8 swap_info[0x6c4];
+#else
+	u8 swap_info[0x6c5];
+#endif
+	u32 unkint;
+	u8 swap_info_null;
+} __packed;
+
+struct DCP_FW_NAME(dcp_map_reg_req) {
+	char obj[4];
+	u32 index;
+	u32 flags;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u8 unk_u64_null;
+#endif
+	u8 addr_null;
+	u8 length_null;
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u8 padding[1];
+#else
+	u8 padding[2];
+#endif
+} __packed;
+
+struct DCP_FW_NAME(dcp_map_reg_resp) {
+#if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
+	u64 dva;
+#endif
+	u64 addr;
+	u64 length;
+	u32 ret;
+} __packed;
+
+
+struct apple_dcp;
+
+void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state);
+void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp);
+void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp);
+void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp);
+void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp);
+void DCP_FW_NAME(iomfb_shutdown)(struct apple_dcp *dcp);
+
+#endif
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
new file mode 100644
index 00000000000000..354abbfdb24c36
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#include "iomfb_v12_3.h"
+#include "iomfb_v13_2.h"
+#include "version_utils.h"
+
+static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
+	IOMFB_METHOD("A000", dcpep_late_init_signal),
+	IOMFB_METHOD("A029", dcpep_setup_video_limits),
+	IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched),
+	IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched),
+	IOMFB_METHOD("A357", dcpep_set_create_dfb),
+	IOMFB_METHOD("A358", iomfbep_a358_vi_set_temperature_hint),
+	IOMFB_METHOD("A401", dcpep_start_signal),
+	IOMFB_METHOD("A407", dcpep_swap_start),
+	IOMFB_METHOD("A408", dcpep_swap_submit),
+	IOMFB_METHOD("A410", dcpep_set_display_device),
+	IOMFB_METHOD("A411", dcpep_is_main_display),
+	IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
+	IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
+	IOMFB_METHOD("A439", dcpep_set_parameter_dcp),
+	IOMFB_METHOD("A443", dcpep_create_default_fb),
+	IOMFB_METHOD("A447", dcpep_enable_disable_video_power_savings),
+	IOMFB_METHOD("A454", dcpep_first_client_open),
+	IOMFB_METHOD("A455", iomfbep_last_client_close),
+	IOMFB_METHOD("A460", dcpep_set_display_refresh_properties),
+	IOMFB_METHOD("A463", dcpep_flush_supports_power),
+	IOMFB_METHOD("A468", dcpep_set_power_state),
+};
+
+#define DCP_FW v12_3
+#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0)
+
+#include "iomfb_template.c"
+
+static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
+	[0] = trampoline_true, /* did_boot_signal */
+	[1] = trampoline_true, /* did_power_on_signal */
+	[2] = trampoline_nop, /* will_power_off_signal */
+	[3] = trampoline_rt_bandwidth,
+	[100] = iomfbep_cb_match_pmu_service,
+	[101] = trampoline_zero, /* get_display_default_stride */
+	[102] = trampoline_nop, /* set_number_property */
+	[103] = trampoline_nop, /* set_boolean_property */
+	[106] = trampoline_nop, /* remove_property */
+	[107] = trampoline_true, /* create_provider_service */
+	[108] = trampoline_true, /* create_product_service */
+	[109] = trampoline_true, /* create_pmu_service */
+	[110] = trampoline_true, /* create_iomfb_service */
+	[111] = trampoline_true, /* create_backlight_service */
+	[116] = dcpep_cb_boot_1,
+	[117] = trampoline_false, /* is_dark_boot */
+	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
+	[120] = trampoline_read_edt_data,
+	[122] = trampoline_prop_start,
+	[123] = trampoline_prop_chunk,
+	[124] = trampoline_prop_end,
+	[201] = trampoline_map_piodma,
+	[202] = trampoline_unmap_piodma,
+	[206] = iomfbep_cb_match_pmu_service_2,
+	[207] = iomfbep_cb_match_backlight_service,
+	[208] = trampoline_get_time,
+	[211] = trampoline_nop, /* update_backlight_factor_prop */
+	[300] = trampoline_pr_publish,
+	[401] = trampoline_get_uint_prop,
+	[404] = trampoline_nop, /* sr_set_uint_prop */
+	[406] = trampoline_set_fx_prop,
+	[408] = trampoline_get_frequency,
+	[411] = trampoline_map_reg,
+	[413] = trampoline_true, /* sr_set_property_dict */
+	[414] = trampoline_sr_set_property_int,
+	[415] = trampoline_true, /* sr_set_property_bool */
+	[451] = trampoline_allocate_buffer,
+	[452] = trampoline_map_physical,
+	[456] = trampoline_release_mem_desc,
+	[552] = trampoline_true, /* set_property_dict_0 */
+	[561] = trampoline_true, /* set_property_dict */
+	[563] = trampoline_true, /* set_property_int */
+	[565] = trampoline_true, /* set_property_bool */
+	[567] = trampoline_true, /* set_property_str */
+	[574] = trampoline_zero, /* power_up_dart */
+	[576] = trampoline_hotplug,
+	[577] = trampoline_nop, /* powerstate_notify */
+	[582] = trampoline_true, /* create_default_fb_surface */
+	[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
+	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
+	[589] = trampoline_swap_complete,
+	[591] = trampoline_swap_complete_intent_gated,
+	[593] = trampoline_enable_backlight_message_ap_gated,
+	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
+	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
+	[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
+	[598] = trampoline_nop, /* find_swap_function_gated */
+};
+
+void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp)
+{
+	dcp->cb_handlers = cb_handlers;
+
+	dcp_start_signal(dcp, false, dcp_started, NULL);
+}
+
+#undef DCP_FW_VER
+#undef DCP_FW
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.h b/drivers/gpu/drm/apple/iomfb_v12_3.h
new file mode 100644
index 00000000000000..7359685d981fe5
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#ifndef __APPLE_IOMFB_V12_3_H__
+#define __APPLE_IOMFB_V12_3_H__
+
+#include "version_utils.h"
+
+#define DCP_FW v12_3
+#define DCP_FW_VER DCP_FW_VERSION(12, 3, 0)
+
+#include "iomfb_template.h"
+
+#undef DCP_FW_VER
+#undef DCP_FW
+
+#endif /* __APPLE_IOMFB_V12_3_H__ */
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c
new file mode 100644
index 00000000000000..27f1d84e928a69
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_v13_2.c
@@ -0,0 +1,105 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#include "iomfb_v12_3.h"
+#include "iomfb_v13_2.h"
+#include "version_utils.h"
+
+static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
+	IOMFB_METHOD("A000", dcpep_late_init_signal),
+	IOMFB_METHOD("A029", dcpep_setup_video_limits),
+	IOMFB_METHOD("A131", iomfbep_a131_pmu_service_matched),
+	IOMFB_METHOD("A132", iomfbep_a132_backlight_service_matched),
+	IOMFB_METHOD("A373", dcpep_set_create_dfb),
+	IOMFB_METHOD("A374", iomfbep_a358_vi_set_temperature_hint),
+	IOMFB_METHOD("A401", dcpep_start_signal),
+	IOMFB_METHOD("A407", dcpep_swap_start),
+	IOMFB_METHOD("A408", dcpep_swap_submit),
+	IOMFB_METHOD("A410", dcpep_set_display_device),
+	IOMFB_METHOD("A411", dcpep_is_main_display),
+	IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
+	IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
+	IOMFB_METHOD("A441", dcpep_set_parameter_dcp),
+	IOMFB_METHOD("A445", dcpep_create_default_fb),
+	IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings),
+	IOMFB_METHOD("A456", dcpep_first_client_open),
+	IOMFB_METHOD("A457", iomfbep_last_client_close),
+	IOMFB_METHOD("A462", dcpep_set_display_refresh_properties),
+	IOMFB_METHOD("A465", dcpep_flush_supports_power),
+	IOMFB_METHOD("A471", dcpep_set_power_state),
+};
+
+#define DCP_FW v13_2
+#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0)
+
+#include "iomfb_template.c"
+
+static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
+	[0] = trampoline_true, /* did_boot_signal */
+	[1] = trampoline_true, /* did_power_on_signal */
+	[2] = trampoline_nop, /* will_power_off_signal */
+	[3] = trampoline_rt_bandwidth,
+	[100] = iomfbep_cb_match_pmu_service,
+	[101] = trampoline_zero, /* get_display_default_stride */
+	[102] = trampoline_nop, /* set_number_property */
+	[103] = trampoline_nop, /* set_boolean_property */
+	[106] = trampoline_nop, /* remove_property */
+	[107] = trampoline_true, /* create_provider_service */
+	[108] = trampoline_true, /* create_product_service */
+	[109] = trampoline_true, /* create_pmu_service */
+	[110] = trampoline_true, /* create_iomfb_service */
+	[111] = trampoline_true, /* create_backlight_service */
+	[112] = trampoline_true, /* create_nvram_servce? */
+	[119] = dcpep_cb_boot_1,
+	[120] = trampoline_false, /* is_dark_boot */
+	[121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
+	[123] = trampoline_read_edt_data,
+	[125] = trampoline_prop_start,
+	[126] = trampoline_prop_chunk,
+	[127] = trampoline_prop_end,
+	[201] = trampoline_map_piodma,
+	[202] = trampoline_unmap_piodma,
+	[206] = iomfbep_cb_match_pmu_service_2,
+	[207] = iomfbep_cb_match_backlight_service,
+	[208] = trampoline_get_time,
+	[211] = trampoline_nop, /* update_backlight_factor_prop */
+	[300] = trampoline_pr_publish,
+	[401] = trampoline_get_uint_prop,
+	[404] = trampoline_nop, /* sr_set_uint_prop */
+	[406] = trampoline_set_fx_prop,
+	[408] = trampoline_get_frequency,
+	[411] = trampoline_map_reg,
+	[413] = trampoline_true, /* sr_set_property_dict */
+	[414] = trampoline_sr_set_property_int,
+	[415] = trampoline_true, /* sr_set_property_bool */
+	[451] = trampoline_allocate_buffer,
+	[452] = trampoline_map_physical,
+	[456] = trampoline_release_mem_desc,
+	[552] = trampoline_true, /* set_property_dict_0 */
+	[561] = trampoline_true, /* set_property_dict */
+	[563] = trampoline_true, /* set_property_int */
+	[565] = trampoline_true, /* set_property_bool */
+	[567] = trampoline_true, /* set_property_str */
+	[574] = trampoline_zero, /* power_up_dart */
+	[576] = trampoline_hotplug,
+	[577] = trampoline_nop, /* powerstate_notify */
+	[582] = trampoline_true, /* create_default_fb_surface */
+	[584] = trampoline_nop, /* IOMobileFramebufferAP::clear_default_surface */
+	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
+	[589] = trampoline_swap_complete,
+	[591] = trampoline_swap_complete_intent_gated,
+	[593] = trampoline_enable_backlight_message_ap_gated,
+	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
+	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
+	[597] = trampoline_false, /* IOMobileFramebufferAP::preserveContents */
+	[598] = trampoline_nop, /* find_swap_function_gated */
+};
+void DCP_FW_NAME(iomfb_start)(struct apple_dcp *dcp)
+{
+	dcp->cb_handlers = cb_handlers;
+
+	dcp_start_signal(dcp, false, dcp_started, NULL);
+}
+
+#undef DCP_FW_VER
+#undef DCP_FW
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_2.h
new file mode 100644
index 00000000000000..f3810b727235bc
--- /dev/null
+++ b/drivers/gpu/drm/apple/iomfb_v13_2.h
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#ifndef __APPLE_IOMFB_V13_2_H__
+#define __APPLE_IOMFB_V13_2_H__
+
+#include "version_utils.h"
+
+#define DCP_FW v13_2
+#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0)
+
+#include "iomfb_template.h"
+
+#undef DCP_FW_VER
+#undef DCP_FW
+
+#endif /* __APPLE_IOMFB_V13_2_H__ */
diff --git a/drivers/gpu/drm/apple/version_utils.h b/drivers/gpu/drm/apple/version_utils.h
new file mode 100644
index 00000000000000..5a33ce1db61c47
--- /dev/null
+++ b/drivers/gpu/drm/apple/version_utils.h
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#ifndef __APPLE_VERSION_UTILS_H__
+#define __APPLE_VERSION_UTILS_H__
+
+#include <linux/kernel.h>
+#include <linux/args.h>
+
+#define DCP_FW_UNION(u) (u).DCP_FW
+#define DCP_FW_SUFFIX CONCATENATE(_, DCP_FW)
+#define DCP_FW_NAME(name) CONCATENATE(name, DCP_FW_SUFFIX)
+#define DCP_FW_VERSION(x, y, z) ( ((x) << 16) | ((y) << 8) | (z) )
+
+#endif /*__APPLE_VERSION_UTILS_H__*/

From 7e4b69014c0fc48f030d9bc11d7e2fd2e2ea4744 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 9 Mar 2023 12:44:51 +0100
Subject: [PATCH 0588/1027] drm/apple: ignore surf[3] in clear swap calls

MacOS 13.2 does the same and it is unclear if surf[3] can be used at
all. PRobably not necessary but found during debugging to firmware 13.2.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 25ec66a2f24f53..ed49088953bb64 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -841,7 +841,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 	memset(swap, 0, sizeof(*swap));
 
 	swap->swap.swap_enabled =
-		swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0xF;
+		swap->swap.swap_completed = IOMFB_SET_BACKGROUND | 0x7;
 	swap->swap.bg_color = 0xFF000000;
 
 	/*
@@ -1113,7 +1113,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 	 * sticks around.
 	 */
 	if (!dcp->surfaces_cleared) {
-		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0xF;
+		req->swap.swap_enabled = IOMFB_SET_BACKGROUND | 0x7;
 		req->swap.bg_color = 0xFF000000;
 		dcp->surfaces_cleared = true;
 	}

From b268b511c3778804c0403f7bba672ab41d9781a4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 Mar 2023 21:38:50 +0100
Subject: [PATCH 0589/1027] drm/apple: Support color transformation matrices

kwin 5.27.3 adds support for "Night Color" via drm "CTM" properties.
Wire CTM support up via the "set_matrix" iomfb call.

Link: https://bugs.kde.org/show_bug.cgi?id=455720
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c      |  1 +
 drivers/gpu/drm/apple/iomfb.h          | 14 ++++++++++++++
 drivers/gpu/drm/apple/iomfb_template.c | 20 +++++++++++++++++++-
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  1 +
 drivers/gpu/drm/apple/iomfb_v13_2.c    |  1 +
 5 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 0b5d6b2d462c6f..5cc6ddbf28d483 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -335,6 +335,7 @@ static int apple_probe_per_dcp(struct device *dev,
 		return ret;
 
 	drm_crtc_helper_add(&crtc->base, &apple_crtc_helper_funcs);
+	drm_crtc_enable_color_mgmt(&crtc->base, 0, true, 0);
 
 	enc = drmm_simple_encoder_alloc(drm, struct apple_encoder, base,
 					DRM_MODE_ENCODER_TMDS);
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 0c8061bb96e3e0..7cda9124bcf96b 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -160,6 +160,7 @@ enum dcpep_method {
 	iomfbep_a358_vi_set_temperature_hint,
 	iomfbep_get_color_remap_mode,
 	iomfbep_last_client_close,
+	iomfbep_set_matrix,
 	dcpep_num_methods
 };
 
@@ -350,4 +351,17 @@ struct iomfb_last_client_close_resp {
 	u32 unkint;
 } __packed;
 
+struct iomfb_set_matrix_req {
+	u32 unk_u32; // maybe length?
+	u64 r[3];
+	u64 g[3];
+	u64 b[3];
+	u8 matrix_null;
+	u8 padding[3];
+} __packed;
+
+struct iomfb_set_matrix_resp {
+	u32 ret;
+} __packed;
+
 #endif
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index ed49088953bb64..10286b1ad0f96d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -55,6 +55,7 @@ DCP_THUNK_OUT(iomfb_a131_pmu_service_matched, iomfbep_a131_pmu_service_matched,
 DCP_THUNK_OUT(iomfb_a132_backlight_service_matched, iomfbep_a132_backlight_service_matched, u32);
 DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperature_hint, u32);
 
+IOMFB_THUNK_INOUT(set_matrix);
 IOMFB_THUNK_INOUT(get_color_remap_mode);
 IOMFB_THUNK_INOUT(last_client_close);
 
@@ -1285,7 +1286,24 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 		dcp->brightness.update = false;
 	}
 
-	do_swap(dcp, NULL, NULL);
+	if (crtc_state->color_mgmt_changed && crtc_state->ctm) {
+		struct iomfb_set_matrix_req mat;
+		struct drm_color_ctm *ctm = (struct drm_color_ctm *)crtc_state->ctm->data;
+
+		mat.unk_u32 = 9;
+		mat.r[0] = ctm->matrix[0];
+		mat.r[1] = ctm->matrix[1];
+		mat.r[2] = ctm->matrix[2];
+		mat.g[0] = ctm->matrix[3];
+		mat.g[1] = ctm->matrix[4];
+		mat.g[2] = ctm->matrix[5];
+		mat.b[0] = ctm->matrix[6];
+		mat.b[1] = ctm->matrix[7];
+		mat.b[2] = ctm->matrix[8];
+
+		iomfb_set_matrix(dcp, false, &mat, do_swap, NULL);
+	} else
+		do_swap(dcp, NULL, NULL);
 }
 
 static void res_is_main_display(struct apple_dcp *dcp, void *out, void *cookie)
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index 354abbfdb24c36..c226a1139a84c8 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	IOMFB_METHOD("A410", dcpep_set_display_device),
 	IOMFB_METHOD("A411", dcpep_is_main_display),
 	IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
+	IOMFB_METHOD("A422", iomfbep_set_matrix),
 	IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
 	IOMFB_METHOD("A439", dcpep_set_parameter_dcp),
 	IOMFB_METHOD("A443", dcpep_create_default_fb),
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c
index 27f1d84e928a69..63ae1e79adda10 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_2.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_2.c
@@ -18,6 +18,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	IOMFB_METHOD("A410", dcpep_set_display_device),
 	IOMFB_METHOD("A411", dcpep_is_main_display),
 	IOMFB_METHOD("A412", dcpep_set_digital_out_mode),
+	IOMFB_METHOD("A422", iomfbep_set_matrix),
 	IOMFB_METHOD("A426", iomfbep_get_color_remap_mode),
 	IOMFB_METHOD("A441", dcpep_set_parameter_dcp),
 	IOMFB_METHOD("A445", dcpep_create_default_fb),

From d8bfaf63a5fe6ed986bed009f306f17fe84bd77b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 23 Mar 2023 08:40:42 +0100
Subject: [PATCH 0590/1027] drm/apple: Drop unsupported DRM_FORMAT_ARGB2101010

Depends on https://gitlab.freedesktop.org/asahi/mesa/-/merge_requests/5

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 1 -
 drivers/gpu/drm/apple/iomfb.c     | 1 -
 2 files changed, 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 5cc6ddbf28d483..d6d03708142888 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -146,7 +146,6 @@ static const struct drm_plane_funcs apple_plane_funcs = {
  */
 static const u32 dcp_formats[] = {
 	DRM_FORMAT_XRGB2101010,
-	DRM_FORMAT_ARGB2101010,
 	DRM_FORMAT_XRGB8888,
 	DRM_FORMAT_ARGB8888,
 	DRM_FORMAT_XBGR8888,
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 447de730af721f..1ce32eae212ddb 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -412,7 +412,6 @@ u32 drm_format_to_dcp(u32 drm)
 	case DRM_FORMAT_ABGR8888:
 		return fourcc_code('A', 'B', 'G', 'R');
 
-	case DRM_FORMAT_ARGB2101010:
 	case DRM_FORMAT_XRGB2101010:
 		return fourcc_code('r', '0', '3', 'w');
 	}

From 334d98443ef57b4088cbc4fcefcbd292614f0b29 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 21:57:22 +0900
Subject: [PATCH 0591/1027] dcp: Allow unused trampolines

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/iomfb_internal.h | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_internal.h b/drivers/gpu/drm/apple/iomfb_internal.h
index 401b6ec32848d3..09f8857d30c341 100644
--- a/drivers/gpu/drm/apple/iomfb_internal.h
+++ b/drivers/gpu/drm/apple/iomfb_internal.h
@@ -57,7 +57,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
  */
 
 #define TRAMPOLINE_VOID(func, handler)                                        \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
 	{                                                                     \
 		trace_iomfb_callback(dcp, tag, #handler);                     \
 		handler(dcp);                                                 \
@@ -67,7 +67,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
 #define TRAMPOLINE_IN(func, handler, T_in)                                    \
 	typedef void (*callback_##handler)(struct apple_dcp *, T_in *);       \
                                                                               \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
 	{                                                                     \
 		callback_##handler cb = handler;                              \
                                                                               \
@@ -79,7 +79,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
 #define TRAMPOLINE_INOUT(func, handler, T_in, T_out)                          \
 	typedef T_out (*callback_##handler)(struct apple_dcp *, T_in *);      \
                                                                               \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
 	{                                                                     \
 		T_out *typed_out = out;                                       \
 		callback_##handler cb = handler;                              \
@@ -90,7 +90,7 @@ typedef void (*dcp_callback_t)(struct apple_dcp *, void *, void *);
 	}
 
 #define TRAMPOLINE_OUT(func, handler, T_out)                                  \
-	static bool func(struct apple_dcp *dcp, int tag, void *out, void *in) \
+	static bool __maybe_unused func(struct apple_dcp *dcp, int tag, void *out, void *in) \
 	{                                                                     \
 		T_out *typed_out = out;                                       \
                                                                               \

From cc0cd4601d412ff2a6756a2a3225566f3a7374ff Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 21:57:38 +0900
Subject: [PATCH 0592/1027] dcp: Add get_tiling_state

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/iomfb.h          | 13 +++++++++++++
 drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++
 drivers/gpu/drm/apple/iomfb_v13_2.c    |  2 ++
 3 files changed, 27 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 7cda9124bcf96b..6602bf19107d43 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -364,4 +364,17 @@ struct iomfb_set_matrix_resp {
 	u32 ret;
 } __packed;
 
+struct dcpep_get_tiling_state_req {
+	u32 event;
+	u32 param;
+	u32 value;
+	u8 value_null;
+	u8 padding[3];
+} __packed;
+
+struct dcpep_get_tiling_state_resp {
+	u32 value;
+	u32 ret;
+} __packed;
+
 #endif
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 10286b1ad0f96d..9f8186e4ae6846 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -977,6 +977,16 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 		info->width, info->height);
 }
 
+static struct dcpep_get_tiling_state_resp
+dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
+			  struct dcpep_get_tiling_state_req *req)
+{
+	return (struct dcpep_get_tiling_state_resp){
+		.value = 0,
+		.ret = 1,
+	};
+}
+
 TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
 TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
 TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
@@ -1022,6 +1032,8 @@ TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
 	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
 TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
 	      struct iomfb_property);
+TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
+		 struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
 
 /*
  * Callback for swap requests. If a swap failed, we'll never get a swap
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_2.c
index 63ae1e79adda10..356a2aa2433be0 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_2.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_2.c
@@ -51,6 +51,8 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[110] = trampoline_true, /* create_iomfb_service */
 	[111] = trampoline_true, /* create_backlight_service */
 	[112] = trampoline_true, /* create_nvram_servce? */
+	[113] = trampoline_get_tiling_state,
+	[114] = trampoline_false, /* set_tiling_state */
 	[119] = dcpep_cb_boot_1,
 	[120] = trampoline_false, /* is_dark_boot */
 	[121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/

From 502ef5eede6c6a503efec52dbf183e3e06e12fc7 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 22:54:28 +0900
Subject: [PATCH 0593/1027] dcp: 42-bit DMA masks

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/apple_drv.c    | 2 +-
 drivers/gpu/drm/apple/dcp.c          | 2 +-
 drivers/gpu/drm/apple/dummy-piodma.c | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index d6d03708142888..8220b7d7aa062e 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -460,7 +460,7 @@ static int apple_drm_init(struct device *dev)
 	resource_size_t fb_size;
 	int ret;
 
-	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42));
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index b49a15be0fb103..ac64fdeb6570e3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -419,7 +419,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	u32 cpu_ctrl;
 	int ret;
 
-	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36));
+	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(42));
 	if (ret)
 		return ret;
 
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
index ec5d4fecf356c0..eaa0476854a603 100644
--- a/drivers/gpu/drm/apple/dummy-piodma.c
+++ b/drivers/gpu/drm/apple/dummy-piodma.c
@@ -26,7 +26,7 @@ static const struct component_ops dcp_piodma_comp_ops = {
 };
 static int dcp_piodma_probe(struct platform_device *pdev)
 {
-	int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(36));
+	int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
 	if (ret)
 		return ret;
 

From 6d35aae9c9688cf1a55588579636d07e5ede723b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 22:55:25 +0900
Subject: [PATCH 0594/1027] dcp: T602X bwreq support

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/iomfb_template.c | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 9f8186e4ae6846..72b4429fb53b49 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -35,6 +35,7 @@
 /* Register defines used in bandwidth setup structure */
 #define REG_SCRATCH (0x14)
 #define REG_SCRATCH_T600X (0x988)
+#define REG_SCRATCH_T602X (0x1208)
 #define REG_DOORBELL (0x0)
 #define REG_DOORBELL_BIT (2)
 
@@ -636,7 +637,7 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
 
 static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 {
-	if (dcp->disp_registers[5] && dcp->disp_registers[6])
+	if (dcp->disp_registers[5] && dcp->disp_registers[6]) {
 		return (struct dcp_rt_bandwidth){
 			.reg_scratch =
 				dcp->disp_registers[5]->start + REG_SCRATCH,
@@ -646,19 +647,24 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 
 			.padding[3] = 0x4, // XXX: required by 11.x firmware
 		};
-	else if (dcp->disp_registers[4])
+	} else if (dcp->disp_registers[4]) {
+		u32 offset = REG_SCRATCH_T600X;
+		if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp"))
+			offset = REG_SCRATCH_T602X;
+
 		return (struct dcp_rt_bandwidth){
 			.reg_scratch = dcp->disp_registers[4]->start +
-				       REG_SCRATCH_T600X,
+				       offset,
 			.reg_doorbell = 0,
 			.doorbell_bit = 0,
 		};
-	else
+	} else {
 		return (struct dcp_rt_bandwidth){
 			.reg_scratch = 0,
 			.reg_doorbell = 0,
 			.doorbell_bit = 0,
 		};
+	}
 }
 
 /* Callback to get the current time as milliseconds since the UNIX epoch */

From 62bc9ea57971f9e8a57a77e26c899f12dab571b4 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 11 Apr 2023 22:55:46 +0900
Subject: [PATCH 0595/1027] dcp: Warn if DMA mapping fails

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/iomfb_template.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 72b4429fb53b49..66dad06ea995e2 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -412,6 +412,7 @@ static struct dcp_map_physical_resp
 dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 {
 	int size = ALIGN(req->size, 4096);
+	dma_addr_t dva;
 	u32 id;
 
 	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
@@ -425,11 +426,13 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 	dcp->memdesc[id].size = size;
 	dcp->memdesc[id].reg = req->paddr;
 
+	dva = dma_map_resource(dcp->dev, req->paddr, size, DMA_BIDIRECTIONAL, 0);
+	WARN_ON(dva == DMA_MAPPING_ERROR);
+
 	return (struct dcp_map_physical_resp){
 		.dva_size = size,
 		.mem_desc_id = id,
-		.dva = dma_map_resource(dcp->dev, req->paddr, size,
-					DMA_BIDIRECTIONAL, 0),
+		.dva = dva,
 	};
 }
 

From 1a0c1822a0997533cacfddced84e694ba5e5e347 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 14 Apr 2023 08:07:52 +0200
Subject: [PATCH 0596/1027] WIP: drm/apple: Port to incompatible V13.3 firmware
 interface

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile                |  2 +-
 drivers/gpu/drm/apple/dcp-internal.h          |  6 +--
 drivers/gpu/drm/apple/dcp.c                   |  4 +-
 drivers/gpu/drm/apple/iomfb.c                 | 24 ++++-----
 drivers/gpu/drm/apple/iomfb.h                 | 14 +++++
 drivers/gpu/drm/apple/iomfb_template.c        | 10 ++++
 drivers/gpu/drm/apple/iomfb_template.h        |  1 +
 drivers/gpu/drm/apple/iomfb_v12_3.c           |  2 +-
 .../apple/{iomfb_v13_2.c => iomfb_v13_3.c}    | 52 ++++++++++---------
 .../apple/{iomfb_v13_2.h => iomfb_v13_3.h}    | 10 ++--
 10 files changed, 76 insertions(+), 49 deletions(-)
 rename drivers/gpu/drm/apple/{iomfb_v13_2.c => iomfb_v13_3.c} (73%)
 rename drivers/gpu/drm/apple/{iomfb_v13_2.h => iomfb_v13_3.h} (52%)

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 3ee61beeb7857b..67e0bae6caf004 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -6,7 +6,7 @@ appledrm-y := apple_drv.o
 
 apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o
 apple_dcp-y += iomfb_v12_3.o
-apple_dcp-y += iomfb_v13_2.o
+apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
 apple_piodma-y := dummy-piodma.o
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 59777809a7f370..fee15867b5c0cf 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -12,7 +12,7 @@
 
 #include "iomfb.h"
 #include "iomfb_v12_3.h"
-#include "iomfb_v13_2.h"
+#include "iomfb_v13_3.h"
 
 #define DCP_MAX_PLANES 2
 
@@ -21,7 +21,7 @@ struct apple_dcp;
 enum dcp_firmware_version {
 	DCP_FIRMWARE_UNKNOWN,
 	DCP_FIRMWARE_V_12_3,
-	DCP_FIRMWARE_V_13_2,
+	DCP_FIRMWARE_V_13_3,
 };
 
 enum {
@@ -146,7 +146,7 @@ struct apple_dcp {
 	/* Queued swap. Owned by the DCP to avoid per-swap memory allocation */
 	union {
 		struct dcp_swap_submit_req_v12_3 v12_3;
-		struct dcp_swap_submit_req_v13_2 v13_2;
+		struct dcp_swap_submit_req_v13_3 v13_3;
 	} swap;
 
 	/* Current display mode */
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index ac64fdeb6570e3..9522690dfde89b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -403,8 +403,8 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
 
 	if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0)
 		return DCP_FIRMWARE_V_12_3;
-	if (strncmp(compat_str, "13.2.0", sizeof(compat_str)) == 0)
-		return DCP_FIRMWARE_V_13_2;
+	if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0)
+		return DCP_FIRMWARE_V_13_3;
 
 	dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n",
 		compat_str, fw_str);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 1ce32eae212ddb..335fa804ee24aa 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -225,8 +225,8 @@ void dcp_sleep(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_sleep_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_sleep_v13_2(dcp);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_sleep_v13_3(dcp);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
@@ -242,8 +242,8 @@ void dcp_poweron(struct platform_device *pdev)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_poweron_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_poweron_v13_2(dcp);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_poweron_v13_3(dcp);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
@@ -260,8 +260,8 @@ void dcp_poweroff(struct platform_device *pdev)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_poweroff_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_poweroff_v13_2(dcp);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_poweroff_v13_3(dcp);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
@@ -505,8 +505,8 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_flush_v12_3(dcp, crtc, state);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_flush_v13_2(dcp, crtc, state);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_flush_v13_3(dcp, crtc, state);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
@@ -521,8 +521,8 @@ static void iomfb_start(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_start_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_start_v13_2(dcp);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_start_v13_3(dcp);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
@@ -574,8 +574,8 @@ void iomfb_shutdown(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_shutdown_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_2:
-		iomfb_shutdown_v13_2(dcp);
+	case DCP_FIRMWARE_V_13_3:
+		iomfb_shutdown_v13_3(dcp);
 		break;
 	default:
 		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 6602bf19107d43..e8168338d374ef 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -136,6 +136,20 @@ struct dcp_rt_bandwidth {
 	u32 padding[7];
 } __packed;
 
+struct frame_sync_props {
+	u8 unk[28];
+};
+
+struct dcp_set_frame_sync_props_req {
+	struct frame_sync_props props;
+	u8 frame_sync_props_null;
+	u8 padding[3];
+} __packed;
+
+struct dcp_set_frame_sync_props_resp {
+	struct frame_sync_props props;
+} __packed;
+
 /* Method calls */
 
 enum dcpep_method {
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 66dad06ea995e2..67cf1499a86707 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -670,6 +670,13 @@ static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 	}
 }
 
+static struct dcp_set_frame_sync_props_resp
+dcpep_cb_set_frame_sync_props(struct apple_dcp *dcp,
+			      struct dcp_set_frame_sync_props_req *req)
+{
+	return (struct dcp_set_frame_sync_props_resp){};
+}
+
 /* Callback to get the current time as milliseconds since the UNIX epoch */
 static u64 dcpep_cb_get_time(struct apple_dcp *dcp)
 {
@@ -1031,6 +1038,9 @@ TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
 		 struct dcp_set_dcpav_prop_end_req, u8);
 TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
 	       struct dcp_rt_bandwidth);
+TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props,
+	       struct dcp_set_frame_sync_props_req,
+	       struct dcp_set_frame_sync_props_resp);
 TRAMPOLINE_OUT(trampoline_get_frequency, dcpep_cb_get_frequency, u64);
 TRAMPOLINE_OUT(trampoline_get_time, dcpep_cb_get_time, u64);
 TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h
index 539ec65e5825f4..e9c249609f46cb 100644
--- a/drivers/gpu/drm/apple/iomfb_template.h
+++ b/drivers/gpu/drm/apple/iomfb_template.h
@@ -48,6 +48,7 @@ struct DCP_FW_NAME(dcp_swap) {
 	u8 unk_2f3[0x2d];
 #if DCP_FW_VER >= DCP_FW_VERSION(13, 2, 0)
 	u8 unk_320[0x13f];
+	u64 unk_1;
 #endif
 } __packed;
 
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index c226a1139a84c8..8188321004a63f 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -2,7 +2,7 @@
 /* Copyright The Asahi Linux Contributors */
 
 #include "iomfb_v12_3.h"
-#include "iomfb_v13_2.h"
+#include "iomfb_v13_3.h"
 #include "version_utils.h"
 
 static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
similarity index 73%
rename from drivers/gpu/drm/apple/iomfb_v13_2.c
rename to drivers/gpu/drm/apple/iomfb_v13_3.c
index 356a2aa2433be0..18020c6cd39493 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_2.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -2,7 +2,7 @@
 /* Copyright The Asahi Linux Contributors */
 
 #include "iomfb_v12_3.h"
-#include "iomfb_v13_2.h"
+#include "iomfb_v13_3.h"
 #include "version_utils.h"
 
 static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
@@ -25,13 +25,13 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	IOMFB_METHOD("A449", dcpep_enable_disable_video_power_savings),
 	IOMFB_METHOD("A456", dcpep_first_client_open),
 	IOMFB_METHOD("A457", iomfbep_last_client_close),
-	IOMFB_METHOD("A462", dcpep_set_display_refresh_properties),
-	IOMFB_METHOD("A465", dcpep_flush_supports_power),
-	IOMFB_METHOD("A471", dcpep_set_power_state),
+	IOMFB_METHOD("A463", dcpep_set_display_refresh_properties),
+	IOMFB_METHOD("A466", dcpep_flush_supports_power),
+	IOMFB_METHOD("A472", dcpep_set_power_state),
 };
 
-#define DCP_FW v13_2
-#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0)
+#define DCP_FW v13_3
+#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0)
 
 #include "iomfb_template.c"
 
@@ -40,32 +40,34 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[1] = trampoline_true, /* did_power_on_signal */
 	[2] = trampoline_nop, /* will_power_off_signal */
 	[3] = trampoline_rt_bandwidth,
+	[6] = trampoline_set_frame_sync_props,
 	[100] = iomfbep_cb_match_pmu_service,
 	[101] = trampoline_zero, /* get_display_default_stride */
 	[102] = trampoline_nop, /* set_number_property */
-	[103] = trampoline_nop, /* set_boolean_property */
-	[106] = trampoline_nop, /* remove_property */
-	[107] = trampoline_true, /* create_provider_service */
-	[108] = trampoline_true, /* create_product_service */
-	[109] = trampoline_true, /* create_pmu_service */
-	[110] = trampoline_true, /* create_iomfb_service */
-	[111] = trampoline_true, /* create_backlight_service */
-	[112] = trampoline_true, /* create_nvram_servce? */
-	[113] = trampoline_get_tiling_state,
-	[114] = trampoline_false, /* set_tiling_state */
-	[119] = dcpep_cb_boot_1,
-	[120] = trampoline_false, /* is_dark_boot */
-	[121] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
-	[123] = trampoline_read_edt_data,
-	[125] = trampoline_prop_start,
-	[126] = trampoline_prop_chunk,
-	[127] = trampoline_prop_end,
+	[103] = trampoline_nop, /* trigger_user_cal_loader */
+	[104] = trampoline_nop, /* set_boolean_property */
+	[107] = trampoline_nop, /* remove_property */
+	[108] = trampoline_true, /* create_provider_service */
+	[109] = trampoline_true, /* create_product_service */
+	[110] = trampoline_true, /* create_pmu_service */
+	[111] = trampoline_true, /* create_iomfb_service */
+	[112] = trampoline_true, /* create_backlight_service */
+	[113] = trampoline_true, /* create_nvram_servce? */
+	[114] = trampoline_get_tiling_state,
+	[115] = trampoline_false, /* set_tiling_state */
+	[120] = dcpep_cb_boot_1,
+	[121] = trampoline_false, /* is_dark_boot */
+	[122] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
+	[124] = trampoline_read_edt_data,
+	[126] = trampoline_prop_start,
+	[127] = trampoline_prop_chunk,
+	[128] = trampoline_prop_end,
 	[201] = trampoline_map_piodma,
 	[202] = trampoline_unmap_piodma,
 	[206] = iomfbep_cb_match_pmu_service_2,
 	[207] = iomfbep_cb_match_backlight_service,
-	[208] = trampoline_get_time,
-	[211] = trampoline_nop, /* update_backlight_factor_prop */
+	[208] = trampoline_nop, /* update_backlight_factor_prop */
+	[209] = trampoline_get_time,
 	[300] = trampoline_pr_publish,
 	[401] = trampoline_get_uint_prop,
 	[404] = trampoline_nop, /* sr_set_uint_prop */
diff --git a/drivers/gpu/drm/apple/iomfb_v13_2.h b/drivers/gpu/drm/apple/iomfb_v13_3.h
similarity index 52%
rename from drivers/gpu/drm/apple/iomfb_v13_2.h
rename to drivers/gpu/drm/apple/iomfb_v13_3.h
index f3810b727235bc..bbb3156b40f893 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_2.h
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.h
@@ -1,17 +1,17 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
 /* Copyright The Asahi Linux Contributors */
 
-#ifndef __APPLE_IOMFB_V13_2_H__
-#define __APPLE_IOMFB_V13_2_H__
+#ifndef __APPLE_IOMFB_V13_3_H__
+#define __APPLE_IOMFB_V13_3_H__
 
 #include "version_utils.h"
 
-#define DCP_FW v13_2
-#define DCP_FW_VER DCP_FW_VERSION(13, 2, 0)
+#define DCP_FW v13_3
+#define DCP_FW_VER DCP_FW_VERSION(13, 3, 0)
 
 #include "iomfb_template.h"
 
 #undef DCP_FW_VER
 #undef DCP_FW
 
-#endif /* __APPLE_IOMFB_V13_2_H__ */
+#endif /* __APPLE_IOMFB_V13_3_H__ */

From c24f6a9c03023a15361d83d33b7c8320b84adb2b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 17 Sep 2024 20:02:15 +0200
Subject: [PATCH 0597/1027] squash! PCI: apple: Avoid PERST# deassertion
 through gpiod initialization

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/pci/controller/pcie-apple.c | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/pci/controller/pcie-apple.c b/drivers/pci/controller/pcie-apple.c
index d37e954fcd20e6..b0dd964a37b300 100644
--- a/drivers/pci/controller/pcie-apple.c
+++ b/drivers/pci/controller/pcie-apple.c
@@ -619,12 +619,13 @@ static int apple_pcie_setup_link(struct apple_pcie *pcie,
 	int ret;
 
 	/*
-	 * Leave PERST# as is. The Aquantia AQC113 10GB nic used desktop macs is
-	 * sensitive to deasserting it without prior clock setup.
+	 * Assert PERST# and configure the pin as output.
+	 * The Aquantia AQC113 10GB nic used desktop macs is sensitive to
+	 * deasserting it without prior clock setup.
 	 * Observed on M1 Max/Ultra Mac Studios under m1n1's hypervisor.
 	 */
 	reset = devm_fwnode_gpiod_get(pcie->dev, of_fwnode_handle(np), "reset",
-				      GPIOD_ASIS, "PERST#");
+				      GPIOD_OUT_HIGH, "PERST#");
 	if (IS_ERR(reset))
 		return PTR_ERR(reset);
 

From a0e6ad6c47ac1d0bd05f7aca1813667ec296ef9f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 20 Sep 2024 12:55:32 +0200
Subject: [PATCH 0598/1027] fixup! arm64: Implement Apple IMPDEF TSO memory
 model control

Signed-off-by: Janne Grunau <j@jannau.net>
---
 arch/arm64/kernel/cpufeature_impdef.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
index 9325d1eb12f402..96965d47f36a6e 100644
--- a/arch/arm64/kernel/cpufeature_impdef.c
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -44,6 +44,7 @@ static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 		.field_width = 1,
 		.sign = FTR_UNSIGNED,
 		.min_field_value = 1,
+		.max_field_value = 1,
 	},
 	{
 		.desc = "TSO memory model (Fixed)",

From 8c1a7823bb1547f41d13a696dece753a0a8a313d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 21 Sep 2024 22:54:43 +0900
Subject: [PATCH 0599/1027] arm64: cpufeature: Unify SCOPE_LOCAL_CPU early &
 late behavior

SCOPE_LOCAL_CPU is mostly used for CPU errata. The early feature logic
prior to this patch will enable a feature if any secondary matches it,
but will not do anything once the feature is already enabled.

However, the late CPU verification logic is more flexible, with flags:

- ARM64_CPUCAP_OPTIONAL_FOR_LATE_CPU means "any cpu" logic applies
- ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU means "all cpus" logic applies

This means the early secondary feature code right now behaves as if
OPTIONAL && !PERMITTED was specified (it ignores secondaries missing the
feature and forces the system state to active if any secondary has the
feature).

Change this so that the early feature detection code inspects the flags
too and applies the logic to make feature state consistent:

- If a feature is NOT OPTIONAL and missing on a secondary, remove it
  from the system set (we can do this before finalization)
- If a feature is PERMITTED and missing on the boot CPU, don't enable it
  but rather leave it off.

This allows SCOPE_LOCAL_CPU to be used for feature detection as well as
CPU errata.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 arch/arm64/kernel/cpufeature.c | 34 +++++++++++++++++++++++++++++++---
 1 file changed, 31 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c
index 31134846ea5641..d9c865fd0d0b3e 100644
--- a/arch/arm64/kernel/cpufeature.c
+++ b/arch/arm64/kernel/cpufeature.c
@@ -3144,10 +3144,38 @@ static void update_cpu_capabilities(u16 scope_mask)
 
 	scope_mask &= ARM64_CPUCAP_SCOPE_MASK;
 	for (i = 0; i < ARM64_NCAPS; i++) {
+		bool matches;
+
 		caps = cpucap_ptrs[i];
-		if (!caps || !(caps->type & scope_mask) ||
-		    cpus_have_cap(caps->capability) ||
-		    !caps->matches(caps, cpucap_default_scope(caps)))
+		if (!caps || !(caps->type & scope_mask))
+			continue;
+
+		if (!(scope_mask & SCOPE_LOCAL_CPU) && cpus_have_cap(caps->capability))
+			continue;
+
+		matches = caps->matches(caps, cpucap_default_scope(caps));
+
+		if (matches == cpus_have_cap(caps->capability))
+			continue;
+
+		if (!matches) {
+			/*
+			 * Cap detected on boot CPU but not this CPU,
+			 * disable it if not optional.
+			 */
+			if (!cpucap_late_cpu_optional(caps)) {
+				__clear_bit(caps->capability, system_cpucaps);
+				pr_info("missing on secondary: %s\n", caps->desc);
+			}
+			continue;
+		}
+
+		if (!(scope_mask & (SCOPE_BOOT_CPU | SCOPE_SYSTEM)) &&
+		    cpucap_late_cpu_permitted(caps))
+			/*
+			 * Cap detected on this CPU but not boot CPU,
+			 * skip it if permitted for late CPUs.
+			 */
 			continue;
 
 		if (caps->desc && !caps->cpus)

From f15d4064524971e328106bbff41c456e5f44ece1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 21 Sep 2024 23:15:22 +0900
Subject: [PATCH 0600/1027] fixup! arm64: Implement Apple IMPDEF TSO memory
 model control

---
 arch/arm64/kernel/cpufeature_impdef.c | 35 ++++++++++++++++++++++++---
 1 file changed, 32 insertions(+), 3 deletions(-)

diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
index 96965d47f36a6e..4523fcff230645 100644
--- a/arch/arm64/kernel/cpufeature_impdef.c
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -3,14 +3,19 @@
  * Contains implementation-defined CPU feature definitions.
  */
 
+#define pr_fmt(fmt) "CPU features: " fmt
+
 #include <asm/cpufeature.h>
 #include <asm/apple_cpufeature.h>
+#include <linux/irqflags.h>
+#include <linux/preempt.h>
+#include <linux/printk.h>
 
 #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
 static bool has_apple_feature(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	u64 val;
-	WARN_ON(scope != SCOPE_SYSTEM);
+	WARN_ON(scope == SCOPE_LOCAL_CPU && preemptible());
 
 	if (read_cpuid_implementor() != ARM_CPU_IMP_APPLE)
 		return false;
@@ -19,6 +24,30 @@ static bool has_apple_feature(const struct arm64_cpu_capabilities *entry, int sc
 	return cpufeature_matches(val, entry);
 }
 
+static bool has_apple_tso(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	u64 val;
+
+	if (!has_apple_feature(entry, scope))
+		return false;
+
+	/*
+	 * KVM and old versions of the macOS hypervisor will advertise TSO in
+	 * AIDR_EL1, but then ignore writes to ACTLR_EL1. Test that the bit is
+	 * actually writable before enabling TSO.
+	 */
+
+	val = read_sysreg(actlr_el1);
+	write_sysreg(val ^ ACTLR_APPLE_TSO, actlr_el1);
+	if (!((val ^ read_sysreg(actlr_el1)) & ACTLR_APPLE_TSO)) {
+		pr_info_once("CPU advertises Apple TSO but it is broken, ignoring\n");
+		return false;
+	}
+
+	write_sysreg(val, actlr_el1);
+	return true;
+}
+
 static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope)
 {
 	/* List of CPUs that always use the TSO memory model */
@@ -38,8 +67,8 @@ static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 	{
 		.desc = "TSO memory model (Apple)",
 		.capability = ARM64_HAS_TSO_APPLE,
-		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
-		.matches = has_apple_feature,
+		.type = SCOPE_LOCAL_CPU | ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU,
+		.matches = has_apple_tso,
 		.field_pos = AIDR_APPLE_TSO_SHIFT,
 		.field_width = 1,
 		.sign = FTR_UNSIGNED,

From f7850cabf835cc8ebebf600681d60e2e91344d37 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 21 Sep 2024 23:15:47 +0900
Subject: [PATCH 0601/1027] fixup! arm64: Implement PR_{GET,SET}_MEM_MODEL for
 always-TSO CPUs

---
 arch/arm64/kernel/cpufeature_impdef.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
index 4523fcff230645..3b0807bf90cddd 100644
--- a/arch/arm64/kernel/cpufeature_impdef.c
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -78,7 +78,7 @@ static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 	{
 		.desc = "TSO memory model (Fixed)",
 		.capability = ARM64_HAS_TSO_FIXED,
-		.type = ARM64_CPUCAP_SYSTEM_FEATURE,
+		.type = SCOPE_LOCAL_CPU | ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU,
 		.matches = has_tso_fixed,
 	},
 #endif

From 1fcbc13f11112e1291a364dd0b9805b45b37493c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 22 Sep 2024 00:43:17 +0900
Subject: [PATCH 0602/1027] fixup! KVM: arm64: Expose TSO capability to guests
 and context switch

---
 arch/arm64/include/asm/kvm_emulate.h       |  6 +++--
 arch/arm64/kernel/cpufeature_impdef.c      | 26 ++++++++++++++++++++++
 arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h | 19 +++++++++++-----
 arch/arm64/tools/cpucaps                   |  2 ++
 4 files changed, 45 insertions(+), 8 deletions(-)

diff --git a/arch/arm64/include/asm/kvm_emulate.h b/arch/arm64/include/asm/kvm_emulate.h
index 691559b3331eb6..8088b15c7c3ad3 100644
--- a/arch/arm64/include/asm/kvm_emulate.h
+++ b/arch/arm64/include/asm/kvm_emulate.h
@@ -80,8 +80,10 @@ static inline void vcpu_reset_hcr(struct kvm_vcpu *vcpu)
 {
 	if (!vcpu_has_run_once(vcpu))
 		vcpu->arch.hcr_el2 = HCR_GUEST_FLAGS;
-	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
-		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) && (
+			alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT) ||
+			alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE)
+		))
 		vcpu->arch.hcr_el2 &= ~HCR_TACR;
 
 	/*
diff --git a/arch/arm64/kernel/cpufeature_impdef.c b/arch/arm64/kernel/cpufeature_impdef.c
index 3b0807bf90cddd..3407034b7878eb 100644
--- a/arch/arm64/kernel/cpufeature_impdef.c
+++ b/arch/arm64/kernel/cpufeature_impdef.c
@@ -62,6 +62,20 @@ static bool has_tso_fixed(const struct arm64_cpu_capabilities *entry, int scope)
 }
 #endif
 
+static bool has_apple_actlr_virt_impdef(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	u64 midr = read_cpuid_id() & MIDR_CPU_MODEL_MASK;
+
+	return midr >= MIDR_APPLE_M1_ICESTORM && midr <= MIDR_APPLE_M1_FIRESTORM_MAX;
+}
+
+static bool has_apple_actlr_virt(const struct arm64_cpu_capabilities *entry, int scope)
+{
+	u64 midr = read_cpuid_id() & MIDR_CPU_MODEL_MASK;
+
+	return midr >= MIDR_APPLE_M2_BLIZZARD && midr <= MIDR_CPU_MODEL(ARM_CPU_IMP_APPLE, 0xfff);
+}
+
 static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 #ifdef CONFIG_ARM64_MEMORY_MODEL_CONTROL
 	{
@@ -82,6 +96,18 @@ static const struct arm64_cpu_capabilities arm64_impdef_features[] = {
 		.matches = has_tso_fixed,
 	},
 #endif
+	{
+		.desc = "ACTLR virtualization (IMPDEF, Apple)",
+		.capability = ARM64_HAS_ACTLR_VIRT_APPLE,
+		.type = SCOPE_LOCAL_CPU | ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU,
+		.matches = has_apple_actlr_virt_impdef,
+	},
+	{
+		.desc = "ACTLR virtualization (architectural?)",
+		.capability = ARM64_HAS_ACTLR_VIRT,
+		.type = SCOPE_LOCAL_CPU | ARM64_CPUCAP_PERMITTED_FOR_LATE_CPU,
+		.matches = has_apple_actlr_virt,
+	},
 	{},
 };
 
diff --git a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
index ff4295af8ff05c..210e9f8081b6e8 100644
--- a/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
+++ b/arch/arm64/kvm/hyp/include/hyp/sysreg-sr.h
@@ -17,6 +17,7 @@
 #include <asm/kvm_mmu.h>
 
 #define SYS_IMP_APL_ACTLR_EL12		sys_reg(3, 6, 15, 14, 6)
+#define SYS_ACTLR_EL12			sys_reg(3, 5, 1, 0, 1)
 
 static inline void __sysreg_save_common_state(struct kvm_cpu_context *ctxt)
 {
@@ -103,9 +104,12 @@ static inline void __sysreg_save_el1_state(struct kvm_cpu_context *ctxt)
 	ctxt_sys_reg(ctxt, SP_EL1)	= read_sysreg(sp_el1);
 	ctxt_sys_reg(ctxt, ELR_EL1)	= read_sysreg_el1(SYS_ELR);
 	ctxt_sys_reg(ctxt, SPSR_EL1)	= read_sysreg_el1(SYS_SPSR);
-	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
-		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
-		ctxt_sys_reg(ctxt, ACTLR_EL1)	= read_sysreg_s(SYS_IMP_APL_ACTLR_EL12);
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE)) {
+		if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT))
+			ctxt_sys_reg(ctxt, ACTLR_EL1)	= read_sysreg_s(SYS_ACTLR_EL12);
+		else if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE))
+			ctxt_sys_reg(ctxt, ACTLR_EL1)	= read_sysreg_s(SYS_IMP_APL_ACTLR_EL12);
+	}
 }
 
 static inline void __sysreg_save_el2_return_state(struct kvm_cpu_context *ctxt)
@@ -176,9 +180,12 @@ static inline void __sysreg_restore_el1_state(struct kvm_cpu_context *ctxt)
 	write_sysreg(ctxt_sys_reg(ctxt, PAR_EL1),	par_el1);
 	write_sysreg(ctxt_sys_reg(ctxt, TPIDR_EL1),	tpidr_el1);
 
-	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE) &&
-		alternative_has_cap_unlikely(ARM64_HAS_TSO_APPLE))
-		write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1),	SYS_IMP_APL_ACTLR_EL12);
+	if (IS_ENABLED(CONFIG_ARM64_ACTLR_STATE)) {
+		if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT))
+			write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1), SYS_ACTLR_EL12);
+		else if (alternative_has_cap_unlikely(ARM64_HAS_ACTLR_VIRT_APPLE))
+			write_sysreg_s(ctxt_sys_reg(ctxt, ACTLR_EL1), SYS_IMP_APL_ACTLR_EL12);
+	}
 
 	if (ctxt_has_mte(ctxt)) {
 		write_sysreg_el1(ctxt_sys_reg(ctxt, TFSR_EL1), SYS_TFSR);
diff --git a/arch/arm64/tools/cpucaps b/arch/arm64/tools/cpucaps
index a913c0aed32fdf..f615e04be81b92 100644
--- a/arch/arm64/tools/cpucaps
+++ b/arch/arm64/tools/cpucaps
@@ -8,6 +8,8 @@ BTI
 # Unreliable: use system_supports_32bit_el0() instead.
 HAS_32BIT_EL0_DO_NOT_USE
 HAS_32BIT_EL1
+HAS_ACTLR_VIRT
+HAS_ACTLR_VIRT_APPLE
 HAS_ADDRESS_AUTH
 HAS_ADDRESS_AUTH_ARCH_QARMA3
 HAS_ADDRESS_AUTH_ARCH_QARMA5

From 654e07b1ecf19c1991a1576607f790779dc67863 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Fri, 17 Feb 2023 17:02:10 +0100
Subject: [PATCH 0603/1027] ALSA: Support nonatomic dmaengine PCMs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

*** possible v6.11 conflict: _snd_dmaengine_pcm_close

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 sound/core/pcm_dmaengine.c | 29 ++++++++++++++++++++++++++++-
 1 file changed, 28 insertions(+), 1 deletion(-)

diff --git a/sound/core/pcm_dmaengine.c b/sound/core/pcm_dmaengine.c
index 4c5c0f92e4ff57..8a53bb89095fbc 100644
--- a/sound/core/pcm_dmaengine.c
+++ b/sound/core/pcm_dmaengine.c
@@ -22,6 +22,8 @@
 struct dmaengine_pcm_runtime_data {
 	struct dma_chan *dma_chan;
 	dma_cookie_t cookie;
+	struct work_struct complete_wq; /* for nonatomic PCM */
+	struct snd_pcm_substream *substream;
 
 	unsigned int pos;
 };
@@ -145,6 +147,21 @@ static void dmaengine_pcm_dma_complete(void *arg)
 	snd_pcm_period_elapsed(substream);
 }
 
+static void dmaengine_pcm_dma_complete_nonatomic(struct work_struct *wq)
+{
+	struct dmaengine_pcm_runtime_data *prtd = \
+				container_of(wq, struct dmaengine_pcm_runtime_data, complete_wq);
+	struct snd_pcm_substream *substream = prtd->substream;
+	dmaengine_pcm_dma_complete(substream);
+}
+
+static void dmaengine_pcm_dma_complete_nonatomic_callback(void *arg)
+{
+	struct snd_pcm_substream *substream = arg;
+	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
+	schedule_work(&prtd->complete_wq);
+}
+
 static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
 {
 	struct dmaengine_pcm_runtime_data *prtd = substream_to_prtd(substream);
@@ -167,7 +184,11 @@ static int dmaengine_pcm_prepare_and_submit(struct snd_pcm_substream *substream)
 	if (!desc)
 		return -ENOMEM;
 
-	desc->callback = dmaengine_pcm_dma_complete;
+	if (substream->pcm->nonatomic)
+		desc->callback = dmaengine_pcm_dma_complete_nonatomic_callback;
+	else
+		desc->callback = dmaengine_pcm_dma_complete;
+
 	desc->callback_param = substream;
 	prtd->cookie = dmaengine_submit(desc);
 
@@ -320,6 +341,10 @@ int snd_dmaengine_pcm_open(struct snd_pcm_substream *substream,
 	if (!prtd)
 		return -ENOMEM;
 
+	if (substream->pcm->nonatomic)
+		INIT_WORK(&prtd->complete_wq, dmaengine_pcm_dma_complete_nonatomic);
+
+	prtd->substream = substream;
 	prtd->dma_chan = chan;
 
 	substream->runtime->private_data = prtd;
@@ -380,6 +405,8 @@ static void __snd_dmaengine_pcm_close(struct snd_pcm_substream *substream,
 	 */
 	dmaengine_terminate_async(prtd->dma_chan);
 	dmaengine_synchronize(prtd->dma_chan);
+	if (substream->pcm->nonatomic)
+		flush_work(&prtd->complete_wq);
 	if (release_channel)
 		dma_release_channel(prtd->dma_chan);
 	kfree(prtd);

From f1fa2542d8ab9ab0d223d8279f41a494171d1920 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 15 Dec 2023 20:38:32 +0900
Subject: [PATCH 0604/1027] READ COMMIT MESSAGE! macaudio: Enable first round
 of models

Enables j313, j293, j493, j314, j414, j274, j375, j473, j474, j475

*** WARNING FOR DISTRO PACKAGERS WANTING TO APPLY THIS: ***
*** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE ***

https://github.com/lsp-plugins/lsp-dsp-lib/pull/20

Do NOT enable speakers without that patch, on any model. It can/will
result in nasty noise that could damage them.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 20 ++++++++++++--------
 1 file changed, 12 insertions(+), 8 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index ff898cc419c18e..1c1f59f2ef96cc 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1490,23 +1490,27 @@ struct macaudio_platform_cfg macaudio_j180_cfg = {
 	false,	AMP_SN012776,	SPKR_1W1T,	false,	10,	-20,
 };
 struct macaudio_platform_cfg macaudio_j274_cfg = {
-	false,	AMP_TAS5770,	SPKR_1W,	false,	20,	-20,
+	true,	AMP_TAS5770,	SPKR_1W,	false,	20,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j293_cfg = {
-	false,	AMP_TAS5770,	SPKR_2W,	true,	15,	-20,
+	true,	AMP_TAS5770,	SPKR_2W,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j313_cfg = {
-	false,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
+	true,	AMP_TAS5770,	SPKR_1W,	true,	10,	-20,
 };
 
-struct macaudio_platform_cfg macaudio_j314_j316_cfg = {
+struct macaudio_platform_cfg macaudio_j314_cfg = {
+	true,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
+};
+
+struct macaudio_platform_cfg macaudio_j316_cfg = {
 	false,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
-	false,	AMP_SN012776,	SPKR_1W,	false,	20,	-20,
+	true,	AMP_SN012776,	SPKR_1W,	false,	20,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j413_cfg = {
@@ -1522,7 +1526,7 @@ struct macaudio_platform_cfg macaudio_j45x_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j493_cfg = {
-	false,	AMP_SN012776,	SPKR_2W,	true,	15,	-20,
+	true,	AMP_SN012776,	SPKR_2W,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_fallback_cfg = {
@@ -1558,9 +1562,9 @@ static const struct of_device_id macaudio_snd_device_id[]  = {
 	/* j313    AID4    tas5770     10      2× 1W */
 	{ .compatible = "apple,j313-macaudio", .data = &macaudio_j313_cfg },
 	/* j314    AID8    sn012776    15      2× 2W+1T */
-	{ .compatible = "apple,j314-macaudio", .data = &macaudio_j314_j316_cfg },
+	{ .compatible = "apple,j314-macaudio", .data = &macaudio_j314_cfg },
 	/* j316    AID9    sn012776    15      2× 2W+1T */
-	{ .compatible = "apple,j316-macaudio", .data = &macaudio_j314_j316_cfg },
+	{ .compatible = "apple,j316-macaudio", .data = &macaudio_j316_cfg },
 	/* j375    AID10   sn012776    15      1× 1W */
 	{ .compatible = "apple,j375-macaudio", .data = &macaudio_j37x_j47x_cfg },
 	/* j413    AID13   sn012776    15      2× 1W+1T */

From f8cddd86933bacda3a01e632a0b5f922e0b52d98 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 15 Dec 2023 20:40:53 +0900
Subject: [PATCH 0605/1027] READ COMMIT MESSAGE! macaudio: Enable second round
 of models

Enables j316, j413, j415, j416

*** WARNING FOR DISTRO PACKAGERS WANTING TO APPLY THIS: ***
*** YOU ABSOLUTELY NEED THIS PATCH IN YOUR LSP-PLUGINS PACKAGE ***

https://github.com/lsp-plugins/lsp-dsp-lib/pull/20

Do NOT enable speakers without that patch, on any model. It can/will
result in nasty noise that could damage them.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 sound/soc/apple/macaudio.c | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index 1c1f59f2ef96cc..b30155194f4d36 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -1506,7 +1506,7 @@ struct macaudio_platform_cfg macaudio_j314_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j316_cfg = {
-	false,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
+	true,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
@@ -1514,11 +1514,11 @@ struct macaudio_platform_cfg macaudio_j37x_j47x_cfg = {
 };
 
 struct macaudio_platform_cfg macaudio_j413_cfg = {
-	false,	AMP_SN012776,	SPKR_1W1T,	true,	15,	-20,
+	true,	AMP_SN012776,	SPKR_1W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j415_cfg = {
-	false,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
+	true,	AMP_SN012776,	SPKR_2W1T,	true,	15,	-20,
 };
 
 struct macaudio_platform_cfg macaudio_j45x_cfg = {

From 2812deafef2116f1967006ebb882d3782234deea Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 24 Sep 2024 19:47:25 +0200
Subject: [PATCH 0606/1027] fixup! ASoC: macaudio: Tune DT parsing error
 messages

---
 sound/soc/apple/macaudio.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/macaudio.c b/sound/soc/apple/macaudio.c
index b30155194f4d36..7c843fb18ac91c 100644
--- a/sound/soc/apple/macaudio.c
+++ b/sound/soc/apple/macaudio.c
@@ -504,7 +504,7 @@ static int macaudio_parse_of(struct macaudio_snd_data *ma)
 		cpu = of_get_child_by_name(np, "cpu");
 		if (!cpu) {
 			ret = dev_err_probe(dev, -EINVAL,
-				"missing CPU DAI node at %pOF\n", np);;
+				"missing CPU DAI node at %pOF\n", np);
 			goto err_free;
 		}
 

From b59a10c55e6ccb2797f6712f50bd72317ae55e78 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 24 Sep 2024 19:23:45 +0200
Subject: [PATCH 0607/1027] fixup! ASoC: apple: Add macaudio machine driver

---
 sound/soc/apple/Kconfig | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index d95a6341dc3fdf..87062d1c1c7289 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -12,12 +12,12 @@ config SND_SOC_APPLE_MACAUDIO
 	depends on ARCH_APPLE || COMPILE_TEST
 	select SND_SOC_APPLE_MCA
 	select SND_SIMPLE_CARD_UTILS
-	select APPLE_ADMAC
+	select APPLE_ADMAC if DMADEVICES
 	select COMMON_CLK_APPLE_NCO if COMMON_CLK
-	select SND_SOC_TAS2764
-	select SND_SOC_TAS2770
-	select SND_SOC_CS42L83
-	select SND_SOC_CS42L84
+	select SND_SOC_TAS2764 if I2C
+	select SND_SOC_TAS2770 if I2C
+	select SND_SOC_CS42L83 if I2C
+	select SND_SOC_CS42L84 if I2C
 	select REGULATOR_FIXED_VOLTAGE
 	default ARCH_APPLE
 	help

From fca8dbbece94487c99eca8777789842962e4e1d9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 24 Sep 2024 19:25:26 +0200
Subject: [PATCH 0608/1027] fixup! macaudio: Fix missing kconfig requirement

---
 sound/soc/apple/Kconfig | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/apple/Kconfig b/sound/soc/apple/Kconfig
index 87062d1c1c7289..6e47dadabe0e22 100644
--- a/sound/soc/apple/Kconfig
+++ b/sound/soc/apple/Kconfig
@@ -18,7 +18,7 @@ config SND_SOC_APPLE_MACAUDIO
 	select SND_SOC_TAS2770 if I2C
 	select SND_SOC_CS42L83 if I2C
 	select SND_SOC_CS42L84 if I2C
-	select REGULATOR_FIXED_VOLTAGE
+	select REGULATOR_FIXED_VOLTAGE if REGULATOR
 	default ARCH_APPLE
 	help
 	  This option enables an ASoC machine-level driver for Apple Silicon Macs

From 819ac8ed6c5777d4b18f0c88fc4a5e55a9ab9fdd Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 24 Sep 2024 19:09:47 +0200
Subject: [PATCH 0609/1027] fixup! WIP: phy: apple: Add Apple Type-C PHY

---
 drivers/phy/apple/Kconfig | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/phy/apple/Kconfig b/drivers/phy/apple/Kconfig
index d3b9148eaf7e08..66f251e6eda70e 100644
--- a/drivers/phy/apple/Kconfig
+++ b/drivers/phy/apple/Kconfig
@@ -4,7 +4,8 @@ config PHY_APPLE_ATC
 	depends on ARCH_APPLE || COMPILE_TEST
 	default ARCH_APPLE
 	select GENERIC_PHY
-	select TYPEC
+	depends on USB_SUPPORT
+	depends on TYPEC
 	help
 	  Enable this to add support for the Apple Type-C PHY, switch
 	  and mux found in Apple SoCs such as the M1.

From bf41afa052f99b6a7b6d433203ad42dbd5b37a87 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 9 Dec 2021 21:55:49 +0900
Subject: [PATCH 0610/1027] spi: apple: Add driver for Apple SPI controller

This SPI controller is present in Apple SoCs such as the M1 (t8103) and
M1 Pro/Max (t600x). It is a relatively straightforward design with two
16-entry FIFOs, arbitrary transfer sizes (up to 2**32 - 1) and fully
configurable word size up to 32 bits. It supports one hardware CS line
which can also be driven via the pinctrl/GPIO driver instead, if
desired. TX and RX can be independently enabled.

There are a surprising number of knobs for tweaking details of the
transfer, most of which we do not use right now. Hardware CS control
is available, but we haven't found a way to make it stay low across
multiple logical transfers, so we just use software CS control for now.

There is also a shared DMA offload coprocessor that can be used to handle
larger transfers without requiring an IRQ every 8-16 words, but that
feature depends on a bunch of scaffolding that isn't ready to be
upstreamed yet, so leave it for later.

The hardware shares some register bit definitions with spi-s3c24xx which
suggests it has a shared legacy with Samsung SoCs, but it is too
different to warrant sharing a driver.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/spi/Kconfig     |   8 +
 drivers/spi/Makefile    |   1 +
 drivers/spi/spi-apple.c | 543 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 552 insertions(+)
 create mode 100644 drivers/spi/spi-apple.c

diff --git a/drivers/spi/Kconfig b/drivers/spi/Kconfig
index ec1550c698d5f3..c3581179315af4 100644
--- a/drivers/spi/Kconfig
+++ b/drivers/spi/Kconfig
@@ -96,6 +96,14 @@ config SPI_AMLOGIC_SPIFC_A1
 	  This enables master mode support for the SPIFC (SPI flash
 	  controller) available in Amlogic A1 (A113L SoC).
 
+config SPI_APPLE
+	tristate "Apple SoC SPI Controller platform driver"
+	depends on ARCH_APPLE || COMPILE_TEST
+	help
+	  This enables support for the SPI controller present on
+	  many Apple SoCs, including the t8103 (M1) and t600x
+	  (M1 Pro/Max).
+
 config SPI_AR934X
 	tristate "Qualcomm Atheros AR934X/QCA95XX SPI controller driver"
 	depends on ATH79 || COMPILE_TEST
diff --git a/drivers/spi/Makefile b/drivers/spi/Makefile
index a9b1bc259b68d1..f059e2a5e5c6c9 100644
--- a/drivers/spi/Makefile
+++ b/drivers/spi/Makefile
@@ -19,6 +19,7 @@ obj-$(CONFIG_SPI_ALTERA)		+= spi-altera-platform.o
 obj-$(CONFIG_SPI_ALTERA_CORE)		+= spi-altera-core.o
 obj-$(CONFIG_SPI_ALTERA_DFL)		+= spi-altera-dfl.o
 obj-$(CONFIG_SPI_AMLOGIC_SPIFC_A1)	+= spi-amlogic-spifc-a1.o
+obj-$(CONFIG_SPI_APPLE)			+= spi-apple.o
 obj-$(CONFIG_SPI_AR934X)		+= spi-ar934x.o
 obj-$(CONFIG_SPI_ARMADA_3700)		+= spi-armada-3700.o
 obj-$(CONFIG_SPI_ASPEED_SMC)		+= spi-aspeed-smc.o
diff --git a/drivers/spi/spi-apple.c b/drivers/spi/spi-apple.c
new file mode 100644
index 00000000000000..bd432d7ee1a553
--- /dev/null
+++ b/drivers/spi/spi-apple.c
@@ -0,0 +1,543 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple SoC SPI device driver
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * Based on spi-sifive.c, Copyright 2018 SiFive, Inc.
+ */
+
+#include <linux/bitfield.h>
+#include <linux/bits.h>
+#include <linux/clk.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/spi/spi.h>
+
+#define APPLE_SPI_CTRL			0x000
+#define APPLE_SPI_CTRL_RUN		BIT(0)
+#define APPLE_SPI_CTRL_TX_RESET		BIT(2)
+#define APPLE_SPI_CTRL_RX_RESET		BIT(3)
+
+#define APPLE_SPI_CFG			0x004
+#define APPLE_SPI_CFG_CPHA		BIT(1)
+#define APPLE_SPI_CFG_CPOL		BIT(2)
+#define APPLE_SPI_CFG_MODE		GENMASK(6, 5)
+#define APPLE_SPI_CFG_MODE_POLLED	0
+#define APPLE_SPI_CFG_MODE_IRQ		1
+#define APPLE_SPI_CFG_MODE_DMA		2
+#define APPLE_SPI_CFG_IE_RXCOMPLETE	BIT(7)
+#define APPLE_SPI_CFG_IE_TXRXTHRESH	BIT(8)
+#define APPLE_SPI_CFG_LSB_FIRST		BIT(13)
+#define APPLE_SPI_CFG_WORD_SIZE		GENMASK(16, 15)
+#define APPLE_SPI_CFG_WORD_SIZE_8B	0
+#define APPLE_SPI_CFG_WORD_SIZE_16B	1
+#define APPLE_SPI_CFG_WORD_SIZE_32B	2
+#define APPLE_SPI_CFG_FIFO_THRESH	GENMASK(18, 17)
+#define APPLE_SPI_CFG_FIFO_THRESH_8B	0
+#define APPLE_SPI_CFG_FIFO_THRESH_4B	1
+#define APPLE_SPI_CFG_FIFO_THRESH_1B	2
+#define APPLE_SPI_CFG_IE_TXCOMPLETE	BIT(21)
+
+#define APPLE_SPI_STATUS		0x008
+#define APPLE_SPI_STATUS_RXCOMPLETE	BIT(0)
+#define APPLE_SPI_STATUS_TXRXTHRESH	BIT(1)
+#define APPLE_SPI_STATUS_TXCOMPLETE	BIT(2)
+
+#define APPLE_SPI_PIN			0x00c
+#define APPLE_SPI_PIN_KEEP_MOSI		BIT(0)
+#define APPLE_SPI_PIN_CS		BIT(1)
+
+#define APPLE_SPI_TXDATA		0x010
+#define APPLE_SPI_RXDATA		0x020
+#define APPLE_SPI_CLKDIV		0x030
+#define APPLE_SPI_CLKDIV_MAX		0x7ff
+#define APPLE_SPI_RXCNT			0x034
+#define APPLE_SPI_WORD_DELAY		0x038
+#define APPLE_SPI_TXCNT			0x04c
+
+#define APPLE_SPI_FIFOSTAT		0x10c
+#define APPLE_SPI_FIFOSTAT_TXFULL	BIT(4)
+#define APPLE_SPI_FIFOSTAT_LEVEL_TX	GENMASK(15, 8)
+#define APPLE_SPI_FIFOSTAT_RXEMPTY	BIT(20)
+#define APPLE_SPI_FIFOSTAT_LEVEL_RX	GENMASK(31, 24)
+
+#define APPLE_SPI_IE_XFER		0x130
+#define APPLE_SPI_IF_XFER		0x134
+#define APPLE_SPI_XFER_RXCOMPLETE	BIT(0)
+#define APPLE_SPI_XFER_TXCOMPLETE	BIT(1)
+
+#define APPLE_SPI_IE_FIFO		0x138
+#define APPLE_SPI_IF_FIFO		0x13c
+#define APPLE_SPI_FIFO_RXTHRESH		BIT(4)
+#define APPLE_SPI_FIFO_TXTHRESH		BIT(5)
+#define APPLE_SPI_FIFO_RXFULL		BIT(8)
+#define APPLE_SPI_FIFO_TXEMPTY		BIT(9)
+#define APPLE_SPI_FIFO_RXUNDERRUN	BIT(16)
+#define APPLE_SPI_FIFO_TXOVERFLOW	BIT(17)
+
+#define APPLE_SPI_SHIFTCFG		0x150
+#define APPLE_SPI_SHIFTCFG_CLK_ENABLE	BIT(0)
+#define APPLE_SPI_SHIFTCFG_CS_ENABLE	BIT(1)
+#define APPLE_SPI_SHIFTCFG_AND_CLK_DATA	BIT(8)
+#define APPLE_SPI_SHIFTCFG_CS_AS_DATA	BIT(9)
+#define APPLE_SPI_SHIFTCFG_TX_ENABLE	BIT(10)
+#define APPLE_SPI_SHIFTCFG_RX_ENABLE	BIT(11)
+#define APPLE_SPI_SHIFTCFG_BITS		GENMASK(21, 16)
+#define APPLE_SPI_SHIFTCFG_OVERRIDE_CS	BIT(24)
+
+#define APPLE_SPI_PINCFG		0x154
+#define APPLE_SPI_PINCFG_KEEP_CLK	BIT(0)
+#define APPLE_SPI_PINCFG_KEEP_CS	BIT(1)
+#define APPLE_SPI_PINCFG_KEEP_MOSI	BIT(2)
+#define APPLE_SPI_PINCFG_CLK_IDLE_VAL	BIT(8)
+#define APPLE_SPI_PINCFG_CS_IDLE_VAL	BIT(9)
+#define APPLE_SPI_PINCFG_MOSI_IDLE_VAL	BIT(10)
+
+#define APPLE_SPI_DELAY_PRE		0x160
+#define APPLE_SPI_DELAY_POST		0x168
+#define APPLE_SPI_DELAY_ENABLE		BIT(0)
+#define APPLE_SPI_DELAY_NO_INTERBYTE	BIT(1)
+#define APPLE_SPI_DELAY_SET_SCK		BIT(4)
+#define APPLE_SPI_DELAY_SET_MOSI	BIT(6)
+#define APPLE_SPI_DELAY_SCK_VAL		BIT(8)
+#define APPLE_SPI_DELAY_MOSI_VAL	BIT(12)
+
+#define APPLE_SPI_FIFO_DEPTH		16
+
+/*
+ * The slowest refclock available is 24MHz, the highest divider is 0x7ff,
+ * the largest word size is 32 bits, the FIFO depth is 16, the maximum
+ * intra-word delay is 0xffff refclocks. So the maximum time a transfer
+ * cycle can take is:
+ *
+ * (0x7ff * 32 + 0xffff) * 16 / 24e6 Hz ~= 87ms
+ *
+ * Double it and round it up to 200ms for good measure.
+ */
+#define APPLE_SPI_TIMEOUT_MS		200
+
+struct apple_spi {
+	void __iomem      *regs;        /* MMIO register address */
+	struct clk        *clk;         /* bus clock */
+	struct completion done;         /* wake-up from interrupt */
+};
+
+static inline void reg_write(struct apple_spi *spi, int offset, u32 value)
+{
+	writel_relaxed(value, spi->regs + offset);
+}
+
+static inline u32 reg_read(struct apple_spi *spi, int offset)
+{
+	return readl_relaxed(spi->regs + offset);
+}
+
+static inline void reg_mask(struct apple_spi *spi, int offset, u32 clear, u32 set)
+{
+	u32 val = reg_read(spi, offset);
+
+	val &= ~clear;
+	val |= set;
+	reg_write(spi, offset, val);
+}
+
+static void apple_spi_init(struct apple_spi *spi)
+{
+	/* Set CS high (inactive) and disable override and auto-CS */
+	reg_write(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS);
+	reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_OVERRIDE_CS, 0);
+	reg_mask(spi, APPLE_SPI_PINCFG, APPLE_SPI_PINCFG_CS_IDLE_VAL, APPLE_SPI_PINCFG_KEEP_CS);
+
+	/* Reset FIFOs */
+	reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET);
+
+	/* Configure defaults */
+	reg_write(spi, APPLE_SPI_CFG,
+		  FIELD_PREP(APPLE_SPI_CFG_FIFO_THRESH, APPLE_SPI_CFG_FIFO_THRESH_8B) |
+		  FIELD_PREP(APPLE_SPI_CFG_MODE, APPLE_SPI_CFG_MODE_IRQ) |
+		  FIELD_PREP(APPLE_SPI_CFG_WORD_SIZE, APPLE_SPI_CFG_WORD_SIZE_8B));
+
+	/* Disable IRQs */
+	reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+	reg_write(spi, APPLE_SPI_IE_XFER, 0);
+
+	/* Disable delays */
+	reg_write(spi, APPLE_SPI_DELAY_PRE, 0);
+	reg_write(spi, APPLE_SPI_DELAY_POST, 0);
+}
+
+static int apple_spi_prepare_message(struct spi_controller *ctlr, struct spi_message *msg)
+{
+	struct apple_spi *spi = spi_controller_get_devdata(ctlr);
+	struct spi_device *device = msg->spi;
+
+	u32 cfg = ((device->mode & SPI_CPHA ? APPLE_SPI_CFG_CPHA : 0) |
+		   (device->mode & SPI_CPOL ? APPLE_SPI_CFG_CPOL : 0) |
+		   (device->mode & SPI_LSB_FIRST ? APPLE_SPI_CFG_LSB_FIRST : 0));
+
+	/* Update core config */
+	reg_mask(spi, APPLE_SPI_CFG,
+		 APPLE_SPI_CFG_CPHA | APPLE_SPI_CFG_CPOL | APPLE_SPI_CFG_LSB_FIRST, cfg);
+
+	return 0;
+}
+
+static void apple_spi_set_cs(struct spi_device *device, bool is_high)
+{
+	struct apple_spi *spi = spi_controller_get_devdata(device->controller);
+
+	reg_mask(spi, APPLE_SPI_PIN, APPLE_SPI_PIN_CS, is_high ? APPLE_SPI_PIN_CS : 0);
+}
+
+static bool apple_spi_prep_transfer(struct apple_spi *spi, struct spi_transfer *t)
+{
+	u32 cr, fifo_threshold;
+
+	/* Calculate and program the clock rate */
+	cr = DIV_ROUND_UP(clk_get_rate(spi->clk), t->speed_hz);
+	reg_write(spi, APPLE_SPI_CLKDIV, min_t(u32, cr, APPLE_SPI_CLKDIV_MAX));
+
+	/* Update bits per word */
+	reg_mask(spi, APPLE_SPI_SHIFTCFG, APPLE_SPI_SHIFTCFG_BITS,
+		 FIELD_PREP(APPLE_SPI_SHIFTCFG_BITS, t->bits_per_word));
+
+	/* We will want to poll if the time we need to wait is
+	 * less than the context switching time.
+	 * Let's call that threshold 5us. The operation will take:
+	 *    bits_per_word * fifo_threshold / hz <= 5 * 10^-6
+	 *    200000 * bits_per_word * fifo_threshold <= hz
+	 */
+	fifo_threshold = APPLE_SPI_FIFO_DEPTH / 2;
+	return (200000 * t->bits_per_word * fifo_threshold) <= t->speed_hz;
+}
+
+static irqreturn_t apple_spi_irq(int irq, void *dev_id)
+{
+	struct apple_spi *spi = dev_id;
+	u32 fifo = reg_read(spi, APPLE_SPI_IF_FIFO) & reg_read(spi, APPLE_SPI_IE_FIFO);
+	u32 xfer = reg_read(spi, APPLE_SPI_IF_XFER) & reg_read(spi, APPLE_SPI_IE_XFER);
+
+	if (fifo || xfer) {
+		/* Disable interrupts until next transfer */
+		reg_write(spi, APPLE_SPI_IE_XFER, 0);
+		reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+		complete(&spi->done);
+		return IRQ_HANDLED;
+	}
+
+	return IRQ_NONE;
+}
+
+static int apple_spi_wait(struct apple_spi *spi, u32 fifo_bit, u32 xfer_bit, int poll)
+{
+	int ret = 0;
+
+	if (poll) {
+		u32 fifo, xfer;
+		unsigned long timeout = jiffies + APPLE_SPI_TIMEOUT_MS * HZ / 1000;
+
+		do {
+			fifo = reg_read(spi, APPLE_SPI_IF_FIFO);
+			xfer = reg_read(spi, APPLE_SPI_IF_XFER);
+			if (time_after(jiffies, timeout)) {
+				ret = -ETIMEDOUT;
+				break;
+			}
+		} while (!((fifo & fifo_bit) || (xfer & xfer_bit)));
+	} else {
+		reinit_completion(&spi->done);
+		reg_write(spi, APPLE_SPI_IE_XFER, xfer_bit);
+		reg_write(spi, APPLE_SPI_IE_FIFO, fifo_bit);
+
+		if (!wait_for_completion_timeout(&spi->done,
+						 msecs_to_jiffies(APPLE_SPI_TIMEOUT_MS)))
+			ret = -ETIMEDOUT;
+
+		reg_write(spi, APPLE_SPI_IE_XFER, 0);
+		reg_write(spi, APPLE_SPI_IE_FIFO, 0);
+	}
+
+	return ret;
+}
+
+static void apple_spi_tx(struct apple_spi *spi, const void **tx_ptr, u32 *left,
+			 unsigned int bytes_per_word)
+{
+	u32 inuse, words, wrote;
+
+	if (!*tx_ptr)
+		return;
+
+	inuse = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_TX, reg_read(spi, APPLE_SPI_FIFOSTAT));
+	words = wrote = min_t(u32, *left, APPLE_SPI_FIFO_DEPTH - inuse);
+
+	if (!words)
+		return;
+
+	*left -= words;
+
+	switch (bytes_per_word) {
+	case 1: {
+		const u8 *p = *tx_ptr;
+
+		while (words--)
+			reg_write(spi, APPLE_SPI_TXDATA, *p++);
+		break;
+	}
+	case 2: {
+		const u16 *p = *tx_ptr;
+
+		while (words--)
+			reg_write(spi, APPLE_SPI_TXDATA, *p++);
+		break;
+	}
+	case 4: {
+		const u32 *p = *tx_ptr;
+
+		while (words--)
+			reg_write(spi, APPLE_SPI_TXDATA, *p++);
+		break;
+	}
+	default:
+		WARN_ON(1);
+	}
+
+	*tx_ptr = ((u8 *)*tx_ptr) + bytes_per_word * wrote;
+}
+
+static void apple_spi_rx(struct apple_spi *spi, void **rx_ptr, u32 *left,
+			 unsigned int bytes_per_word)
+{
+	u32 words, read;
+
+	if (!*rx_ptr)
+		return;
+
+	words = read = FIELD_GET(APPLE_SPI_FIFOSTAT_LEVEL_RX, reg_read(spi, APPLE_SPI_FIFOSTAT));
+	WARN_ON(words > *left);
+
+	if (!words)
+		return;
+
+	*left -= min_t(u32, *left, words);
+
+	switch (bytes_per_word) {
+	case 1: {
+		u8 *p = *rx_ptr;
+
+		while (words--)
+			*p++ = reg_read(spi, APPLE_SPI_RXDATA);
+		break;
+	}
+	case 2: {
+		u16 *p = *rx_ptr;
+
+		while (words--)
+			*p++ = reg_read(spi, APPLE_SPI_RXDATA);
+		break;
+	}
+	case 4: {
+		u32 *p = *rx_ptr;
+
+		while (words--)
+			*p++ = reg_read(spi, APPLE_SPI_RXDATA);
+		break;
+	}
+	default:
+		WARN_ON(1);
+	}
+
+	*rx_ptr = ((u8 *)*rx_ptr) + bytes_per_word * read;
+}
+
+static int apple_spi_transfer_one(struct spi_controller *ctlr, struct spi_device *device,
+				  struct spi_transfer *t)
+{
+	struct apple_spi *spi = spi_controller_get_devdata(ctlr);
+	bool poll = apple_spi_prep_transfer(spi, t);
+	const void *tx_ptr = t->tx_buf;
+	void *rx_ptr = t->rx_buf;
+	unsigned int bytes_per_word;
+	u32 words, remaining_tx, remaining_rx;
+	u32 xfer_flags = 0;
+	u32 fifo_flags;
+	int retries = 100;
+	int ret = 0;
+
+	if (t->bits_per_word > 16)
+		bytes_per_word = 4;
+	else if (t->bits_per_word > 8)
+		bytes_per_word = 2;
+	else
+		bytes_per_word = 1;
+
+	words = t->len / bytes_per_word;
+	remaining_tx = tx_ptr ? words : 0;
+	remaining_rx = rx_ptr ? words : 0;
+
+	/* Reset FIFOs */
+	reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RX_RESET | APPLE_SPI_CTRL_TX_RESET);
+
+	/* Clear IRQ flags */
+	reg_write(spi, APPLE_SPI_IF_XFER, ~0);
+	reg_write(spi, APPLE_SPI_IF_FIFO, ~0);
+
+	/* Determine transfer completion flags we wait for */
+	if (tx_ptr)
+		xfer_flags |= APPLE_SPI_XFER_TXCOMPLETE;
+	if (rx_ptr)
+		xfer_flags |= APPLE_SPI_XFER_RXCOMPLETE;
+
+	/* Set transfer length */
+	reg_write(spi, APPLE_SPI_TXCNT, remaining_tx);
+	reg_write(spi, APPLE_SPI_RXCNT, remaining_rx);
+
+	/* Prime transmit FIFO */
+	apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+
+	/* Start transfer */
+	reg_write(spi, APPLE_SPI_CTRL, APPLE_SPI_CTRL_RUN);
+
+	/* TX again since a few words get popped off immediately */
+	apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+
+	while (xfer_flags) {
+		fifo_flags = 0;
+
+		if (remaining_tx)
+			fifo_flags |= APPLE_SPI_FIFO_TXTHRESH;
+		if (remaining_rx)
+			fifo_flags |= APPLE_SPI_FIFO_RXTHRESH;
+
+		/* Wait for anything to happen */
+		ret = apple_spi_wait(spi, fifo_flags, xfer_flags, poll);
+		if (ret) {
+			dev_err(&ctlr->dev, "transfer timed out (remaining %d tx, %d rx)\n",
+				remaining_tx, remaining_rx);
+			goto err;
+		}
+
+		/* Stop waiting on transfer halves once they complete */
+		xfer_flags &= ~reg_read(spi, APPLE_SPI_IF_XFER);
+
+		/* Transmit and receive everything we can */
+		apple_spi_tx(spi, &tx_ptr, &remaining_tx, bytes_per_word);
+		apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word);
+	}
+
+	/*
+	 * Sometimes the transfer completes before the last word is in the RX FIFO.
+	 * Normally one retry is all it takes to get the last word out.
+	 */
+	while (remaining_rx && retries--)
+		apple_spi_rx(spi, &rx_ptr, &remaining_rx, bytes_per_word);
+
+	if (remaining_tx)
+		dev_err(&ctlr->dev, "transfer completed with %d words left to transmit\n",
+			remaining_tx);
+	if (remaining_rx)
+		dev_err(&ctlr->dev, "transfer completed with %d words left to receive\n",
+			remaining_rx);
+
+err:
+	fifo_flags = reg_read(spi, APPLE_SPI_IF_FIFO);
+	WARN_ON(fifo_flags & APPLE_SPI_FIFO_TXOVERFLOW);
+	WARN_ON(fifo_flags & APPLE_SPI_FIFO_RXUNDERRUN);
+
+	/* Stop transfer */
+	reg_write(spi, APPLE_SPI_CTRL, 0);
+
+	return ret;
+}
+
+static void apple_spi_clk_disable_unprepare(void *data)
+{
+        clk_disable_unprepare(data);
+}
+
+static int apple_spi_probe(struct platform_device *pdev)
+{
+	struct apple_spi *spi;
+	int ret, irq;
+	struct spi_controller *ctlr;
+
+	ctlr = devm_spi_alloc_master(&pdev->dev, sizeof(struct apple_spi));
+	if (!ctlr)
+		return dev_err_probe(&pdev->dev, -ENOMEM, "out of memory\n");
+
+	spi = spi_controller_get_devdata(ctlr);
+	init_completion(&spi->done);
+	platform_set_drvdata(pdev, ctlr);
+
+	spi->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(spi->regs))
+		return PTR_ERR(spi->regs);
+
+	spi->clk = devm_clk_get(&pdev->dev, NULL);
+	if (IS_ERR(spi->clk))
+		return dev_err_probe(&pdev->dev, PTR_ERR(spi->clk), "Unable to find bus clock\n");
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		return irq;
+	}
+
+	ret = devm_request_irq(&pdev->dev, irq, apple_spi_irq, 0,
+			       dev_name(&pdev->dev), spi);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Unable to bind to interrupt\n");
+
+	ret = clk_prepare_enable(spi->clk);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Unable to enable bus clock\n");
+
+	ret = devm_add_action_or_reset(&pdev->dev, apple_spi_clk_disable_unprepare, spi->clk);
+	if (ret)
+		return ret;
+
+	ctlr->dev.of_node = pdev->dev.of_node;
+	ctlr->bus_num = pdev->id;
+	ctlr->num_chipselect = 1;
+	ctlr->mode_bits = SPI_CPHA | SPI_CPOL | SPI_LSB_FIRST;
+	ctlr->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);
+	ctlr->flags = 0;
+	ctlr->prepare_message = apple_spi_prepare_message;
+	ctlr->set_cs = apple_spi_set_cs;
+	ctlr->transfer_one = apple_spi_transfer_one;
+	ctlr->auto_runtime_pm = true;
+
+	pm_runtime_set_active(&pdev->dev);
+	devm_pm_runtime_enable(&pdev->dev);
+
+	apple_spi_init(spi);
+
+	ret = devm_spi_register_controller(&pdev->dev, ctlr);
+	if (ret < 0)
+		return dev_err_probe(&pdev->dev, ret, "devm_spi_register_controller failed\n");
+
+	return 0;
+}
+
+static const struct of_device_id apple_spi_of_match[] = {
+	{ .compatible = "apple,spi", },
+	{}
+};
+MODULE_DEVICE_TABLE(of, apple_spi_of_match);
+
+static struct platform_driver apple_spi_driver = {
+	.probe = apple_spi_probe,
+	.driver = {
+		.name = "apple-spi",
+		.of_match_table = apple_spi_of_match,
+	},
+};
+module_platform_driver(apple_spi_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("Apple SoC SPI driver");
+MODULE_LICENSE("GPL");

From f33b57d60744d0987fc2237a0a0741cc8381e90d Mon Sep 17 00:00:00 2001
From: Jean-Francois Bortolotti <jeff@borto.fr>
Date: Fri, 4 Feb 2022 00:06:13 +0100
Subject: [PATCH 0611/1027] spmi: add a first basic spmi driver for Apple SoC

Signed-off-by: Jean-Francois Bortolotti <jeff@borto.fr>
---
 drivers/spmi/Kconfig                 |   8 +
 drivers/spmi/Makefile                |   1 +
 drivers/spmi/spmi-apple-controller.c | 221 +++++++++++++++++++++++++++
 3 files changed, 230 insertions(+)
 create mode 100644 drivers/spmi/spmi-apple-controller.c

diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig
index 73780204631463..96c73c5b572022 100644
--- a/drivers/spmi/Kconfig
+++ b/drivers/spmi/Kconfig
@@ -45,4 +45,12 @@ config SPMI_MTK_PMIF
 	  This is required for communicating with Mediatek PMICs and
 	  other devices that have the SPMI interface.
 
+config SPMI_APPLE
+	tristate "Apple SoC SPMI Controller platform driver"
+	depends on ARCH_APPLE || COMPILE_TEST
+	help
+	  This enables basic support for the SPMI controller present on
+	  many Apple SoCs, including the t8103 (M1) and t600x
+	  (M1 Pro/Max).
+
 endif
diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile
index 7f152167bb05b2..8c80236dfac41b 100644
--- a/drivers/spmi/Makefile
+++ b/drivers/spmi/Makefile
@@ -7,3 +7,4 @@ obj-$(CONFIG_SPMI)	+= spmi.o spmi-devres.o
 obj-$(CONFIG_SPMI_HISI3670)	+= hisi-spmi-controller.o
 obj-$(CONFIG_SPMI_MSM_PMIC_ARB)	+= spmi-pmic-arb.o
 obj-$(CONFIG_SPMI_MTK_PMIF)	+= spmi-mtk-pmif.o
+obj-$(CONFIG_SPMI_APPLE)	+= spmi-apple-controller.o
diff --git a/drivers/spmi/spmi-apple-controller.c b/drivers/spmi/spmi-apple-controller.c
new file mode 100644
index 00000000000000..5a9acc642c1fd0
--- /dev/null
+++ b/drivers/spmi/spmi-apple-controller.c
@@ -0,0 +1,221 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Apple SoC SPMI device driver
+ *
+ * Copyright The Asahi Linux Contributors
+ *
+ * Inspired by:
+ *		OpenBSD support Copyright (c) 2021 Mark Kettenis <kettenis@openbsd.org>
+ *		Correllium support Copyright (C) 2021 Corellium LLC
+ *		hisi-spmi-controller.c
+ *		spmi-pmic-ard.c Copyright (c) 2021, The Linux Foundation.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/spmi.h>
+
+/* SPMI Controller Registers */
+#define SPMI_STATUS_REG 0
+#define SPMI_CMD_REG 0x4
+#define SPMI_RSP_REG 0x8
+
+#define SPMI_RX_FIFO_EMPTY BIT(24)
+#define SPMI_TX_FIFO_EMPTY BIT(8)
+
+/* Apple SPMI controler */
+struct apple_spmi {
+	void __iomem *regs;
+	struct spmi_controller *ctrl;
+};
+
+static inline u32 read_reg(struct apple_spmi *spmi, int offset)
+{
+	return (readl_relaxed(spmi->regs + offset));
+}
+
+static inline void write_reg(u32 value, struct apple_spmi *spmi, int offset)
+{
+	writel_relaxed(value, spmi->regs + offset);
+}
+
+static int spmi_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 slave_id,
+			 u16 slave_addr, u8 *__buf, size_t bc)
+{
+	struct apple_spmi *spmi;
+	u32 spmi_cmd = opc | slave_id << 8 | slave_addr << 16 | (bc - 1) |
+		       (1 << 15);
+	u32 rsp;
+	volatile u32 status;
+	size_t len_to_read;
+	u8 i;
+
+	spmi = spmi_controller_get_drvdata(ctrl);
+
+	write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+
+	/* Wait for Rx FIFO to have something */
+	/* Quite ugly msleep, need to find a better way to do it */
+	i = 0;
+	do {
+		status = read_reg(spmi, SPMI_STATUS_REG);
+		msleep(10);
+		i += 1;
+	} while ((status & SPMI_RX_FIFO_EMPTY) && i < 5);
+
+	if (i >= 5) {
+		dev_err(&ctrl->dev,
+			"spmi_read_cmd:took to long to get the status");
+		return -1;
+	}
+
+	/* Read SPMI reply status */
+	rsp = read_reg(spmi, SPMI_RSP_REG);
+
+	len_to_read = 0;
+	/* Read SPMI data reply */
+	while (!(status & SPMI_RX_FIFO_EMPTY) && (len_to_read < bc)) {
+		rsp = read_reg(spmi, SPMI_RSP_REG);
+		i = 0;
+		while ((len_to_read < bc) && (i < 4)) {
+			__buf[len_to_read++] = ((0xff << (8 * i)) & rsp) >>
+					       (8 * i);
+			i += 1;
+		}
+	}
+
+	return 0;
+}
+
+static int spmi_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 slave_id,
+			  u16 slave_addr, const u8 *__buf, size_t bc)
+{
+	struct apple_spmi *spmi;
+	u32 spmi_cmd = opc | slave_id << 8 | slave_addr << 16 | (bc - 1) |
+		       (1 << 15);
+	volatile u32 rsp;
+	volatile u32 status;
+	size_t i = 0, j;
+
+	spmi = spmi_controller_get_drvdata(ctrl);
+
+	write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+
+	while (i < bc) {
+		j = 0;
+		spmi_cmd = 0;
+		while ((j < 4) & (i < bc)) {
+			spmi_cmd |= __buf[i++] << (j++ * 8);
+		}
+		write_reg(spmi_cmd, spmi, SPMI_CMD_REG);
+	}
+
+	/* Wait for Rx FIFO to have something */
+	/* Quite ugly msleep, need to find a better way to do it */
+	i = 0;
+	do {
+		status = read_reg(spmi, SPMI_STATUS_REG);
+		msleep(10);
+		i += 1;
+	} while ((status & SPMI_RX_FIFO_EMPTY) && i < 5);
+
+	if (i >= 5) {
+		dev_err(&ctrl->dev,
+			"spmi_write_cmd:took to long to get the status");
+		return -1;
+	}
+
+	rsp = read_reg(spmi, SPMI_RSP_REG);
+	(void)rsp; // TODO: check stuff here
+
+	return 0;
+}
+
+static int spmi_controller_probe(struct platform_device *pdev)
+{
+	struct apple_spmi *spmi;
+	struct spmi_controller *ctrl;
+	int ret;
+
+	ctrl = spmi_controller_alloc(&pdev->dev, sizeof(struct apple_spmi));
+	if (IS_ERR(ctrl)) {
+		dev_err_probe(&pdev->dev, PTR_ERR(ctrl),
+			      "Can't allocate spmi_controller data\n");
+		return -ENOMEM;
+	}
+
+	spmi = spmi_controller_get_drvdata(ctrl);
+	spmi->ctrl = ctrl;
+	platform_set_drvdata(pdev, ctrl);
+
+	spmi->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(spmi->regs)) {
+		dev_err_probe(&pdev->dev, PTR_ERR(spmi->regs),
+			      "Can't get ioremap regs.\n");
+		return PTR_ERR(spmi->regs);
+	}
+
+	ctrl->dev.of_node = of_node_get(pdev->dev.of_node);
+
+	/* Callbacks */
+	ctrl->read_cmd = spmi_read_cmd;
+	ctrl->write_cmd = spmi_write_cmd;
+
+	ret = spmi_controller_add(ctrl);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"spmi_controller_add failed with error %d!\n", ret);
+		goto err_put_controller;
+	}
+
+	/* Let's look for other nodes in device tree like the rtc */
+	ret = devm_of_platform_populate(&pdev->dev);
+	if (ret) {
+		dev_err(&pdev->dev,
+			"spmi_controller_probe: devm_of_platform_populate failed with error %d!\n",
+			ret);
+		goto err_devm_of_platform_populate;
+	}
+
+	return 0;
+
+err_put_controller:
+	spmi_controller_put(ctrl);
+err_devm_of_platform_populate:
+	return ret;
+}
+
+static void spmi_del_controller(struct platform_device *pdev)
+{
+	struct spmi_controller *ctrl = platform_get_drvdata(pdev);
+
+	spmi_controller_remove(ctrl);
+	spmi_controller_put(ctrl);
+}
+
+static const struct of_device_id spmi_controller_match_table[] = {
+	{
+		.compatible = "apple,spmi",
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, spmi_controller_match_table);
+
+static struct platform_driver spmi_controller_driver = {
+	.probe		= spmi_controller_probe,
+	.remove		= spmi_del_controller,
+	.driver		= {
+		.name	= "apple-spmi",
+		.of_match_table = spmi_controller_match_table,
+	},
+};
+module_platform_driver(spmi_controller_driver);
+
+MODULE_AUTHOR("Jean-Francois Bortolotti <jeff@borto.fr>");
+MODULE_DESCRIPTION("Apple SoC SPMI driver");
+MODULE_LICENSE("GPL");

From bd55a95f68dc2965cd91c9e63dbef972adf143df Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 11 Dec 2022 23:24:41 +0100
Subject: [PATCH 0612/1027] HID: transport: spi: Add suspend support

Working suspend and resume. The keyboard can not yet used as wakeup
source. Most likely caused by missing irq_set_wake support in the
gpio/pinctrl driver.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/spi-hid/spi-hid-apple-core.c | 89 ++++++++++++++++++++++++
 drivers/hid/spi-hid/spi-hid-apple-of.c   | 19 ++++-
 drivers/hid/spi-hid/spi-hid-apple.h      |  4 ++
 3 files changed, 110 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
index 29317a824c7270..787dcd9ceb4ffd 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-core.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -115,6 +115,10 @@ struct spihid_apple {
 
 	/* state tracking flags */
 	bool status_booted;
+
+#ifdef IRQ_WAKE_SUPPORT
+	bool irq_wake_enabled;
+#endif
 };
 
 /**
@@ -1035,6 +1039,91 @@ void spihid_apple_core_shutdown(struct spi_device *spi)
 }
 EXPORT_SYMBOL_GPL(spihid_apple_core_shutdown);
 
+#ifdef CONFIG_PM_SLEEP
+static int spihid_apple_core_suspend(struct device *dev)
+{
+	int ret;
+#ifdef IRQ_WAKE_SUPPORT
+	int wake_status;
+#endif
+	struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev));
+
+	if (spihid->tp.hid) {
+		ret = hid_driver_suspend(spihid->tp.hid, PMSG_SUSPEND);
+		if (ret < 0)
+			return ret;
+	}
+
+	if (spihid->kbd.hid) {
+		ret = hid_driver_suspend(spihid->kbd.hid, PMSG_SUSPEND);
+		if (ret < 0) {
+			if (spihid->tp.hid)
+				hid_driver_resume(spihid->tp.hid);
+			return ret;
+		}
+	}
+
+	/* Save some power */
+	spihid->ops->disable_irq(spihid->ops);
+
+#ifdef IRQ_WAKE_SUPPORT
+	if (device_may_wakeup(dev)) {
+		wake_status = spihid->ops->enable_irq_wake(spihid->ops);
+		if (!wake_status)
+			spihid->irq_wake_enabled = true;
+		else
+			dev_warn(dev, "Failed to enable irq wake: %d\n",
+				wake_status);
+	} else {
+		spihid->ops->power_off(spihid->ops);
+	}
+#else
+	spihid->ops->power_off(spihid->ops);
+#endif
+
+	return 0;
+}
+
+static int spihid_apple_core_resume(struct device *dev)
+{
+	int ret_tp = 0, ret_kbd = 0;
+	struct spihid_apple *spihid = spi_get_drvdata(to_spi_device(dev));
+#ifdef IRQ_WAKE_SUPPORT
+	int wake_status;
+
+	if (!device_may_wakeup(dev)) {
+		spihid->ops->power_on(spihid->ops);
+	} else if (spihid->irq_wake_enabled) {
+		wake_status = spihid->ops->disable_irq_wake(spihid->ops);
+		if (!wake_status)
+			spihid->irq_wake_enabled = false;
+		else
+			dev_warn(dev, "Failed to disable irq wake: %d\n",
+				wake_status);
+	}
+#endif
+
+	spihid->ops->enable_irq(spihid->ops);
+	spihid->ops->power_on(spihid->ops);
+
+	if (spihid->tp.hid)
+		ret_tp = hid_driver_reset_resume(spihid->tp.hid);
+	if (spihid->kbd.hid)
+		ret_kbd = hid_driver_reset_resume(spihid->kbd.hid);
+
+	if (ret_tp < 0)
+		return ret_tp;
+
+	return ret_kbd;
+}
+#endif
+
+const struct dev_pm_ops spihid_apple_core_pm = {
+	SET_SYSTEM_SLEEP_PM_OPS(spihid_apple_core_suspend,
+				spihid_apple_core_resume)
+};
+EXPORT_SYMBOL_GPL(spihid_apple_core_pm);
+
 MODULE_DESCRIPTION("Apple SPI HID transport driver");
 MODULE_AUTHOR("Janne Grunau <j@jannau.net>");
 MODULE_LICENSE("GPL");
diff --git a/drivers/hid/spi-hid/spi-hid-apple-of.c b/drivers/hid/spi-hid/spi-hid-apple-of.c
index f1380bfc52672e..3f87b299351dfd 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-of.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-of.c
@@ -65,6 +65,20 @@ static int spihid_apple_of_disable_irq(struct spihid_apple_ops *ops)
 	return 0;
 }
 
+static int spihid_apple_of_enable_irq_wake(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	return enable_irq_wake(sh_of->irq);
+}
+
+static int spihid_apple_of_disable_irq_wake(struct spihid_apple_ops *ops)
+{
+	struct spihid_apple_of *sh_of = container_of(ops, struct spihid_apple_of, ops);
+
+	return disable_irq_wake(sh_of->irq);
+}
+
 static int spihid_apple_of_probe(struct spi_device *spi)
 {
 	struct device *dev = &spi->dev;
@@ -79,6 +93,8 @@ static int spihid_apple_of_probe(struct spi_device *spi)
 	spihid_of->ops.power_off = spihid_apple_of_power_off;
 	spihid_of->ops.enable_irq = spihid_apple_of_enable_irq;
 	spihid_of->ops.disable_irq = spihid_apple_of_disable_irq;
+	spihid_of->ops.enable_irq_wake = spihid_apple_of_enable_irq_wake;
+	spihid_of->ops.disable_irq_wake = spihid_apple_of_disable_irq_wake;
 
 	spihid_of->enable_gpio = devm_gpiod_get_index(dev, "spien", 0, 0);
 	if (IS_ERR(spihid_of->enable_gpio)) {
@@ -120,8 +136,7 @@ MODULE_DEVICE_TABLE(spi, spihid_apple_of_id);
 static struct spi_driver spihid_apple_of_driver = {
 	.driver = {
 		.name	= "spi-hid-apple-of",
-		//.pm	= &spi_hid_apple_of_pm,
-		.owner = THIS_MODULE,
+		.pm	= &spihid_apple_core_pm,
 		.of_match_table = of_match_ptr(spihid_apple_of_match),
 	},
 
diff --git a/drivers/hid/spi-hid/spi-hid-apple.h b/drivers/hid/spi-hid/spi-hid-apple.h
index 2d9554e8a5f819..9abecd1ba78028 100644
--- a/drivers/hid/spi-hid/spi-hid-apple.h
+++ b/drivers/hid/spi-hid/spi-hid-apple.h
@@ -20,6 +20,8 @@ struct spihid_apple_ops {
     int (*power_off)(struct spihid_apple_ops *ops);
     int (*enable_irq)(struct spihid_apple_ops *ops);
     int (*disable_irq)(struct spihid_apple_ops *ops);
+    int (*enable_irq_wake)(struct spihid_apple_ops *ops);
+    int (*disable_irq_wake)(struct spihid_apple_ops *ops);
 };
 
 irqreturn_t spihid_apple_core_irq(int irq, void *data);
@@ -28,4 +30,6 @@ int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops
 void spihid_apple_core_remove(struct spi_device *spi);
 void spihid_apple_core_shutdown(struct spi_device *spi);
 
+extern const struct dev_pm_ops spihid_apple_core_pm;
+
 #endif /* SPI_HID_APPLE_H */

From 5945bf3b60aa8d0957a0e34e6f5353da3af9d289 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 10 Apr 2023 22:44:44 +0900
Subject: [PATCH 0613/1027] HID: Bump maximum report size to 16384

This maximum is arbitrary. Recent Apple devices have some vendor-defined
reports with 16384 here which fail to parse without this, so let's bump
it to that.

This value is used as follows:

report->size += parser->global.report_size * parser->global.report_count;

[...]

/* Total size check: Allow for possible report index byte */
if (report->size > (max_buffer_size - 1) << 3) {
	hid_err(parser->device, "report is too long\n");
	return -1;
}

All of these fields are unsigned integers, and report_count is bounded
by HID_MAX_USAGES (12288). Therefore, as long as the respective maximums
do not overflow an unsigned integer (let's say a signed integer just in
case), we're safe. This holds for 16384.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-core.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c
index 403c2b2522d3e3..e3a268193c8ebf 100644
--- a/drivers/hid/hid-core.c
+++ b/drivers/hid/hid-core.c
@@ -436,7 +436,10 @@ static int hid_parser_global(struct hid_parser *parser, struct hid_item *item)
 
 	case HID_GLOBAL_ITEM_TAG_REPORT_SIZE:
 		parser->global.report_size = item_udata(item);
-		if (parser->global.report_size > 256) {
+		/* Arbitrary maximum. Some Apple devices have 16384 here.
+		 * This * HID_MAX_USAGES must fit in a signed integer.
+		 */
+		if (parser->global.report_size > 16384) {
 			hid_err(parser->device, "invalid report_size %d\n",
 					parser->global.report_size);
 			return -1;

From 0ba809f5c72124832801b75b5609263782c88fe1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 30 Apr 2023 23:48:45 +0900
Subject: [PATCH 0614/1027] HID: magicmouse: Handle touch controller resets on
 SPI devices

On at least some SPI devices (e.g. recent Apple Silicon machines), the
Broadcom touch controller is prone to crashing. When this happens, the
STM eventually notices and resets it. It then notifies the driver via
HID report 0x60, and the driver needs to re-enable MT mode to make
things work again.

This poses an additional issue: the hidinput core will close the
low-level transport while the device is closed, which can cause us to
miss a reset notification. To fix this, override the input open/close
callbacks and send the MT enable every time the HID device is opened,
instead of only once on probe. This should increase general robustness,
even if the reset mechanism doesn't work for some reason, so it's worth
doing it for USB devices too. MTP devices are exempt since they do not
require the MT enable at all.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-magicmouse.c | 203 +++++++++++++++++++++++------------
 1 file changed, 133 insertions(+), 70 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 00320e934883a8..74f4549ee5c734 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -60,6 +60,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define MOUSE2_REPORT_ID   0x12
 #define DOUBLE_REPORT_ID   0xf7
 #define SPI_REPORT_ID      0x02
+#define SPI_RESET_REPORT_ID 0x60
 #define MTP_REPORT_ID      0x75
 #define USB_BATTERY_TIMEOUT_MS 60000
 
@@ -173,6 +174,97 @@ struct magicmouse_sc {
 	struct magicmouse_input_ops input_ops;
 };
 
+static int magicmouse_enable_multitouch(struct hid_device *hdev)
+{
+	const u8 *feature;
+	const u8 feature_mt[] = { 0xD7, 0x01 };
+	const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
+	const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
+	const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
+	u8 *buf;
+	int ret;
+	int feature_size;
+
+	if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
+		if (hdev->vendor == BT_VENDOR_ID_APPLE) {
+			feature_size = sizeof(feature_mt_trackpad2_bt);
+			feature = feature_mt_trackpad2_bt;
+		} else { /* USB_VENDOR_ID_APPLE */
+			feature_size = sizeof(feature_mt_trackpad2_usb);
+			feature = feature_mt_trackpad2_usb;
+		}
+	} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
+		feature_size = sizeof(feature_mt_trackpad2_usb);
+		feature = feature_mt_trackpad2_usb;
+	} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+		feature_size = sizeof(feature_mt_mouse2);
+		feature = feature_mt_mouse2;
+	} else {
+		feature_size = sizeof(feature_mt);
+		feature = feature_mt;
+	}
+
+	buf = kmemdup(feature, feature_size, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+
+	ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
+				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
+	kfree(buf);
+	return ret;
+}
+
+static void magicmouse_enable_mt_work(struct work_struct *work)
+{
+	struct magicmouse_sc *msc =
+		container_of(work, struct magicmouse_sc, work.work);
+	int ret;
+
+	ret = magicmouse_enable_multitouch(msc->hdev);
+	if (ret < 0)
+		hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
+}
+
+static int magicmouse_open(struct input_dev *dev)
+{
+	struct hid_device *hdev = input_get_drvdata(dev);
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
+	int ret;
+
+	ret = hid_hw_open(hdev);
+	if (ret)
+		return ret;
+
+	/*
+	 * Some devices repond with 'invalid report id' when feature
+	 * report switching it into multitouch mode is sent to it.
+	 *
+	 * This results in -EIO from the _raw low-level transport callback,
+	 * but there seems to be no other way of switching the mode.
+	 * Thus the super-ugly hacky success check below.
+	 */
+	ret = magicmouse_enable_multitouch(hdev);
+	if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+		schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+		return 0;
+	}
+	if (ret < 0)
+		hid_err(hdev, "unable to request touch data (%d)\n", ret);
+
+	/*
+	 * MT enable is usually not required after the first time, so don't
+	 * consider it fatal.
+	 */
+	return 0;
+}
+
+static void magicmouse_close(struct input_dev *dev)
+{
+	struct hid_device *hdev = input_get_drvdata(dev);
+
+	hid_hw_close(hdev);
+}
+
 static int magicmouse_firm_touch(struct magicmouse_sc *msc)
 {
 	int touch = -1;
@@ -694,12 +786,19 @@ static int magicmouse_raw_event_mtp(struct hid_device *hdev,
 static int magicmouse_raw_event_spi(struct hid_device *hdev,
 		struct hid_report *report, u8 *data, int size)
 {
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
 	const size_t hdr_sz = sizeof(struct tp_mouse_report);
 
-	if (size < hdr_sz)
+	if (!size)
 		return 0;
 
-	if (data[0] != TRACKPAD2_USB_REPORT_ID)
+	if (data[0] == SPI_RESET_REPORT_ID) {
+		hid_info(hdev, "Touch controller was reset, re-enabling touch mode\n");
+		schedule_delayed_work(&msc->work, msecs_to_jiffies(10));
+		return 1;
+	}
+
+	if (data[0] != TRACKPAD2_USB_REPORT_ID || size < hdr_sz)
 		return 0;
 
 	return magicmouse_raw_event_mtp(hdev, report, data + hdr_sz, size - hdr_sz);
@@ -881,10 +980,17 @@ static int magicmouse_setup_input_usb(struct input_dev *input,
 	 */
 	__clear_bit(EV_REP, input->evbit);
 
+	/*
+	 * This isn't strictly speaking needed for USB, but enabling MT on
+	 * device open is probably more robust than only doing it once on probe
+	 * even if USB devices are not known to suffer from the SPI reset issue.
+	 */
+	input->open = magicmouse_open;
+	input->close = magicmouse_close;
 	return 0;
 }
 
-static int magicmouse_setup_input_spi(struct input_dev *input,
+static int magicmouse_setup_input_mtp(struct input_dev *input,
 				      struct hid_device *hdev)
 {
 	int error;
@@ -957,6 +1063,25 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
 	return 0;
 }
 
+static int magicmouse_setup_input_spi(struct input_dev *input,
+				      struct hid_device *hdev)
+{
+	int ret = magicmouse_setup_input_mtp(input, hdev);
+	if (ret)
+		return ret;
+
+	/*
+	 * Override the default input->open function to send the MT
+	 * enable every time the device is opened. This ensures it works
+	 * even if we missed a reset event due to the device being closed.
+	 * input->close is overridden for symmetry.
+	 */
+	input->open = magicmouse_open;
+	input->close = magicmouse_close;
+
+	return 0;
+}
+
 static int magicmouse_input_mapping(struct hid_device *hdev,
 		struct hid_input *hi, struct hid_field *field,
 		struct hid_usage *usage, unsigned long **bit, int *max)
@@ -993,57 +1118,6 @@ static int magicmouse_input_configured(struct hid_device *hdev,
 	return 0;
 }
 
-static int magicmouse_enable_multitouch(struct hid_device *hdev)
-{
-	const u8 *feature;
-	const u8 feature_mt[] = { 0xD7, 0x01 };
-	const u8 feature_mt_mouse2[] = { 0xF1, 0x02, 0x01 };
-	const u8 feature_mt_trackpad2_usb[] = { 0x02, 0x01 };
-	const u8 feature_mt_trackpad2_bt[] = { 0xF1, 0x02, 0x01 };
-	u8 *buf;
-	int ret;
-	int feature_size;
-
-	if (hdev->product == USB_DEVICE_ID_APPLE_MAGICTRACKPAD2) {
-		if (hdev->vendor == BT_VENDOR_ID_APPLE) {
-			feature_size = sizeof(feature_mt_trackpad2_bt);
-			feature = feature_mt_trackpad2_bt;
-		} else { /* USB_VENDOR_ID_APPLE */
-			feature_size = sizeof(feature_mt_trackpad2_usb);
-			feature = feature_mt_trackpad2_usb;
-		}
-	} else if (hdev->vendor == SPI_VENDOR_ID_APPLE) {
-		feature_size = sizeof(feature_mt_trackpad2_usb);
-		feature = feature_mt_trackpad2_usb;
-	} else if (hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
-		feature_size = sizeof(feature_mt_mouse2);
-		feature = feature_mt_mouse2;
-	} else {
-		feature_size = sizeof(feature_mt);
-		feature = feature_mt;
-	}
-
-	buf = kmemdup(feature, feature_size, GFP_KERNEL);
-	if (!buf)
-		return -ENOMEM;
-
-	ret = hid_hw_raw_request(hdev, buf[0], buf, feature_size,
-				HID_FEATURE_REPORT, HID_REQ_SET_REPORT);
-	kfree(buf);
-	return ret;
-}
-
-static void magicmouse_enable_mt_work(struct work_struct *work)
-{
-	struct magicmouse_sc *msc =
-		container_of(work, struct magicmouse_sc, work.work);
-	int ret;
-
-	ret = magicmouse_enable_multitouch(msc->hdev);
-	if (ret < 0)
-		hid_err(msc->hdev, "unable to request touch data (%d)\n", ret);
-}
-
 static int magicmouse_fetch_battery(struct hid_device *hdev)
 {
 #ifdef CONFIG_HID_BATTERY_STRENGTH
@@ -1103,7 +1177,7 @@ static int magicmouse_probe(struct hid_device *hdev,
 	// conflicts with the report ID.
 	if (id->bus == BUS_HOST) {
 		msc->input_ops.raw_event = magicmouse_raw_event_mtp;
-		msc->input_ops.setup_input = magicmouse_setup_input_spi;
+		msc->input_ops.setup_input = magicmouse_setup_input_mtp;
 	} else if (id->bus == BUS_SPI) {
 		msc->input_ops.raw_event = magicmouse_raw_event_spi;
 		msc->input_ops.setup_input = magicmouse_setup_input_spi;
@@ -1183,21 +1257,10 @@ static int magicmouse_probe(struct hid_device *hdev,
 	if (id->bus == BUS_HOST)
 		return 0;
 
-	/*
-	 * Some devices repond with 'invalid report id' when feature
-	 * report switching it into multitouch mode is sent to it.
-	 *
-	 * This results in -EIO from the _raw low-level transport callback,
-	 * but there seems to be no other way of switching the mode.
-	 * Thus the super-ugly hacky success check below.
-	 */
-	ret = magicmouse_enable_multitouch(hdev);
-	if (ret != -EIO && ret < 0) {
-		hid_err(hdev, "unable to request touch data (%d)\n", ret);
-		goto err_stop_hw;
-	}
-	if (ret == -EIO && id->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
-		schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+	/* SPI devices need to watch for reset events to re-send the MT enable */
+	if (id->bus == BUS_SPI) {
+		report = hid_register_report(hdev, HID_INPUT_REPORT, SPI_RESET_REPORT_ID, 0);
+		report->size = 2;
 	}
 
 	return 0;

From 3641fcba89e3282551210b6acbdd2efb6c2cb41c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 3 Dec 2023 23:10:21 +0900
Subject: [PATCH 0615/1027] HID: transport: spi: Implement GET FEATURE

This is used for fetching trackpad dimensions.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/spi-hid/spi-hid-apple-core.c | 51 ++++++++++++++++++++++--
 1 file changed, 48 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
index 787dcd9ceb4ffd..bfcae013cb5cc2 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-core.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -65,6 +65,8 @@ struct spihid_interface {
 	u32 max_input_report_len;
 	u32 max_output_report_len;
 	u8 name[32];
+	u8 reply_buf[SPIHID_DESC_MAX];
+	u32 reply_len;
 	bool ready;
 };
 
@@ -327,6 +329,7 @@ static int apple_ll_raw_request(struct hid_device *hdev,
 {
 	struct spihid_interface *idev = hdev->driver_data;
 	struct spihid_apple *spihid = spihid_get_data(idev);
+	int ret;
 
 	dev_dbg(&spihid->spidev->dev,
 		"apple_ll_raw_request: device:%u reportnum:%hhu rtype:%hhu",
@@ -334,7 +337,25 @@ static int apple_ll_raw_request(struct hid_device *hdev,
 
 	switch (reqtype) {
 	case HID_REQ_GET_REPORT:
-		return -EINVAL; // spihid_get_raw_report();
+		if (rtype != HID_FEATURE_REPORT)
+			return -EINVAL;
+
+		idev->reply_len = 0;
+		ret = spihid_apple_request(spihid, idev->id, 0x32, reportnum, 0x00, len, NULL, 0);
+		if (ret < 0)
+			return ret;
+
+		ret = wait_event_interruptible_timeout(spihid->wait, idev->reply_len,
+						       SPIHID_DEF_WAIT);
+		if (ret == 0)
+			ret = -ETIMEDOUT;
+		if (ret < 0) {
+			dev_err(&spihid->spidev->dev, "waiting for get report failed: %d", ret);
+			return ret;
+		}
+		memcpy(buf, idev->reply_buf, max_t(size_t, len, idev->reply_len));
+		return idev->reply_len;
+
 	case HID_REQ_SET_REPORT:
 		if (buf[0] != reportnum)
 			return -EINVAL;
@@ -606,7 +627,27 @@ static bool spihid_process_iface_hid_report_desc(struct spihid_apple *spihid,
 	return true;
 }
 
-static bool spihid_process_response(struct spihid_apple *spihid,
+static bool spihid_process_iface_get_report(struct spihid_apple *spihid,
+					    u32 device, u8 report,
+					    u8 *payload, size_t len)
+{
+	struct spihid_interface *iface = spihid_get_iface(spihid, device);
+
+	if (!iface)
+		return false;
+
+	if (len > sizeof(iface->reply_buf) || len < 1)
+		return false;
+
+	memcpy(iface->reply_buf, payload, len);
+	iface->reply_len = len;
+
+	wake_up_interruptible(&spihid->wait);
+
+	return true;
+}
+
+static bool spihid_process_response(struct spihid_apple *spihid, u32 device,
 				    struct spihid_msg_hdr *hdr, u8 *payload,
 				    size_t len)
 {
@@ -626,6 +667,10 @@ static bool spihid_process_response(struct spihid_apple *spihid,
 		}
 	}
 
+	if (hdr->unknown0 == 0x32) {
+		return spihid_process_iface_get_report(spihid, device, hdr->unknown1, payload, len);
+	}
+
 	return false;
 }
 
@@ -653,7 +698,7 @@ static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
 						      payload, le16_to_cpu(hdr->length));
 		break;
 	case SPIHID_WRITE_PACKET:
-		handled = spihid_process_response(spihid, hdr, payload,
+		handled = spihid_process_response(spihid, device, hdr, payload,
 						  le16_to_cpu(hdr->length));
 		break;
 	default:

From 258694d635adb08451fef42a8437ef139557ad0a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 3 Dec 2023 21:08:17 +0900
Subject: [PATCH 0616/1027] HID: magicmouse: Query device dimensions via HID
 report

For SPI/MTP trackpads, query the dimensions via HID report instead of
hardcoding values.

TODO: Does this work for the USB/BT devices? Maybe we can get rid of the
hardcoded sizes everywhere?

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/hid/hid-magicmouse.c | 102 +++++++++++++++++++++++++++--------
 1 file changed, 81 insertions(+), 21 deletions(-)

diff --git a/drivers/hid/hid-magicmouse.c b/drivers/hid/hid-magicmouse.c
index 74f4549ee5c734..7061cc1f93a1cf 100644
--- a/drivers/hid/hid-magicmouse.c
+++ b/drivers/hid/hid-magicmouse.c
@@ -62,6 +62,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define SPI_REPORT_ID      0x02
 #define SPI_RESET_REPORT_ID 0x60
 #define MTP_REPORT_ID      0x75
+#define SENSOR_DIMENSIONS_REPORT_ID 0xd9
 #define USB_BATTERY_TIMEOUT_MS 60000
 
 #define MAX_CONTACTS 16
@@ -116,6 +117,7 @@ MODULE_PARM_DESC(report_undeciphered, "Report undeciphered multi-touch state fie
 #define TRACKPAD2_RES_Y \
 	((TRACKPAD2_MAX_Y - TRACKPAD2_MIN_Y) / (TRACKPAD2_DIMENSION_Y / 100))
 
+/* These are fallback values, since the real values will be queried from the device. */
 #define J314_TP_DIMENSION_X (float)13000
 #define J314_TP_MIN_X -5900
 #define J314_TP_MAX_X 6500
@@ -139,6 +141,7 @@ struct magicmouse_input_ops {
  * struct magicmouse_sc - Tracks Magic Mouse-specific data.
  * @input: Input device through which we report events.
  * @quirks: Currently unused.
+ * @query_dimensions: Whether to query and update dimensions on first open
  * @ntouches: Number of touches in most recent touch report.
  * @scroll_accel: Number of consecutive scroll motions.
  * @scroll_jiffies: Time of last scroll motion.
@@ -151,6 +154,7 @@ struct magicmouse_input_ops {
 struct magicmouse_sc {
 	struct input_dev *input;
 	unsigned long quirks;
+	bool query_dimensions;
 
 	int ntouches;
 	int scroll_accel;
@@ -174,6 +178,11 @@ struct magicmouse_sc {
 	struct magicmouse_input_ops input_ops;
 };
 
+static inline int le16_to_int(__le16 x)
+{
+	return (signed short)le16_to_cpu(x);
+}
+
 static int magicmouse_enable_multitouch(struct hid_device *hdev)
 {
 	const u8 *feature;
@@ -242,19 +251,71 @@ static int magicmouse_open(struct input_dev *dev)
 	 * This results in -EIO from the _raw low-level transport callback,
 	 * but there seems to be no other way of switching the mode.
 	 * Thus the super-ugly hacky success check below.
+	 *
+	 * MTP devices do not need this.
 	 */
-	ret = magicmouse_enable_multitouch(hdev);
-	if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
-		schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
-		return 0;
+	if (hdev->bus != BUS_HOST) {
+		ret = magicmouse_enable_multitouch(hdev);
+		if (ret == -EIO && hdev->product == USB_DEVICE_ID_APPLE_MAGICMOUSE2) {
+			schedule_delayed_work(&msc->work, msecs_to_jiffies(500));
+			return 0;
+		}
+		if (ret < 0)
+			hid_err(hdev, "unable to request touch data (%d)\n", ret);
 	}
-	if (ret < 0)
-		hid_err(hdev, "unable to request touch data (%d)\n", ret);
-
 	/*
 	 * MT enable is usually not required after the first time, so don't
 	 * consider it fatal.
 	 */
+
+	/*
+	 * For Apple Silicon trackpads, we want to query the dimensions on
+	 * device open. This is because doing so requires the firmware, but
+	 * we don't want to force a firmware load until the device is opened
+	 * for the first time. So do that here and update the input properties
+	 * just in time before userspace queries them.
+	 */
+	if (msc->query_dimensions) {
+		struct input_dev *input = msc->input;
+		u8 buf[32];
+		struct {
+			__le32 width;
+			__le32 height;
+			__le16 min_x;
+			__le16 min_y;
+			__le16 max_x;
+			__le16 max_y;
+		} dim;
+		uint32_t x_span, y_span;
+
+		ret = hid_hw_raw_request(hdev, SENSOR_DIMENSIONS_REPORT_ID, buf, sizeof(buf), HID_FEATURE_REPORT, HID_REQ_GET_REPORT);
+		if (ret < (int)(1 + sizeof(dim))) {
+			hid_err(hdev, "unable to request dimensions (%d)\n", ret);
+			return ret;
+		}
+
+		memcpy(&dim, buf + 1, sizeof(dim));
+
+		/* finger position */
+		input_set_abs_params(input, ABS_MT_POSITION_X,
+				     le16_to_int(dim.min_x), le16_to_int(dim.max_x), 0, 0);
+		/* Y axis is inverted */
+		input_set_abs_params(input, ABS_MT_POSITION_Y,
+				     -le16_to_int(dim.max_y), -le16_to_int(dim.min_y), 0, 0);
+		x_span = le16_to_int(dim.max_x) - le16_to_int(dim.min_x);
+		y_span = le16_to_int(dim.max_y) - le16_to_int(dim.min_y);
+
+		/* X/Y resolution */
+		input_abs_set_res(input, ABS_MT_POSITION_X, 100 * x_span / le32_to_cpu(dim.width) );
+		input_abs_set_res(input, ABS_MT_POSITION_Y, 100 * y_span / le32_to_cpu(dim.height) );
+
+		/* copy info, as input_mt_init_slots() does */
+		dev->absinfo[ABS_X] = dev->absinfo[ABS_MT_POSITION_X];
+		dev->absinfo[ABS_Y] = dev->absinfo[ABS_MT_POSITION_Y];
+
+		msc->query_dimensions = false;
+	}
+
 	return 0;
 }
 
@@ -695,11 +756,6 @@ struct tp_mouse_report {
 	u8 padding[4];
 };
 
-static inline int le16_to_int(__le16 x)
-{
-	return (signed short)le16_to_cpu(x);
-}
-
 static void report_finger_data(struct input_dev *input, int slot,
 			       const struct input_mt_pos *pos,
 			       const struct tp_finger *f)
@@ -995,6 +1051,7 @@ static int magicmouse_setup_input_mtp(struct input_dev *input,
 {
 	int error;
 	int mt_flags = 0;
+	struct magicmouse_sc *msc = hid_get_drvdata(hdev);
 
 	__set_bit(INPUT_PROP_BUTTONPAD, input->propbit);
 	__clear_bit(BTN_0, input->keybit);
@@ -1060,6 +1117,18 @@ static int magicmouse_setup_input_mtp(struct input_dev *input,
 	if (error)
 		return error;
 
+	/*
+	 * Override the default input->open function to send the MT
+	 * enable every time the device is opened. This ensures it works
+	 * even if we missed a reset event due to the device being closed.
+	 * input->close is overridden for symmetry.
+	 *
+	 * This also takes care of the dimensions query.
+	 */
+	input->open = magicmouse_open;
+	input->close = magicmouse_close;
+	msc->query_dimensions = true;
+
 	return 0;
 }
 
@@ -1070,15 +1139,6 @@ static int magicmouse_setup_input_spi(struct input_dev *input,
 	if (ret)
 		return ret;
 
-	/*
-	 * Override the default input->open function to send the MT
-	 * enable every time the device is opened. This ensures it works
-	 * even if we missed a reset event due to the device being closed.
-	 * input->close is overridden for symmetry.
-	 */
-	input->open = magicmouse_open;
-	input->close = magicmouse_close;
-
 	return 0;
 }
 

From fb24eb42511c684020d3c357f134ced72dcaed3d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Feb 2022 12:47:07 +0900
Subject: [PATCH 0617/1027] platform/apple: Add new Apple Mac SMC driver

This driver implements support for the SMC (System Management
Controller) in Apple Macs. In contrast to the existing applesmc driver,
it uses pluggable backends that allow it to support different SMC
implementations, and uses the MFD subsystem to expose the core SMC
functionality so that specific features (gpio, hwmon, battery, etc.) can
be implemented by separate drivers in their respective downstream
subsystems.

The initial RTKit backend adds support for Apple Silicon Macs (M1 et
al). We hope a backend for T2 Macs will be written in the future
(since those are not supported by applesmc), and eventually an x86
backend would allow us to fully deprecate applesmc in favor of this
driver.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/platform/Kconfig           |   2 +
 drivers/platform/Makefile          |   1 +
 drivers/platform/apple/Kconfig     |  49 ++++
 drivers/platform/apple/Makefile    |  11 +
 drivers/platform/apple/smc.h       |  28 ++
 drivers/platform/apple/smc_core.c  | 249 ++++++++++++++++
 drivers/platform/apple/smc_rtkit.c | 451 +++++++++++++++++++++++++++++
 include/linux/mfd/macsmc.h         |  88 ++++++
 8 files changed, 879 insertions(+)
 create mode 100644 drivers/platform/apple/Kconfig
 create mode 100644 drivers/platform/apple/Makefile
 create mode 100644 drivers/platform/apple/smc.h
 create mode 100644 drivers/platform/apple/smc_core.c
 create mode 100644 drivers/platform/apple/smc_rtkit.c
 create mode 100644 include/linux/mfd/macsmc.h

diff --git a/drivers/platform/Kconfig b/drivers/platform/Kconfig
index 960fd6a82450a4..9f5d5251161b0d 100644
--- a/drivers/platform/Kconfig
+++ b/drivers/platform/Kconfig
@@ -17,4 +17,6 @@ source "drivers/platform/surface/Kconfig"
 
 source "drivers/platform/x86/Kconfig"
 
+source "drivers/platform/apple/Kconfig"
+
 source "drivers/platform/arm64/Kconfig"
diff --git a/drivers/platform/Makefile b/drivers/platform/Makefile
index 19ac54648586eb..1e35f82c01e224 100644
--- a/drivers/platform/Makefile
+++ b/drivers/platform/Makefile
@@ -12,4 +12,5 @@ obj-$(CONFIG_GOLDFISH)		+= goldfish/
 obj-$(CONFIG_CHROME_PLATFORMS)	+= chrome/
 obj-$(CONFIG_CZNIC_PLATFORMS)	+= cznic/
 obj-$(CONFIG_SURFACE_PLATFORMS)	+= surface/
+obj-$(CONFIG_APPLE_PLATFORMS)	+= apple/
 obj-$(CONFIG_ARM64_PLATFORM_DEVICES)	+= arm64/
diff --git a/drivers/platform/apple/Kconfig b/drivers/platform/apple/Kconfig
new file mode 100644
index 00000000000000..5bcadd349493ac
--- /dev/null
+++ b/drivers/platform/apple/Kconfig
@@ -0,0 +1,49 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Apple Platform-Specific Drivers
+#
+
+menuconfig APPLE_PLATFORMS
+	bool "Apple Mac Platform-Specific Device Drivers"
+	default y
+	help
+	  Say Y here to get to see options for platform-specific device drivers
+	  for Apple devices. This option alone does not add any kernel code.
+
+	  If you say N, all options in this submenu will be skipped and disabled.
+
+if APPLE_PLATFORMS
+
+config APPLE_SMC
+	tristate "Apple SMC Driver"
+	depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
+	default ARCH_APPLE
+	select MFD_CORE
+	help
+	  Build support for the Apple System Management Controller present in
+	  Apple Macs. This driver currently supports the SMC in Apple Silicon
+	  Macs. For x86 Macs, see the applesmc driver (SENSORS_APPLESMC).
+
+	  Say Y here if you have an Apple Silicon Mac.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called macsmc.
+
+if APPLE_SMC
+
+config APPLE_SMC_RTKIT
+	tristate "RTKit (Apple Silicon) backend"
+	depends on ARCH_APPLE || (COMPILE_TEST && 64BIT)
+	depends on APPLE_RTKIT
+	default ARCH_APPLE
+	help
+	  Build support for SMC communications via the RTKit backend. This is
+	  required for Apple Silicon Macs.
+
+	  Say Y here if you have an Apple Silicon Mac.
+
+	  To compile this driver as a module, choose M here: the module will
+	  be called macsmc-rtkit.
+
+endif
+endif
diff --git a/drivers/platform/apple/Makefile b/drivers/platform/apple/Makefile
new file mode 100644
index 00000000000000..79fac195398b0c
--- /dev/null
+++ b/drivers/platform/apple/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: GPL-2.0
+#
+# Makefile for linux/drivers/platform/apple
+# Apple Platform-Specific Drivers
+#
+
+macsmc-y				+= smc_core.o
+macsmc-rtkit-y				+= smc_rtkit.o
+
+obj-$(CONFIG_APPLE_SMC)			+= macsmc.o
+obj-$(CONFIG_APPLE_SMC_RTKIT)		+= macsmc-rtkit.o
diff --git a/drivers/platform/apple/smc.h b/drivers/platform/apple/smc.h
new file mode 100644
index 00000000000000..8ae51887b2c553
--- /dev/null
+++ b/drivers/platform/apple/smc.h
@@ -0,0 +1,28 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC internal core definitions
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#ifndef _SMC_H
+#define _SMC_H
+
+#include <linux/mfd/macsmc.h>
+
+struct apple_smc_backend_ops {
+	int (*read_key)(void *cookie, smc_key key, void *buf, size_t size);
+	int (*write_key)(void *cookie, smc_key key, void *buf, size_t size);
+	int (*write_key_atomic)(void *cookie, smc_key key, void *buf, size_t size);
+	int (*rw_key)(void *cookie, smc_key key, void *wbuf, size_t wsize,
+		      void *rbuf, size_t rsize);
+	int (*get_key_by_index)(void *cookie, int index, smc_key *key);
+	int (*get_key_info)(void *cookie, smc_key key, struct apple_smc_key_info *info);
+};
+
+struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
+				  void *cookie);
+void *apple_smc_get_cookie(struct apple_smc *smc);
+int apple_smc_remove(struct apple_smc *smc);
+void apple_smc_event_received(struct apple_smc *smc, uint32_t event);
+
+#endif
diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
new file mode 100644
index 00000000000000..daf029cd072f52
--- /dev/null
+++ b/drivers/platform/apple/smc_core.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC core framework
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/device.h>
+#include <linux/mfd/core.h>
+#include <linux/mutex.h>
+#include <linux/notifier.h>
+#include "smc.h"
+
+struct apple_smc {
+	struct device *dev;
+
+	void *be_cookie;
+	const struct apple_smc_backend_ops *be;
+
+	struct mutex mutex;
+
+	u32 key_count;
+	smc_key first_key;
+	smc_key last_key;
+
+	struct blocking_notifier_head event_handlers;
+};
+
+static const struct mfd_cell apple_smc_devs[] = {
+	{
+		.name = "macsmc-gpio",
+	},
+	{
+		.name = "macsmc-hid",
+	},
+	{
+		.name = "macsmc-power",
+	},
+	{
+		.name = "macsmc-reboot",
+	},
+	{
+		.name = "macsmc-rtc",
+	},
+};
+
+int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+	int ret;
+
+	mutex_lock(&smc->mutex);
+	ret = smc->be->read_key(smc->be_cookie, key, buf, size);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_read);
+
+int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+	int ret;
+
+	mutex_lock(&smc->mutex);
+	ret = smc->be->write_key(smc->be_cookie, key, buf, size);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_write);
+
+int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size)
+{
+	int ret;
+
+	/*
+	 * Will fail if SMC is busy. This is only used by SMC reboot/poweroff
+	 * final calls, so it doesn't really matter at that point.
+	 */
+	if (!mutex_trylock(&smc->mutex))
+		return -EBUSY;
+
+	ret = smc->be->write_key_atomic(smc->be_cookie, key, buf, size);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_write_atomic);
+
+int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize,
+		 void *rbuf, size_t rsize)
+{
+	int ret;
+
+	mutex_lock(&smc->mutex);
+	ret = smc->be->rw_key(smc->be_cookie, key, wbuf, wsize, rbuf, rsize);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_rw);
+
+int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
+{
+	int ret;
+
+	mutex_lock(&smc->mutex);
+	ret = smc->be->get_key_by_index(smc->be_cookie, index, key);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_get_key_by_index);
+
+int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info)
+{
+	int ret;
+
+	mutex_lock(&smc->mutex);
+	ret = smc->be->get_key_info(smc->be_cookie, key, info);
+	mutex_unlock(&smc->mutex);
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_get_key_info);
+
+int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key)
+{
+	int start = 0, count = smc->key_count;
+	int ret;
+
+	if (key <= smc->first_key)
+		return 0;
+	if (key > smc->last_key)
+		return smc->key_count;
+
+	while (count > 1) {
+		int pivot = start + ((count - 1) >> 1);
+		smc_key pkey;
+
+		ret = apple_smc_get_key_by_index(smc, pivot, &pkey);
+		if (ret < 0)
+			return ret;
+
+		if (pkey == key)
+			return pivot;
+
+		pivot++;
+
+		if (pkey < key) {
+			count -= pivot - start;
+			start = pivot;
+		} else {
+			count = pivot - start;
+		}
+	}
+
+	return start;
+}
+EXPORT_SYMBOL(apple_smc_find_first_key_index);
+
+int apple_smc_get_key_count(struct apple_smc *smc)
+{
+	return smc->key_count;
+}
+EXPORT_SYMBOL(apple_smc_get_key_count);
+
+void apple_smc_event_received(struct apple_smc *smc, uint32_t event)
+{
+	dev_dbg(smc->dev, "Event: 0x%08x\n", event);
+	blocking_notifier_call_chain(&smc->event_handlers, event, NULL);
+}
+EXPORT_SYMBOL(apple_smc_event_received);
+
+int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n)
+{
+	return blocking_notifier_chain_register(&smc->event_handlers, n);
+}
+EXPORT_SYMBOL(apple_smc_register_notifier);
+
+int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n)
+{
+	return blocking_notifier_chain_unregister(&smc->event_handlers, n);
+}
+EXPORT_SYMBOL(apple_smc_unregister_notifier);
+
+void *apple_smc_get_cookie(struct apple_smc *smc)
+{
+	return smc->be_cookie;
+}
+EXPORT_SYMBOL(apple_smc_get_cookie);
+
+struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie)
+{
+	struct apple_smc *smc;
+	u32 count;
+	int ret;
+
+	smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
+	if (!smc)
+		return ERR_PTR(-ENOMEM);
+
+	smc->dev = dev;
+	smc->be_cookie = cookie;
+	smc->be = ops;
+	mutex_init(&smc->mutex);
+	BLOCKING_INIT_NOTIFIER_HEAD(&smc->event_handlers);
+
+	ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get key count"));
+	smc->key_count = be32_to_cpu(count);
+
+	ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get first key"));
+
+	ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get last key"));
+
+	/* Enable notifications */
+	apple_smc_write_flag(smc, SMC_KEY(NTAP), 1);
+
+	dev_info(dev, "Initialized (%d keys %p4ch..%p4ch)\n",
+		 smc->key_count, &smc->first_key, &smc->last_key);
+
+	dev_set_drvdata(dev, smc);
+
+	ret = mfd_add_devices(dev, -1, apple_smc_devs, ARRAY_SIZE(apple_smc_devs), NULL, 0, NULL);
+	if (ret)
+		return ERR_PTR(dev_err_probe(dev, ret, "Subdevice initialization failed"));
+
+	return smc;
+}
+EXPORT_SYMBOL(apple_smc_probe);
+
+int apple_smc_remove(struct apple_smc *smc)
+{
+	mfd_remove_devices(smc->dev);
+
+	/* Disable notifications */
+	apple_smc_write_flag(smc, SMC_KEY(NTAP), 1);
+
+	return 0;
+}
+EXPORT_SYMBOL(apple_smc_remove);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC core");
diff --git a/drivers/platform/apple/smc_rtkit.c b/drivers/platform/apple/smc_rtkit.c
new file mode 100644
index 00000000000000..5b662cb68fce78
--- /dev/null
+++ b/drivers/platform/apple/smc_rtkit.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC RTKit backend
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <asm/unaligned.h>
+#include <linux/bitfield.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/soc/apple/rtkit.h>
+#include "smc.h"
+
+#define SMC_ENDPOINT			0x20
+
+/* Guess */
+#define SMC_SHMEM_SIZE			0x1000
+
+#define SMC_MSG_READ_KEY		0x10
+#define SMC_MSG_WRITE_KEY		0x11
+#define SMC_MSG_GET_KEY_BY_INDEX	0x12
+#define SMC_MSG_GET_KEY_INFO		0x13
+#define SMC_MSG_INITIALIZE		0x17
+#define SMC_MSG_NOTIFICATION		0x18
+#define SMC_MSG_RW_KEY			0x20
+
+#define SMC_DATA			GENMASK(63, 32)
+#define SMC_WSIZE			GENMASK(31, 24)
+#define SMC_SIZE			GENMASK(23, 16)
+#define SMC_ID				GENMASK(15, 12)
+#define SMC_MSG				GENMASK(7, 0)
+#define SMC_RESULT			SMC_MSG
+
+#define SMC_RECV_TIMEOUT		500
+
+struct apple_smc_rtkit {
+	struct device *dev;
+	struct apple_smc *core;
+	struct apple_rtkit *rtk;
+
+	struct completion init_done;
+	bool initialized;
+	bool alive;
+
+	struct resource *sram;
+	void __iomem *sram_base;
+	struct apple_rtkit_shmem shmem;
+
+	unsigned int msg_id;
+
+	bool atomic_pending;
+	struct completion cmd_done;
+	u64 cmd_ret;
+};
+
+static int apple_smc_rtkit_write_key_atomic(void *cookie, smc_key key, void *buf, size_t size)
+{
+	struct apple_smc_rtkit *smc = cookie;
+	int ret;
+	u64 msg;
+	u8 result;
+
+	if (size > SMC_SHMEM_SIZE || size == 0)
+		return -EINVAL;
+
+	if (!smc->alive)
+		return -EIO;
+
+	memcpy_toio(smc->shmem.iomem, buf, size);
+	smc->msg_id = (smc->msg_id + 1) & 0xf;
+	msg = (FIELD_PREP(SMC_MSG, SMC_MSG_WRITE_KEY) |
+	       FIELD_PREP(SMC_SIZE, size) |
+	       FIELD_PREP(SMC_ID, smc->msg_id) |
+	       FIELD_PREP(SMC_DATA, key));
+	smc->atomic_pending = true;
+
+	ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, true);
+	if (ret < 0) {
+		dev_err(smc->dev, "Failed to send command (%d)\n", ret);
+		return ret;
+	}
+
+	while (smc->atomic_pending) {
+		ret = apple_rtkit_poll(smc->rtk);
+		if (ret < 0) {
+			dev_err(smc->dev, "RTKit poll failed (%llx)", msg);
+			return ret;
+		}
+		udelay(100);
+	}
+
+	if (FIELD_GET(SMC_ID, smc->cmd_ret) != smc->msg_id) {
+		dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n",
+			smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret));
+		return -EIO;
+	}
+
+	result = FIELD_GET(SMC_RESULT, smc->cmd_ret);
+	if (result != 0)
+		return -result;
+
+	return FIELD_GET(SMC_SIZE, smc->cmd_ret);
+}
+
+static int apple_smc_cmd(struct apple_smc_rtkit *smc, u64 cmd, u64 arg,
+			 u64 size, u64 wsize, u32 *ret_data)
+{
+	int ret;
+	u64 msg;
+	u8 result;
+
+	if (!smc->alive)
+		return -EIO;
+
+	reinit_completion(&smc->cmd_done);
+
+	smc->msg_id = (smc->msg_id + 1) & 0xf;
+	msg = (FIELD_PREP(SMC_MSG, cmd) |
+	       FIELD_PREP(SMC_SIZE, size) |
+	       FIELD_PREP(SMC_WSIZE, wsize) |
+	       FIELD_PREP(SMC_ID, smc->msg_id) |
+	       FIELD_PREP(SMC_DATA, arg));
+
+	ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT, msg, NULL, false);
+	if (ret < 0) {
+		dev_err(smc->dev, "Failed to send command\n");
+		return ret;
+	}
+
+	do {
+		if (wait_for_completion_timeout(&smc->cmd_done,
+						msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
+			dev_err(smc->dev, "Command timed out (%llx)", msg);
+			return -ETIMEDOUT;
+		}
+		if (FIELD_GET(SMC_ID, smc->cmd_ret) == smc->msg_id)
+			break;
+		dev_err(smc->dev, "Command sequence mismatch (expected %d, got %d)\n",
+			smc->msg_id, (unsigned int)FIELD_GET(SMC_ID, smc->cmd_ret));
+	} while(1);
+
+	result = FIELD_GET(SMC_RESULT, smc->cmd_ret);
+	if (result != 0)
+		return -result;
+
+	if (ret_data)
+		*ret_data = FIELD_GET(SMC_DATA, smc->cmd_ret);
+
+	return FIELD_GET(SMC_SIZE, smc->cmd_ret);
+}
+
+static int _apple_smc_rtkit_read_key(struct apple_smc_rtkit *smc, smc_key key,
+				     void *buf, size_t size, size_t wsize)
+{
+	int ret;
+	u32 rdata;
+	u64 cmd;
+
+	if (size > SMC_SHMEM_SIZE || size == 0)
+		return -EINVAL;
+
+	cmd = wsize ? SMC_MSG_RW_KEY : SMC_MSG_READ_KEY;
+
+	ret = apple_smc_cmd(smc, cmd, key, size, wsize, &rdata);
+	if (ret < 0)
+		return ret;
+
+	if (size <= 4)
+		memcpy(buf, &rdata, size);
+	else
+		memcpy_fromio(buf, smc->shmem.iomem, size);
+
+	return ret;
+}
+
+static int apple_smc_rtkit_read_key(void *cookie, smc_key key, void *buf, size_t size)
+{
+	return _apple_smc_rtkit_read_key(cookie, key, buf, size, 0);
+}
+
+static int apple_smc_rtkit_write_key(void *cookie, smc_key key, void *buf, size_t size)
+{
+	struct apple_smc_rtkit *smc = cookie;
+
+	if (size > SMC_SHMEM_SIZE || size == 0)
+		return -EINVAL;
+
+	memcpy_toio(smc->shmem.iomem, buf, size);
+	return apple_smc_cmd(smc, SMC_MSG_WRITE_KEY, key, size, 0, NULL);
+}
+
+static int apple_smc_rtkit_rw_key(void *cookie, smc_key key,
+				  void *wbuf, size_t wsize, void *rbuf, size_t rsize)
+{
+	struct apple_smc_rtkit *smc = cookie;
+
+	if (wsize > SMC_SHMEM_SIZE || wsize == 0)
+		return -EINVAL;
+
+	memcpy_toio(smc->shmem.iomem, wbuf, wsize);
+	return _apple_smc_rtkit_read_key(smc, key, rbuf, rsize, wsize);
+}
+
+static int apple_smc_rtkit_get_key_by_index(void *cookie, int index, smc_key *key)
+{
+	struct apple_smc_rtkit *smc = cookie;
+	int ret;
+
+	ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_BY_INDEX, index, 0, 0, key);
+
+	*key = swab32(*key);
+	return ret;
+}
+
+static int apple_smc_rtkit_get_key_info(void *cookie, smc_key key, struct apple_smc_key_info *info)
+{
+	struct apple_smc_rtkit *smc = cookie;
+	u8 key_info[6];
+	int ret;
+
+	ret = apple_smc_cmd(smc, SMC_MSG_GET_KEY_INFO, key, 0, 0, NULL);
+	if (ret >= 0 && info) {
+		memcpy_fromio(key_info, smc->shmem.iomem, sizeof(key_info));
+		info->size = key_info[0];
+		info->type_code = get_unaligned_be32(&key_info[1]);
+		info->flags = key_info[5];
+	}
+	return ret;
+}
+
+static const struct apple_smc_backend_ops apple_smc_rtkit_be_ops = {
+	.read_key = apple_smc_rtkit_read_key,
+	.write_key = apple_smc_rtkit_write_key,
+	.write_key_atomic = apple_smc_rtkit_write_key_atomic,
+	.rw_key = apple_smc_rtkit_rw_key,
+	.get_key_by_index = apple_smc_rtkit_get_key_by_index,
+	.get_key_info = apple_smc_rtkit_get_key_info,
+};
+
+static void apple_smc_rtkit_crashed(void *cookie)
+{
+	struct apple_smc_rtkit *smc = cookie;
+
+	dev_err(smc->dev, "SMC crashed! Your system will reboot in a few seconds...\n");
+	smc->alive = false;
+}
+
+static int apple_smc_rtkit_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	struct apple_smc_rtkit *smc = cookie;
+	struct resource res = {
+		.start = bfr->iova,
+		.end = bfr->iova + bfr->size - 1,
+		.name = "rtkit_map",
+		.flags = smc->sram->flags,
+	};
+
+	if (!bfr->iova) {
+		dev_err(smc->dev, "RTKit wants a RAM buffer\n");
+		return -EIO;
+	}
+
+	if (res.end < res.start || !resource_contains(smc->sram, &res)) {
+		dev_err(smc->dev,
+			"RTKit buffer request outside SRAM region: %pR", &res);
+		return -EFAULT;
+	}
+
+	bfr->iomem = smc->sram_base + (res.start - smc->sram->start);
+	bfr->is_mapped = true;
+
+	return 0;
+}
+
+static void apple_smc_rtkit_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
+{
+	// no-op
+}
+
+static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message)
+{
+	struct apple_smc_rtkit *smc = cookie;
+
+	if (endpoint != SMC_ENDPOINT) {
+		dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
+		return false;
+	}
+
+	if (!smc->initialized) {
+		int ret;
+
+		smc->shmem.iova = message;
+		smc->shmem.size = SMC_SHMEM_SIZE;
+		ret = apple_smc_rtkit_shmem_setup(smc, &smc->shmem);
+		if (ret < 0)
+			dev_err(smc->dev, "Failed to initialize shared memory\n");
+		else
+			smc->alive = true;
+		smc->initialized = true;
+		complete(&smc->init_done);
+	} else if (FIELD_GET(SMC_MSG, message) == SMC_MSG_NOTIFICATION) {
+		/* Handle these in the RTKit worker thread */
+		return false;
+	} else {
+		smc->cmd_ret = message;
+		if (smc->atomic_pending) {
+			smc->atomic_pending = false;
+		} else {
+			complete(&smc->cmd_done);
+		}
+	}
+
+	return true;
+}
+
+static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message)
+{
+	struct apple_smc_rtkit *smc = cookie;
+
+	if (endpoint != SMC_ENDPOINT) {
+		dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
+		return;
+	}
+
+	if (FIELD_GET(SMC_MSG, message) != SMC_MSG_NOTIFICATION) {
+		dev_err(smc->dev, "Received unknown message from worker: 0x%llx\n", message);
+		return;
+	}
+
+	apple_smc_event_received(smc->core, FIELD_GET(SMC_DATA, message));
+}
+
+static const struct apple_rtkit_ops apple_smc_rtkit_ops = {
+	.crashed = apple_smc_rtkit_crashed,
+	.recv_message = apple_smc_rtkit_recv,
+	.recv_message_early = apple_smc_rtkit_recv_early,
+	.shmem_setup = apple_smc_rtkit_shmem_setup,
+	.shmem_destroy = apple_smc_rtkit_shmem_destroy,
+};
+
+static int apple_smc_rtkit_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct apple_smc_rtkit *smc;
+	int ret;
+
+	smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
+	if (!smc)
+		return -ENOMEM;
+
+	smc->dev = dev;
+
+	smc->sram = platform_get_resource_byname(pdev, IORESOURCE_MEM, "sram");
+	if (!smc->sram)
+		return dev_err_probe(dev, EIO,
+				     "No SRAM region");
+
+	smc->sram_base = devm_ioremap_resource(dev, smc->sram);
+	if (IS_ERR(smc->sram_base))
+		return dev_err_probe(dev, PTR_ERR(smc->sram_base),
+				     "Failed to map SRAM region");
+
+	smc->rtk =
+		devm_apple_rtkit_init(dev, smc, NULL, 0, &apple_smc_rtkit_ops);
+	if (IS_ERR(smc->rtk))
+		return dev_err_probe(dev, PTR_ERR(smc->rtk),
+				     "Failed to intialize RTKit");
+
+	ret = apple_rtkit_wake(smc->rtk);
+	if (ret != 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to wake up SMC");
+
+	ret = apple_rtkit_start_ep(smc->rtk, SMC_ENDPOINT);
+	if (ret != 0) {
+		dev_err(dev, "Failed to start endpoint");
+		goto cleanup;
+	}
+
+	init_completion(&smc->init_done);
+	init_completion(&smc->cmd_done);
+
+	ret = apple_rtkit_send_message(smc->rtk, SMC_ENDPOINT,
+				       FIELD_PREP(SMC_MSG, SMC_MSG_INITIALIZE), NULL, false);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				     "Failed to send init message");
+
+	if (wait_for_completion_timeout(&smc->init_done,
+					msecs_to_jiffies(SMC_RECV_TIMEOUT)) == 0) {
+		ret = -ETIMEDOUT;
+		dev_err(dev, "Timed out initializing SMC");
+		goto cleanup;
+	}
+
+	if (!smc->alive) {
+		ret = -EIO;
+		goto cleanup;
+	}
+
+	smc->core = apple_smc_probe(dev, &apple_smc_rtkit_be_ops, smc);
+	if (IS_ERR(smc->core)) {
+		ret = PTR_ERR(smc->core);
+		goto cleanup;
+	}
+
+	return 0;
+
+cleanup:
+	/* Try to shut down RTKit, if it's not completely wedged */
+	if (apple_rtkit_is_running(smc->rtk))
+		apple_rtkit_quiesce(smc->rtk);
+
+	return ret;
+}
+
+static void apple_smc_rtkit_remove(struct platform_device *pdev)
+{
+	struct apple_smc *core = platform_get_drvdata(pdev);
+	struct apple_smc_rtkit *smc = apple_smc_get_cookie(core);
+
+	apple_smc_remove(core);
+
+	if (apple_rtkit_is_running(smc->rtk))
+		apple_rtkit_quiesce(smc->rtk);
+}
+
+static const struct of_device_id apple_smc_rtkit_of_match[] = {
+	{ .compatible = "apple,smc" },
+	{},
+};
+MODULE_DEVICE_TABLE(of, apple_smc_rtkit_of_match);
+
+static struct platform_driver apple_smc_rtkit_driver = {
+	.driver = {
+		.name = "macsmc-rtkit",
+		.of_match_table = apple_smc_rtkit_of_match,
+	},
+	.probe = apple_smc_rtkit_probe,
+	.remove = apple_smc_rtkit_remove,
+};
+module_platform_driver(apple_smc_rtkit_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC RTKit backend driver");
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
new file mode 100644
index 00000000000000..e5f50b5d62dfd3
--- /dev/null
+++ b/include/linux/mfd/macsmc.h
@@ -0,0 +1,88 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC core definitions
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#ifndef _LINUX_MFD_MACSMC_H
+#define _LINUX_MFD_MACSMC_H
+
+struct apple_smc;
+
+typedef u32 smc_key;
+
+#define SMC_KEY(s) (smc_key)(_SMC_KEY(#s))
+#define _SMC_KEY(s) (((s)[0] << 24) | ((s)[1] << 16) | ((s)[2] << 8) | (s)[3])
+#define __SMC_KEY(a, b, c, d) (((u32)(a) << 24) | ((u32)(b) << 16) | \
+                               ((u32)(c) <<  8) |  (u32)(d))
+
+#define APPLE_SMC_READABLE BIT(7)
+#define APPLE_SMC_WRITABLE BIT(6)
+#define APPLE_SMC_FUNCTION BIT(4)
+
+struct apple_smc_key_info {
+	u8 size;
+	u32 type_code;
+	u8 flags;
+};
+
+int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_write(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_write_atomic(struct apple_smc *smc, smc_key key, void *buf, size_t size);
+int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize,
+		 void *rbuf, size_t rsize);
+
+int apple_smc_get_key_count(struct apple_smc *smc);
+int apple_smc_find_first_key_index(struct apple_smc *smc, smc_key key);
+int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key);
+int apple_smc_get_key_info(struct apple_smc *smc, smc_key key, struct apple_smc_key_info *info);
+
+static inline bool apple_smc_key_exists(struct apple_smc *smc, smc_key key)
+{
+	return apple_smc_get_key_info(smc, key, NULL) >= 0;
+}
+
+#define APPLE_SMC_TYPE_OPS(type) \
+	static inline int apple_smc_read_##type(struct apple_smc *smc, smc_key key, type *p) \
+	{ \
+		int ret = apple_smc_read(smc, key, p, sizeof(*p)); \
+		return (ret < 0) ? ret : ((ret != sizeof(*p)) ? -EINVAL : 0); \
+	} \
+	static inline int apple_smc_write_##type(struct apple_smc *smc, smc_key key, type p) \
+	{ \
+		return apple_smc_write(smc, key, &p, sizeof(p)); \
+	} \
+	static inline int apple_smc_write_##type##_atomic(struct apple_smc *smc, smc_key key, type p) \
+	{ \
+		return apple_smc_write_atomic(smc, key, &p, sizeof(p)); \
+	} \
+	static inline int apple_smc_rw_##type(struct apple_smc *smc, smc_key key, \
+					      type w, type *r) \
+	{ \
+		int ret = apple_smc_rw(smc, key, &w, sizeof(w), r, sizeof(*r)); \
+		return (ret < 0) ? ret : ((ret != sizeof(*r)) ? -EINVAL : 0); \
+	}
+
+APPLE_SMC_TYPE_OPS(u64)
+APPLE_SMC_TYPE_OPS(u32)
+APPLE_SMC_TYPE_OPS(u16)
+APPLE_SMC_TYPE_OPS(u8)
+APPLE_SMC_TYPE_OPS(s64)
+APPLE_SMC_TYPE_OPS(s32)
+APPLE_SMC_TYPE_OPS(s16)
+APPLE_SMC_TYPE_OPS(s8)
+
+static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key)
+{
+	u8 val;
+	int ret = apple_smc_read_u8(smc, key, &val);
+	if (ret < 0)
+		return ret;
+	return val ? 1 : 0;
+}
+#define apple_smc_write_flag apple_smc_write_u8
+
+int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n);
+int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n);
+
+#endif

From a042c8e621d6935ec4c7a8cd3e6ecc131c2ea805 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Feb 2022 12:52:52 +0900
Subject: [PATCH 0618/1027] gpio: Add new gpio-macsmc driver for Apple Macs

This driver implements the GPIO service on top of the SMC framework
on Apple Mac machines. In particular, these are the GPIOs present in the
PMU IC which are used to control power to certain on-board devices.

Although the underlying hardware supports various pin config settings
(input/output, open drain, etc.), this driver does not implement that
functionality and leaves it up to the firmware to configure things
properly. We also don't yet support interrupts/events. This is
sufficient for device power control, which is the only thing we need to
support at this point. More features will be implemented when needed.

To our knowledge, only Apple Silicon Macs implement this SMC feature.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpio/Kconfig       |  11 ++
 drivers/gpio/Makefile      |   1 +
 drivers/gpio/gpio-macsmc.c | 238 +++++++++++++++++++++++++++++++++++++
 3 files changed, 250 insertions(+)
 create mode 100644 drivers/gpio/gpio-macsmc.c

diff --git a/drivers/gpio/Kconfig b/drivers/gpio/Kconfig
index 58f43bcced7c1f..4d917186174f53 100644
--- a/drivers/gpio/Kconfig
+++ b/drivers/gpio/Kconfig
@@ -1420,6 +1420,17 @@ config GPIO_LP87565
 	  This driver can also be built as a module. If so, the module will be
 	  called gpio-lp87565.
 
+config GPIO_MACSMC
+	tristate "Apple Mac SMC GPIO"
+	depends on APPLE_SMC
+	default ARCH_APPLE
+	help
+	  Support for GPIOs controlled by the SMC microcontroller on Apple Mac
+	  systems.
+
+	  This driver can also be built as a module. If so, the module will be
+	  called gpio-macsmc.
+
 config GPIO_MADERA
 	tristate "Cirrus Logic Madera class codecs"
 	depends on PINCTRL_MADERA
diff --git a/drivers/gpio/Makefile b/drivers/gpio/Makefile
index 64dd6d9d730d5a..514c7e03620de4 100644
--- a/drivers/gpio/Makefile
+++ b/drivers/gpio/Makefile
@@ -92,6 +92,7 @@ obj-$(CONFIG_GPIO_LP873X)		+= gpio-lp873x.o
 obj-$(CONFIG_GPIO_LP87565)		+= gpio-lp87565.o
 obj-$(CONFIG_GPIO_LPC18XX)		+= gpio-lpc18xx.o
 obj-$(CONFIG_GPIO_LPC32XX)		+= gpio-lpc32xx.o
+obj-$(CONFIG_GPIO_MACSMC)		+= gpio-macsmc.o
 obj-$(CONFIG_GPIO_MADERA)		+= gpio-madera.o
 obj-$(CONFIG_GPIO_MAX3191X)		+= gpio-max3191x.o
 obj-$(CONFIG_GPIO_MAX7300)		+= gpio-max7300.o
diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c
new file mode 100644
index 00000000000000..ff9950afb69af0
--- /dev/null
+++ b/drivers/gpio/gpio-macsmc.c
@@ -0,0 +1,238 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC GPIO driver
+ * Copyright The Asahi Linux Contributors
+ *
+ * This driver implements basic SMC PMU GPIO support that can read inputs
+ * and write outputs. Mode changes and IRQ config are not yet implemented.
+ */
+
+#include <linux/bitmap.h>
+#include <linux/device.h>
+#include <linux/gpio/driver.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+
+#define MAX_GPIO 64
+
+/*
+ * Commands 0-6 are, presumably, the intended API.
+ * Command 0xff lets you get/set the pin configuration in detail directly,
+ * but the bit meanings seem not to be stable between devices/PMU hardware
+ * versions.
+ *
+ * We're going to try to make do with the low commands for now.
+ * We don't implement pin mode changes at this time.
+ */
+
+#define CMD_ACTION	(0 << 24)
+#define CMD_OUTPUT	(1 << 24)
+#define CMD_INPUT	(2 << 24)
+#define CMD_PINMODE	(3 << 24)
+#define CMD_IRQ_ENABLE	(4 << 24)
+#define CMD_IRQ_ACK	(5 << 24)
+#define CMD_IRQ_MODE	(6 << 24)
+#define CMD_CONFIG	(0xff << 24)
+
+#define MODE_INPUT	0
+#define MODE_OUTPUT	1
+#define MODE_VALUE_0	0
+#define MODE_VALUE_1	2
+
+#define IRQ_MODE_HIGH		0
+#define IRQ_MODE_LOW		1
+#define IRQ_MODE_RISING		2
+#define IRQ_MODE_FALLING	3
+#define IRQ_MODE_BOTH		4
+
+#define CONFIG_MASK	GENMASK(23, 16)
+#define CONFIG_VAL	GENMASK(7, 0)
+
+#define CONFIG_OUTMODE	GENMASK(7, 6)
+#define CONFIG_IRQMODE	GENMASK(5, 3)
+#define CONFIG_PULLDOWN	BIT(2)
+#define CONFIG_PULLUP	BIT(1)
+#define CONFIG_OUTVAL	BIT(0)
+
+/*
+ * output modes seem to differ depending on the PMU in use... ?
+ * j274 / M1 (Sera PMU):
+ *   0 = input
+ *   1 = output
+ *   2 = open drain
+ *   3 = disable
+ * j314 / M1Pro (Maverick PMU):
+ *   0 = input
+ *   1 = open drain
+ *   2 = output
+ *   3 = ?
+ */
+
+struct macsmc_gpio {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct gpio_chip gc;
+
+	int first_index;
+};
+
+static int macsmc_gpio_nr(smc_key key)
+{
+	int low = hex_to_bin(key & 0xff);
+	int high = hex_to_bin((key >> 8) & 0xff);
+
+	if (low < 0 || high < 0)
+		return -1;
+
+	return low | (high << 4);
+}
+
+static int macsmc_gpio_key(unsigned int offset)
+{
+	return _SMC_KEY("gP\0\0") | (hex_asc_hi(offset) << 8) | hex_asc_lo(offset);
+}
+
+static int macsmc_gpio_get_direction(struct gpio_chip *gc, unsigned int offset)
+{
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	smc_key key = macsmc_gpio_key(offset);
+	u32 val;
+	int ret;
+
+	/* First try reading the explicit pin mode register */
+	ret = apple_smc_rw_u32(smcgp->smc, key, CMD_PINMODE, &val);
+	if (!ret)
+		return (val & MODE_OUTPUT) ? GPIO_LINE_DIRECTION_OUT : GPIO_LINE_DIRECTION_IN;
+
+	/*
+	 * Less common IRQ configs cause CMD_PINMODE to fail, and so does open drain mode.
+	 * Fall back to reading IRQ mode, which will only succeed for inputs.
+	 */
+	ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
+	return (!ret) ? GPIO_LINE_DIRECTION_IN : GPIO_LINE_DIRECTION_OUT;
+}
+
+static int macsmc_gpio_get(struct gpio_chip *gc, unsigned int offset)
+{
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	smc_key key = macsmc_gpio_key(offset);
+	u32 val;
+	int ret;
+
+	ret = macsmc_gpio_get_direction(gc, offset);
+	if (ret < 0)
+		return ret;
+
+	if (ret == GPIO_LINE_DIRECTION_OUT)
+		ret = apple_smc_rw_u32(smcgp->smc, key, CMD_OUTPUT, &val);
+	else
+		ret = apple_smc_rw_u32(smcgp->smc, key, CMD_INPUT, &val);
+
+	if (ret < 0)
+		return ret;
+
+	return val ? 1 : 0;
+}
+
+static void macsmc_gpio_set(struct gpio_chip *gc, unsigned int offset, int value)
+{
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	smc_key key = macsmc_gpio_key(offset);
+	int ret;
+
+	value |= CMD_OUTPUT;
+	ret = apple_smc_write_u32(smcgp->smc, key, CMD_OUTPUT | value);
+	if (ret < 0)
+		dev_err(smcgp->dev, "GPIO set failed %p4ch = 0x%x\n", &key, value);
+}
+
+static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
+				       unsigned long *valid_mask, unsigned int ngpios)
+{
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	int count = apple_smc_get_key_count(smcgp->smc) - smcgp->first_index;
+	int i;
+
+	if (count > MAX_GPIO)
+		count = MAX_GPIO;
+
+	bitmap_zero(valid_mask, ngpios);
+
+	for (i = 0; i < count; i++) {
+		smc_key key;
+		int gpio_nr;
+		int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
+
+		if (ret < 0)
+			return ret;
+
+		if (key > SMC_KEY(gPff))
+			break;
+
+		gpio_nr = macsmc_gpio_nr(key);
+		if (gpio_nr < 0 || gpio_nr > MAX_GPIO) {
+			dev_err(smcgp->dev, "Bad GPIO key %p4ch\n", &key);
+			continue;
+		}
+
+		set_bit(gpio_nr, valid_mask);
+	}
+
+	return 0;
+}
+
+static int macsmc_gpio_probe(struct platform_device *pdev)
+{
+	struct macsmc_gpio *smcgp;
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	smc_key key;
+	int ret;
+
+	smcgp = devm_kzalloc(&pdev->dev, sizeof(*smcgp), GFP_KERNEL);
+	if (!smcgp)
+		return -ENOMEM;
+
+	pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "gpio");
+
+	smcgp->dev = &pdev->dev;
+	smcgp->smc = smc;
+	smcgp->first_index = apple_smc_find_first_key_index(smc, SMC_KEY(gP00));
+
+	if (smcgp->first_index >= apple_smc_get_key_count(smc))
+		return -ENODEV;
+
+	ret = apple_smc_get_key_by_index(smc, smcgp->first_index, &key);
+	if (ret < 0)
+		return ret;
+
+	if (key > macsmc_gpio_key(MAX_GPIO - 1))
+		return -ENODEV;
+
+	dev_info(smcgp->dev, "First GPIO key: %p4ch\n", &key);
+
+	smcgp->gc.label = "macsmc-pmu-gpio";
+	smcgp->gc.owner = THIS_MODULE;
+	smcgp->gc.get = macsmc_gpio_get;
+	smcgp->gc.set = macsmc_gpio_set;
+	smcgp->gc.get_direction = macsmc_gpio_get_direction;
+	smcgp->gc.init_valid_mask = macsmc_gpio_init_valid_mask;
+	smcgp->gc.can_sleep = true;
+	smcgp->gc.ngpio = MAX_GPIO;
+	smcgp->gc.base = -1;
+	smcgp->gc.parent = &pdev->dev;
+
+	return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp);
+}
+
+static struct platform_driver macsmc_gpio_driver = {
+	.driver = {
+		.name = "macsmc-gpio",
+	},
+	.probe = macsmc_gpio_probe,
+};
+module_platform_driver(macsmc_gpio_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC GPIO driver");
+MODULE_ALIAS("platform:macsmc-gpio");

From 9e561522d0cb5c542b53f6a24ac230b0ecbed05d Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 8 Feb 2022 02:30:16 +0900
Subject: [PATCH 0619/1027] power: supply: macsmc_power: Driver for Apple SMC
 power/battery stats

This driver implements support for battery stats on top of the macsmc
framework, to support Apple M1 Mac machines.

Co-authored-by: Joey Gouly <joey.gouly@arm.com>
Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/Kconfig        |   7 +
 drivers/power/supply/Makefile       |   1 +
 drivers/power/supply/macsmc_power.c | 255 ++++++++++++++++++++++++++++
 3 files changed, 263 insertions(+)
 create mode 100644 drivers/power/supply/macsmc_power.c

diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index bcfa63fb9f1e20..b51426d4e11079 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -1018,4 +1018,11 @@ config FUEL_GAUGE_MM8013
 	  the state of charge, temperature, cycle count, actual and design
 	  capacity, etc.
 
+config CHARGER_MACSMC
+	tristate "Apple SMC Charger / Battery support"
+	depends on APPLE_SMC
+	help
+	  Say Y here to enable support for the charger and battery controls on
+	  Apple SMC controllers, as used on Apple Silicon Macs.
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 8dcb4154531718..71952aee32f703 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -75,6 +75,7 @@ obj-$(CONFIG_CHARGER_GPIO)	+= gpio-charger.o
 obj-$(CONFIG_CHARGER_MANAGER)	+= charger-manager.o
 obj-$(CONFIG_CHARGER_LT3651)	+= lt3651-charger.o
 obj-$(CONFIG_CHARGER_LTC4162L)	+= ltc4162-l-charger.o
+obj-$(CONFIG_CHARGER_MACSMC)	+= macsmc_power.o
 obj-$(CONFIG_CHARGER_MAX14577)	+= max14577_charger.o
 obj-$(CONFIG_CHARGER_DETECTOR_MAX14656)	+= max14656_charger_detector.o
 obj-$(CONFIG_CHARGER_MAX77650)	+= max77650-charger.o
diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
new file mode 100644
index 00000000000000..7fb29490503078
--- /dev/null
+++ b/drivers/power/supply/macsmc_power.c
@@ -0,0 +1,255 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Power/Battery Management
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/power_supply.h>
+
+#define MAX_STRING_LENGTH 256
+
+struct macsmc_power {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct power_supply *psy;
+	char model_name[MAX_STRING_LENGTH];
+	char serial_number[MAX_STRING_LENGTH];
+
+	struct notifier_block nb;
+};
+
+static int macsmc_battery_get_status(struct macsmc_power *power)
+{
+	u8 val;
+	int ret;
+
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(BSFC), &val);
+	if (ret)
+		return ret;
+	if (val == 1)
+		return POWER_SUPPLY_STATUS_FULL;
+
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHSC), &val);
+	if (ret)
+		return ret;
+	if (val == 1)
+		return POWER_SUPPLY_STATUS_CHARGING;
+
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCC), &val);
+	if (ret)
+		return ret;
+	if (val == 0)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCE), &val);
+	if (ret)
+		return ret;
+	if (val == 0)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+	else
+		return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int macsmc_battery_get_property(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	struct macsmc_power *power = power_supply_get_drvdata(psy);
+	int ret = 0;
+	u16 vu16;
+	u32 vu32;
+	s16 vs16;
+	s32 vs32;
+	s64 vs64;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = macsmc_battery_get_status(power);
+		ret = val->intval < 0 ? val->intval : 0;
+		break;
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
+		val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+		break;
+	case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TF), &vu16);
+		val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
+		val->intval = vu16;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		ret = apple_smc_read_s16(power->smc, SMC_KEY(B0AC), &vs16);
+		val->intval = vs16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_POWER_NOW:
+		ret = apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &vs32);
+		val->intval = vs32 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+		ret = apple_smc_read_u32(power->smc, SMC_KEY(CSIL), &vu32);
+		val->intval = vu32 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RV), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
+		val->intval = swab16(vu16) * 1000;
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
+		val->intval = vu16 - 2732;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+		ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
+		val->intval = vs64;
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = power->model_name;
+		break;
+	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+		val->strval = power->serial_number;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property macsmc_battery_props[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_POWER_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_CHARGE_COUNTER,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+};
+
+static const struct power_supply_desc macsmc_battery_desc = {
+	.name		= "macsmc-battery",
+	.type		= POWER_SUPPLY_TYPE_BATTERY,
+	.get_property	= macsmc_battery_get_property,
+	.properties	= macsmc_battery_props,
+	.num_properties	= ARRAY_SIZE(macsmc_battery_props),
+};
+
+static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+	struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
+
+	if ((event & 0xffffff00) == 0x71010100) {
+		bool charging = (event & 0xff) != 0;
+
+		dev_info(power->dev, "Charging: %d\n", charging);
+		power_supply_changed(power->psy);
+
+		return NOTIFY_OK;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int macsmc_power_probe(struct platform_device *pdev)
+{
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	struct power_supply_config psy_cfg = {};
+	struct macsmc_power *power;
+	int ret;
+
+	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+	if (!power)
+		return -ENOMEM;
+
+	power->dev = &pdev->dev;
+	power->smc = smc;
+	dev_set_drvdata(&pdev->dev, power);
+
+	/* Ignore devices without a charger/battery */
+	if (macsmc_battery_get_status(power) <= POWER_SUPPLY_STATUS_UNKNOWN)
+		return -ENODEV;
+
+	/* Fetch string properties */
+	apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, sizeof(power->model_name) - 1);
+	apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
+
+	psy_cfg.drv_data = power;
+	power->psy = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
+	if (IS_ERR(power->psy)) {
+		dev_err(&pdev->dev, "Failed to register power supply\n");
+		ret = PTR_ERR(power->psy);
+		return ret;
+	}
+
+	power->nb.notifier_call = macsmc_power_event;
+	apple_smc_register_notifier(power->smc, &power->nb);
+
+	return 0;
+}
+
+static void macsmc_power_remove(struct platform_device *pdev)
+{
+	struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
+
+	apple_smc_unregister_notifier(power->smc, &power->nb);
+}
+
+static struct platform_driver macsmc_power_driver = {
+	.driver = {
+		.name = "macsmc-power",
+		.owner = THIS_MODULE,
+	},
+	.probe = macsmc_power_probe,
+	.remove = macsmc_power_remove,
+};
+module_platform_driver(macsmc_power_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC battery and power management driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-power");

From 687a74377917d693cab53f553725b2d9070da087 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 8 Feb 2022 02:51:35 +0900
Subject: [PATCH 0620/1027] power: supply: macsmc_power: Add cycle count and
 health props

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 11 +++++++++++
 1 file changed, 11 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 7fb29490503078..e2324ea37ba778 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -136,6 +136,15 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_s64(power->smc, SMC_KEY(BAAC), &vs64);
 		val->intval = vs64;
 		break;
+	case POWER_SUPPLY_PROP_CYCLE_COUNT:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
+		val->intval = vu16;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD));
+		val->intval = ret == 1 ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
+		ret = ret < 0 ? ret : 0;
+		break;
 	case POWER_SUPPLY_PROP_MODEL_NAME:
 		val->strval = power->model_name;
 		break;
@@ -167,6 +176,8 @@ static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_CHARGE_NOW,
 	POWER_SUPPLY_PROP_TEMP,
 	POWER_SUPPLY_PROP_CHARGE_COUNTER,
+	POWER_SUPPLY_PROP_CYCLE_COUNT,
+	POWER_SUPPLY_PROP_HEALTH,
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_SERIAL_NUMBER,
 };

From ff6c2a89a941408411ea268102aca309fc7ada00 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 8 Feb 2022 11:01:17 +0900
Subject: [PATCH 0621/1027] power: supply: macsmc_power: Add present prop

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index e2324ea37ba778..3969c189a13e84 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -72,6 +72,9 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		val->intval = macsmc_battery_get_status(power);
 		ret = val->intval < 0 ? val->intval : 0;
 		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = 1;
+		break;
 	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
 		val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
@@ -160,6 +163,7 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 
 static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
 	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_CAPACITY,

From a8503287aab912c8825a023276860c58578cb85b Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 02:20:20 +0900
Subject: [PATCH 0622/1027] power: supply: macsmc_power: Add more props, rework
 others

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 223 +++++++++++++++++++++++++---
 1 file changed, 202 insertions(+), 21 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 3969c189a13e84..0f1eb83d690e97 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -4,6 +4,7 @@
  * Copyright The Asahi Linux Contributors
  */
 
+#include <linux/ctype.h>
 #include <linux/module.h>
 #include <linux/of.h>
 #include <linux/platform_device.h>
@@ -19,48 +20,177 @@ struct macsmc_power {
 	struct power_supply *psy;
 	char model_name[MAX_STRING_LENGTH];
 	char serial_number[MAX_STRING_LENGTH];
+	char mfg_date[MAX_STRING_LENGTH];
 
 	struct notifier_block nb;
 };
 
+#define CHNC_BATTERY_FULL	BIT(0)
+#define CHNC_NO_CHARGER		BIT(7)
+#define CHNC_NOCHG_CH0C		BIT(14)
+#define CHNC_NOCHG_CH0B_CH0K	BIT(15)
+#define CHNC_BATTERY_FULL_2	BIT(18)
+#define CHNC_BMS_BUSY		BIT(23)
+#define CHNC_NOAC_CH0J		BIT(53)
+#define CHNC_NOAC_CH0I		BIT(54)
+
+#define CH0R_LOWER_FLAGS	GENMASK(15, 0)
+#define CH0R_NOAC_CH0I		BIT(0)
+#define CH0R_NOAC_CH0J		BIT(5)
+#define CH0R_BMS_BUSY		BIT(8)
+#define CH0R_NOAC_CH0K		BIT(9)
+
+#define CH0X_CH0C		BIT(0)
+#define CH0X_CH0B		BIT(1)
+
 static int macsmc_battery_get_status(struct macsmc_power *power)
 {
-	u8 val;
+	u64 nocharge_flags;
+	u32 nopower_flags;
+	u16 ac_current;
 	int ret;
 
-	ret = apple_smc_read_u8(power->smc, SMC_KEY(BSFC), &val);
-	if (ret)
+	/*
+	 * Note: there are fallbacks in case some of these SMC keys disappear in the future
+	 * or are not present on some machines. We treat the absence of the CHCE/CHCC/BSFC/CHSC
+	 * flags as an error, since they are quite fundamental and simple booleans.
+	 */
+
+	/*
+	 * If power input is inhibited, we are definitely discharging.
+	 * However, if the only reason is the BMS is doing a balancing cycle,
+	 * go ahead and ignore that one to avoid spooking users.
+	 */
+	ret = apple_smc_read_u32(power->smc, SMC_KEY(CH0R), &nopower_flags);
+	if (!ret && (nopower_flags & CH0R_LOWER_FLAGS & ~CH0R_BMS_BUSY))
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	/* If no charger is present, we are definitely discharging. */
+	ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCE));
+	if (ret < 0)
+		return ret;
+	else if (!ret)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	/* If AC is not charge capable, we are definitely discharging. */
+	ret = apple_smc_read_flag(power->smc, SMC_KEY(CHCC));
+	if (ret < 0)
 		return ret;
-	if (val == 1)
+	else if (!ret)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	/*
+	 * If the AC input current limit is tiny or 0, we are discharging no matter
+	 * how much the BMS believes it can charge.
+	 */
+	ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &ac_current);
+	if (!ret && ac_current < 100)
+		return POWER_SUPPLY_STATUS_DISCHARGING;
+
+	/* If the battery is full, report it as such. */
+	ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC));
+	if (ret < 0)
+		return ret;
+	else if (ret)
 		return POWER_SUPPLY_STATUS_FULL;
 
-	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHSC), &val);
-	if (ret)
+	/* If there are reasons we aren't charging... */
+	ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
+	if (!ret) {
+		/* Perhaps the battery is full after all */
+		if (nocharge_flags & CHNC_BATTERY_FULL)
+			return POWER_SUPPLY_STATUS_FULL;
+		/* Or maybe the BMS is just busy doing something, if so call it charging anyway */
+		else if (nocharge_flags == CHNC_BMS_BUSY)
+			return POWER_SUPPLY_STATUS_CHARGING;
+		/* If we have other reasons we aren't charging, say we aren't */
+		else if (nocharge_flags)
+			return POWER_SUPPLY_STATUS_NOT_CHARGING;
+		/* Else we're either charging or about to charge */
+		else
+			return POWER_SUPPLY_STATUS_CHARGING;
+	}
+
+	/* As a fallback, use the system charging flag. */
+	ret = apple_smc_read_flag(power->smc, SMC_KEY(CHSC));
+	if (ret < 0)
 		return ret;
-	if (val == 1)
+	if (!ret)
+		return POWER_SUPPLY_STATUS_NOT_CHARGING;
+	else
 		return POWER_SUPPLY_STATUS_CHARGING;
+}
 
-	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCC), &val);
+static int macsmc_battery_get_charge_behaviour(struct macsmc_power *power)
+{
+	int ret;
+	u8 val;
+
+	/* CH0I returns a bitmask like the low byte of CH0R */
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0I), &val);
 	if (ret)
 		return ret;
-	if (val == 0)
-		return POWER_SUPPLY_STATUS_DISCHARGING;
+	if (val & CH0R_NOAC_CH0I)
+		return POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE;
 
-	ret = apple_smc_read_u8(power->smc, SMC_KEY(CHCE), &val);
+	/* CH0C returns a bitmask containing CH0B/CH0C flags */
+	ret = apple_smc_read_u8(power->smc, SMC_KEY(CH0C), &val);
 	if (ret)
 		return ret;
-	if (val == 0)
-		return POWER_SUPPLY_STATUS_DISCHARGING;
+	if (val & CH0X_CH0C)
+		return POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE;
 	else
-		return POWER_SUPPLY_STATUS_NOT_CHARGING;
+		return POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
+}
+
+static int macsmc_battery_set_charge_behaviour(struct macsmc_power *power, int val)
+{
+	u8 ch0i, ch0c;
+	int ret;
+
+	/*
+	 * CH0I/CH0C are "hard" controls that will allow the battery to run down to 0.
+	 * CH0K/CH0B are "soft" controls that are reset to 0 when SOC drops below 50%;
+	 * we don't expose these yet.
+	 */
+
+	switch (val) {
+	case POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO:
+		ch0i = ch0c = 0;
+		break;
+	case POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE:
+		ch0i = 0;
+		ch0c = 1;
+		break;
+	case POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE:
+		ch0i = 1;
+		ch0c = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	ret = apple_smc_write_u8(power->smc, SMC_KEY(CH0I), ch0i);
+	if (ret)
+		return ret;
+	return apple_smc_write_u8(power->smc, SMC_KEY(CH0C), ch0c);
+}
+
+static int macsmc_battery_get_date(const char *s, int *out)
+{
+	if (!isdigit(s[0]) || !isdigit(s[1]))
+		return -ENOTSUPP;
+
+	*out = (s[0] - '0') * 10 + s[1] - '0';
+	return 0;
 }
 
 static int macsmc_battery_get_property(struct power_supply *psy,
-		enum power_supply_property psp,
-		union power_supply_propval *val)
+				       enum power_supply_property psp,
+				       union power_supply_propval *val)
 {
 	struct macsmc_power *power = power_supply_get_drvdata(psy);
 	int ret = 0;
+	u8 vu8;
 	u16 vu16;
 	u32 vu32;
 	s16 vs16;
@@ -75,6 +205,10 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_PRESENT:
 		val->intval = 1;
 		break;
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		val->intval = macsmc_battery_get_charge_behaviour(power);
+		ret = val->intval < 0 ? val->intval : 0;
+		break;
 	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0TE), &vu16);
 		val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
@@ -143,6 +277,9 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0CT), &vu16);
 		val->intval = vu16;
 		break;
+	case POWER_SUPPLY_PROP_SCOPE:
+		val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+		break;
 	case POWER_SUPPLY_PROP_HEALTH:
 		ret = apple_smc_read_flag(power->smc, SMC_KEY(BBAD));
 		val->intval = ret == 1 ? POWER_SUPPLY_HEALTH_DEAD : POWER_SUPPLY_HEALTH_GOOD;
@@ -154,6 +291,16 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_SERIAL_NUMBER:
 		val->strval = power->serial_number;
 		break;
+	case POWER_SUPPLY_PROP_MANUFACTURE_YEAR:
+		ret = macsmc_battery_get_date(&power->mfg_date[0], &val->intval);
+		val->intval += 2000 - 8; /* -8 is a fixup for a firmware bug... */
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURE_MONTH:
+		ret = macsmc_battery_get_date(&power->mfg_date[2], &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
+		ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -161,9 +308,36 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 	return ret;
 }
 
+static int macsmc_battery_set_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       const union power_supply_propval *val)
+{
+	struct macsmc_power *power = power_supply_get_drvdata(psy);
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		return macsmc_battery_set_charge_behaviour(power, val->intval);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int macsmc_battery_property_is_writeable(struct power_supply *psy,
+						enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
+		return true;
+	default:
+		return false;
+	}
+}
+
+
 static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
 	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_CAPACITY,
@@ -181,17 +355,23 @@ static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_TEMP,
 	POWER_SUPPLY_PROP_CHARGE_COUNTER,
 	POWER_SUPPLY_PROP_CYCLE_COUNT,
+	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_HEALTH,
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+	POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
+	POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
+	POWER_SUPPLY_PROP_MANUFACTURE_DAY,
 };
 
 static const struct power_supply_desc macsmc_battery_desc = {
-	.name		= "macsmc-battery",
-	.type		= POWER_SUPPLY_TYPE_BATTERY,
-	.get_property	= macsmc_battery_get_property,
-	.properties	= macsmc_battery_props,
-	.num_properties	= ARRAY_SIZE(macsmc_battery_props),
+	.name			= "macsmc-battery",
+	.type			= POWER_SUPPLY_TYPE_BATTERY,
+	.get_property		= macsmc_battery_get_property,
+	.set_property		= macsmc_battery_set_property,
+	.property_is_writeable	= macsmc_battery_property_is_writeable,
+	.properties		= macsmc_battery_props,
+	.num_properties		= ARRAY_SIZE(macsmc_battery_props),
 };
 
 static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
@@ -232,6 +412,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	/* Fetch string properties */
 	apple_smc_read(smc, SMC_KEY(BMDN), power->model_name, sizeof(power->model_name) - 1);
 	apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
+	apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1);
 
 	psy_cfg.drv_data = power;
 	power->psy = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);

From 0f0368fbe4f70074cb6ca4978090eabae4fe7464 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 02:23:33 +0900
Subject: [PATCH 0623/1027] power: supply: macsmc_power: Use BUIC instead of
 BRSC for charge

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 0f1eb83d690e97..04f36750ecbf93 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -218,8 +218,8 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		val->intval = vu16 == 0xffff ? 0 : vu16 * 60;
 		break;
 	case POWER_SUPPLY_PROP_CAPACITY:
-		ret = apple_smc_read_u16(power->smc, SMC_KEY(BRSC), &vu16);
-		val->intval = vu16;
+		ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
+		val->intval = vu8;
 		break;
 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);

From 740d19b277d182937870fb9af6a9962188fb578a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 02:24:13 +0900
Subject: [PATCH 0624/1027] power: supply: macsmc_power: Turn off OBC flags if
 macOS left them on

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 04f36750ecbf93..b8bc2a7ad22e74 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -414,6 +414,10 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_read(smc, SMC_KEY(BMSN), power->serial_number, sizeof(power->serial_number) - 1);
 	apple_smc_read(smc, SMC_KEY(BMDT), power->mfg_date, sizeof(power->mfg_date) - 1);
 
+	/* Turn off the "optimized battery charging" flags, in case macOS left them on */
+	apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
+	apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
+
 	psy_cfg.drv_data = power;
 	power->psy = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
 	if (IS_ERR(power->psy)) {

From 254c49f738c751196110cc94e5b9d2f9ea9b055f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 02:24:42 +0900
Subject: [PATCH 0625/1027] power: supply: macsmc_power: Add AC power supply

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 72 ++++++++++++++++++++++++++---
 1 file changed, 65 insertions(+), 7 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index b8bc2a7ad22e74..cb5fc5309a6ceb 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -17,11 +17,14 @@
 struct macsmc_power {
 	struct device *dev;
 	struct apple_smc *smc;
-	struct power_supply *psy;
+
+	struct power_supply *batt;
 	char model_name[MAX_STRING_LENGTH];
 	char serial_number[MAX_STRING_LENGTH];
 	char mfg_date[MAX_STRING_LENGTH];
 
+	struct power_supply *ac;
+
 	struct notifier_block nb;
 };
 
@@ -333,7 +336,6 @@ static int macsmc_battery_property_is_writeable(struct power_supply *psy,
 	}
 }
 
-
 static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_PRESENT,
@@ -374,6 +376,54 @@ static const struct power_supply_desc macsmc_battery_desc = {
 	.num_properties		= ARRAY_SIZE(macsmc_battery_props),
 };
 
+static int macsmc_ac_get_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       union power_supply_propval *val)
+{
+	struct macsmc_power *power = power_supply_get_drvdata(psy);
+	int ret = 0;
+	u16 vu16;
+	u32 vu32;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_ONLINE:
+		ret = apple_smc_read_u32(power->smc, SMC_KEY(CHIS), &vu32);
+		val->intval = !!vu32;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-n), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(AC-i), &vu16);
+		val->intval = vu16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_POWER_LIMIT:
+		ret = apple_smc_read_u32(power->smc, SMC_KEY(ACPW), &vu32);
+		val->intval = vu32 * 1000;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return ret;
+}
+
+static enum power_supply_property macsmc_ac_props[] = {
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+	POWER_SUPPLY_PROP_INPUT_POWER_LIMIT,
+};
+
+static const struct power_supply_desc macsmc_ac_desc = {
+	.name			= "macsmc-ac",
+	.type			= POWER_SUPPLY_TYPE_MAINS,
+	.get_property		= macsmc_ac_get_property,
+	.properties		= macsmc_ac_props,
+	.num_properties		= ARRAY_SIZE(macsmc_ac_props),
+};
+
 static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
 {
 	struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
@@ -382,7 +432,8 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
 		bool charging = (event & 0xff) != 0;
 
 		dev_info(power->dev, "Charging: %d\n", charging);
-		power_supply_changed(power->psy);
+		power_supply_changed(power->batt);
+		power_supply_changed(power->ac);
 
 		return NOTIFY_OK;
 	}
@@ -419,10 +470,17 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
 
 	psy_cfg.drv_data = power;
-	power->psy = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
-	if (IS_ERR(power->psy)) {
-		dev_err(&pdev->dev, "Failed to register power supply\n");
-		ret = PTR_ERR(power->psy);
+	power->batt = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
+	if (IS_ERR(power->batt)) {
+		dev_err(&pdev->dev, "Failed to register battery\n");
+		ret = PTR_ERR(power->batt);
+		return ret;
+	}
+
+	power->ac = devm_power_supply_register(&pdev->dev, &macsmc_ac_desc, &psy_cfg);
+	if (IS_ERR(power->ac)) {
+		dev_err(&pdev->dev, "Failed to register AC adapter\n");
+		ret = PTR_ERR(power->ac);
 		return ret;
 	}
 

From 5bbef0a2e6a7602d86da058c90cd97b29f016060 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 8 Feb 2022 19:17:40 +0900
Subject: [PATCH 0626/1027] power: reset: macsmc-reboot: Add driver for
 rebooting via Apple SMC

This driver implements the reboot/shutdown support exposed by the SMC
on Apple Silicon machines, such as Apple M1 Macs.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/reset/Kconfig         |  12 +
 drivers/power/reset/Makefile        |   1 +
 drivers/power/reset/macsmc-reboot.c | 333 ++++++++++++++++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 drivers/power/reset/macsmc-reboot.c

diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index fece990af4a75b..d5c1d3fff97f6d 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -117,6 +117,18 @@ config POWER_RESET_LINKSTATION
 
 	  Say Y here if you have a Buffalo LinkStation LS421D/E.
 
+config POWER_RESET_MACSMC
+	tristate "Apple SMC reset/power-off driver"
+	depends on ARCH_APPLE || COMPILE_TEST
+	depends on APPLE_SMC
+	depends on OF
+	default ARCH_APPLE
+	help
+	  This driver supports reset and power-off on Apple Mac machines
+	  that implement this functionality via the SMC.
+
+	  Say Y here if you have an Apple Silicon Mac.
+
 config POWER_RESET_MSM
 	bool "Qualcomm MSM power-off driver"
 	depends on ARCH_QCOM
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index a95d1bd275d187..615536ca49e9cc 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -12,6 +12,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
 obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
 obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
 obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o
+obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o
 obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
 obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o
 obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
diff --git a/drivers/power/reset/macsmc-reboot.c b/drivers/power/reset/macsmc-reboot.c
new file mode 100644
index 00000000000000..780f7cd7b50e1c
--- /dev/null
+++ b/drivers/power/reset/macsmc-reboot.c
@@ -0,0 +1,333 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC Reboot/Poweroff Handler
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/delay.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/slab.h>
+
+struct macsmc_reboot_nvmem {
+	struct nvmem_cell *shutdown_flag;
+	struct nvmem_cell *pm_setting;
+	struct nvmem_cell *boot_stage;
+	struct nvmem_cell *boot_error_count;
+	struct nvmem_cell *panic_count;
+};
+
+static const char *nvmem_names[] = {
+	"shutdown_flag",
+	"pm_setting",
+	"boot_stage",
+	"boot_error_count",
+	"panic_count",
+};
+
+enum boot_stage {
+	BOOT_STAGE_SHUTDOWN		= 0x00, /* Clean shutdown */
+	BOOT_STAGE_IBOOT_DONE		= 0x2f, /* Last stage of bootloader */
+	BOOT_STAGE_KERNEL_STARTED	= 0x30, /* Normal OS booting */
+};
+
+enum pm_setting {
+	PM_SETTING_AC_POWER_RESTORE	= 0x02,
+	PM_SETTING_AC_POWER_OFF		= 0x03,
+};
+
+static const char *ac_power_modes[] = { "off", "restore" };
+
+static int ac_power_mode_map[] = {
+	PM_SETTING_AC_POWER_OFF,
+	PM_SETTING_AC_POWER_RESTORE,
+};
+
+struct macsmc_reboot {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct notifier_block reboot_notify;
+
+	union {
+		struct macsmc_reboot_nvmem nvm;
+		struct nvmem_cell *nvm_cells[ARRAY_SIZE(nvmem_names)];
+	};
+};
+
+/* Helpers to read/write a u8 given a struct nvmem_cell */
+static int nvmem_cell_get_u8(struct nvmem_cell *cell)
+{
+	size_t len;
+	u8 val;
+	void *ret = nvmem_cell_read(cell, &len);
+
+	if (IS_ERR(ret))
+		return PTR_ERR(ret);
+
+	if (len < 1) {
+		kfree(ret);
+		return -EINVAL;
+	}
+
+	val = *(u8 *)ret;
+	kfree(ret);
+	return val;
+}
+
+static int nvmem_cell_set_u8(struct nvmem_cell *cell, u8 val)
+{
+	return nvmem_cell_write(cell, &val, sizeof(val));
+}
+
+static ssize_t macsmc_ac_power_mode_store(struct device *dev, struct device_attribute *attr,
+					  const char *buf, size_t n)
+{
+	struct macsmc_reboot *reboot = dev_get_drvdata(dev);
+	int mode;
+	int ret;
+
+	mode = sysfs_match_string(ac_power_modes, buf);
+	if (mode < 0)
+		return mode;
+
+	ret = nvmem_cell_set_u8(reboot->nvm.pm_setting, ac_power_mode_map[mode]);
+	if (ret < 0)
+		return ret;
+
+	return n;
+}
+
+static ssize_t macsmc_ac_power_mode_show(struct device *dev,
+					 struct device_attribute *attr, char *buf)
+{
+	struct macsmc_reboot *reboot = dev_get_drvdata(dev);
+	int len = 0;
+	int i;
+	int mode = nvmem_cell_get_u8(reboot->nvm.pm_setting);
+
+	if (mode < 0)
+		return mode;
+
+	for (i = 0; i < ARRAY_SIZE(ac_power_mode_map); i++)
+		if (mode == ac_power_mode_map[i])
+			len += scnprintf(buf+len, PAGE_SIZE-len,
+					 "[%s] ", ac_power_modes[i]);
+		else
+			len += scnprintf(buf+len, PAGE_SIZE-len,
+					 "%s ", ac_power_modes[i]);
+	buf[len-1] = '\n';
+	return len;
+}
+static DEVICE_ATTR(ac_power_mode, 0644, macsmc_ac_power_mode_show,
+		   macsmc_ac_power_mode_store);
+
+/*
+ * SMC 'MBSE' key actions:
+ *
+ * 'offw' - shutdown warning
+ * 'slpw' - sleep warning
+ * 'rest' - restart warning
+ * 'off1' - shutdown (needs PMU bit set to stay on)
+ * 'susp' - suspend
+ * 'phra' - restart ("PE Halt Restart Action"?)
+ * 'panb' - panic beginning
+ * 'pane' - panic end
+ */
+
+static int macsmc_power_off(struct sys_off_data *data)
+{
+	struct macsmc_reboot *reboot = data->cb_data;
+
+	dev_info(reboot->dev, "Issuing power off (off1)\n");
+
+	if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(off1)) < 0) {
+		dev_err(reboot->dev, "Failed to issue MBSE = off1 (power_off)\n");
+	} else {
+		mdelay(100);
+		WARN_ON(1);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int macsmc_restart(struct sys_off_data *data)
+{
+	struct macsmc_reboot *reboot = data->cb_data;
+
+	dev_info(reboot->dev, "Issuing restart (phra)\n");
+
+	if (apple_smc_write_u32_atomic(reboot->smc, SMC_KEY(MBSE), SMC_KEY(phra)) < 0) {
+		dev_err(reboot->dev, "Failed to issue MBSE = phra (restart)\n");
+	} else {
+		mdelay(100);
+		WARN_ON(1);
+	}
+
+	return NOTIFY_OK;
+}
+
+static int macsmc_reboot_notify(struct notifier_block *this, unsigned long action, void *data)
+{
+	struct macsmc_reboot *reboot = container_of(this, struct macsmc_reboot, reboot_notify);
+	u32 val;
+	u8 shutdown_flag;
+
+	switch (action) {
+		case SYS_RESTART:
+			val = SMC_KEY(rest);
+			shutdown_flag = 0;
+			break;
+		case SYS_POWER_OFF:
+			val = SMC_KEY(offw);
+			shutdown_flag = 1;
+			break;
+		default:
+			return NOTIFY_DONE;
+	}
+
+	dev_info(reboot->dev, "Preparing for reboot (%p4ch)\n", &val);
+
+	/* On the Mac Mini, this will turn off the LED for power off */
+	if (apple_smc_write_u32(reboot->smc, SMC_KEY(MBSE), val) < 0)
+		dev_err(reboot->dev, "Failed to issue MBSE = %p4ch (reboot_prepare)\n", &val);
+
+	/* Set the boot_stage to 0, which means we're doing a clean shutdown/reboot. */
+	if (reboot->nvm.boot_stage &&
+	    nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_SHUTDOWN) < 0)
+		dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+	/*
+	 * Set the PMU flag to actually reboot into the off state.
+	 * Without this, the device will just reboot. We make it optional in case it is no longer
+	 * necessary on newer hardware.
+	 */
+	if (reboot->nvm.shutdown_flag &&
+	    nvmem_cell_set_u8(reboot->nvm.shutdown_flag, shutdown_flag) < 0)
+		dev_err(reboot->dev, "Failed to write shutdown_flag\n");
+
+	return NOTIFY_OK;
+}
+
+static void macsmc_power_init_error_counts(struct macsmc_reboot *reboot)
+{
+	int boot_error_count, panic_count;
+
+	if (!reboot->nvm.boot_error_count || !reboot->nvm.panic_count)
+		return;
+
+	boot_error_count = nvmem_cell_get_u8(reboot->nvm.boot_error_count);
+	if (boot_error_count < 0) {
+		dev_err(reboot->dev, "Failed to read boot_error_count (%d)\n", boot_error_count);
+		return;
+	}
+
+	panic_count = nvmem_cell_get_u8(reboot->nvm.panic_count);
+	if (panic_count < 0) {
+		dev_err(reboot->dev, "Failed to read panic_count (%d)\n", panic_count);
+		return;
+	}
+
+	if (!boot_error_count && !panic_count)
+		return;
+
+	dev_warn(reboot->dev, "PMU logged %d boot error(s) and %d panic(s)\n",
+		 boot_error_count, panic_count);
+
+	if (nvmem_cell_set_u8(reboot->nvm.panic_count, 0) < 0)
+		dev_err(reboot->dev, "Failed to reset panic_count\n");
+	if (nvmem_cell_set_u8(reboot->nvm.boot_error_count, 0) < 0)
+		dev_err(reboot->dev, "Failed to reset boot_error_count\n");
+}
+
+static int macsmc_reboot_probe(struct platform_device *pdev)
+{
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	struct macsmc_reboot *reboot;
+	int ret, i;
+
+	/* Ignore devices without this functionality */
+	if (!apple_smc_key_exists(smc, SMC_KEY(MBSE)))
+		return -ENODEV;
+
+	reboot = devm_kzalloc(&pdev->dev, sizeof(*reboot), GFP_KERNEL);
+	if (!reboot)
+		return -ENOMEM;
+
+	reboot->dev = &pdev->dev;
+	reboot->smc = smc;
+
+	platform_set_drvdata(pdev, reboot);
+
+	pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "reboot");
+
+	for (i = 0; i < ARRAY_SIZE(nvmem_names); i++) {
+		struct nvmem_cell *cell;
+		cell = devm_nvmem_cell_get(&pdev->dev,
+					   nvmem_names[i]);
+		if (IS_ERR(cell)) {
+			if (PTR_ERR(cell) == -EPROBE_DEFER)
+				return -EPROBE_DEFER;
+			dev_warn(&pdev->dev, "Missing NVMEM cell %s (%ld)\n",
+				 nvmem_names[i], PTR_ERR(cell));
+			/* Non fatal, we'll deal with it */
+			cell = NULL;
+		}
+		reboot->nvm_cells[i] = cell;
+	}
+
+	/* Set the boot_stage to indicate we're running the OS kernel */
+	if (reboot->nvm.boot_stage &&
+	    nvmem_cell_set_u8(reboot->nvm.boot_stage, BOOT_STAGE_KERNEL_STARTED) < 0)
+		dev_err(reboot->dev, "Failed to write boot_stage\n");
+
+	/* Display and clear the error counts */
+	macsmc_power_init_error_counts(reboot);
+
+	reboot->reboot_notify.notifier_call = macsmc_reboot_notify;
+
+	ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_POWER_OFF, SYS_OFF_PRIO_HIGH,
+					    macsmc_power_off, reboot);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to register power-off handler\n");
+
+	ret = devm_register_sys_off_handler(&pdev->dev, SYS_OFF_MODE_RESTART, SYS_OFF_PRIO_HIGH,
+					    macsmc_restart, reboot);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to register restart handler\n");
+
+	ret = devm_register_reboot_notifier(&pdev->dev, &reboot->reboot_notify);
+	if (ret)
+		return dev_err_probe(&pdev->dev, ret, "Failed to register reboot notifier\n");
+
+	dev_info(&pdev->dev, "Handling reboot and poweroff requests via SMC\n");
+
+	if (device_create_file(&pdev->dev, &dev_attr_ac_power_mode))
+		dev_warn(&pdev->dev, "could not create sysfs file\n");
+
+	return 0;
+}
+
+static void macsmc_reboot_remove(struct platform_device *pdev)
+{
+	device_remove_file(&pdev->dev, &dev_attr_ac_power_mode);
+}
+
+
+static struct platform_driver macsmc_reboot_driver = {
+	.driver = {
+		.name = "macsmc-reboot",
+	},
+	.probe = macsmc_reboot_probe,
+	.remove = macsmc_reboot_remove,
+};
+module_platform_driver(macsmc_reboot_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC reboot/poweroff driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-reboot");

From 94ba2b4c08bd7cb5eee30c5fb65cb03f4425861e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:47:13 +0900
Subject: [PATCH 0627/1027] rtc: Add new rtc-macsmc driver for Apple Silicon
 Macs

Apple Silicon Macs (M1, etc.) have an RTC that is part of the PMU IC,
but most of the PMU functionality is abstracted out by the SMC.
On T600x machines, the RTC counter must be accessed via the SMC to
get full functionality, and it seems likely that future machines
will move towards making SMC handle all RTC functionality.

The SMC RTC counter access is implemented on all current machines
as of the time of this writing, on firmware 12.x. However, the RTC
offset (needed to set the time) is still only accessible via direct
PMU access. To handle this, we expose the RTC offset as an NVMEM
cell from the SPMI PMU device node, and this driver consumes that
cell and uses it to compute/set the current time.

Alarm functionality is not yet implemented. This would also go via
the PMU today, but could change in the future.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/rtc/Kconfig      |  13 ++++
 drivers/rtc/Makefile     |   1 +
 drivers/rtc/rtc-macsmc.c | 129 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 143 insertions(+)
 create mode 100644 drivers/rtc/rtc-macsmc.c

diff --git a/drivers/rtc/Kconfig b/drivers/rtc/Kconfig
index 2a95b05982ad25..10a0c57350fd2f 100644
--- a/drivers/rtc/Kconfig
+++ b/drivers/rtc/Kconfig
@@ -2043,4 +2043,17 @@ config RTC_DRV_SSD202D
 	  This driver can also be built as a module, if so, the module
 	  will be called "rtc-ssd20xd".
 
+config RTC_DRV_MACSMC
+	tristate "Apple Mac SMC RTC"
+	depends on ARCH_APPLE || COMPILE_TEST
+	depends on APPLE_SMC
+	depends on OF
+	default ARCH_APPLE
+	help
+	  If you say yes here you get support for RTC functions
+	  inside Apple SPMI PMUs.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called rtc-macsmc.
+
 endif # RTC_CLASS
diff --git a/drivers/rtc/Makefile b/drivers/rtc/Makefile
index 3004e372f25f0a..4b833307e8d58f 100644
--- a/drivers/rtc/Makefile
+++ b/drivers/rtc/Makefile
@@ -89,6 +89,7 @@ obj-$(CONFIG_RTC_DRV_M48T35)	+= rtc-m48t35.o
 obj-$(CONFIG_RTC_DRV_M48T59)	+= rtc-m48t59.o
 obj-$(CONFIG_RTC_DRV_M48T86)	+= rtc-m48t86.o
 obj-$(CONFIG_RTC_DRV_MA35D1)	+= rtc-ma35d1.o
+obj-$(CONFIG_RTC_DRV_MACSMC)	+= rtc-macsmc.o
 obj-$(CONFIG_RTC_DRV_MAX31335)	+= rtc-max31335.o
 obj-$(CONFIG_RTC_DRV_MAX6900)	+= rtc-max6900.o
 obj-$(CONFIG_RTC_DRV_MAX6902)	+= rtc-max6902.o
diff --git a/drivers/rtc/rtc-macsmc.c b/drivers/rtc/rtc-macsmc.c
new file mode 100644
index 00000000000000..2f377a643c19e3
--- /dev/null
+++ b/drivers/rtc/rtc-macsmc.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC RTC driver
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/bitops.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+
+/* 48-bit RTC */
+#define RTC_BYTES 6
+#define RTC_BITS (8 * RTC_BYTES)
+
+/* 32768 Hz clock */
+#define RTC_SEC_SHIFT 15
+
+struct macsmc_rtc {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct rtc_device *rtc_dev;
+	struct nvmem_cell *rtc_offset;
+};
+
+static int macsmc_rtc_get_time(struct device *dev, struct rtc_time *tm)
+{
+	struct macsmc_rtc *rtc = dev_get_drvdata(dev);
+	u64 ctr = 0, off = 0;
+	time64_t now;
+	void *p_off;
+	size_t len;
+	int ret;
+
+	ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+	if (ret != RTC_BYTES)
+		return ret < 0 ? ret : -EIO;
+
+	p_off = nvmem_cell_read(rtc->rtc_offset, &len);
+	if (IS_ERR(p_off))
+		return PTR_ERR(p_off);
+	if (len < RTC_BYTES) {
+		kfree(p_off);
+		return -EIO;
+	}
+
+	memcpy(&off, p_off, RTC_BYTES);
+	kfree(p_off);
+
+	/* Sign extend from 48 to 64 bits, then arithmetic shift right 15 bits to get seconds */
+	now = sign_extend64(ctr + off, RTC_BITS - 1) >> RTC_SEC_SHIFT;
+	rtc_time64_to_tm(now, tm);
+
+	return ret;
+}
+
+static int macsmc_rtc_set_time(struct device *dev, struct rtc_time *tm)
+{
+	struct macsmc_rtc *rtc = dev_get_drvdata(dev);
+	u64 ctr = 0, off = 0;
+	int ret;
+
+	ret = apple_smc_read(rtc->smc, SMC_KEY(CLKM), &ctr, RTC_BYTES);
+	if (ret != RTC_BYTES)
+		return ret < 0 ? ret : -EIO;
+
+	/* This sets the offset such that the set second begins now */
+	off = (rtc_tm_to_time64(tm) << RTC_SEC_SHIFT) - ctr;
+	return nvmem_cell_write(rtc->rtc_offset, &off, RTC_BYTES);
+}
+
+static const struct rtc_class_ops macsmc_rtc_ops = {
+	.read_time = macsmc_rtc_get_time,
+	.set_time = macsmc_rtc_set_time,
+};
+
+static int macsmc_rtc_probe(struct platform_device *pdev)
+{
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	struct macsmc_rtc *rtc;
+
+	/* Ignore devices without this functionality */
+	if (!apple_smc_key_exists(smc, SMC_KEY(CLKM)))
+		return -ENODEV;
+
+	rtc = devm_kzalloc(&pdev->dev, sizeof(*rtc), GFP_KERNEL);
+	if (!rtc)
+		return -ENOMEM;
+
+	rtc->dev = &pdev->dev;
+	rtc->smc = smc;
+
+	pdev->dev.of_node = of_get_child_by_name(pdev->dev.parent->of_node, "rtc");
+
+	rtc->rtc_offset = devm_nvmem_cell_get(&pdev->dev, "rtc_offset");
+	if (IS_ERR(rtc->rtc_offset))
+		return dev_err_probe(&pdev->dev, PTR_ERR(rtc->rtc_offset),
+				     "Failed to get rtc_offset NVMEM cell\n");
+
+	rtc->rtc_dev = devm_rtc_allocate_device(&pdev->dev);
+	if (IS_ERR(rtc->rtc_dev))
+		return PTR_ERR(rtc->rtc_dev);
+
+	rtc->rtc_dev->ops = &macsmc_rtc_ops;
+	rtc->rtc_dev->range_min = S64_MIN >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
+	rtc->rtc_dev->range_max = S64_MAX >> (RTC_SEC_SHIFT + (64 - RTC_BITS));
+
+	platform_set_drvdata(pdev, rtc);
+
+	return devm_rtc_register_device(rtc->rtc_dev);
+}
+
+static struct platform_driver macsmc_rtc_driver = {
+	.driver = {
+		.name = "macsmc-rtc",
+	},
+	.probe = macsmc_rtc_probe,
+};
+module_platform_driver(macsmc_rtc_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC RTC driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_ALIAS("platform:macsmc-rtc");

From 381db492495a03efeb0bade10dc035010694aac2 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 4 Mar 2022 19:21:19 +0900
Subject: [PATCH 0628/1027] Input: macsmc-hid: New driver to handle the Apple
 Mac SMC buttons/lid

This driver implements power button and lid switch support for Apple Mac
devices using SMC controllers driven by the macsmc driver.

In addition to basic input support, this also responds to the final
shutdown warning (when the power button is held down long enough) by
doing an emergency kernel poweroff. This allows the NVMe controller to
be cleanly shut down, which prevents data loss for in-cache data.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/input/misc/Kconfig      |  12 +++
 drivers/input/misc/Makefile     |   1 +
 drivers/input/misc/macsmc-hid.c | 157 ++++++++++++++++++++++++++++++++
 3 files changed, 170 insertions(+)
 create mode 100644 drivers/input/misc/macsmc-hid.c

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index 6a852c76331b62..15f87c7c5633a1 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -956,4 +956,16 @@ config INPUT_STPMIC1_ONKEY
 	  To compile this driver as a module, choose M here: the
 	  module will be called stpmic1_onkey.
 
+config INPUT_MACSMC_HID
+	tristate "Apple Mac SMC lid/buttons"
+	depends on APPLE_SMC
+	default ARCH_APPLE
+	help
+	  Say Y here if you want to use the input events delivered via the
+	  SMC controller on Apple Mac machines using the macsmc driver.
+	  This includes lid open/close and the power button.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called macsmc-hid.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 4f7f736831ba84..b088ada64f9f69 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -51,6 +51,7 @@ obj-$(CONFIG_INPUT_IQS7222)		+= iqs7222.o
 obj-$(CONFIG_INPUT_KEYSPAN_REMOTE)	+= keyspan_remote.o
 obj-$(CONFIG_INPUT_KXTJ9)		+= kxtj9.o
 obj-$(CONFIG_INPUT_M68K_BEEP)		+= m68kspkr.o
+obj-$(CONFIG_INPUT_MACSMC_HID)		+= macsmc-hid.o
 obj-$(CONFIG_INPUT_MAX77650_ONKEY)	+= max77650-onkey.o
 obj-$(CONFIG_INPUT_MAX77693_HAPTIC)	+= max77693-haptic.o
 obj-$(CONFIG_INPUT_MAX8925_ONKEY)	+= max8925_onkey.o
diff --git a/drivers/input/misc/macsmc-hid.c b/drivers/input/misc/macsmc-hid.c
new file mode 100644
index 00000000000000..1c0f7476081f30
--- /dev/null
+++ b/drivers/input/misc/macsmc-hid.c
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC input event driver
+ * Copyright The Asahi Linux Contributors
+ *
+ * This driver exposes HID events from the SMC as an input device.
+ * This includes the lid open/close and power button notifications.
+ */
+
+#include <linux/device.h>
+#include <linux/input.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/reboot.h>
+
+struct macsmc_hid {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct input_dev *input;
+	struct notifier_block nb;
+};
+
+#define SMC_EV_BTN 0x7201
+#define SMC_EV_LID 0x7203
+
+#define BTN_POWER	0x06
+#define BTN_POWER_HELD1	0xfe
+#define BTN_POWER_HELD2	0x00
+
+static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+	struct macsmc_hid *smchid = container_of(nb, struct macsmc_hid, nb);
+	u16 type = event >> 16;
+	u8 d1 = (event >> 8) & 0xff;
+	u8 d2 = event & 0xff;
+
+	switch (type) {
+	case SMC_EV_BTN:
+		switch (d1) {
+		case BTN_POWER:
+			input_report_key(smchid->input, KEY_POWER, d2);
+			input_sync(smchid->input);
+			break;
+		case BTN_POWER_HELD1:
+			/*
+			 * TODO: is this pre-warning useful?
+			 */
+			if (d2)
+				dev_warn(smchid->dev, "Power button held down\n");
+			break;
+		case BTN_POWER_HELD2:
+			/*
+			 * If we get here, we have about 4 seconds before forced shutdown.
+			 * Try to do an emergency shutdown to make sure the NVMe cache is
+			 * flushed. macOS actually does this by panicing (!)...
+			 */
+			if (d2) {
+				dev_crit(smchid->dev, "Triggering forced shutdown!\n");
+				if (kernel_can_power_off())
+					kernel_power_off();
+				else /* Missing macsmc-reboot driver? */
+					kernel_restart("SMC power button triggered restart");
+			}
+			break;
+		default:
+			dev_info(smchid->dev, "Unknown SMC button event: %02x %02x\n", d1, d2);
+			break;
+		}
+		return NOTIFY_OK;
+	case SMC_EV_LID:
+		input_report_switch(smchid->input, SW_LID, d1);
+		input_sync(smchid->input);
+		return NOTIFY_OK;
+	}
+
+	return NOTIFY_DONE;
+}
+
+static int macsmc_hid_probe(struct platform_device *pdev)
+{
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	struct macsmc_hid *smchid;
+	bool have_lid, have_power;
+	int ret;
+
+	have_lid = apple_smc_key_exists(smc, SMC_KEY(MSLD));
+	have_power = apple_smc_key_exists(smc, SMC_KEY(bHLD));
+
+	if (!have_lid && !have_power)
+		return -ENODEV;
+
+	smchid = devm_kzalloc(&pdev->dev, sizeof(*smchid), GFP_KERNEL);
+	if (!smchid)
+		return -ENOMEM;
+
+	smchid->dev = &pdev->dev;
+	smchid->smc = smc;
+
+	smchid->input = devm_input_allocate_device(&pdev->dev);
+	if (!smchid->input)
+		return -ENOMEM;
+
+	smchid->input->phys = "macsmc-hid (0)";
+	smchid->input->name = "Apple SMC power/lid events";
+
+	if (have_lid)
+		input_set_capability(smchid->input, EV_SW, SW_LID);
+	if (have_power)
+		input_set_capability(smchid->input, EV_KEY, KEY_POWER);
+
+	ret = input_register_device(smchid->input);
+	if (ret) {
+		dev_err(&pdev->dev, "Failed to register input device: %d\n", ret);
+		return ret;
+	}
+
+	if (have_lid) {
+		u8 val;
+
+		ret = apple_smc_read_u8(smc, SMC_KEY(MSLD), &val);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "Failed to read initial lid state\n");
+		} else {
+			input_report_switch(smchid->input, SW_LID, val);
+		}
+	}
+	if (have_power) {
+		u32 val;
+
+		ret = apple_smc_read_u32(smc, SMC_KEY(bHLD), &val);
+		if (ret < 0) {
+			dev_err(&pdev->dev, "Failed to read initial power button state\n");
+		} else {
+			input_report_key(smchid->input, KEY_POWER, val & 1);
+		}
+	}
+
+	input_sync(smchid->input);
+
+	smchid->nb.notifier_call = macsmc_hid_event;
+	apple_smc_register_notifier(smc, &smchid->nb);
+
+	return 0;
+}
+
+static struct platform_driver macsmc_hid_driver = {
+	.driver = {
+		.name = "macsmc-hid",
+	},
+	.probe = macsmc_hid_probe,
+};
+module_platform_driver(macsmc_hid_driver);
+
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Apple SMC GPIO driver");
+MODULE_ALIAS("platform:macsmc-hid");

From e299ade407ac3e1267d6c7ce107be09eb604a161 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 5 Mar 2022 01:08:04 +0900
Subject: [PATCH 0629/1027] Input: macsmc-hid: Support button/lid wakeups

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/input/misc/macsmc-hid.c | 40 +++++++++++++++++++++++++++++++--
 1 file changed, 38 insertions(+), 2 deletions(-)

diff --git a/drivers/input/misc/macsmc-hid.c b/drivers/input/misc/macsmc-hid.c
index 1c0f7476081f30..d26752eaa6a687 100644
--- a/drivers/input/misc/macsmc-hid.c
+++ b/drivers/input/misc/macsmc-hid.c
@@ -18,6 +18,7 @@ struct macsmc_hid {
 	struct apple_smc *smc;
 	struct input_dev *input;
 	struct notifier_block nb;
+	bool wakeup_mode;
 };
 
 #define SMC_EV_BTN 0x7201
@@ -38,8 +39,15 @@ static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void
 	case SMC_EV_BTN:
 		switch (d1) {
 		case BTN_POWER:
-			input_report_key(smchid->input, KEY_POWER, d2);
-			input_sync(smchid->input);
+			if (smchid->wakeup_mode) {
+				if (d2) {
+					dev_info(smchid->dev, "Button wakeup\n");
+					pm_wakeup_hard_event(smchid->dev);
+				}
+			} else {
+				input_report_key(smchid->input, KEY_POWER, d2);
+				input_sync(smchid->input);
+			}
 			break;
 		case BTN_POWER_HELD1:
 			/*
@@ -68,6 +76,10 @@ static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void
 		}
 		return NOTIFY_OK;
 	case SMC_EV_LID:
+		if (smchid->wakeup_mode && !d1) {
+			dev_info(smchid->dev, "Lid wakeup\n");
+			pm_wakeup_hard_event(smchid->dev);
+		}
 		input_report_switch(smchid->input, SW_LID, d1);
 		input_sync(smchid->input);
 		return NOTIFY_OK;
@@ -95,6 +107,7 @@ static int macsmc_hid_probe(struct platform_device *pdev)
 
 	smchid->dev = &pdev->dev;
 	smchid->smc = smc;
+	platform_set_drvdata(pdev, smchid);
 
 	smchid->input = devm_input_allocate_device(&pdev->dev);
 	if (!smchid->input)
@@ -140,12 +153,35 @@ static int macsmc_hid_probe(struct platform_device *pdev)
 	smchid->nb.notifier_call = macsmc_hid_event;
 	apple_smc_register_notifier(smc, &smchid->nb);
 
+	device_init_wakeup(&pdev->dev, 1);
+
+	return 0;
+}
+
+static int macsmc_hid_pm_prepare(struct device *dev)
+{
+	struct macsmc_hid *smchid = dev_get_drvdata(dev);
+
+	smchid->wakeup_mode = true;
 	return 0;
 }
 
+static void macsmc_hid_pm_complete(struct device *dev)
+{
+	struct macsmc_hid *smchid = dev_get_drvdata(dev);
+
+	smchid->wakeup_mode = false;
+}
+
+static const struct dev_pm_ops macsmc_hid_pm_ops = {
+	.prepare = macsmc_hid_pm_prepare,
+	.complete = macsmc_hid_pm_complete,
+};
+
 static struct platform_driver macsmc_hid_driver = {
 	.driver = {
 		.name = "macsmc-hid",
+		.pm = &macsmc_hid_pm_ops,
 	},
 	.probe = macsmc_hid_probe,
 };

From f88454ec4879a8f666c530cb2cf5c7326d5251ab Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 May 2022 01:38:19 +0900
Subject: [PATCH 0630/1027] gpio: macsmc: Add IRQ support

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpio/gpio-macsmc.c | 150 +++++++++++++++++++++++++++++++++++++
 1 file changed, 150 insertions(+)

diff --git a/drivers/gpio/gpio-macsmc.c b/drivers/gpio/gpio-macsmc.c
index ff9950afb69af0..98fc74af69d4c1 100644
--- a/drivers/gpio/gpio-macsmc.c
+++ b/drivers/gpio/gpio-macsmc.c
@@ -10,6 +10,7 @@
 #include <linux/bitmap.h>
 #include <linux/device.h>
 #include <linux/gpio/driver.h>
+#include <linux/irq.h>
 #include <linux/mfd/core.h>
 #include <linux/mfd/macsmc.h>
 
@@ -68,10 +69,21 @@
  *   3 = ?
  */
 
+#define SMC_EV_GPIO 0x7202
+
 struct macsmc_gpio {
 	struct device *dev;
 	struct apple_smc *smc;
 	struct gpio_chip gc;
+	struct irq_chip ic;
+	struct notifier_block nb;
+
+	struct mutex irq_mutex;
+	DECLARE_BITMAP(irq_supported, MAX_GPIO);
+	DECLARE_BITMAP(irq_enable_shadow, MAX_GPIO);
+	DECLARE_BITMAP(irq_enable, MAX_GPIO);
+	u32 irq_mode_shadow[MAX_GPIO];
+	u32 irq_mode[MAX_GPIO];
 
 	int first_index;
 };
@@ -161,6 +173,7 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
 	for (i = 0; i < count; i++) {
 		smc_key key;
 		int gpio_nr;
+		u32 val;
 		int ret = apple_smc_get_key_by_index(smcgp->smc, smcgp->first_index + i, &key);
 
 		if (ret < 0)
@@ -176,11 +189,127 @@ static int macsmc_gpio_init_valid_mask(struct gpio_chip *gc,
 		}
 
 		set_bit(gpio_nr, valid_mask);
+
+		/* Check for IRQ support */
+		ret = apple_smc_rw_u32(smcgp->smc, key, CMD_IRQ_MODE, &val);
+		if (!ret)
+			set_bit(gpio_nr, smcgp->irq_supported);
+	}
+
+	return 0;
+}
+
+static int macsmc_gpio_event(struct notifier_block *nb, unsigned long event, void *data)
+{
+	struct macsmc_gpio *smcgp = container_of(nb, struct macsmc_gpio, nb);
+	u16 type = event >> 16;
+	u8 offset = (event >> 8) & 0xff;
+	smc_key key = macsmc_gpio_key(offset);
+	unsigned long flags;
+
+	if (type != SMC_EV_GPIO)
+		return NOTIFY_DONE;
+
+	if (offset > MAX_GPIO) {
+		dev_err(smcgp->dev, "GPIO event index %d out of range\n", offset);
+		return NOTIFY_BAD;
+	}
+
+	local_irq_save(flags);
+	generic_handle_irq_desc(irq_resolve_mapping(smcgp->gc.irq.domain, offset));
+	local_irq_restore(flags);
+
+	if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ACK | 1) < 0)
+		dev_err(smcgp->dev, "GPIO IRQ ack failed for %p4ch\n", &key);
+
+	return NOTIFY_OK;
+}
+
+static void macsmc_gpio_irq_enable(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+	set_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow);
+}
+
+static void macsmc_gpio_irq_disable(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+	clear_bit(irqd_to_hwirq(d), smcgp->irq_enable_shadow);
+}
+
+static int macsmc_gpio_irq_set_type(struct irq_data *d, unsigned int type)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	int offset = irqd_to_hwirq(d);
+	u32 mode;
+
+	if (!test_bit(offset, smcgp->irq_supported))
+		return -EINVAL;
+
+	switch (type & IRQ_TYPE_SENSE_MASK) {
+	case IRQ_TYPE_LEVEL_HIGH:
+		mode = IRQ_MODE_HIGH;
+		break;
+	case IRQ_TYPE_LEVEL_LOW:
+		mode = IRQ_MODE_LOW;
+		break;
+	case IRQ_TYPE_EDGE_RISING:
+		mode = IRQ_MODE_RISING;
+		break;
+	case IRQ_TYPE_EDGE_FALLING:
+		mode = IRQ_MODE_FALLING;
+		break;
+	case IRQ_TYPE_EDGE_BOTH:
+		mode = IRQ_MODE_BOTH;
+		break;
+	default:
+		return -EINVAL;
 	}
 
+	smcgp->irq_mode_shadow[offset] = mode;
 	return 0;
 }
 
+static void macsmc_gpio_irq_bus_lock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+
+	mutex_lock(&smcgp->irq_mutex);
+}
+
+static void macsmc_gpio_irq_bus_sync_unlock(struct irq_data *d)
+{
+	struct gpio_chip *gc = irq_data_get_irq_chip_data(d);
+	struct macsmc_gpio *smcgp = gpiochip_get_data(gc);
+	smc_key key = macsmc_gpio_key(irqd_to_hwirq(d));
+	int offset = irqd_to_hwirq(d);
+	bool val;
+
+	if (smcgp->irq_mode_shadow[offset] != smcgp->irq_mode[offset]) {
+		u32 cmd = CMD_IRQ_MODE | smcgp->irq_mode_shadow[offset];
+		if (apple_smc_write_u32(smcgp->smc, key, cmd) < 0)
+			dev_err(smcgp->dev, "GPIO IRQ config failed for %p4ch = 0x%x\n", &key, cmd);
+		else
+			smcgp->irq_mode_shadow[offset] = smcgp->irq_mode[offset];
+	}
+
+	val = test_bit(offset, smcgp->irq_enable_shadow);
+	if (test_bit(offset, smcgp->irq_enable) != val) {
+		if (apple_smc_write_u32(smcgp->smc, key, CMD_IRQ_ENABLE | val) < 0)
+			dev_err(smcgp->dev, "GPIO IRQ en/disable failed for %p4ch\n", &key);
+		else
+			change_bit(offset, smcgp->irq_enable);
+	}
+
+	mutex_unlock(&smcgp->irq_mutex);
+}
+
 static int macsmc_gpio_probe(struct platform_device *pdev)
 {
 	struct macsmc_gpio *smcgp;
@@ -221,6 +350,27 @@ static int macsmc_gpio_probe(struct platform_device *pdev)
 	smcgp->gc.base = -1;
 	smcgp->gc.parent = &pdev->dev;
 
+	smcgp->ic.name = "macsmc-pmu-gpio";
+	smcgp->ic.irq_mask = macsmc_gpio_irq_disable;
+	smcgp->ic.irq_unmask = macsmc_gpio_irq_enable;
+	smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type;
+	smcgp->ic.irq_bus_lock = macsmc_gpio_irq_bus_lock;
+	smcgp->ic.irq_bus_sync_unlock = macsmc_gpio_irq_bus_sync_unlock;
+	smcgp->ic.irq_set_type = macsmc_gpio_irq_set_type;
+	smcgp->ic.flags = IRQCHIP_SET_TYPE_MASKED | IRQCHIP_MASK_ON_SUSPEND;
+
+	smcgp->gc.irq.chip = &smcgp->ic;
+	smcgp->gc.irq.parent_handler = NULL;
+	smcgp->gc.irq.num_parents = 0;
+	smcgp->gc.irq.parents = NULL;
+	smcgp->gc.irq.default_type = IRQ_TYPE_NONE;
+	smcgp->gc.irq.handler = handle_simple_irq;
+
+	mutex_init(&smcgp->irq_mutex);
+
+	smcgp->nb.notifier_call = macsmc_gpio_event;
+	apple_smc_register_notifier(smc, &smcgp->nb);
+
 	return devm_gpiochip_add_data(&pdev->dev, &smcgp->gc, smcgp);
 }
 

From e4c6d8705ffb69cb5eebbfc6ff343e2291c39d46 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 18 Sep 2022 23:06:02 +0900
Subject: [PATCH 0631/1027] Input: macsmc-hid: Support the power button on
 desktops

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/input/misc/macsmc-hid.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/input/misc/macsmc-hid.c b/drivers/input/misc/macsmc-hid.c
index d26752eaa6a687..aeb658a5321e32 100644
--- a/drivers/input/misc/macsmc-hid.c
+++ b/drivers/input/misc/macsmc-hid.c
@@ -24,7 +24,8 @@ struct macsmc_hid {
 #define SMC_EV_BTN 0x7201
 #define SMC_EV_LID 0x7203
 
-#define BTN_POWER	0x06
+#define BTN_POWER	0x01
+#define BTN_TOUCHID	0x06
 #define BTN_POWER_HELD1	0xfe
 #define BTN_POWER_HELD2	0x00
 
@@ -39,6 +40,7 @@ static int macsmc_hid_event(struct notifier_block *nb, unsigned long event, void
 	case SMC_EV_BTN:
 		switch (d1) {
 		case BTN_POWER:
+		case BTN_TOUCHID:
 			if (smchid->wakeup_mode) {
 				if (d2) {
 					dev_info(smchid->dev, "Button wakeup\n");

From dbbf3cfa93729a2853db4e01fc75672e0ace80de Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 4 Dec 2022 21:51:01 +0900
Subject: [PATCH 0632/1027] power: supply: macsmc_power: Add critical level
 shutdown & misc events

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 123 ++++++++++++++++++++++++++++
 1 file changed, 123 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index cb5fc5309a6ceb..b3cedc8d4d16b1 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -11,6 +11,9 @@
 #include <linux/mfd/core.h>
 #include <linux/mfd/macsmc.h>
 #include <linux/power_supply.h>
+#include <linux/reboot.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
 
 #define MAX_STRING_LENGTH 256
 
@@ -26,6 +29,9 @@ struct macsmc_power {
 	struct power_supply *ac;
 
 	struct notifier_block nb;
+
+	struct work_struct critical_work;
+	bool shutdown_started;
 };
 
 #define CHNC_BATTERY_FULL	BIT(0)
@@ -46,6 +52,9 @@ struct macsmc_power {
 #define CH0X_CH0C		BIT(0)
 #define CH0X_CH0B		BIT(1)
 
+#define ACSt_CAN_BOOT_AP	BIT(2)
+#define ACSt_CAN_BOOT_IBOOT	BIT(1)
+
 static int macsmc_battery_get_status(struct macsmc_power *power)
 {
 	u64 nocharge_flags;
@@ -187,6 +196,34 @@ static int macsmc_battery_get_date(const char *s, int *out)
 	return 0;
 }
 
+static int macsmc_battery_get_capacity_level(struct macsmc_power *power)
+{
+	u32 val;
+	int ret;
+
+	/* Check for emergency shutdown condition */
+	if (apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val) >= 0 && val)
+		return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+	/* Check AC status for whether we could boot in this state */
+	if (apple_smc_read_u32(power->smc, SMC_KEY(ACSt), &val) >= 0) {
+		if (!(val & ACSt_CAN_BOOT_IBOOT))
+			return POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+
+		if (!(val & ACSt_CAN_BOOT_AP))
+			return POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+	}
+
+	/* Check battery full flag */
+	ret = apple_smc_read_flag(power->smc, SMC_KEY(BSFC));
+	if (ret > 0)
+		return POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+	else if (ret == 0)
+		return POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+	else
+		return POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+}
+
 static int macsmc_battery_get_property(struct power_supply *psy,
 				       enum power_supply_property psp,
 				       union power_supply_propval *val)
@@ -224,6 +261,10 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &vu8);
 		val->intval = vu8;
 		break;
+	case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+		val->intval = macsmc_battery_get_capacity_level(power);
+		ret = val->intval < 0 ? val->intval : 0;
+		break;
 	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &vu16);
 		val->intval = vu16 * 1000;
@@ -343,6 +384,7 @@ static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
 	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
 	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
 	POWER_SUPPLY_PROP_VOLTAGE_NOW,
 	POWER_SUPPLY_PROP_CURRENT_NOW,
 	POWER_SUPPLY_PROP_POWER_NOW,
@@ -424,6 +466,59 @@ static const struct power_supply_desc macsmc_ac_desc = {
 	.num_properties		= ARRAY_SIZE(macsmc_ac_props),
 };
 
+static void macsmc_power_critical_work(struct work_struct *wrk) {
+	struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
+	int ret;
+	u32 bcf0;
+	u16 bitv, b0av;
+
+	/*
+	 * Check if the battery voltage is below the design voltage. If it is,
+	 * we have a few seconds until the machine dies. Explicitly shut down,
+	 * which at least gets the NVMe controller to flush its cache.
+	 */
+	if (apple_smc_read_u16(power->smc, SMC_KEY(BITV), &bitv) >= 0 &&
+	    apple_smc_read_u16(power->smc, SMC_KEY(B0AV), &b0av) >= 0 &&
+	    b0av < bitv) {
+		dev_crit(power->dev, "Emergency notification: Battery is critical\n");
+		if (kernel_can_power_off())
+			kernel_power_off();
+		else /* Missing macsmc-reboot driver? In this state, this will not boot anyway. */
+			kernel_restart("Battery is critical");
+	}
+
+	/* This spams once per second, so make sure we only trigger shutdown once. */
+	if (power->shutdown_started)
+		return;
+
+	/* Check for battery empty condition */
+	ret = apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &bcf0);
+	if (ret < 0) {
+		dev_err(power->dev,
+				"Emergency notification: Failed to read battery status\n");
+	} else if (bcf0 == 0) {
+		dev_warn(power->dev, "Emergency notification: Battery status is OK?\n");
+		return;
+	} else {
+		dev_warn(power->dev, "Emergency notification: Battery is empty\n");
+	}
+
+	power->shutdown_started = true;
+
+	/*
+	 * Attempt to trigger an orderly shutdown. At this point, we should have a few
+	 * minutes of reserve capacity left, enough to do a clean shutdown.
+	 */
+	dev_warn(power->dev, "Shutting down in 10 seconds\n");
+	ssleep(10);
+
+	/*
+	 * Don't force it; if this stalls or fails, the last-resort check above will
+	 * trigger a hard shutdown when shutdown is truly imminent.
+	 */
+	orderly_poweroff(false);
+}
+
 static int macsmc_power_event(struct notifier_block *nb, unsigned long event, void *data)
 {
 	struct macsmc_power *power = container_of(nb, struct macsmc_power, nb);
@@ -435,6 +530,28 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
 		power_supply_changed(power->batt);
 		power_supply_changed(power->ac);
 
+		return NOTIFY_OK;
+	} else if (event == 0x71020000) {
+		schedule_work(&power->critical_work);
+
+		return NOTIFY_OK;
+	} else if ((event & 0xffff0000) == 0x71060000) {
+		u8 changed_port = event >> 8;
+		u8 cur_port;
+
+		/* Port charging state change? */
+		if (apple_smc_read_u8(power->smc, SMC_KEY(AC-W), &cur_port) >= 0) {
+			dev_info(power->dev, "Port %d state change (charge port: %d)\n",
+				 changed_port + 1, cur_port);
+		}
+
+		power_supply_changed(power->batt);
+		power_supply_changed(power->ac);
+
+		return NOTIFY_OK;
+	} else if ((event & 0xff000000) == 0x71000000) {
+		dev_info(power->dev, "Unknown charger event 0x%lx\n", event);
+
 		return NOTIFY_OK;
 	}
 
@@ -446,6 +563,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
 	struct power_supply_config psy_cfg = {};
 	struct macsmc_power *power;
+	u32 val;
 	int ret;
 
 	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -469,6 +587,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
 
+	/* Doing one read of this flag enables critical shutdown notifications */
+	apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
+
 	psy_cfg.drv_data = power;
 	power->batt = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
 	if (IS_ERR(power->batt)) {
@@ -487,6 +608,8 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	power->nb.notifier_call = macsmc_power_event;
 	apple_smc_register_notifier(power->smc, &power->nb);
 
+	INIT_WORK(&power->critical_work, macsmc_power_critical_work);
+
 	return 0;
 }
 

From bd8aaf609e38cdaed4b1c698e97a6c59619f4f39 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 12 Dec 2022 23:51:41 +0900
Subject: [PATCH 0633/1027] platform/apple: smc: Add apple_smc_read_f32_scaled

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/platform/apple/smc_core.c | 48 +++++++++++++++++++++++++++++++
 include/linux/mfd/macsmc.h        |  2 ++
 2 files changed, 50 insertions(+)

diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
index daf029cd072f52..27605398be3b24 100644
--- a/drivers/platform/apple/smc_core.c
+++ b/drivers/platform/apple/smc_core.c
@@ -4,6 +4,7 @@
  * Copyright The Asahi Linux Contributors
  */
 
+#include <linux/bitfield.h>
 #include <linux/device.h>
 #include <linux/mfd/core.h>
 #include <linux/mutex.h>
@@ -98,6 +99,53 @@ int apple_smc_rw(struct apple_smc *smc, smc_key key, void *wbuf, size_t wsize,
 }
 EXPORT_SYMBOL(apple_smc_rw);
 
+int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale)
+{
+	u32 fval;
+	u64 val;
+	int ret, exp;
+
+	ret = apple_smc_read_u32(smc, key, &fval);
+	if (ret < 0)
+		return ret;
+
+	val = ((u64)((fval & GENMASK(22, 0)) | BIT(23)));
+	exp = ((fval >> 23) & 0xff) - 127 - 23;
+	if (scale < 0) {
+		val <<= 32;
+		exp -= 32;
+		val /= -scale;
+	} else {
+		val *= scale;
+	}
+
+	if (exp > 63)
+		val = U64_MAX;
+	else if (exp < -63)
+		val = 0;
+	else if (exp < 0)
+		val >>= -exp;
+	else if (exp != 0 && (val & ~((1UL << (64 - exp)) - 1))) /* overflow */
+		val = U64_MAX;
+	else
+		val <<= exp;
+
+	if (fval & BIT(31)) {
+		if (val > (-(s64)INT_MIN))
+			*p = INT_MIN;
+		else
+			*p = -val;
+	} else {
+		if (val > INT_MAX)
+			*p = INT_MAX;
+		else
+			*p = val;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(apple_smc_read_f32_scaled);
+
 int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
 {
 	int ret;
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
index e5f50b5d62dfd3..0f7775203b3712 100644
--- a/include/linux/mfd/macsmc.h
+++ b/include/linux/mfd/macsmc.h
@@ -82,6 +82,8 @@ static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key)
 }
 #define apple_smc_write_flag apple_smc_write_u8
 
+int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale);
+
 int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n);
 int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n);
 

From b175f297cf7270a6f0cda7b224c4012cc87d3616 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 12 Dec 2022 23:36:17 +0900
Subject: [PATCH 0634/1027] power: supply: macsmc_power: Add a debug mode to
 print power usage

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 81 ++++++++++++++++++++++++++++-
 1 file changed, 80 insertions(+), 1 deletion(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index b3cedc8d4d16b1..de02bda5298fd4 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -32,8 +32,25 @@ struct macsmc_power {
 
 	struct work_struct critical_work;
 	bool shutdown_started;
+
+	struct delayed_work dbg_log_work;
+};
+
+static int macsmc_log_power_set(const char *val, const struct kernel_param *kp);
+
+static const struct kernel_param_ops macsmc_log_power_ops = {
+        .set = macsmc_log_power_set,
+        .get = param_get_bool,
 };
 
+static bool log_power = false;
+module_param_cb(log_power, &macsmc_log_power_ops, &log_power, 0644);
+MODULE_PARM_DESC(log_power, "Periodically log power consumption for debugging");
+
+#define POWER_LOG_INTERVAL (HZ)
+
+static struct macsmc_power *g_power;
+
 #define CHNC_BATTERY_FULL	BIT(0)
 #define CHNC_NO_CHARGER		BIT(7)
 #define CHNC_NOCHG_CH0C		BIT(14)
@@ -466,7 +483,58 @@ static const struct power_supply_desc macsmc_ac_desc = {
 	.num_properties		= ARRAY_SIZE(macsmc_ac_props),
 };
 
-static void macsmc_power_critical_work(struct work_struct *wrk) {
+static int macsmc_log_power_set(const char *val, const struct kernel_param *kp)
+{
+	int ret = param_set_bool(val, kp);
+
+	if (ret < 0)
+		return ret;
+
+	if (log_power && g_power)
+		schedule_delayed_work(&g_power->dbg_log_work, 0);
+
+	return 0;
+}
+
+static void macsmc_dbg_work(struct work_struct *wrk)
+{
+	struct macsmc_power *power = container_of(to_delayed_work(wrk),
+						  struct macsmc_power, dbg_log_work);
+	int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0;
+	s32 p_bat = 0;
+	s16 t_full = 0, t_empty = 0;
+	u8 charge = 0;
+
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PDTR), &p_in, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSTR), &p_sys, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PMVR), &p_3v8, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PHPC), &p_cpu, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSVR), &p_clvr, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPMC), &p_mpmu, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPSC), &p_spmu, 1000);
+	apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &p_bat);
+	apple_smc_read_s16(power->smc, SMC_KEY(B0TE), &t_empty);
+	apple_smc_read_s16(power->smc, SMC_KEY(B0TF), &t_full);
+	apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &charge);
+
+#define FD3(x) ((x) / 1000), abs((x) % 1000)
+
+	dev_info(power->dev,
+		 "In %2d.%03dW Sys %2d.%03dW 3V8 %2d.%03dW MPMU %2d.%03dW SPMU %2d.%03dW "
+		 "CLVR %2d.%03dW CPU %2d.%03dW Batt %2d.%03dW %d%% T%s %dm\n",
+		 FD3(p_in), FD3(p_sys), FD3(p_3v8), FD3(p_mpmu), FD3(p_spmu), FD3(p_clvr),
+		 FD3(p_cpu), FD3(p_bat), charge,
+		 t_full >= 0 ? "full" : "empty",
+		 t_full >= 0 ? t_full : t_empty);
+
+#undef FD3
+
+	if (log_power)
+		schedule_delayed_work(&power->dbg_log_work, POWER_LOG_INTERVAL);
+}
+
+static void macsmc_power_critical_work(struct work_struct *wrk)
+{
 	struct macsmc_power *power = container_of(wrk, struct macsmc_power, critical_work);
 	int ret;
 	u32 bcf0;
@@ -609,6 +677,12 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_register_notifier(power->smc, &power->nb);
 
 	INIT_WORK(&power->critical_work, macsmc_power_critical_work);
+	INIT_DELAYED_WORK(&power->dbg_log_work, macsmc_dbg_work);
+
+	g_power = power;
+
+	if (log_power)
+		schedule_delayed_work(&power->dbg_log_work, 0);
 
 	return 0;
 }
@@ -617,6 +691,11 @@ static void macsmc_power_remove(struct platform_device *pdev)
 {
 	struct macsmc_power *power = dev_get_drvdata(&pdev->dev);
 
+	cancel_work(&power->critical_work);
+	cancel_delayed_work(&power->dbg_log_work);
+
+	g_power = NULL;
+
 	apple_smc_unregister_notifier(power->smc, &power->nb);
 }
 

From b2bb5b47da9e01600f264f29332dafe193759885 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Apr 2023 17:14:05 +0900
Subject: [PATCH 0635/1027] macsmc: Fix race between backend and core on
 notifications

The core enables notifications (NTAP) before returning from the probe
function, before the backend has a chance to set smc->core. This leads
to a race if a notification arrives early.

We already rely on the core setting the drvdata to itself, so move that
ahead of NTAP=1 and drop smc->core entirely. That way it's safe for the
backend notification callback to fire before the core probe function
returns.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/platform/apple/smc.h       |  3 +--
 drivers/platform/apple/smc_core.c  | 18 +++++++++---------
 drivers/platform/apple/smc_rtkit.c | 10 ++++------
 3 files changed, 14 insertions(+), 17 deletions(-)

diff --git a/drivers/platform/apple/smc.h b/drivers/platform/apple/smc.h
index 8ae51887b2c553..34131f77fe09cb 100644
--- a/drivers/platform/apple/smc.h
+++ b/drivers/platform/apple/smc.h
@@ -19,8 +19,7 @@ struct apple_smc_backend_ops {
 	int (*get_key_info)(void *cookie, smc_key key, struct apple_smc_key_info *info);
 };
 
-struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops,
-				  void *cookie);
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie);
 void *apple_smc_get_cookie(struct apple_smc *smc);
 int apple_smc_remove(struct apple_smc *smc);
 void apple_smc_event_received(struct apple_smc *smc, uint32_t event);
diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
index 27605398be3b24..ee1df85e0aecff 100644
--- a/drivers/platform/apple/smc_core.c
+++ b/drivers/platform/apple/smc_core.c
@@ -236,7 +236,7 @@ void *apple_smc_get_cookie(struct apple_smc *smc)
 }
 EXPORT_SYMBOL(apple_smc_get_cookie);
 
-struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie)
+int apple_smc_probe(struct device *dev, const struct apple_smc_backend_ops *ops, void *cookie)
 {
 	struct apple_smc *smc;
 	u32 count;
@@ -244,7 +244,7 @@ struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_bac
 
 	smc = devm_kzalloc(dev, sizeof(*smc), GFP_KERNEL);
 	if (!smc)
-		return ERR_PTR(-ENOMEM);
+		return -ENOMEM;
 
 	smc->dev = dev;
 	smc->be_cookie = cookie;
@@ -254,16 +254,18 @@ struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_bac
 
 	ret = apple_smc_read_u32(smc, SMC_KEY(#KEY), &count);
 	if (ret)
-		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get key count"));
+		return dev_err_probe(dev, ret, "Failed to get key count");
 	smc->key_count = be32_to_cpu(count);
 
 	ret = apple_smc_get_key_by_index(smc, 0, &smc->first_key);
 	if (ret)
-		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get first key"));
+		return dev_err_probe(dev, ret, "Failed to get first key");
 
 	ret = apple_smc_get_key_by_index(smc, smc->key_count - 1, &smc->last_key);
 	if (ret)
-		return ERR_PTR(dev_err_probe(dev, ret, "Failed to get last key"));
+		return dev_err_probe(dev, ret, "Failed to get last key");
+
+	dev_set_drvdata(dev, smc);
 
 	/* Enable notifications */
 	apple_smc_write_flag(smc, SMC_KEY(NTAP), 1);
@@ -271,13 +273,11 @@ struct apple_smc *apple_smc_probe(struct device *dev, const struct apple_smc_bac
 	dev_info(dev, "Initialized (%d keys %p4ch..%p4ch)\n",
 		 smc->key_count, &smc->first_key, &smc->last_key);
 
-	dev_set_drvdata(dev, smc);
-
 	ret = mfd_add_devices(dev, -1, apple_smc_devs, ARRAY_SIZE(apple_smc_devs), NULL, 0, NULL);
 	if (ret)
-		return ERR_PTR(dev_err_probe(dev, ret, "Subdevice initialization failed"));
+		return dev_err_probe(dev, ret, "Subdevice initialization failed");
 
-	return smc;
+	return 0;
 }
 EXPORT_SYMBOL(apple_smc_probe);
 
diff --git a/drivers/platform/apple/smc_rtkit.c b/drivers/platform/apple/smc_rtkit.c
index 5b662cb68fce78..4ef489f9558062 100644
--- a/drivers/platform/apple/smc_rtkit.c
+++ b/drivers/platform/apple/smc_rtkit.c
@@ -40,7 +40,6 @@
 
 struct apple_smc_rtkit {
 	struct device *dev;
-	struct apple_smc *core;
 	struct apple_rtkit *rtk;
 
 	struct completion init_done;
@@ -321,6 +320,7 @@ static bool apple_smc_rtkit_recv_early(void *cookie, u8 endpoint, u64 message)
 static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message)
 {
 	struct apple_smc_rtkit *smc = cookie;
+	struct apple_smc *core = dev_get_drvdata(smc->dev);
 
 	if (endpoint != SMC_ENDPOINT) {
 		dev_err(smc->dev, "Received message for unknown endpoint 0x%x\n", endpoint);
@@ -332,7 +332,7 @@ static void apple_smc_rtkit_recv(void *cookie, u8 endpoint, u64 message)
 		return;
 	}
 
-	apple_smc_event_received(smc->core, FIELD_GET(SMC_DATA, message));
+	apple_smc_event_received(core, FIELD_GET(SMC_DATA, message));
 }
 
 static const struct apple_rtkit_ops apple_smc_rtkit_ops = {
@@ -403,11 +403,9 @@ static int apple_smc_rtkit_probe(struct platform_device *pdev)
 		goto cleanup;
 	}
 
-	smc->core = apple_smc_probe(dev, &apple_smc_rtkit_be_ops, smc);
-	if (IS_ERR(smc->core)) {
-		ret = PTR_ERR(smc->core);
+	ret = apple_smc_probe(dev, &apple_smc_rtkit_be_ops, smc);
+	if (ret)
 		goto cleanup;
-	}
 
 	return 0;
 

From 91dbd5ba32a341daf79289c73be20b9fd281a378 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Apr 2023 04:18:14 +0900
Subject: [PATCH 0636/1027] power: supply: macsmc_power: Log power data on
 button presses

This helps catch s2idle power stats, since we get early data when the
system resumes due to a power button press.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 64 +++++++++++++++++------------
 1 file changed, 37 insertions(+), 27 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index de02bda5298fd4..b6daf15703e733 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -72,6 +72,36 @@ static struct macsmc_power *g_power;
 #define ACSt_CAN_BOOT_AP	BIT(2)
 #define ACSt_CAN_BOOT_IBOOT	BIT(1)
 
+static void macsmc_do_dbg(struct macsmc_power *power)
+{
+	int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0;
+	s32 p_bat = 0;
+	s16 t_full = 0, t_empty = 0;
+	u8 charge = 0;
+
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PDTR), &p_in, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSTR), &p_sys, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PMVR), &p_3v8, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PHPC), &p_cpu, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSVR), &p_clvr, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPMC), &p_mpmu, 1000);
+	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPSC), &p_spmu, 1000);
+	apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &p_bat);
+	apple_smc_read_s16(power->smc, SMC_KEY(B0TE), &t_empty);
+	apple_smc_read_s16(power->smc, SMC_KEY(B0TF), &t_full);
+	apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &charge);
+
+#define FD3(x) ((x) / 1000), abs((x) % 1000)
+	dev_info(power->dev,
+		 "In %2d.%03dW Sys %2d.%03dW 3V8 %2d.%03dW MPMU %2d.%03dW SPMU %2d.%03dW "
+		 "CLVR %2d.%03dW CPU %2d.%03dW Batt %2d.%03dW %d%% T%s %dm\n",
+		 FD3(p_in), FD3(p_sys), FD3(p_3v8), FD3(p_mpmu), FD3(p_spmu), FD3(p_clvr),
+		 FD3(p_cpu), FD3(p_bat), charge,
+		 t_full >= 0 ? "full" : "empty",
+		 t_full >= 0 ? t_full : t_empty);
+#undef FD3
+}
+
 static int macsmc_battery_get_status(struct macsmc_power *power)
 {
 	u64 nocharge_flags;
@@ -500,34 +530,8 @@ static void macsmc_dbg_work(struct work_struct *wrk)
 {
 	struct macsmc_power *power = container_of(to_delayed_work(wrk),
 						  struct macsmc_power, dbg_log_work);
-	int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0;
-	s32 p_bat = 0;
-	s16 t_full = 0, t_empty = 0;
-	u8 charge = 0;
 
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PDTR), &p_in, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSTR), &p_sys, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PMVR), &p_3v8, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PHPC), &p_cpu, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PSVR), &p_clvr, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPMC), &p_mpmu, 1000);
-	apple_smc_read_f32_scaled(power->smc, SMC_KEY(PPSC), &p_spmu, 1000);
-	apple_smc_read_s32(power->smc, SMC_KEY(B0AP), &p_bat);
-	apple_smc_read_s16(power->smc, SMC_KEY(B0TE), &t_empty);
-	apple_smc_read_s16(power->smc, SMC_KEY(B0TF), &t_full);
-	apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &charge);
-
-#define FD3(x) ((x) / 1000), abs((x) % 1000)
-
-	dev_info(power->dev,
-		 "In %2d.%03dW Sys %2d.%03dW 3V8 %2d.%03dW MPMU %2d.%03dW SPMU %2d.%03dW "
-		 "CLVR %2d.%03dW CPU %2d.%03dW Batt %2d.%03dW %d%% T%s %dm\n",
-		 FD3(p_in), FD3(p_sys), FD3(p_3v8), FD3(p_mpmu), FD3(p_spmu), FD3(p_clvr),
-		 FD3(p_cpu), FD3(p_bat), charge,
-		 t_full >= 0 ? "full" : "empty",
-		 t_full >= 0 ? t_full : t_empty);
-
-#undef FD3
+	macsmc_do_dbg(power);
 
 	if (log_power)
 		schedule_delayed_work(&power->dbg_log_work, POWER_LOG_INTERVAL);
@@ -620,6 +624,12 @@ static int macsmc_power_event(struct notifier_block *nb, unsigned long event, vo
 	} else if ((event & 0xff000000) == 0x71000000) {
 		dev_info(power->dev, "Unknown charger event 0x%lx\n", event);
 
+		return NOTIFY_OK;
+	} else if ((event & 0xffff0000) == 0x72010000) {
+		/* Button event handled by macsmc-hid, but let's do a debug print */
+		if (log_power)
+			macsmc_do_dbg(power);
+
 		return NOTIFY_OK;
 	}
 

From 42228fb7491365ef08821e84e2073cd44d55666c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 30 May 2023 19:52:09 +0900
Subject: [PATCH 0637/1027] power: supply: macsmc_power: Add CHWA charge
 thresholds

This is a hardcoded charge threshold feature present in firmware 13.0 or
newer. Userspace settings are rounded to one of the two possible
behaviors.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 67 +++++++++++++++++++++++++++--
 1 file changed, 63 insertions(+), 4 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index b6daf15703e733..b0cf889a07c384 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -20,11 +20,13 @@
 struct macsmc_power {
 	struct device *dev;
 	struct apple_smc *smc;
+	struct power_supply_desc batt_desc;
 
 	struct power_supply *batt;
 	char model_name[MAX_STRING_LENGTH];
 	char serial_number[MAX_STRING_LENGTH];
 	char mfg_date[MAX_STRING_LENGTH];
+	bool has_chwa;
 
 	struct power_supply *ac;
 
@@ -62,9 +64,11 @@ static struct macsmc_power *g_power;
 
 #define CH0R_LOWER_FLAGS	GENMASK(15, 0)
 #define CH0R_NOAC_CH0I		BIT(0)
+#define CH0R_NOAC_DISCONNECTED	BIT(4)
 #define CH0R_NOAC_CH0J		BIT(5)
 #define CH0R_BMS_BUSY		BIT(8)
 #define CH0R_NOAC_CH0K		BIT(9)
+#define CH0R_NOAC_CHWA		BIT(11)
 
 #define CH0X_CH0C		BIT(0)
 #define CH0X_CH0B		BIT(1)
@@ -72,6 +76,10 @@ static struct macsmc_power *g_power;
 #define ACSt_CAN_BOOT_AP	BIT(2)
 #define ACSt_CAN_BOOT_IBOOT	BIT(1)
 
+#define CHWA_FIXED_START_THRESHOLD	75
+#define CHWA_FIXED_END_THRESHOLD	80
+#define CHWA_PROP_WRITE_THRESHOLD	95
+
 static void macsmc_do_dbg(struct macsmc_power *power)
 {
 	int p_in = 0, p_sys = 0, p_3v8 = 0, p_mpmu = 0, p_spmu = 0, p_clvr = 0, p_cpu = 0;
@@ -107,6 +115,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 	u64 nocharge_flags;
 	u32 nopower_flags;
 	u16 ac_current;
+	bool chwa_limit = false;
 	int ret;
 
 	/*
@@ -153,14 +162,29 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 	else if (ret)
 		return POWER_SUPPLY_STATUS_FULL;
 
+	/*
+	 * If we have charge limits supported and enabled and the SoC is > 75%,
+	 * that means we are not charging for that reason (if not charging).
+	 */
+	if (power->has_chwa && apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) == 1) {
+		u8 buic = 0;
+
+		if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
+			buic >= CHWA_FIXED_START_THRESHOLD)
+			chwa_limit = true;
+	}
+
 	/* If there are reasons we aren't charging... */
 	ret = apple_smc_read_u64(power->smc, SMC_KEY(CHNC), &nocharge_flags);
 	if (!ret) {
 		/* Perhaps the battery is full after all */
 		if (nocharge_flags & CHNC_BATTERY_FULL)
 			return POWER_SUPPLY_STATUS_FULL;
-		/* Or maybe the BMS is just busy doing something, if so call it charging anyway */
-		else if (nocharge_flags == CHNC_BMS_BUSY)
+		/*
+		 * Or maybe the BMS is just busy doing something, if so call it charging anyway.
+		 * But CHWA limits show up as this, so exclude those.
+		 */
+		else if (nocharge_flags == CHNC_BMS_BUSY && !chwa_limit)
 			return POWER_SUPPLY_STATUS_CHARGING;
 		/* If we have other reasons we aren't charging, say we aren't */
 		else if (nocharge_flags)
@@ -392,6 +416,16 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_MANUFACTURE_DAY:
 		ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
 		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+		ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
+		val->intval = ret == 1 ? CHWA_FIXED_START_THRESHOLD : 100;
+		ret = ret < 0 ? ret : 0;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
+		val->intval = ret == 1 ? CHWA_FIXED_END_THRESHOLD : 100;
+		ret = ret < 0 ? ret : 0;
+		break;
 	default:
 		return -EINVAL;
 	}
@@ -408,6 +442,16 @@ static int macsmc_battery_set_property(struct power_supply *psy,
 	switch (psp) {
 	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
 		return macsmc_battery_set_charge_behaviour(power, val->intval);
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+		/*
+		 * Ignore, we allow writes so userspace isn't confused but this is
+		 * not configurable independently, it always is 75 or 100 depending
+		 * on the end_threshold boolean setting.
+		 */
+		return 0;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		return apple_smc_write_flag(power->smc, SMC_KEY(CHWA),
+					    val->intval <= CHWA_PROP_WRITE_THRESHOLD);
 	default:
 		return -EINVAL;
 	}
@@ -416,15 +460,20 @@ static int macsmc_battery_set_property(struct power_supply *psy,
 static int macsmc_battery_property_is_writeable(struct power_supply *psy,
 						enum power_supply_property psp)
 {
+	struct macsmc_power *power = power_supply_get_drvdata(psy);
+
 	switch (psp) {
 	case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
 		return true;
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+		return power->has_chwa;
 	default:
 		return false;
 	}
 }
 
-static enum power_supply_property macsmc_battery_props[] = {
+static const enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_STATUS,
 	POWER_SUPPLY_PROP_PRESENT,
 	POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
@@ -453,6 +502,8 @@ static enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_MANUFACTURE_YEAR,
 	POWER_SUPPLY_PROP_MANUFACTURE_MONTH,
 	POWER_SUPPLY_PROP_MANUFACTURE_DAY,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+	POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD
 };
 
 static const struct power_supply_desc macsmc_battery_desc = {
@@ -650,6 +701,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
 
 	power->dev = &pdev->dev;
 	power->smc = smc;
+	power->batt_desc = macsmc_battery_desc;
 	dev_set_drvdata(&pdev->dev, power);
 
 	/* Ignore devices without a charger/battery */
@@ -665,11 +717,18 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
 
+	if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) >= 0) {
+		power->has_chwa = true;
+	} else {
+		/* Remove the last 2 properties that control the charge threshold */
+		power->batt_desc.num_properties -= 2;
+	}
+
 	/* Doing one read of this flag enables critical shutdown notifications */
 	apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
 
 	psy_cfg.drv_data = power;
-	power->batt = devm_power_supply_register(&pdev->dev, &macsmc_battery_desc, &psy_cfg);
+	power->batt = devm_power_supply_register(&pdev->dev, &power->batt_desc, &psy_cfg);
 	if (IS_ERR(power->batt)) {
 		dev_err(&pdev->dev, "Failed to register battery\n");
 		ret = PTR_ERR(power->batt);

From f4517a1187f9ca26b77e346cbb52ed56114a59a9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20Wei=C3=9Fschuh?= <linux@weissschuh.net>
Date: Wed, 6 Mar 2024 19:40:39 +0100
Subject: [PATCH 0638/1027] power: supply: macsmc_power: Report available
 charge_behaviours
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The generic handling if charge_behaviours in the power_supply core
requires power_supply_desc.charge_behaviours to be set.

Signed-off-by: Thomas Weißschuh <linux@weissschuh.net>
---
 drivers/power/supply/macsmc_power.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index b0cf889a07c384..0bff4d8a5f22ea 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -514,6 +514,9 @@ static const struct power_supply_desc macsmc_battery_desc = {
 	.property_is_writeable	= macsmc_battery_property_is_writeable,
 	.properties		= macsmc_battery_props,
 	.num_properties		= ARRAY_SIZE(macsmc_battery_props),
+	.charge_behaviours	= BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)
+				| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE)
+				| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE),
 };
 
 static int macsmc_ac_get_property(struct power_supply *psy,

From c73f516f3882c2fa6c5a7e2309cb2ccaf32bb9da Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 28 Jun 2024 14:21:22 +0900
Subject: [PATCH 0639/1027] power: supply: macsmc_power: Add more properties

Report more voltages from the battery, and also fudge energy numbers
from charge numbers. This way userspace doesn't try to convert on its
own (and gets it very wrong).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 57 +++++++++++++++++++++++++++++
 1 file changed, 57 insertions(+)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 0bff4d8a5f22ea..88be3fe42ace39 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -17,6 +17,17 @@
 
 #define MAX_STRING_LENGTH 256
 
+/*
+ * This number is not reported anywhere by SMC, but seems to be a good
+ * conversion factor for charge to energy across machines. We need this
+ * to convert in the driver, since if we don't userspace will try to do
+ * the conversion with a randomly guessed voltage and get it wrong.
+ *
+ * Ideally there would be a power supply prop to inform userspace of this
+ * number, but there isn't, only min/max.
+ */
+#define MACSMC_NOMINAL_CELL_VOLTAGE_MV 3800
+
 struct macsmc_power {
 	struct device *dev;
 	struct apple_smc *smc;
@@ -27,6 +38,8 @@ struct macsmc_power {
 	char serial_number[MAX_STRING_LENGTH];
 	char mfg_date[MAX_STRING_LENGTH];
 	bool has_chwa;
+	u8 num_cells;
+	int nominal_voltage_mv;
 
 	struct power_supply *ac;
 
@@ -352,6 +365,29 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(BITV), &vu16);
 		val->intval = vu16 * 1000;
 		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+		/*
+		 * Battery cell max voltage? BVV* seem to return per-cell voltages,
+		 * BVV[NOP] are probably the max voltages for the 3 cells but we don't
+		 * know what will happen if they ever change the number of cells.
+		 * So go with BVVN and multiply by the cell count (BNCB).
+		 * BVVL seems to be the per-cell limit adjusted dynamically.
+		 * Guess: BVVL = Limit, BVVN = Nominal, and the other cells got filled
+		 * in around nearby letters?
+		 */
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(BVVN), &vu16);
+		val->intval = vu16 * 1000 * power->num_cells;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		/* Lifetime min */
+		ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPM), &vs16);
+		val->intval = vs16 * 1000;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		/* Lifetime max */
+		ret = apple_smc_read_s16(power->smc, SMC_KEY(BLPX), &vs16);
+		val->intval = vs16 * 1000;
+		break;
 	case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
 		val->intval = vu16 * 1000;
@@ -380,6 +416,18 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
 		val->intval = swab16(vu16) * 1000;
 		break;
+	case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0DC), &vu16);
+		val->intval = vu16 * power->nominal_voltage_mv;
+		break;
+	case POWER_SUPPLY_PROP_ENERGY_FULL:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0FC), &vu16);
+		val->intval = vu16 * power->nominal_voltage_mv;
+		break;
+	case POWER_SUPPLY_PROP_ENERGY_NOW:
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RM), &vu16);
+		val->intval = swab16(vu16) * power->nominal_voltage_mv;
+		break;
 	case POWER_SUPPLY_PROP_TEMP:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0AT), &vu16);
 		val->intval = vu16 - 2732;
@@ -485,6 +533,9 @@ static const enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_CURRENT_NOW,
 	POWER_SUPPLY_PROP_POWER_NOW,
 	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
@@ -492,6 +543,9 @@ static const enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
 	POWER_SUPPLY_PROP_CHARGE_FULL,
 	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+	POWER_SUPPLY_PROP_ENERGY_FULL,
+	POWER_SUPPLY_PROP_ENERGY_NOW,
 	POWER_SUPPLY_PROP_TEMP,
 	POWER_SUPPLY_PROP_CHARGE_COUNTER,
 	POWER_SUPPLY_PROP_CYCLE_COUNT,
@@ -727,6 +781,9 @@ static int macsmc_power_probe(struct platform_device *pdev)
 		power->batt_desc.num_properties -= 2;
 	}
 
+	apple_smc_read_u8(power->smc, SMC_KEY(BNCB), &power->num_cells);
+	power->nominal_voltage_mv = MACSMC_NOMINAL_CELL_VOLTAGE_MV * power->num_cells;
+
 	/* Doing one read of this flag enables critical shutdown notifications */
 	apple_smc_read_u32(power->smc, SMC_KEY(BCF0), &val);
 

From 06d2e7834e9a5d027fda9d82553d080894579951 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 2 Oct 2023 00:13:48 +0200
Subject: [PATCH 0640/1027] platform/apple: smc: Add apple_smc_read_ioft_scaled

"ioft" is a 48.16 fixed point type.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/platform/apple/smc_core.c | 19 +++++++++++++++++++
 include/linux/mfd/macsmc.h        |  1 +
 2 files changed, 20 insertions(+)

diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
index ee1df85e0aecff..bb4bc9b112a98b 100644
--- a/drivers/platform/apple/smc_core.c
+++ b/drivers/platform/apple/smc_core.c
@@ -146,6 +146,25 @@ int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int sc
 }
 EXPORT_SYMBOL(apple_smc_read_f32_scaled);
 
+/*
+ * ioft is a 48.16 fixed point type
+ */
+int apple_smc_read_ioft_scaled(struct apple_smc *smc, smc_key key, u64 *p,
+			       int scale)
+{
+	u64 val;
+	int ret;
+
+	ret = apple_smc_read_u64(smc, key, &val);
+	if (ret < 0)
+		return ret;
+
+	*p = mult_frac(val, scale, 65536);
+
+	return 0;
+}
+EXPORT_SYMBOL(apple_smc_read_ioft_scaled);
+
 int apple_smc_get_key_by_index(struct apple_smc *smc, int index, smc_key *key)
 {
 	int ret;
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
index 0f7775203b3712..a63da99ed5ed2d 100644
--- a/include/linux/mfd/macsmc.h
+++ b/include/linux/mfd/macsmc.h
@@ -83,6 +83,7 @@ static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key)
 #define apple_smc_write_flag apple_smc_write_u8
 
 int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale);
+int apple_smc_read_ioft_scaled(struct apple_smc *smc, smc_key key, u64 *p, int scale);
 
 int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n);
 int apple_smc_unregister_notifier(struct apple_smc *smc, struct notifier_block *n);

From 4a31e83da1f9425d2c54740dcfcb2d7d083ce26f Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Fri, 26 Apr 2024 14:01:23 +1000
Subject: [PATCH 0641/1027] hwmon: add macsmc-hwmon driver

Apple Silicon devices, particularly Macs, continue Apple's tradition of
integrating a ridiculous number of sensors on their products. Most of these
report to the System Management Controller, and their data is exposed by
the SMC firmware as simple key-value pairs.

This commit adds an hwmon drive for retrieving the data reported by the
temperature, voltage, current and power sensors, as well as fan data.

Devices each expose their own unique set of sensors and fans, even with
the SMC being baked into the SoC. This driver walks a devicetree node
in order to discover the sensors present on the current device at runtime.

Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 MAINTAINERS                       |   9 +
 drivers/hwmon/Kconfig             |  13 +
 drivers/hwmon/Makefile            |   1 +
 drivers/hwmon/macsmc-hwmon.c      | 623 ++++++++++++++++++++++++++++++
 drivers/platform/apple/smc_core.c |   3 +
 5 files changed, 649 insertions(+)
 create mode 100644 drivers/hwmon/macsmc-hwmon.c

diff --git a/MAINTAINERS b/MAINTAINERS
index cc40a9d9b8cd10..beafa25d391504 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2090,6 +2090,15 @@ F:	include/dt-bindings/interrupt-controller/apple-aic.h
 F:	include/dt-bindings/pinctrl/apple.h
 F:	include/linux/soc/apple/*
 
+ARM/APPLE SMC HWMON DRIVER
+M:	James Calligeros <jcalligeros99@gmail.com>
+L:	asahi@lists.linux.dev
+S:	Maintained
+W:	https://asahilinux.org
+B:	https://github.com/AsahiLinux/linux/issues
+C:	irc://irc.oftc.net/asahi-dev
+F:	drivers/hwmon/macsmc-hwmon.c
+
 ARM/ARTPEC MACHINE SUPPORT
 M:	Jesper Nilsson <jesper.nilsson@axis.com>
 M:	Lars Persson <lars.persson@axis.com>
diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig
index b60fe2e58ad6cb..079cc13e8a32c9 100644
--- a/drivers/hwmon/Kconfig
+++ b/drivers/hwmon/Kconfig
@@ -1560,6 +1560,19 @@ config SENSORS_LM95245
 	  This driver can also be built as a module. If so, the module
 	  will be called lm95245.
 
+config SENSORS_MACSMC
+	tristate "Apple SMC (Apple Silicon)"
+	depends on APPLE_SMC && OF
+	depends on ARCH_APPLE && ARM64
+	help
+	  This driver exposes the temperature, voltage, current, power, and fan
+	  sensors present on Apple Silicon devices, such as the M-series Macs.
+
+	  Say Y here if you have an Apple Silicon device.
+
+	  This driver can also be built as a module. If so, the module will be called
+	  macsmc_hwmon.
+
 config SENSORS_PC87360
 	tristate "National Semiconductor PC87360 family"
 	depends on HAS_IOPORT
diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile
index b1c7056c37db59..5b5863e3603dd5 100644
--- a/drivers/hwmon/Makefile
+++ b/drivers/hwmon/Makefile
@@ -142,6 +142,7 @@ obj-$(CONFIG_SENSORS_LTC4260)	+= ltc4260.o
 obj-$(CONFIG_SENSORS_LTC4261)	+= ltc4261.o
 obj-$(CONFIG_SENSORS_LTC4282)	+= ltc4282.o
 obj-$(CONFIG_SENSORS_LTQ_CPUTEMP) += ltq-cputemp.o
+obj-$(CONFIG_SENSORS_MACSMC) += macsmc-hwmon.o
 obj-$(CONFIG_SENSORS_MAX1111)	+= max1111.o
 obj-$(CONFIG_SENSORS_MAX127)	+= max127.o
 obj-$(CONFIG_SENSORS_MAX16065)	+= max16065.o
diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
new file mode 100644
index 00000000000000..9ba584b5619081
--- /dev/null
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -0,0 +1,623 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple SMC hwmon driver for Apple Silicon platforms
+ *
+ * The System Management Controller on Apple Silicon devices is responsible for
+ * measuring data from sensors across the SoC and machine. These include power,
+ * temperature, voltage and current sensors. Some "sensors" actually expose
+ * derived values. An example of this is the key PHPC, which is an estimate
+ * of the heat energy being dissipated by the SoC.
+ *
+ * While each SoC only has one SMC variant, each platform exposes a different
+ * set of sensors. For example, M1 MacBooks expose battery telemetry sensors
+ * which are not present on the M1 Mac mini. For this reason, the available
+ * sensors for a given platform are described in the device tree in a child
+ * node of the SMC device. We must walk this list of available sensors and
+ * populate the required hwmon data structures at runtime.
+ *
+ * Originally based on a prototype by Jean-Francois Bortolotti <jeff@borto.fr>
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/hwmon.h>
+#include <linux/hwmon-sysfs.h>
+#include <linux/mfd/macsmc.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+
+#define MAX_LABEL_LENGTH 32
+#define NUM_SENSOR_TYPES 5 /* temp, volt, current, power, fan */
+
+struct macsmc_hwmon_sensor {
+	struct apple_smc_key_info info;
+	smc_key macsmc_key;
+	char label[MAX_LABEL_LENGTH];
+};
+
+struct macsmc_hwmon_fan {
+	struct macsmc_hwmon_sensor now;
+	struct macsmc_hwmon_sensor min;
+	struct macsmc_hwmon_sensor max;
+	struct macsmc_hwmon_sensor set;
+	char label[MAX_LABEL_LENGTH];
+	u32 attrs;
+};
+
+struct macsmc_hwmon_sensors {
+	struct macsmc_hwmon_sensor *sensors;
+	u32 n_sensors;
+};
+
+struct macsmc_hwmon_fans {
+	struct macsmc_hwmon_fan *fans;
+	u32 n_fans;
+};
+
+struct macsmc_hwmon {
+	struct device *dev;
+	struct apple_smc *smc;
+	struct device *hwmon_dev;
+	/* Chip + sensor types + NULL */
+	struct hwmon_channel_info channel_infos[1 + NUM_SENSOR_TYPES + 1];
+	struct macsmc_hwmon_sensors temp;
+	struct macsmc_hwmon_sensors volt;
+	struct macsmc_hwmon_sensors curr;
+	struct macsmc_hwmon_sensors power;
+	struct macsmc_hwmon_fans fan;
+};
+
+static int macsmc_hwmon_read_label(struct device *dev,
+				enum hwmon_sensor_types type, u32 attr,
+				int channel, const char **str)
+{
+	struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+
+	switch (type) {
+	case hwmon_temp:
+		if (channel >= hwmon->temp.n_sensors)
+			return -EINVAL;
+		*str = hwmon->temp.sensors[channel].label;
+		break;
+	case hwmon_in:
+		if (channel >= hwmon->volt.n_sensors)
+			return -EINVAL;
+		*str = hwmon->volt.sensors[channel].label;
+		break;
+	case hwmon_curr:
+		if (channel >= hwmon->curr.n_sensors)
+			return -EINVAL;
+		*str = hwmon->curr.sensors[channel].label;
+		break;
+	case hwmon_power:
+		if (channel >= hwmon->power.n_sensors)
+			return -EINVAL;
+		*str = hwmon->power.sensors[channel].label;
+		break;
+	case hwmon_fan:
+		if (channel >= hwmon->fan.n_fans)
+			return -EINVAL;
+		*str = hwmon->fan.fans[channel].label;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return 0;
+}
+
+/*
+ * The SMC has keys of multiple types, denoted by a FourCC of the same format
+ * as the key ID. We don't know what data type a key encodes until we poke at it.
+ *
+ * TODO: support other key types
+ */
+static int macsmc_hwmon_read_key(struct apple_smc *smc,
+				struct macsmc_hwmon_sensor *sensor, int scale,
+				long *val)
+{
+	int ret = 0;
+
+	switch (sensor->info.type_code) {
+	/* 32-bit IEEE 754 float */
+	case __SMC_KEY('f', 'l', 't', ' '): {
+		u32 flt_ = 0;
+
+		ret = apple_smc_read_f32_scaled(smc, sensor->macsmc_key, &flt_,
+						scale);
+		*val = flt_;
+		break;
+	}
+	/* 48.16 fixed point decimal */
+	case __SMC_KEY('i', 'o', 'f', 't'): {
+		u64 ioft = 0;
+
+		ret = apple_smc_read_ioft_scaled(smc, sensor->macsmc_key, &ioft,
+						scale);
+		*val = (long)ioft;
+		break;
+	}
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	if (ret)
+		return -EINVAL;
+
+
+	return 0;
+}
+
+static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, long *val)
+{
+	if (!(hwmon->fan.fans[chan].attrs & BIT(attr)))
+		return -EINVAL;
+
+	switch (attr) {
+	case hwmon_fan_input:
+		return macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[chan].now,
+					     1, val);
+	case hwmon_fan_min:
+		return macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[chan].min,
+					     1, val);
+	case hwmon_fan_max:
+		return macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[chan].max,
+					     1, val);
+	case hwmon_fan_target:
+		return macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[chan].set,
+					     1, val);
+	default:
+		return -EINVAL;
+	}
+}
+
+static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+			u32 attr, int channel, long *val)
+{
+	struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+	int ret = 0;
+
+	switch (type) {
+	case hwmon_temp:
+		ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->temp.sensors[channel],
+					    1000, val);
+		break;
+	case hwmon_in:
+		ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->volt.sensors[channel],
+					    1000, val);
+		break;
+	case hwmon_curr:
+		ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->curr.sensors[channel],
+					    1000, val);
+		break;
+	case hwmon_power:
+		/* SMC returns power in Watts with acceptable precision to scale to uW */
+		ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->power.sensors[channel],
+					    1000000, val);
+		break;
+	case hwmon_fan:
+		ret = macsmc_hwmon_read_fan(hwmon, attr, channel, val);
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return ret;
+}
+
+static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+			u32 attr, int channel, long val)
+{
+	return -EOPNOTSUPP;
+}
+
+static umode_t macsmc_hwmon_is_visible(const void *data,
+				enum hwmon_sensor_types type, u32 attr,
+				int channel)
+{
+	return 0444;
+}
+
+static const struct hwmon_ops macsmc_hwmon_ops = {
+	.is_visible = macsmc_hwmon_is_visible,
+	.read = macsmc_hwmon_read,
+	.read_string = macsmc_hwmon_read_label,
+	.write = macsmc_hwmon_write,
+};
+
+static struct hwmon_chip_info macsmc_hwmon_info = {
+	.ops = &macsmc_hwmon_ops,
+	.info = NULL, /* see macsmc_hwmon_create_infos */
+};
+
+/*
+ * Get the key metadata, including key data type, from the SMC.
+ */
+static int macsmc_hwmon_parse_key(struct device *dev, struct apple_smc *smc,
+			struct macsmc_hwmon_sensor *sensor, const char *key)
+{
+	int ret = 0;
+
+	ret = apple_smc_get_key_info(smc, _SMC_KEY(key), &sensor->info);
+	if (ret) {
+		dev_err(dev, "Failed to retrieve key info for %s\n", key);
+		return ret;
+	}
+	sensor->macsmc_key = _SMC_KEY(key);
+
+	return 0;
+}
+
+/*
+ * A sensor is a single key-value pair as made available by the SMC.
+ * The devicetree gives us the SMC key ID and a friendly name where the
+ * purpose of the sensor is known.
+ */
+static int macsmc_hwmon_create_sensor(struct device *dev, struct apple_smc *smc,
+				struct device_node *sensor_node,
+				struct macsmc_hwmon_sensor *sensor)
+{
+	const char *key, *label;
+	int ret = 0;
+
+	ret = of_property_read_string(sensor_node, "apple,key-id", &key);
+	if (ret) {
+		dev_err(dev, "Could not find apple,key-id in sensor node");
+		return ret;
+	}
+
+	ret = macsmc_hwmon_parse_key(dev, smc, sensor, key);
+	if (ret)
+		return ret;
+
+	if (!of_property_read_string(sensor_node, "label", &label))
+		strscpy_pad(sensor->label, label, sizeof(sensor->label));
+	else
+		strscpy_pad(sensor->label, key, sizeof(sensor->label));
+
+	return 0;
+}
+
+/*
+ * Fan data is exposed by the SMC as multiple sensors.
+ *
+ * The devicetree schema reuses apple,key-id for the actual fan speed sensor.
+ * Mix, max and target keys do not need labels, so we can reuse label
+ * for naming the entire fan.
+ */
+static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
+				struct device_node *fan_node, struct macsmc_hwmon_fan *fan)
+{
+	const char *label;
+	const char *now;
+	const char *min;
+	const char *max;
+	const char *set;
+	int ret = 0;
+
+	ret = of_property_read_string(fan_node, "apple,key-id", &now);
+	if (ret) {
+		dev_err(dev, "apple,key-id not found in fan node!");
+		return -EINVAL;
+	}
+
+	ret = macsmc_hwmon_parse_key(dev, smc, &fan->now, now);
+	if (ret)
+		return ret;
+
+	if (!of_property_read_string(fan_node, "label", &label))
+		strscpy_pad(fan->label, label, sizeof(fan->label));
+	else
+		strscpy_pad(fan->label, now, sizeof(fan->label));
+
+	fan->attrs = HWMON_F_LABEL | HWMON_F_INPUT;
+
+	ret = of_property_read_string(fan_node, "apple,fan-minimum", &min);
+	if (ret)
+		dev_warn(dev, "No minimum fan speed key for %s", fan->label);
+	else {
+		if (!macsmc_hwmon_parse_key(dev, smc, &fan->min, min))
+			fan->attrs |= HWMON_F_MIN;
+	}
+
+	ret = of_property_read_string(fan_node, "apple,fan-maximum", &max);
+	if (ret)
+		dev_warn(dev, "No maximum fan speed key for %s", fan->label);
+	else {
+		if (!macsmc_hwmon_parse_key(dev, smc, &fan->max, max))
+			fan->attrs |= HWMON_F_MAX;
+	}
+
+	ret = of_property_read_string(fan_node, "apple,fan-target", &set);
+	if (ret)
+		dev_warn(dev, "No target fan speed key for %s", fan->label);
+	else {
+		if (!macsmc_hwmon_parse_key(dev, smc, &fan->set, set))
+			fan->attrs |= HWMON_F_TARGET;
+	}
+
+	return 0;
+}
+
+static int macsmc_hwmon_populate_sensors(struct macsmc_hwmon *hwmon,
+					struct device_node *hwmon_node)
+{
+	struct device_node *group_node = NULL;
+
+	for_each_child_of_node(hwmon_node, group_node) {
+		struct device_node *key_node = NULL;
+		struct macsmc_hwmon_sensors *sensor_group = NULL;
+		struct macsmc_hwmon_fans *fan_group = NULL;
+		u32 n_keys = 0;
+		int i = 0;
+
+		n_keys = of_get_child_count(group_node);
+		if (!n_keys) {
+			dev_err(hwmon->dev, "No keys found in %s!\n", group_node->name);
+			continue;
+		}
+
+		if (strcmp(group_node->name, "apple,temp-keys") == 0)
+			sensor_group = &hwmon->temp;
+		else if (strcmp(group_node->name, "apple,volt-keys") == 0)
+			sensor_group = &hwmon->volt;
+		else if (strcmp(group_node->name, "apple,current-keys") == 0)
+			sensor_group = &hwmon->curr;
+		else if (strcmp(group_node->name, "apple,power-keys") == 0)
+			sensor_group = &hwmon->power;
+		else if (strcmp(group_node->name, "apple,fan-keys") == 0)
+			fan_group = &hwmon->fan;
+		else {
+			dev_err(hwmon->dev, "Invalid group node: %s", group_node->name);
+			continue;
+		}
+
+		if (sensor_group) {
+			sensor_group->sensors = devm_kzalloc(hwmon->dev,
+					sizeof(struct macsmc_hwmon_sensor) * n_keys,
+					GFP_KERNEL);
+			if (!sensor_group->sensors) {
+				of_node_put(group_node);
+				return -ENOMEM;
+			}
+
+			for_each_child_of_node(group_node, key_node) {
+				if (!macsmc_hwmon_create_sensor(hwmon->dev, hwmon->smc,
+							key_node, &sensor_group->sensors[i]))
+					i++;
+			}
+
+			sensor_group->n_sensors = i;
+			if (!sensor_group->n_sensors) {
+				dev_err(hwmon->dev,
+					"No valid sensor keys found in %s\n",
+					group_node->name);
+				continue;
+			}
+		} else if (fan_group) {
+			fan_group->fans = devm_kzalloc(hwmon->dev,
+					sizeof(struct macsmc_hwmon_fan) * n_keys,
+					GFP_KERNEL);
+
+			if (!fan_group->fans) {
+				of_node_put(group_node);
+				return -ENOMEM;
+			}
+
+			for_each_child_of_node(group_node, key_node) {
+				if (!macsmc_hwmon_create_fan(hwmon->dev,
+					hwmon->smc, key_node,
+					&fan_group->fans[i]))
+					i++;
+			}
+
+			fan_group->n_fans = i;
+			if (!fan_group->n_fans) {
+				dev_err(hwmon->dev,
+					"No valid sensor fans found in %s\n",
+					group_node->name);
+				continue;
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Create NULL-terminated config arrays
+ */
+static void macsmc_hwmon_populate_configs(u32 *configs,
+					u32 num_keys, u32 flags)
+{
+	int idx = 0;
+
+	for (idx = 0; idx < num_keys; idx++)
+		configs[idx] = flags;
+
+	configs[idx + 1] = 0;
+}
+
+static void macsmc_hwmon_populate_fan_configs(u32 *configs,
+					u32 num_keys, struct macsmc_hwmon_fans *fans)
+{
+	int idx = 0;
+
+	for (idx = 0; idx < num_keys; idx++)
+		configs[idx] = fans->fans[idx].attrs;
+
+	configs[idx + 1] = 0;
+}
+
+static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon,
+				struct hwmon_channel_info **info_list)
+{
+	int i = 0;
+
+	/* chip */
+	hwmon->channel_infos[i].type = hwmon_chip;
+	hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev, sizeof(u32) * 2,
+						GFP_KERNEL);
+	if (!hwmon->channel_infos[i].config)
+		return -ENOMEM;
+	macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config, 1,
+				HWMON_C_REGISTER_TZ);
+	info_list[i] = &hwmon->channel_infos[i];
+
+	if (hwmon->temp.n_sensors) {
+		i++;
+		hwmon->channel_infos[i].type = hwmon_temp;
+		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+						sizeof(u32) * hwmon->temp.n_sensors + 1,
+						GFP_KERNEL);
+		if (!hwmon->channel_infos[i].config)
+			return -ENOMEM;
+
+		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+						hwmon->temp.n_sensors,
+						(HWMON_T_INPUT | HWMON_T_LABEL));
+
+		info_list[i] = &hwmon->channel_infos[i];
+
+	}
+
+	if (hwmon->volt.n_sensors) {
+		i++;
+		hwmon->channel_infos[i].type = hwmon_in;
+		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+						sizeof(u32) * hwmon->volt.n_sensors + 1,
+						GFP_KERNEL);
+		if (!hwmon->channel_infos[i].config)
+			return -ENOMEM;
+
+		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+						hwmon->volt.n_sensors,
+						(HWMON_I_INPUT | HWMON_I_LABEL));
+
+		info_list[i] = &hwmon->channel_infos[i];
+	}
+
+	if (hwmon->curr.n_sensors) {
+		i++;
+		hwmon->channel_infos[i].type = hwmon_curr;
+		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+						sizeof(u32) * hwmon->curr.n_sensors + 1,
+						GFP_KERNEL);
+		if (!hwmon->channel_infos[i].config)
+			return -ENOMEM;
+
+		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+						hwmon->curr.n_sensors,
+						(HWMON_C_INPUT | HWMON_C_LABEL));
+
+		info_list[i] = &hwmon->channel_infos[i];
+	}
+
+	if (hwmon->power.n_sensors) {
+		i++;
+		hwmon->channel_infos[i].type = hwmon_power;
+		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+						sizeof(u32) * hwmon->power.n_sensors + 1,
+						GFP_KERNEL);
+		if (!hwmon->channel_infos[i].config)
+			return -ENOMEM;
+
+		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+						hwmon->power.n_sensors,
+						(HWMON_P_INPUT | HWMON_P_LABEL));
+
+		info_list[i] = &hwmon->channel_infos[i];
+	}
+
+	if (hwmon->fan.n_fans) {
+		i++;
+		hwmon->channel_infos[i].type = hwmon_fan;
+		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+						sizeof(u32) * hwmon->fan.n_fans + 1,
+						GFP_KERNEL);
+		if (!hwmon->channel_infos[i].config)
+			return -ENOMEM;
+
+		macsmc_hwmon_populate_fan_configs((u32 *)hwmon->channel_infos[i].config,
+							hwmon->fan.n_fans, &hwmon->fan);
+
+		info_list[i] = &hwmon->channel_infos[i];
+	}
+
+	return 0;
+}
+
+static int macsmc_hwmon_probe(struct platform_device *pdev)
+{
+	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
+	struct macsmc_hwmon *hwmon;
+	struct device_node *hwmon_node;
+	struct hwmon_channel_info **macsmc_chip_info = NULL;
+	int ret = 0;
+
+	hwmon = devm_kzalloc(&pdev->dev, sizeof(struct macsmc_hwmon), GFP_KERNEL);
+	if (!hwmon)
+		return -ENOMEM;
+
+	hwmon->dev = &pdev->dev;
+	hwmon->smc = smc;
+
+	hwmon_node = of_get_child_by_name(pdev->dev.parent->of_node, "hwmon");
+	if (!hwmon_node) {
+		dev_err(hwmon->dev, "macsmc-hwmon not found in devicetree!\n");
+		return -ENODEV;
+	}
+
+	ret = macsmc_hwmon_populate_sensors(hwmon, hwmon_node);
+	if (ret)
+		dev_info(hwmon->dev, "Could not populate keys!\n");
+
+	of_node_put(hwmon_node);
+
+	if (!hwmon->temp.n_sensors && !hwmon->volt.n_sensors &&
+		!hwmon->curr.n_sensors && !hwmon->power.n_sensors &&
+		!hwmon->fan.n_fans) {
+		dev_err(hwmon->dev, "No valid keys found of any supported type");
+		return -ENODEV;
+	}
+
+	macsmc_chip_info = devm_kzalloc(hwmon->dev,
+			sizeof(struct hwmon_channel_info *) * (1 + NUM_SENSOR_TYPES + 1),
+			GFP_KERNEL);
+	if (!macsmc_chip_info)
+		return -ENOMEM;
+
+	ret = macsmc_hwmon_create_infos(hwmon, macsmc_chip_info);
+	if (ret)
+		return ret;
+
+	macsmc_hwmon_info.info = (const struct hwmon_channel_info **)macsmc_chip_info;
+
+	hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
+						"macsmc_hwmon", hwmon,
+						&macsmc_hwmon_info, NULL);
+	if (IS_ERR(hwmon->hwmon_dev))
+		return dev_err_probe(hwmon->dev, PTR_ERR(hwmon->hwmon_dev),
+				     "Probing SMC hwmon device failed!\n");
+
+	dev_info(hwmon->dev, "Registered SMC hwmon device. Sensors:");
+	dev_info(hwmon->dev, "Temperature: %d, Voltage: %d, Current: %d, Power: %d, Fans: %d",
+		hwmon->temp.n_sensors, hwmon->volt.n_sensors,
+		hwmon->curr.n_sensors, hwmon->power.n_sensors, hwmon->fan.n_fans);
+
+	return 0;
+}
+
+static struct platform_driver macsmc_hwmon_driver = {
+	.probe = macsmc_hwmon_probe,
+	.driver = {
+		.name = "macsmc_hwmon",
+	},
+};
+module_platform_driver(macsmc_hwmon_driver);
+
+MODULE_DESCRIPTION("Apple Silicon SMC hwmon driver");
+MODULE_AUTHOR("James Calligeros <jcalligeros99@gmail.com>");
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_ALIAS("platform:macsmc_hwmon");
diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
index bb4bc9b112a98b..adef33e152530f 100644
--- a/drivers/platform/apple/smc_core.c
+++ b/drivers/platform/apple/smc_core.c
@@ -42,6 +42,9 @@ static const struct mfd_cell apple_smc_devs[] = {
 	{
 		.name = "macsmc-rtc",
 	},
+	{
+		.name = "macsmc_hwmon",
+	},
 };
 
 int apple_smc_read(struct apple_smc *smc, smc_key key, void *buf, size_t size)

From c64dd8dbb3984dc3aaae13aaebfb9409a5e33651 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 23 Jun 2024 11:50:06 +0200
Subject: [PATCH 0642/1027] hwmon: macsmc: Avoid global writable
 hwmon_chip_info

Should be squashed into "hwmon: add macsmc-hwmon driver"

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hwmon/macsmc-hwmon.c | 106 +++++++++++++++--------------------
 1 file changed, 44 insertions(+), 62 deletions(-)

diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
index 9ba584b5619081..f0961e5912eeb2 100644
--- a/drivers/hwmon/macsmc-hwmon.c
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -46,11 +46,13 @@ struct macsmc_hwmon_fan {
 };
 
 struct macsmc_hwmon_sensors {
+	struct hwmon_channel_info channel_info;
 	struct macsmc_hwmon_sensor *sensors;
 	u32 n_sensors;
 };
 
 struct macsmc_hwmon_fans {
+	struct hwmon_channel_info channel_info;
 	struct macsmc_hwmon_fan *fans;
 	u32 n_fans;
 };
@@ -59,8 +61,9 @@ struct macsmc_hwmon {
 	struct device *dev;
 	struct apple_smc *smc;
 	struct device *hwmon_dev;
+	struct hwmon_chip_info chip_info;
 	/* Chip + sensor types + NULL */
-	struct hwmon_channel_info channel_infos[1 + NUM_SENSOR_TYPES + 1];
+	const struct hwmon_channel_info *channel_infos[1 + NUM_SENSOR_TYPES + 1];
 	struct macsmc_hwmon_sensors temp;
 	struct macsmc_hwmon_sensors volt;
 	struct macsmc_hwmon_sensors curr;
@@ -226,11 +229,6 @@ static const struct hwmon_ops macsmc_hwmon_ops = {
 	.write = macsmc_hwmon_write,
 };
 
-static struct hwmon_chip_info macsmc_hwmon_info = {
-	.ops = &macsmc_hwmon_ops,
-	.info = NULL, /* see macsmc_hwmon_create_infos */
-};
-
 /*
  * Get the key metadata, including key data type, from the SMC.
  */
@@ -450,99 +448,89 @@ static void macsmc_hwmon_populate_fan_configs(u32 *configs,
 	configs[idx + 1] = 0;
 }
 
-static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon,
-				struct hwmon_channel_info **info_list)
+static const struct hwmon_channel_info * const macsmc_chip_channel_info =
+	HWMON_CHANNEL_INFO(chip, HWMON_C_REGISTER_TZ);
+
+static int macsmc_hwmon_create_infos(struct macsmc_hwmon *hwmon)
 {
 	int i = 0;
+	struct hwmon_channel_info *channel_info;
 
 	/* chip */
-	hwmon->channel_infos[i].type = hwmon_chip;
-	hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev, sizeof(u32) * 2,
-						GFP_KERNEL);
-	if (!hwmon->channel_infos[i].config)
-		return -ENOMEM;
-	macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config, 1,
-				HWMON_C_REGISTER_TZ);
-	info_list[i] = &hwmon->channel_infos[i];
+	hwmon->channel_infos[i++] = macsmc_chip_channel_info;
 
 	if (hwmon->temp.n_sensors) {
-		i++;
-		hwmon->channel_infos[i].type = hwmon_temp;
-		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+		channel_info = &hwmon->temp.channel_info;
+		channel_info->type = hwmon_temp;
+		channel_info->config = devm_kzalloc(hwmon->dev,
 						sizeof(u32) * hwmon->temp.n_sensors + 1,
 						GFP_KERNEL);
-		if (!hwmon->channel_infos[i].config)
+		if (!channel_info->config)
 			return -ENOMEM;
 
-		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+		macsmc_hwmon_populate_configs((u32 *)channel_info->config,
 						hwmon->temp.n_sensors,
 						(HWMON_T_INPUT | HWMON_T_LABEL));
-
-		info_list[i] = &hwmon->channel_infos[i];
-
+		hwmon->channel_infos[i++] = channel_info;
 	}
 
 	if (hwmon->volt.n_sensors) {
-		i++;
-		hwmon->channel_infos[i].type = hwmon_in;
-		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+		channel_info = &hwmon->volt.channel_info;
+		channel_info->type = hwmon_in;
+		channel_info->config = devm_kzalloc(hwmon->dev,
 						sizeof(u32) * hwmon->volt.n_sensors + 1,
 						GFP_KERNEL);
-		if (!hwmon->channel_infos[i].config)
+		if (!channel_info->config)
 			return -ENOMEM;
 
-		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+		macsmc_hwmon_populate_configs((u32 *)channel_info->config,
 						hwmon->volt.n_sensors,
 						(HWMON_I_INPUT | HWMON_I_LABEL));
-
-		info_list[i] = &hwmon->channel_infos[i];
+		hwmon->channel_infos[i++] = channel_info;
 	}
 
 	if (hwmon->curr.n_sensors) {
-		i++;
-		hwmon->channel_infos[i].type = hwmon_curr;
-		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+		channel_info = &hwmon->curr.channel_info;
+		channel_info->type = hwmon_curr;
+		channel_info->config = devm_kzalloc(hwmon->dev,
 						sizeof(u32) * hwmon->curr.n_sensors + 1,
 						GFP_KERNEL);
-		if (!hwmon->channel_infos[i].config)
+		if (!channel_info->config)
 			return -ENOMEM;
 
-		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+		macsmc_hwmon_populate_configs((u32 *)channel_info->config,
 						hwmon->curr.n_sensors,
 						(HWMON_C_INPUT | HWMON_C_LABEL));
-
-		info_list[i] = &hwmon->channel_infos[i];
+		hwmon->channel_infos[i++] = channel_info;
 	}
 
 	if (hwmon->power.n_sensors) {
-		i++;
-		hwmon->channel_infos[i].type = hwmon_power;
-		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+		channel_info = &hwmon->power.channel_info;
+		channel_info->type = hwmon_power;
+		channel_info->config = devm_kzalloc(hwmon->dev,
 						sizeof(u32) * hwmon->power.n_sensors + 1,
 						GFP_KERNEL);
-		if (!hwmon->channel_infos[i].config)
+		if (!channel_info->config)
 			return -ENOMEM;
 
-		macsmc_hwmon_populate_configs((u32 *)hwmon->channel_infos[i].config,
+		macsmc_hwmon_populate_configs((u32 *)channel_info->config,
 						hwmon->power.n_sensors,
 						(HWMON_P_INPUT | HWMON_P_LABEL));
-
-		info_list[i] = &hwmon->channel_infos[i];
+		hwmon->channel_infos[i++] = channel_info;
 	}
 
 	if (hwmon->fan.n_fans) {
-		i++;
-		hwmon->channel_infos[i].type = hwmon_fan;
-		hwmon->channel_infos[i].config = devm_kzalloc(hwmon->dev,
+		channel_info = &hwmon->fan.channel_info;
+		channel_info->type = hwmon_fan;
+		channel_info->config = devm_kzalloc(hwmon->dev,
 						sizeof(u32) * hwmon->fan.n_fans + 1,
 						GFP_KERNEL);
-		if (!hwmon->channel_infos[i].config)
+		if (!channel_info->config)
 			return -ENOMEM;
 
-		macsmc_hwmon_populate_fan_configs((u32 *)hwmon->channel_infos[i].config,
+		macsmc_hwmon_populate_fan_configs((u32 *)channel_info->config,
 							hwmon->fan.n_fans, &hwmon->fan);
-
-		info_list[i] = &hwmon->channel_infos[i];
+		hwmon->channel_infos[i++] = channel_info;
 	}
 
 	return 0;
@@ -553,7 +541,6 @@ static int macsmc_hwmon_probe(struct platform_device *pdev)
 	struct apple_smc *smc = dev_get_drvdata(pdev->dev.parent);
 	struct macsmc_hwmon *hwmon;
 	struct device_node *hwmon_node;
-	struct hwmon_channel_info **macsmc_chip_info = NULL;
 	int ret = 0;
 
 	hwmon = devm_kzalloc(&pdev->dev, sizeof(struct macsmc_hwmon), GFP_KERNEL);
@@ -582,21 +569,16 @@ static int macsmc_hwmon_probe(struct platform_device *pdev)
 		return -ENODEV;
 	}
 
-	macsmc_chip_info = devm_kzalloc(hwmon->dev,
-			sizeof(struct hwmon_channel_info *) * (1 + NUM_SENSOR_TYPES + 1),
-			GFP_KERNEL);
-	if (!macsmc_chip_info)
-		return -ENOMEM;
-
-	ret = macsmc_hwmon_create_infos(hwmon, macsmc_chip_info);
+	ret = macsmc_hwmon_create_infos(hwmon);
 	if (ret)
 		return ret;
 
-	macsmc_hwmon_info.info = (const struct hwmon_channel_info **)macsmc_chip_info;
+	hwmon->chip_info.ops = &macsmc_hwmon_ops;
+	hwmon->chip_info.info = (const struct hwmon_channel_info *const *)&hwmon->channel_infos;
 
 	hwmon->hwmon_dev = devm_hwmon_device_register_with_info(&pdev->dev,
 						"macsmc_hwmon", hwmon,
-						&macsmc_hwmon_info, NULL);
+						&hwmon->chip_info, NULL);
 	if (IS_ERR(hwmon->hwmon_dev))
 		return dev_err_probe(hwmon->dev, PTR_ERR(hwmon->hwmon_dev),
 				     "Probing SMC hwmon device failed!\n");

From f047162260b84703df5191862d00dd147b7af769 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 15 Aug 2024 05:17:57 +0900
Subject: [PATCH 0643/1027] power: supply: macsmc_power: Add CHLS charge
 thresholds

Since macOS Sequoia firmware, CHLS replaced CHWA and now allows an
arbitrary end charge threshold to be configured.

Prefer CHWA over CHLS since the SMC firmware from iBoot-10151.1.1
(macOS 14.0) is not compatible with our CHGLS usage. It was working
with the SMC firmware from iBoot-10151.121.1 (macOS 14.5).

Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/power/supply/macsmc_power.c | 60 ++++++++++++++++++++++-------
 1 file changed, 46 insertions(+), 14 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 88be3fe42ace39..348ac475c4b1bd 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -38,6 +38,7 @@ struct macsmc_power {
 	char serial_number[MAX_STRING_LENGTH];
 	char mfg_date[MAX_STRING_LENGTH];
 	bool has_chwa;
+	bool has_chls;
 	u8 num_cells;
 	int nominal_voltage_mv;
 
@@ -72,6 +73,7 @@ static struct macsmc_power *g_power;
 #define CHNC_NOCHG_CH0B_CH0K	BIT(15)
 #define CHNC_BATTERY_FULL_2	BIT(18)
 #define CHNC_BMS_BUSY		BIT(23)
+#define CHNC_CHLS_LIMIT		BIT(24)
 #define CHNC_NOAC_CH0J		BIT(53)
 #define CHNC_NOAC_CH0I		BIT(54)
 
@@ -89,7 +91,9 @@ static struct macsmc_power *g_power;
 #define ACSt_CAN_BOOT_AP	BIT(2)
 #define ACSt_CAN_BOOT_IBOOT	BIT(1)
 
-#define CHWA_FIXED_START_THRESHOLD	75
+#define CHWA_CHLS_FIXED_START_OFFSET	5
+#define CHLS_MIN_END_THRESHOLD		10
+#define CHLS_FORCE_DISCHARGE		0x100
 #define CHWA_FIXED_END_THRESHOLD	80
 #define CHWA_PROP_WRITE_THRESHOLD	95
 
@@ -183,7 +187,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 		u8 buic = 0;
 
 		if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
-			buic >= CHWA_FIXED_START_THRESHOLD)
+			buic >= (CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET))
 			chwa_limit = true;
 	}
 
@@ -465,14 +469,23 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = macsmc_battery_get_date(&power->mfg_date[4], &val->intval);
 		break;
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
-		ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
-		val->intval = ret == 1 ? CHWA_FIXED_START_THRESHOLD : 100;
-		ret = ret < 0 ? ret : 0;
-		break;
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
-		ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
-		val->intval = ret == 1 ? CHWA_FIXED_END_THRESHOLD : 100;
-		ret = ret < 0 ? ret : 0;
+		if (power->has_chls) {
+			ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
+			val->intval = vu16 & 0xff;
+			if (val->intval < CHLS_MIN_END_THRESHOLD || val->intval >= 100)
+				val->intval = 100;
+		}
+		else if (power->has_chwa) {
+			ret = apple_smc_read_flag(power->smc, SMC_KEY(CHWA));
+			val->intval = ret == 1 ? CHWA_FIXED_END_THRESHOLD : 100;
+			ret = ret < 0 ? ret : 0;
+		} else {
+			return -EINVAL;
+		}
+		if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD &&
+		    ret >= 0 && val->intval < 100 && val->intval >= CHLS_MIN_END_THRESHOLD)
+			val->intval -= CHWA_CHLS_FIXED_START_OFFSET;
 		break;
 	default:
 		return -EINVAL;
@@ -493,13 +506,25 @@ static int macsmc_battery_set_property(struct power_supply *psy,
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
 		/*
 		 * Ignore, we allow writes so userspace isn't confused but this is
-		 * not configurable independently, it always is 75 or 100 depending
-		 * on the end_threshold boolean setting.
+		 * not configurable independently, it always is end - 5 or 100 depending
+		 * on the end_threshold setting.
 		 */
 		return 0;
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
-		return apple_smc_write_flag(power->smc, SMC_KEY(CHWA),
-					    val->intval <= CHWA_PROP_WRITE_THRESHOLD);
+		if (power->has_chls) {
+			u16 kval = 0;
+			/* TODO: Make CHLS_FORCE_DISCHARGE configurable */
+			if (val->intval < CHLS_MIN_END_THRESHOLD)
+				kval = CHLS_FORCE_DISCHARGE | CHLS_MIN_END_THRESHOLD;
+			else if (val->intval < 100)
+				kval = CHLS_FORCE_DISCHARGE | (val->intval & 0xff);
+			return apple_smc_write_u16(power->smc, SMC_KEY(CHLS), kval);
+		} else if (power->has_chwa) {
+			return apple_smc_write_flag(power->smc, SMC_KEY(CHWA),
+						    val->intval <= CHWA_PROP_WRITE_THRESHOLD);
+		} else {
+			return -EINVAL;
+		}
 	default:
 		return -EINVAL;
 	}
@@ -515,7 +540,7 @@ static int macsmc_battery_property_is_writeable(struct power_supply *psy,
 		return true;
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
 	case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
-		return power->has_chwa;
+		return power->has_chwa || power->has_chls;
 	default:
 		return false;
 	}
@@ -750,6 +775,7 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	struct power_supply_config psy_cfg = {};
 	struct macsmc_power *power;
 	u32 val;
+	u16 vu16;
 	int ret;
 
 	power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
@@ -774,8 +800,14 @@ static int macsmc_power_probe(struct platform_device *pdev)
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0K), 0);
 	apple_smc_write_u8(power->smc, SMC_KEY(CH0B), 0);
 
+	/*
+	 * Prefer CHWA as the SMC firmware from iBoot-10151.1.1 is not compatible with
+	 * this CHLS usage.
+	 */
 	if (apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) >= 0) {
 		power->has_chwa = true;
+	} else if (apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16) >= 0) {
+		power->has_chls = true;
 	} else {
 		/* Remove the last 2 properties that control the charge threshold */
 		power->batt_desc.num_properties -= 2;

From d6db0fa1d82cd71abe82519adda20b182a44ff3a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 15 Aug 2024 05:21:01 +0900
Subject: [PATCH 0644/1027] power: supply: macsmc_power: Remove CSIL

Gone in Sequoia firmware.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/power/supply/macsmc_power.c | 6 ------
 1 file changed, 6 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index 348ac475c4b1bd..b9df5311c15daf 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -320,7 +320,6 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 	int ret = 0;
 	u8 vu8;
 	u16 vu16;
-	u32 vu32;
 	s16 vs16;
 	s32 vs32;
 	s64 vs64;
@@ -396,10 +395,6 @@ static int macsmc_battery_get_property(struct power_supply *psy,
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RC), &vu16);
 		val->intval = vu16 * 1000;
 		break;
-	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
-		ret = apple_smc_read_u32(power->smc, SMC_KEY(CSIL), &vu32);
-		val->intval = vu32 * 1000;
-		break;
 	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
 		ret = apple_smc_read_u16(power->smc, SMC_KEY(B0RI), &vu16);
 		val->intval = vu16 * 1000;
@@ -562,7 +557,6 @@ static const enum power_supply_property macsmc_battery_props[] = {
 	POWER_SUPPLY_PROP_VOLTAGE_MIN,
 	POWER_SUPPLY_PROP_VOLTAGE_MAX,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
-	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
 	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,

From 15147a8c81b653766bd2f1fb52d96931d04aec7d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 15 Aug 2024 00:08:34 +0200
Subject: [PATCH 0645/1027] power: supply: macsmc_power: Report not charging
 for CHLS thresholds

If a CHLS charge threshold is configured and the current SoC is above
the start threshold report a busy BMS as not charging.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/power/supply/macsmc_power.c | 27 +++++++++++++++++++--------
 1 file changed, 19 insertions(+), 8 deletions(-)

diff --git a/drivers/power/supply/macsmc_power.c b/drivers/power/supply/macsmc_power.c
index b9df5311c15daf..925b2ea7e6c049 100644
--- a/drivers/power/supply/macsmc_power.c
+++ b/drivers/power/supply/macsmc_power.c
@@ -132,7 +132,8 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 	u64 nocharge_flags;
 	u32 nopower_flags;
 	u16 ac_current;
-	bool chwa_limit = false;
+	int charge_limit = 0;
+	bool limited = false;
 	int ret;
 
 	/*
@@ -180,15 +181,25 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 		return POWER_SUPPLY_STATUS_FULL;
 
 	/*
-	 * If we have charge limits supported and enabled and the SoC is > 75%,
-	 * that means we are not charging for that reason (if not charging).
+	 * If we have charge limits supported and enabled and the SoC is above
+	 * the start threshold, that means we are not charging for that reason
+	 * (if not charging).
 	 */
-	if (power->has_chwa && apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) == 1) {
-		u8 buic = 0;
+	if (power->has_chls) {
+		u16 vu16;
+		ret = apple_smc_read_u16(power->smc, SMC_KEY(CHLS), &vu16);
+		if (ret == sizeof(vu16) && (vu16 & 0xff) >= CHLS_MIN_END_THRESHOLD)
+			charge_limit = (vu16 & 0xff) - CHWA_CHLS_FIXED_START_OFFSET;
+	} else if (power->has_chwa &&
+		   apple_smc_read_flag(power->smc, SMC_KEY(CHWA)) == 1) {
+		charge_limit = CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET;
+	}
 
+	if (charge_limit > 0) {
+		u8 buic = 0;
 		if (apple_smc_read_u8(power->smc, SMC_KEY(BUIC), &buic) >= 0 &&
-			buic >= (CHWA_FIXED_END_THRESHOLD - CHWA_CHLS_FIXED_START_OFFSET))
-			chwa_limit = true;
+			buic >= charge_limit)
+			limited = true;
 	}
 
 	/* If there are reasons we aren't charging... */
@@ -201,7 +212,7 @@ static int macsmc_battery_get_status(struct macsmc_power *power)
 		 * Or maybe the BMS is just busy doing something, if so call it charging anyway.
 		 * But CHWA limits show up as this, so exclude those.
 		 */
-		else if (nocharge_flags == CHNC_BMS_BUSY && !chwa_limit)
+		else if (nocharge_flags == CHNC_BMS_BUSY && !limited)
 			return POWER_SUPPLY_STATUS_CHARGING;
 		/* If we have other reasons we aren't charging, say we aren't */
 		else if (nocharge_flags)

From 5205920da77ae6bbce7c87ee49ff3151be935242 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 3 Oct 2023 09:35:02 +0200
Subject: [PATCH 0646/1027] platform/apple: smc: Add apple_smc_write_f32_scaled

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/platform/apple/smc_core.c | 41 +++++++++++++++++++++++++++++++
 include/linux/mfd/macsmc.h        |  1 +
 2 files changed, 42 insertions(+)

diff --git a/drivers/platform/apple/smc_core.c b/drivers/platform/apple/smc_core.c
index adef33e152530f..ae85ef2aad9d33 100644
--- a/drivers/platform/apple/smc_core.c
+++ b/drivers/platform/apple/smc_core.c
@@ -149,6 +149,47 @@ int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int sc
 }
 EXPORT_SYMBOL(apple_smc_read_f32_scaled);
 
+#define FLT_SIGN_MASK BIT(31)
+#define FLT_EXP_MASK GENMASK(30, 23)
+#define FLT_MANT_MASK GENMASK(22, 0)
+#define FLT_EXP_BIAS 127
+
+int apple_smc_write_f32_scaled(struct apple_smc *smc, smc_key key, int value,
+			       int scale)
+{
+	u64 val;
+	u32 fval = 0;
+	int exp = 0, neg;
+
+	val = abs(value);
+	neg = val != value;
+
+	if (scale > 1) {
+		val <<= 32;
+		exp = 32;
+		val /= scale;
+	} else if (scale < 1)
+		val *= -scale;
+
+	if (val) {
+		int msb = __fls(val) - exp;
+		if (msb > 23) {
+			val >>= msb - 23;
+			exp -= msb - 23;
+		} else if (msb < 23) {
+			val <<= 23 - msb;
+			exp += msb;
+		}
+
+		fval = FIELD_PREP(FLT_SIGN_MASK, neg) |
+		       FIELD_PREP(FLT_EXP_MASK, exp + FLT_EXP_BIAS) |
+		       FIELD_PREP(FLT_MANT_MASK, val);
+	}
+
+	return apple_smc_write_u32(smc, key, fval);
+}
+EXPORT_SYMBOL(apple_smc_write_f32_scaled);
+
 /*
  * ioft is a 48.16 fixed point type
  */
diff --git a/include/linux/mfd/macsmc.h b/include/linux/mfd/macsmc.h
index a63da99ed5ed2d..b4efba685d8cff 100644
--- a/include/linux/mfd/macsmc.h
+++ b/include/linux/mfd/macsmc.h
@@ -83,6 +83,7 @@ static inline int apple_smc_read_flag(struct apple_smc *smc, smc_key key)
 #define apple_smc_write_flag apple_smc_write_u8
 
 int apple_smc_read_f32_scaled(struct apple_smc *smc, smc_key key, int *p, int scale);
+int apple_smc_write_f32_scaled(struct apple_smc *smc, smc_key key, int p, int scale);
 int apple_smc_read_ioft_scaled(struct apple_smc *smc, smc_key key, u64 *p, int scale);
 
 int apple_smc_register_notifier(struct apple_smc *smc, struct notifier_block *n);

From f9945046233a882ae96d2892640bb0c7cdd74bea Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Mon, 5 Aug 2024 20:58:50 +1000
Subject: [PATCH 0647/1027] hwmon: macsmc: wire up manual fan control support

The SMC provides an interface for manually controlling the speeds of
any fans attached to it. Expose this via the standard hwmon interface.

Once a fan is in manual control, the SMC makes no attempts to save users
from themselves. It is possible to write arbitrary values outside of the
SMC's reported safe range. The driver therefore does its own sanity
checking.

Since we are unsure whether or not leaving the fans in manual mode can
cause damage to Apple Silicon devices, this functionality is gated
behind a very explicit and scary-sounding unsafe module parameter.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 drivers/hwmon/macsmc-hwmon.c | 116 ++++++++++++++++++++++++++++++++++-
 1 file changed, 115 insertions(+), 1 deletion(-)

diff --git a/drivers/hwmon/macsmc-hwmon.c b/drivers/hwmon/macsmc-hwmon.c
index f0961e5912eeb2..53f0264d88d079 100644
--- a/drivers/hwmon/macsmc-hwmon.c
+++ b/drivers/hwmon/macsmc-hwmon.c
@@ -30,6 +30,10 @@
 #define MAX_LABEL_LENGTH 32
 #define NUM_SENSOR_TYPES 5 /* temp, volt, current, power, fan */
 
+static bool melt_my_mac;
+module_param_unsafe(melt_my_mac, bool, 0644);
+MODULE_PARM_DESC(melt_my_mac, "Override the SMC to set your own fan speeds on supported machines");
+
 struct macsmc_hwmon_sensor {
 	struct apple_smc_key_info info;
 	smc_key macsmc_key;
@@ -41,8 +45,10 @@ struct macsmc_hwmon_fan {
 	struct macsmc_hwmon_sensor min;
 	struct macsmc_hwmon_sensor max;
 	struct macsmc_hwmon_sensor set;
+	struct macsmc_hwmon_sensor mode;
 	char label[MAX_LABEL_LENGTH];
 	u32 attrs;
+	bool manual;
 };
 
 struct macsmc_hwmon_sensors {
@@ -152,6 +158,21 @@ static int macsmc_hwmon_read_key(struct apple_smc *smc,
 	return 0;
 }
 
+static int macsmc_hwmon_write_key(struct apple_smc *smc,
+				  struct macsmc_hwmon_sensor *sensor, long val,
+				  int scale)
+{
+	switch (sensor->info.type_code) {
+	/* 32-bit IEEE 754 float */
+	case __SMC_KEY('f', 'l', 't', ' '):
+		return apple_smc_write_f32_scaled(smc, sensor->macsmc_key, val, scale);
+	case __SMC_KEY('u', 'i', '8', ' '):
+		return apple_smc_write_u8(smc, sensor->macsmc_key, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
 static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan, long *val)
 {
 	if (!(hwmon->fan.fans[chan].attrs & BIT(attr)))
@@ -175,6 +196,61 @@ static int macsmc_hwmon_read_fan(struct macsmc_hwmon *hwmon, u32 attr, int chan,
 	}
 }
 
+static int macsmc_hwmon_write_fan(struct device *dev, u32 attr, int channel, long val)
+{
+	struct macsmc_hwmon *hwmon = dev_get_drvdata(dev);
+	int ret = 0;
+	long min = 0;
+	long max = 0;
+
+	if (!melt_my_mac ||
+	    hwmon->fan.fans[channel].mode.macsmc_key == 0)
+		return -EOPNOTSUPP;
+
+	if ((channel >= hwmon->fan.n_fans) ||
+	    !(hwmon->fan.fans[channel].attrs & BIT(attr)) ||
+	    (attr != hwmon_fan_target))
+		return -EINVAL;
+
+	/*
+	 * The SMC does no sanity checks on requested fan speeds, so we need to.
+	 */
+	ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].min, 1, &min);
+	if (ret)
+		return ret;
+	ret = macsmc_hwmon_read_key(hwmon->smc, &hwmon->fan.fans[channel].max, 1, &max);
+	if (ret)
+		return ret;
+
+	if (val >= min && val <= max) {
+		if (!hwmon->fan.fans[channel].manual) {
+			/* Write 1 to mode key for manual control */
+			ret = macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].mode, 1, 1);
+			if (ret < 0)
+				return ret;
+
+			hwmon->fan.fans[channel].manual = true;
+			dev_info(dev, "Fan %d now under manual control! Set target speed to 0 for automatic control.\n",
+				channel + 1);
+		}
+		return macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].set, val, 1);
+	} else if (!val) {
+		if (hwmon->fan.fans[channel].manual) {
+			dev_info(dev, "Returning control of fan %d to SMC.\n", channel + 1);
+			ret = macsmc_hwmon_write_key(hwmon->smc, &hwmon->fan.fans[channel].mode, 0, 1);
+			if (ret < 0)
+				return ret;
+
+			hwmon->fan.fans[channel].manual = false;
+		}
+	} else {
+		dev_err(dev, "Requested fan speed %ld out of range [%ld, %ld]", val, min, max);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
 static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
 			u32 attr, int channel, long *val)
 {
@@ -212,13 +288,38 @@ static int macsmc_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
 static int macsmc_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
 			u32 attr, int channel, long val)
 {
-	return -EOPNOTSUPP;
+	switch (type) {
+	case hwmon_fan:
+		return macsmc_hwmon_write_fan(dev, attr, channel, val);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
+static umode_t macsmc_hwmon_fan_is_visible(const void *data, u32 attr, int channel)
+{
+	const struct macsmc_hwmon *hwmon = data;
+
+	if (channel >= hwmon->fan.n_fans)
+		return -EINVAL;
+
+	if (melt_my_mac && attr == hwmon_fan_target && hwmon->fan.fans[channel].mode.macsmc_key != 0)
+		return 0644;
+
+	return 0444;
 }
 
 static umode_t macsmc_hwmon_is_visible(const void *data,
 				enum hwmon_sensor_types type, u32 attr,
 				int channel)
 {
+	switch (type) {
+	case hwmon_fan:
+		return macsmc_hwmon_fan_is_visible(data, attr, channel);
+	default:
+		break;
+	}
+
 	return 0444;
 }
 
@@ -292,6 +393,7 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
 	const char *min;
 	const char *max;
 	const char *set;
+	const char *mode;
 	int ret = 0;
 
 	ret = of_property_read_string(fan_node, "apple,key-id", &now);
@@ -335,6 +437,18 @@ static int macsmc_hwmon_create_fan(struct device *dev, struct apple_smc *smc,
 			fan->attrs |= HWMON_F_TARGET;
 	}
 
+	ret = of_property_read_string(fan_node, "apple,fan-mode", &mode);
+	if (ret)
+		dev_warn(dev, "No fan mode key for %s", fan->label);
+	else {
+		ret = macsmc_hwmon_parse_key(dev, smc, &fan->mode, mode);
+		if (ret)
+			return ret;
+	}
+
+	/* Initialise fan control mode to automatic */
+	fan->manual = false;
+
 	return 0;
 }
 

From 55ecc7c995efde6b742fd975508c204580bb92cc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:43:17 +0900
Subject: [PATCH 0648/1027] mfd: Add a simple-mfd-spmi driver

This is the SPMI counterpart to simple-mfd-i2c. It merely exposes the
SPMI register address space as an MFD device, such that different
aspects of a device can be managed by separate drivers.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/mfd/Kconfig           | 28 ++++++++++++++++++++
 drivers/mfd/Makefile          |  1 +
 drivers/mfd/simple-mfd-spmi.c | 48 +++++++++++++++++++++++++++++++++++
 3 files changed, 77 insertions(+)
 create mode 100644 drivers/mfd/simple-mfd-spmi.c

diff --git a/drivers/mfd/Kconfig b/drivers/mfd/Kconfig
index bc8be2e593b6bc..a9e20255f4fb2d 100644
--- a/drivers/mfd/Kconfig
+++ b/drivers/mfd/Kconfig
@@ -52,6 +52,21 @@ config MFD_ACT8945A
 	  linear regulators, along with a complete ActivePath battery
 	  charger.
 
+config MFD_APPLE_SPMI_PMU
+	tristate "Apple SPMI PMUs"
+	depends on SPMI
+	depends on ARCH_APPLE || COMPILE_TEST
+	default ARCH_APPLE
+	select MFD_SIMPLE_MFD_SPMI
+	help
+	  Say yes here to enable support for Apple PMUs attached via the
+	  SPMI bus. These can be found on Apple devices such as Apple
+	  Silicon Macs.
+
+	  This driver itself only attaches to the core device, and relies
+	  on subsystem drivers for individual device functions. You must
+	  enable those for it to be useful.
+
 config MFD_SUN4I_GPADC
 	tristate "Allwinner sunxi platforms' GPADC MFD driver"
 	select MFD_CORE
@@ -1324,6 +1339,19 @@ config MFD_SIMPLE_MFD_I2C
 	  sub-devices represented by child nodes in Device Tree will be
 	  subsequently registered.
 
+config MFD_SIMPLE_MFD_SPMI
+	tristate
+	depends on SPMI
+	select MFD_CORE
+	select REGMAP_SPMI
+	help
+	  This driver creates a single register map with the intention for it
+	  to be shared by all sub-devices.
+
+	  Once the register map has been successfully initialised, any
+	  sub-devices represented by child nodes in Device Tree will be
+	  subsequently registered.
+
 config MFD_SL28CPLD
 	tristate "Kontron sl28cpld Board Management Controller"
 	depends on I2C
diff --git a/drivers/mfd/Makefile b/drivers/mfd/Makefile
index 02b651cd753521..213e1b647ca1ff 100644
--- a/drivers/mfd/Makefile
+++ b/drivers/mfd/Makefile
@@ -277,6 +277,7 @@ obj-$(CONFIG_MFD_QCOM_PM8008)	+= qcom-pm8008.o
 
 obj-$(CONFIG_SGI_MFD_IOC3)	+= ioc3.o
 obj-$(CONFIG_MFD_SIMPLE_MFD_I2C)	+= simple-mfd-i2c.o
+obj-$(CONFIG_MFD_SIMPLE_MFD_SPMI)	+= simple-mfd-spmi.o
 obj-$(CONFIG_MFD_SMPRO)		+= smpro-core.o
 
 obj-$(CONFIG_MFD_INTEL_M10_BMC_CORE)   += intel-m10-bmc-core.o
diff --git a/drivers/mfd/simple-mfd-spmi.c b/drivers/mfd/simple-mfd-spmi.c
new file mode 100644
index 00000000000000..8737fc22b932a5
--- /dev/null
+++ b/drivers/mfd/simple-mfd-spmi.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Simple MFD - SPMI
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/regmap.h>
+#include <linux/spmi.h>
+#include <linux/of_platform.h>
+
+static const struct regmap_config spmi_regmap_config = {
+	.reg_bits	= 16,
+	.val_bits	= 8,
+	.max_register	= 0xffff,
+};
+
+static int simple_spmi_probe(struct spmi_device *sdev)
+{
+	struct regmap *regmap;
+
+	regmap = devm_regmap_init_spmi_ext(sdev, &spmi_regmap_config);
+	if (IS_ERR(regmap))
+		return PTR_ERR(regmap);
+
+	return devm_of_platform_populate(&sdev->dev);
+}
+
+static const struct of_device_id simple_spmi_id_table[] = {
+	{ .compatible = "apple,spmi-pmu" },
+	{}
+};
+MODULE_DEVICE_TABLE(of, simple_spmi_id_table);
+
+static struct spmi_driver pmic_spmi_driver = {
+	.probe = simple_spmi_probe,
+	.driver = {
+		.name = "simple-mfd-spmi",
+		.of_match_table = simple_spmi_id_table,
+	},
+};
+module_spmi_driver(pmic_spmi_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_DESCRIPTION("Simple MFD - SPMI driver");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");

From a0ffbf97dd3c9f717081f1dd00101c8af9f960ae Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 15 Feb 2022 18:45:25 +0900
Subject: [PATCH 0649/1027] nvmem: Add spmi-mfd-nvmem driver

This driver exposes part of an SPMI MFD device as an NVMEM device.
It is intended to be used with e.g. PMUs/PMICs that are used to
hold power-management configuration, such as used on Apple Silicon
Macs.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/nvmem/Kconfig          | 13 +++++
 drivers/nvmem/Makefile         |  2 +
 drivers/nvmem/spmi-mfd-nvmem.c | 98 ++++++++++++++++++++++++++++++++++
 3 files changed, 113 insertions(+)
 create mode 100644 drivers/nvmem/spmi-mfd-nvmem.c

diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index 283134498fbc33..9317dfbc972077 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -299,6 +299,19 @@ config NVMEM_SNVS_LPGPR
 	  This driver can also be built as a module. If so, the module
 	  will be called nvmem-snvs-lpgpr.
 
+config NVMEM_SPMI_MFD
+	tristate "Generic SPMI MFD NVMEM"
+	depends on MFD_SIMPLE_MFD_SPMI || COMPILE_TEST
+	default ARCH_APPLE
+	help
+	  Say y here to build a generic driver to expose an SPMI MFD device
+	  as a NVMEM provider. This can be used for PMIC/PMU devices which
+	  are used to store power and RTC-related settings on certain
+	  platforms, such as Apple Silicon Macs.
+
+	  This driver can also be built as a module. If so, the module
+	  will be called nvmem-spmi-mfd.
+
 config NVMEM_SPMI_SDAM
 	tristate "SPMI SDAM Support"
 	depends on SPMI
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index cdd01fbf1313b5..04533daadd58ea 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -62,6 +62,8 @@ obj-$(CONFIG_NVMEM_SC27XX_EFUSE)	+= nvmem-sc27xx-efuse.o
 nvmem-sc27xx-efuse-y			:= sc27xx-efuse.o
 obj-$(CONFIG_NVMEM_SNVS_LPGPR)		+= nvmem_snvs_lpgpr.o
 nvmem_snvs_lpgpr-y			:= snvs_lpgpr.o
+obj-$(CONFIG_NVMEM_SPMI_MFD)		+= nvmem_spmi_mfd.o
+nvmem_spmi_mfd-y 			:= spmi-mfd-nvmem.o
 obj-$(CONFIG_NVMEM_SPMI_SDAM)		+= nvmem_qcom-spmi-sdam.o
 nvmem_qcom-spmi-sdam-y			+= qcom-spmi-sdam.o
 obj-$(CONFIG_NVMEM_SPRD_EFUSE)		+= nvmem_sprd_efuse.o
diff --git a/drivers/nvmem/spmi-mfd-nvmem.c b/drivers/nvmem/spmi-mfd-nvmem.c
new file mode 100644
index 00000000000000..e74ced47e68d85
--- /dev/null
+++ b/drivers/nvmem/spmi-mfd-nvmem.c
@@ -0,0 +1,98 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Generic SPMI MFD NVMEM driver
+ *
+ * Copyright The Asahi Linux Contributors
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+
+struct spmi_mfd_nvmem {
+	struct regmap *regmap;
+	unsigned int base;
+};
+
+static int spmi_mfd_nvmem_read(void *priv, unsigned int offset,
+                               void *val, size_t bytes)
+{
+	struct spmi_mfd_nvmem *nvmem = priv;
+
+        return regmap_bulk_read(nvmem->regmap, nvmem->base + offset, val, bytes);
+}
+
+static int spmi_mfd_nvmem_write(void *priv, unsigned int offset,
+                                void *val, size_t bytes)
+{
+	struct spmi_mfd_nvmem *nvmem = priv;
+
+	return regmap_bulk_write(nvmem->regmap, nvmem->base + offset, val, bytes);
+}
+
+static int spmi_mfd_nvmem_probe(struct platform_device *pdev)
+{
+	struct spmi_mfd_nvmem *nvmem;
+	const __be32 *addr;
+	int len;
+	struct nvmem_config nvmem_cfg = {
+		.dev = &pdev->dev,
+		.name = "spmi_mfd_nvmem",
+		.id = NVMEM_DEVID_AUTO,
+		.word_size = 1,
+		.stride = 1,
+		.reg_read = spmi_mfd_nvmem_read,
+		.reg_write = spmi_mfd_nvmem_write,
+	};
+
+	nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);
+	if (!nvmem)
+		return -ENOMEM;
+
+	nvmem_cfg.priv = nvmem;
+
+	nvmem->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+	if (!nvmem->regmap) {
+		dev_err(&pdev->dev, "Parent regmap unavailable.\n");
+		return -ENXIO;
+	}
+
+	addr = of_get_property(pdev->dev.of_node, "reg", &len);
+	if (!addr) {
+		dev_err(&pdev->dev, "no reg property\n");
+		return -EINVAL;
+	}
+	if (len != 2 * sizeof(u32)) {
+		dev_err(&pdev->dev, "invalid reg property\n");
+		return -EINVAL;
+	}
+
+	nvmem->base = be32_to_cpup(&addr[0]);
+	nvmem_cfg.size = be32_to_cpup(&addr[1]);
+
+	return PTR_ERR_OR_ZERO(devm_nvmem_register(&pdev->dev, &nvmem_cfg));
+}
+
+static const struct of_device_id spmi_mfd_nvmem_id_table[] = {
+	{ .compatible = "apple,spmi-pmu-nvmem" },
+	{ .compatible = "spmi-mfd-nvmem" },
+	{ },
+};
+MODULE_DEVICE_TABLE(of, spmi_mfd_nvmem_id_table);
+
+static struct platform_driver spmi_mfd_nvmem_driver = {
+	.probe = spmi_mfd_nvmem_probe,
+	.driver = {
+		.name = "spmi-mfd-nvmem",
+		.of_match_table	= spmi_mfd_nvmem_id_table,
+	},
+};
+
+module_platform_driver(spmi_mfd_nvmem_driver);
+
+MODULE_LICENSE("Dual MIT/GPL");
+MODULE_AUTHOR("Hector Martin <marcan@marcan.st>");
+MODULE_DESCRIPTION("SPMI MFD NVMEM driver");

From 10d9d1aa36c5c73f412672ddd70665dbb7a47e0e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 27 Feb 2024 22:36:52 +0100
Subject: [PATCH 0650/1027] nvmem: spmi-mfd-nvmem: use legacy fixed layout

Fixes probing of nvmem cells in v6.8-rc6

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/nvmem/spmi-mfd-nvmem.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/nvmem/spmi-mfd-nvmem.c b/drivers/nvmem/spmi-mfd-nvmem.c
index e74ced47e68d85..462f350640d1e4 100644
--- a/drivers/nvmem/spmi-mfd-nvmem.c
+++ b/drivers/nvmem/spmi-mfd-nvmem.c
@@ -46,6 +46,7 @@ static int spmi_mfd_nvmem_probe(struct platform_device *pdev)
 		.stride = 1,
 		.reg_read = spmi_mfd_nvmem_read,
 		.reg_write = spmi_mfd_nvmem_write,
+		.add_legacy_fixed_of_cells = true,
 	};
 
 	nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL);

From 38baa13adce4ad51eb53f0d18206d12db085daeb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 2 Oct 2024 21:52:20 +0200
Subject: [PATCH 0651/1027] phy: apple: atc: Add missing mutex_unlock in error
 case

Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Julia Lawall <julia.lawall@inria.fr>
Closes: https://lore.kernel.org/r/202410021851.sGmBSrDd-lkp@intel.com/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/phy/apple/atc.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/phy/apple/atc.c b/drivers/phy/apple/atc.c
index 6f66a5cd7fb92e..a91a917893957e 100644
--- a/drivers/phy/apple/atc.c
+++ b/drivers/phy/apple/atc.c
@@ -1624,6 +1624,7 @@ static int atcphy_usb3_power_on(struct phy *phy)
 
 	if (atcphy->mode != atcphy->target_mode) {
 		dev_err(atcphy->dev, "ATCPHY did not come up; won't allow dwc3 to come up.\n");
+		mutex_unlock(&atcphy->lock);
 		return -EINVAL;
 	}
 

From 68a933da63484ecd1949d55adc4f608c7fc757e0 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 2 Oct 2024 22:24:36 +0200
Subject: [PATCH 0652/1027] fixup! media: apple: isp: Working t602x and
 multiple formats and more fixes

Use __free() for scope based cleanup of device_node.

Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Julia Lawall <julia.lawall@inria.fr>
Closes: https://lore.kernel.org/r/202410021337.EQ74x7c7-lkp@intel.com/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 26 +++++++++-------------
 1 file changed, 11 insertions(+), 15 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index fdbe93ca14b6d6..b8ae23222de6a8 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -160,11 +160,12 @@ static int isp_of_read_coord(struct device *dev, struct device_node *np,
 static int apple_isp_init_presets(struct apple_isp *isp)
 {
 	struct device *dev = isp->dev;
-	struct device_node *np, *child;
+	struct device_node *child;
 	struct isp_preset *preset;
 	int err = 0;
 
-	np = of_get_child_by_name(dev->of_node, "sensor-presets");
+	struct device_node *np __free(device_node) =
+		of_get_child_by_name(dev->of_node, "sensor-presets");
 	if (!np) {
 		dev_err(dev, "failed to get DT node 'presets'\n");
 		return -EINVAL;
@@ -173,16 +174,13 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 	isp->num_presets = of_get_child_count(np);
 	if (!isp->num_presets) {
 		dev_err(dev, "no sensor presets found\n");
-		err = -EINVAL;
-		goto err;
+		return -EINVAL;
 	}
 
 	isp->presets = devm_kzalloc(
 		dev, sizeof(*isp->presets) * isp->num_presets, GFP_KERNEL);
-	if (!isp->presets) {
-		err = -ENOMEM;
-		goto err;
-	}
+	if (!isp->presets)
+		return -ENOMEM;
 
 	preset = isp->presets;
 	for_each_child_of_node(np, child) {
@@ -193,23 +191,23 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 		if (err) {
 			dev_err(dev, "no apple,config-index property\n");
 			of_node_put(child);
-			goto err;
+			return err;
 		}
 
 		err = isp_of_read_coord(dev, child, "apple,input-size",
 					&preset->input_dim);
 		if (err)
-			goto err;
+			return err;
 		err = isp_of_read_coord(dev, child, "apple,output-size",
 					&preset->output_dim);
 		if (err)
-			goto err;
+			return err;
 
 		err = of_property_read_u32_array(child, "apple,crop", xywh, 4);
 		if (err) {
 			dev_err(dev, "failed to read 'apple,crop' property\n");
 			of_node_put(child);
-			goto err;
+			return err;
 		}
 		preset->crop_offset.x = xywh[0];
 		preset->crop_offset.y = xywh[1];
@@ -219,9 +217,7 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 		preset++;
 	}
 
-err:
-	of_node_put(np);
-	return err;
+	return 0;
 }
 
 static const char * isp_fw2str(enum isp_firmware_version version)

From 992cb955b94be3ffad183370b859b2bd62d7d04c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 2 Oct 2024 22:27:36 +0200
Subject: [PATCH 0653/1027] fixup! media: apple: isp: Working t602x and
 multiple formats and more fixes

Use for_each_child_of_node_scoped() for scope based cleanup of child
device_node. Removes surprising but documented of_node_put() on error in
isp_of_read_coord().

Reported-by: kernel test robot <lkp@intel.com>
Reported-by: Julia Lawall <julia.lawall@inria.fr>
Closes: https://lore.kernel.org/r/202410021337.EQ74x7c7-lkp@intel.com/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/media/platform/apple/isp/isp-drv.c | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
index b8ae23222de6a8..848f7abd535a7f 100644
--- a/drivers/media/platform/apple/isp/isp-drv.c
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -138,7 +138,6 @@ static void apple_isp_free_iommu(struct apple_isp *isp)
 	drm_mm_takedown(&isp->iovad);
 }
 
-/* NOTE: of_node_put()s the OF node on failure. */
 static int isp_of_read_coord(struct device *dev, struct device_node *np,
 			     const char *prop, struct coord *val)
 {
@@ -148,7 +147,6 @@ static int isp_of_read_coord(struct device *dev, struct device_node *np,
 	ret = of_property_read_u32_array(np, prop, xy, 2);
 	if (ret) {
 		dev_err(dev, "failed to read '%s' property\n", prop);
-		of_node_put(np);
 		return ret;
 	}
 
@@ -160,7 +158,6 @@ static int isp_of_read_coord(struct device *dev, struct device_node *np,
 static int apple_isp_init_presets(struct apple_isp *isp)
 {
 	struct device *dev = isp->dev;
-	struct device_node *child;
 	struct isp_preset *preset;
 	int err = 0;
 
@@ -183,14 +180,13 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 		return -ENOMEM;
 
 	preset = isp->presets;
-	for_each_child_of_node(np, child) {
+	for_each_child_of_node_scoped(np, child) {
 		u32 xywh[4];
 
 		err = of_property_read_u32(child, "apple,config-index",
 					   &preset->index);
 		if (err) {
 			dev_err(dev, "no apple,config-index property\n");
-			of_node_put(child);
 			return err;
 		}
 
@@ -206,7 +202,6 @@ static int apple_isp_init_presets(struct apple_isp *isp)
 		err = of_property_read_u32_array(child, "apple,crop", xywh, 4);
 		if (err) {
 			dev_err(dev, "failed to read 'apple,crop' property\n");
-			of_node_put(child);
 			return err;
 		}
 		preset->crop_offset.x = xywh[0];

From 2064b9b9660db9463958b1ea4265badf376fccc1 Mon Sep 17 00:00:00 2001
From: Nick Chan <towinchenmi@gmail.com>
Date: Tue, 24 Sep 2024 23:55:29 +0800
Subject: [PATCH 0654/1027] gpu: drm: adp: Use 16-bit writes for brightness

Some summit panels like the ones found on the main display of iPhone X
has a max brightness of 2047 and needs 16-bit writes.

Tested-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Nick Chan <towinchenmi@gmail.com>
---
 drivers/gpu/drm/adp/panel-summit.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/adp/panel-summit.c b/drivers/gpu/drm/adp/panel-summit.c
index 633651fea92445..af3e158d373d1c 100644
--- a/drivers/gpu/drm/adp/panel-summit.c
+++ b/drivers/gpu/drm/adp/panel-summit.c
@@ -13,8 +13,8 @@ static int summit_set_brightness(struct device *dev)
 {
 	struct summit_data *panel = dev_get_drvdata(dev);
 	int level = backlight_get_brightness(panel->bl);
-	ssize_t err = mipi_dsi_dcs_write(panel->dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
-					 &level, 1);
+	int err = mipi_dsi_dcs_set_display_brightness(panel->dsi, level);
+
 	if (err < 0)
 		return err;
 	return 0;
@@ -70,10 +70,10 @@ static int summit_resume(struct device *dev)
 
 static int summit_suspend(struct device *dev)
 {
-	int level = 0;
 	struct summit_data *panel = dev_get_drvdata(dev);
-	ssize_t err = mipi_dsi_dcs_write(panel->dsi, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
-					 &level, 1);
+
+	int err = mipi_dsi_dcs_set_display_brightness(panel->dsi, 0);
+
 	if (err < 0)
 		return err;
 	return 0;

From 6055e4cd65bcb684cde8ebcb47cb611b62ff661b Mon Sep 17 00:00:00 2001
From: Nick Chan <towinchenmi@gmail.com>
Date: Tue, 24 Sep 2024 23:56:29 +0800
Subject: [PATCH 0655/1027] gpu: drm: adp: Allow max brightness to be set via
 device tree

The max brightness between different summit panels may be different
so allow the max brightness to be set via device tree.

Tested-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Nick Chan <towinchenmi@gmail.com>
---
 drivers/gpu/drm/adp/panel-summit.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/adp/panel-summit.c b/drivers/gpu/drm/adp/panel-summit.c
index af3e158d373d1c..354b534618ed64 100644
--- a/drivers/gpu/drm/adp/panel-summit.c
+++ b/drivers/gpu/drm/adp/panel-summit.c
@@ -46,7 +46,11 @@ static int summit_probe(struct mipi_dsi_device *dsi)
 
 	mipi_dsi_set_drvdata(dsi, panel);
 	panel->dsi = dsi;
-	props.max_brightness = 255;
+
+	int ret = device_property_read_u32(dev, "max-brightness", &props.max_brightness);
+
+	if (ret)
+		props.max_brightness = 255;
 	props.type = BACKLIGHT_RAW;
 
 	panel->bl = devm_backlight_device_register(dev, dev_name(dev),

From 960018148bf7ac93f6832e4363211b9499310871 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 3 Oct 2024 18:40:24 +0200
Subject: [PATCH 0656/1027] ASoC: cs42l84: Double tip and ring sense debounce
 timings

There are user reports of unreliable headset detection on Macbooks with
cs42l84 jack codecs like in #294. One way to reproduce this is to insert
a headset jack not fully but only until tip sense tiggers. The
headset/mic detection fails and is not retried once the jack is fully
inserted.
This might not be related to the issue the user sees. Another
possiblility is that the headset detection parameters / algorithm
differs from macos and doesn't detect this particular headset reliably.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 sound/soc/codecs/cs42l84.c | 8 ++++----
 sound/soc/codecs/cs42l84.h | 3 +++
 2 files changed, 7 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index d8fc7bdb89bb00..b48425b0967d99 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -906,14 +906,14 @@ static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
 			CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
 			CS42L84_RING_SENSE_CTL_RISETIME | CS42L84_RING_SENSE_CTL_FALLTIME,
 			CS42L84_RING_SENSE_CTL_INV | CS42L84_RING_SENSE_CTL_UNK1 |
-			FIELD_PREP(CS42L84_RING_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_125MS) |
-			FIELD_PREP(CS42L84_RING_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+			FIELD_PREP(CS42L84_RING_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_250MS) |
+			FIELD_PREP(CS42L84_RING_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_250MS));
 	regmap_update_bits(cs42l84->regmap, CS42L84_TIP_SENSE_CTL,
 			CS42L84_TIP_SENSE_CTL_INV |
 			CS42L84_TIP_SENSE_CTL_RISETIME | CS42L84_TIP_SENSE_CTL_FALLTIME,
 			CS42L84_TIP_SENSE_CTL_INV |
-			FIELD_PREP(CS42L84_TIP_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_500MS) |
-			FIELD_PREP(CS42L84_TIP_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_125MS));
+			FIELD_PREP(CS42L84_TIP_SENSE_CTL_RISETIME, CS42L84_DEBOUNCE_TIME_1000MS) |
+			FIELD_PREP(CS42L84_TIP_SENSE_CTL_FALLTIME, CS42L84_DEBOUNCE_TIME_250MS));
 	regmap_update_bits(cs42l84->regmap, CS42L84_MSM_BLOCK_EN3,
 			CS42L84_MSM_BLOCK_EN3_TR_SENSE,
 			CS42L84_MSM_BLOCK_EN3_TR_SENSE);
diff --git a/sound/soc/codecs/cs42l84.h b/sound/soc/codecs/cs42l84.h
index 35bd15e2ef17c9..264aac4f249ca4 100644
--- a/sound/soc/codecs/cs42l84.h
+++ b/sound/soc/codecs/cs42l84.h
@@ -208,7 +208,10 @@
 #define CS42L84_ASP_TX_CH2_WIDTH		0x506e
 
 #define CS42L84_DEBOUNCE_TIME_125MS	0b001
+#define CS42L84_DEBOUNCE_TIME_250MS	0b010
 #define CS42L84_DEBOUNCE_TIME_500MS	0b011
+#define CS42L84_DEBOUNCE_TIME_750MS	0b100
+#define CS42L84_DEBOUNCE_TIME_1000MS	0b101
 
 #define CS42L84_BOOT_TIME_US		3000
 #define CS42L84_CLOCK_SWITCH_DELAY_US	150

From 47958f976009fb210500887deb8830ae74397758 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 3 Oct 2024 19:30:23 +0200
Subject: [PATCH 0657/1027] HID: transport: spi: apple: Increase receive buffer
 size

The SPI receive buffer is passed directly to hid_input_report() if it
contains a complete report. It is then passed to hid_report_raw_event()
which computes the expected report size and memsets the "missing
trailing data up to HID_MAX_BUFFER_SIZE (16K) or
hid_ll_driver.max_buffer_size (if set) to zero.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/spi-hid/spi-hid-apple-core.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
index bfcae013cb5cc2..f729c3ba101193 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-core.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -395,6 +395,7 @@ static struct hid_ll_driver apple_hid_ll = {
 	.parse = &apple_ll_parse,
 	.raw_request = &apple_ll_raw_request,
 	.output_report = &apple_ll_output_report,
+	.max_buffer_size = SPIHID_MAX_INPUT_REPORT_SIZE,
 };
 
 static struct spihid_interface *spihid_get_iface(struct spihid_apple *spihid,
@@ -968,9 +969,15 @@ int spihid_apple_core_probe(struct spi_device *spi, struct spihid_apple_ops *ops
 	// init spi
 	spi_set_drvdata(spi, spihid);
 
-	/* allocate SPI buffers */
+	/*
+	 * allocate SPI buffers
+	 * Overallocate the receice buffer since it passed directly into
+	 * hid_input_report / hid_report_raw_event. The later expects the buffer
+	 * to be HID_MAX_BUFFER_SIZE (16k) or hid_ll_driver.max_buffer_size if
+	 * set.
+	 */
 	spihid->rx_buf = devm_kmalloc(
-		&spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
+		&spi->dev, SPIHID_MAX_INPUT_REPORT_SIZE, GFP_KERNEL);
 	spihid->tx_buf = devm_kmalloc(
 		&spi->dev, sizeof(struct spihid_transfer_packet), GFP_KERNEL);
 	spihid->status_buf = devm_kmalloc(

From 2b88946db08db0a983057cd72b4d98d140817c40 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 3 Oct 2024 19:41:32 +0200
Subject: [PATCH 0658/1027] fixup! WIP: HID: transport: spi: add Apple SPI
 transport

HID: transport: spi: apple: verify payload size

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/hid/spi-hid/spi-hid-apple-core.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/drivers/hid/spi-hid/spi-hid-apple-core.c b/drivers/hid/spi-hid/spi-hid-apple-core.c
index f729c3ba101193..ee561d80b4a0d1 100644
--- a/drivers/hid/spi-hid/spi-hid-apple-core.c
+++ b/drivers/hid/spi-hid/spi-hid-apple-core.c
@@ -681,14 +681,17 @@ static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
 	struct device *dev = &spihid->spidev->dev;
 	struct spihid_msg_hdr *hdr;
 	bool handled = false;
+	size_t payload_len;
 	u8 *payload;
 
 	if (!spihid_verify_msg(spihid, data, length))
 		return;
 
 	hdr = (struct spihid_msg_hdr *)data;
+	payload_len = le16_to_cpu(hdr->length);
 
-	if (hdr->length == 0)
+	if (payload_len == 0 ||
+		(payload_len + sizeof(struct spihid_msg_hdr) + 2) > length)
 		return;
 
 	payload = data + sizeof(struct spihid_msg_hdr);
@@ -696,11 +699,11 @@ static void spihid_process_message(struct spihid_apple *spihid, u8 *data,
 	switch (flags) {
 	case SPIHID_READ_PACKET:
 		handled = spihid_process_input_report(spihid, device, hdr,
-						      payload, le16_to_cpu(hdr->length));
+						      payload, payload_len);
 		break;
 	case SPIHID_WRITE_PACKET:
 		handled = spihid_process_response(spihid, device, hdr, payload,
-						  le16_to_cpu(hdr->length));
+						  payload_len);
 		break;
 	default:
 		break;

From 16f85f841f39abe0fedbc37916da752e86616fea Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 6 Oct 2024 11:15:26 +0200
Subject: [PATCH 0659/1027] fixup! ASoC: tas2770: Export 'die_temp' to sysfs

From Johannes Weiner (https://github.com/AsahiLinux/linux/issues/331)

Signed-off-by: Janne Grunau <j@jannau.net>
---
 sound/soc/codecs/tas2770.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/sound/soc/codecs/tas2770.c b/sound/soc/codecs/tas2770.c
index eb4be94c6ffcd6..038ff21f421cad 100644
--- a/sound/soc/codecs/tas2770.c
+++ b/sound/soc/codecs/tas2770.c
@@ -656,6 +656,7 @@ static void tas2770_codec_remove(struct snd_soc_component *component)
 {
 	struct tas2770_priv *tas2770 = snd_soc_component_get_drvdata(component);
 
+	sysfs_remove_groups(&component->dev->kobj, tas2770_sysfs_groups);
 	regulator_disable(tas2770->sdz_reg);
 }
 

From 42f990b1aa22fe315afec40990db5e47a2a6abf8 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 6 Oct 2024 11:13:53 +1000
Subject: [PATCH 0660/1027] ASoC: cs42l84: halve headset detection delay

200ms of sleep was excessive for the standard headset detection
routine. Halve this to 100ms.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/codecs/cs42l84.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index b48425b0967d99..e8824f329f96b3 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -724,7 +724,7 @@ static void cs42l84_detect_hs(struct cs42l84_private *cs42l84)
 		CS42L84_MISC_DET_CTL_PDN_MIC_LVL_DET, 0);
 
 	/* TODO: Optimize */
-	msleep(100);
+	msleep(50);
 
 	/* Connect HSBIAS in CTIA wiring */
 	/* TODO: Should likely be subject of detection */
@@ -745,7 +745,7 @@ static void cs42l84_detect_hs(struct cs42l84_private *cs42l84)
 		FIELD_PREP(CS42L84_MISC_DET_CTL_DETECT_MODE, 3));
 
 	/* TODO: Optimize */
-	msleep(100);
+	msleep(50);
 
 	regmap_read(cs42l84->regmap, CS42L84_HS_DET_STATUS2, &reg);
 	regmap_update_bits(cs42l84->regmap,

From 777d32a0eaf1233b27f2d51dd68a06489bb90596 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 6 Oct 2024 11:19:35 +1000
Subject: [PATCH 0661/1027] ASoC: cs42l84: rearrange tip sense interrupt
 handler

Check and set the plug state only once so that we can guard the
entire handler.

This allows us to more easily add ring sense interrupt handling.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/codecs/cs42l84.c | 25 +++++++++----------------
 1 file changed, 9 insertions(+), 16 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index e8824f329f96b3..b4591bb61ae484 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -834,10 +834,10 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
 		      CS42L84_TS_PLUG_SHIFT;
 
-		switch (current_plug_status) {
-		case CS42L84_PLUG:
-			if (cs42l84->plug_state != CS42L84_PLUG) {
-				cs42l84->plug_state = CS42L84_PLUG;
+		if (current_plug_status != cs42l84->plug_state) {
+			cs42l84->plug_state = current_plug_status;
+			switch (current_plug_status) {
+			case CS42L84_PLUG:
 				dev_dbg(cs42l84->dev, "Plug event\n");
 
 				cs42l84_detect_hs(cs42l84);
@@ -858,26 +858,19 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 					cs42l84->plug_state = CS42L84_UNPLUG;
 					cs42l84_revert_hs(cs42l84);
 				}
-			}
-			break;
-
-		case CS42L84_UNPLUG:
-			if (cs42l84->plug_state != CS42L84_UNPLUG) {
-				cs42l84->plug_state = CS42L84_UNPLUG;
+				break;
+			case CS42L84_UNPLUG:
 				dev_dbg(cs42l84->dev, "Unplug event\n");
 
 				cs42l84_revert_hs(cs42l84);
 				cs42l84->hs_type = 0;
 				snd_soc_jack_report(cs42l84->jack, 0,
 						    SND_JACK_HEADSET);
+				break;
+			default:
+				break;
 			}
-			break;
-
-		default:
-			if (cs42l84->plug_state != CS42L84_TRANS)
-				cs42l84->plug_state = CS42L84_TRANS;
 		}
-	}
 	mutex_unlock(&cs42l84->irq_lock);
 
 	return IRQ_HANDLED;

From dd6afc7252657ce6475381d070c8760758dfa4c2 Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 6 Oct 2024 11:26:03 +1000
Subject: [PATCH 0662/1027] ASoC: cs42l84: allow runtime mask/unmask of
 interrupts

Make cs42L84_set_interrupt_masks accept a value for the interrupt
mask register so that we can enable/disable ring sense interrupts
at runtime.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/codecs/cs42l84.c | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index b4591bb61ae484..2afe1ed9cc6128 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -876,12 +876,13 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 	return IRQ_HANDLED;
 }
 
-static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84)
+static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84,
+					unsigned int val)
 {
 	regmap_update_bits(cs42l84->regmap, CS42L84_TSRS_PLUG_INT_MASK,
 			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG |
 			CS42L84_TS_PLUG | CS42L84_TS_UNPLUG,
-			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG);
+			val);
 }
 
 static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
@@ -1020,8 +1021,8 @@ static int cs42l84_i2c_probe(struct i2c_client *i2c_client)
 	/* Setup plug detection */
 	cs42l84_setup_plug_detect(cs42l84);
 
-	/* Mask/Unmask Interrupts */
-	cs42l84_set_interrupt_masks(cs42l84);
+	/* Mask ring sense interrupts */
+	cs42l84_set_interrupt_masks(cs42l84, CS42L84_RS_PLUG | CS42L84_RS_UNPLUG);
 
 	/* Register codec for machine driver */
 	ret = devm_snd_soc_register_component(&i2c_client->dev,

From efb959d4a6cd9f5627d9994e244e22f750c6f0fc Mon Sep 17 00:00:00 2001
From: James Calligeros <jcalligeros99@gmail.com>
Date: Sun, 6 Oct 2024 11:45:38 +1000
Subject: [PATCH 0663/1027] ASoC: cs42l84: handle ring sense interrupts

Some jacks integrated on devices with this codec, such as certain
Apple Silicon Macs, have quite trigger-happy tip sense switches that
cause a tip sense IRQ before the plug is fully seated. If users are
unfortunate with their timing, this can lead to headsets being detected
as mic-less headphones among other issues with the codec's device
detection routines.

Introduce some rudimentary ring sense interrupt handling so that we
can re-trigger the codec's detection routines when we are certain
that the plug is fully seated.

Signed-off-by: James Calligeros <jcalligeros99@gmail.com>
---
 sound/soc/codecs/cs42l84.c | 72 +++++++++++++++++++++++++++-----------
 1 file changed, 52 insertions(+), 20 deletions(-)

diff --git a/sound/soc/codecs/cs42l84.c b/sound/soc/codecs/cs42l84.c
index 2afe1ed9cc6128..51721e2ec04e7b 100644
--- a/sound/soc/codecs/cs42l84.c
+++ b/sound/soc/codecs/cs42l84.c
@@ -46,7 +46,8 @@ struct cs42l84_private {
 	struct gpio_desc *reset_gpio;
 	struct snd_soc_jack *jack;
 	struct mutex irq_lock;
-	u8 plug_state;
+	u8 tip_state;
+	u8 ring_state;
 	int pll_config;
 	int bclk;
 	u8 pll_mclk_f;
@@ -808,13 +809,23 @@ static void cs42l84_revert_hs(struct cs42l84_private *cs42l84)
 		FIELD_PREP(CS42L84_HS_DET_CTL2_SET, 2));
 }
 
+static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84,
+					unsigned int val)
+{
+	regmap_update_bits(cs42l84->regmap, CS42L84_TSRS_PLUG_INT_MASK,
+			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG |
+			CS42L84_TS_PLUG | CS42L84_TS_UNPLUG,
+			val);
+}
+
 static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 {
 	struct cs42l84_private *cs42l84 = (struct cs42l84_private *)data;
 	unsigned int stickies[1];
 	unsigned int masks[1];
 	unsigned int reg;
-	u8 current_plug_status;
+	u8 current_tip_state;
+	u8 current_ring_state;
 	int i;
 
 	mutex_lock(&cs42l84->irq_lock);
@@ -828,15 +839,22 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 				irq_params_table[i].mask;
 	}
 
+	/* When handling plug sene IRQs, we only care about EITHER tip OR ring.
+	 * Ring is useless on remove, and is only useful on insert for
+	 * detecting if the plug state has changed AFTER we have handled the
+	 * tip sense IRQ, e.g. if the plug was not fully seated within the tip
+	 * sense debounce time. */
+
 	if ((~masks[0]) & irq_params_table[0].mask) {
 		regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
-		current_plug_status = (((char) reg) &
+
+		current_tip_state = (((char) reg) &
 		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
 		      CS42L84_TS_PLUG_SHIFT;
 
-		if (current_plug_status != cs42l84->plug_state) {
-			cs42l84->plug_state = current_plug_status;
-			switch (current_plug_status) {
+		if (current_tip_state != cs42l84->tip_state) {
+			cs42l84->tip_state = current_tip_state;
+			switch (current_tip_state) {
 			case CS42L84_PLUG:
 				dev_dbg(cs42l84->dev, "Plug event\n");
 
@@ -850,41 +868,55 @@ static irqreturn_t cs42l84_irq_thread(int irq, void *data)
 				 * was disconnected at any point during the detection procedure.
 				 */
 				regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
-				current_plug_status = (((char) reg) &
+				current_tip_state = (((char) reg) &
 				      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
 				      CS42L84_TS_PLUG_SHIFT;
-				if (current_plug_status != CS42L84_PLUG) {
+				if (current_tip_state != CS42L84_PLUG) {
 					dev_dbg(cs42l84->dev, "Wobbly connection, detection invalidated\n");
-					cs42l84->plug_state = CS42L84_UNPLUG;
+					cs42l84->tip_state = CS42L84_UNPLUG;
 					cs42l84_revert_hs(cs42l84);
 				}
+
+				/* Unmask ring sense interrupts */
+				cs42l84_set_interrupt_masks(cs42l84, 0);
 				break;
 			case CS42L84_UNPLUG:
+				cs42l84->ring_state = CS42L84_UNPLUG;
 				dev_dbg(cs42l84->dev, "Unplug event\n");
 
 				cs42l84_revert_hs(cs42l84);
 				cs42l84->hs_type = 0;
 				snd_soc_jack_report(cs42l84->jack, 0,
 						    SND_JACK_HEADSET);
+
+				/* Mask ring sense interrupts */
+				cs42l84_set_interrupt_masks(cs42l84, CS42L84_RS_PLUG | CS42L84_RS_UNPLUG);
 				break;
 			default:
-				break;
+				cs42l84->ring_state = CS42L84_TRANS;
 			}
+
+			mutex_unlock(&cs42l84->irq_lock);
+			return IRQ_HANDLED;
 		}
+
+		/* Tip state didn't change, we must've got a ring sense IRQ */
+		current_ring_state = (((char) reg) &
+		      (CS42L84_RS_PLUG | CS42L84_RS_UNPLUG)) >>
+		      CS42L84_RS_PLUG_SHIFT;
+
+		if (current_ring_state != cs42l84->ring_state) {
+			cs42l84->ring_state = current_ring_state;
+			if (current_ring_state == CS42L84_PLUG)
+				cs42l84_detect_hs(cs42l84);
+		}
+	}
+
 	mutex_unlock(&cs42l84->irq_lock);
 
 	return IRQ_HANDLED;
 }
 
-static void cs42l84_set_interrupt_masks(struct cs42l84_private *cs42l84,
-					unsigned int val)
-{
-	regmap_update_bits(cs42l84->regmap, CS42L84_TSRS_PLUG_INT_MASK,
-			CS42L84_RS_PLUG | CS42L84_RS_UNPLUG |
-			CS42L84_TS_PLUG | CS42L84_TS_UNPLUG,
-			val);
-}
-
 static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
 {
 	unsigned int reg;
@@ -914,7 +946,7 @@ static void cs42l84_setup_plug_detect(struct cs42l84_private *cs42l84)
 
 	/* Save the initial status of the tip sense */
 	regmap_read(cs42l84->regmap, CS42L84_TSRS_PLUG_STATUS, &reg);
-	cs42l84->plug_state = (((char) reg) &
+	cs42l84->tip_state = (((char) reg) &
 		      (CS42L84_TS_PLUG | CS42L84_TS_UNPLUG)) >>
 		      CS42L84_TS_PLUG_SHIFT;
 

From 8ebc28aba3457743e0fb58933938364313d9e577 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 21:59:24 +0900
Subject: [PATCH 0664/1027] wifi: brcmfmac: Add missing shared area defines to
 pcie.c

There are many newer flags and extended shared area fields used by newer
firmwares that are not yet defined. Add them for future usage.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 58 +++++++++++++++++++
 1 file changed, 58 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index ce482a3877e90a..7548ab5b3ed602 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -215,11 +215,64 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 #define BRCMF_PCIE_SHARED_VERSION_MASK		0x00FF
 #define BRCMF_PCIE_SHARED_DMA_INDEX		0x10000
 #define BRCMF_PCIE_SHARED_DMA_2B_IDX		0x100000
+#define BRCMF_PCIE_SHARED_USE_MAILBOX		0x2000000
+#define BRCMF_PCIE_SHARED_TIMESTAMP_DB0		0x8000000
 #define BRCMF_PCIE_SHARED_HOSTRDY_DB1		0x10000000
+#define BRCMF_PCIE_SHARED_NO_OOB_DW		0x20000000
+#define BRCMF_PCIE_SHARED_INBAND_DS		0x40000000
+#define BRCMF_PCIE_SHARED_DAR			0x80000000
+
+#define BRCMF_PCIE_SHARED2_EXTENDED_TRAP_DATA	0x1
+#define BRCMF_PCIE_SHARED2_TXSTATUS_METADATA	0x2
+#define BRCMF_PCIE_SHARED2_BT_LOGGING		0x4
+#define BRCMF_PCIE_SHARED2_SNAPSHOT_UPLOAD	0x8
+#define BRCMF_PCIE_SHARED2_SUBMIT_COUNT_WAR	0x10
+#define BRCMF_PCIE_SHARED2_FAST_DELETE_RING	0x20
+#define BRCMF_PCIE_SHARED2_EVTBUF_MAX_MASK	0xC0
+#define BRCMF_PCIE_SHARED2_PKT_TX_STATUS	0x100
+#define BRCMF_PCIE_SHARED2_FW_SMALL_MEMDUMP	0x200
+#define BRCMF_PCIE_SHARED2_FW_HC_ON_TRAP	0x400
+#define BRCMF_PCIE_SHARED2_HSCB			0x800
+#define BRCMF_PCIE_SHARED2_EDL_RING		0x1000
+#define BRCMF_PCIE_SHARED2_DEBUG_BUF_DEST	0x2000
+#define BRCMF_PCIE_SHARED2_PCIE_ENUM_RESET_FLR	0x4000
+#define BRCMF_PCIE_SHARED2_PKT_TIMESTAMP	0x8000
+#define BRCMF_PCIE_SHARED2_HP2P			0x10000
+#define BRCMF_PCIE_SHARED2_HWA			0x20000
+#define BRCMF_PCIE_SHARED2_TRAP_ON_HOST_DB7	0x40000
+#define BRCMF_PCIE_SHARED2_DURATION_SCALE	0x100000
+#define BRCMF_PCIE_SHARED2_D2H_D11_TX_STATUS	0x40000000
+#define BRCMF_PCIE_SHARED2_H2D_D11_TX_STATUS	0x80000000
 
 #define BRCMF_PCIE_FLAGS_HTOD_SPLIT		0x4000
 #define BRCMF_PCIE_FLAGS_DTOH_SPLIT		0x8000
 
+#define BRCMF_HOSTCAP_PCIEAPI_VERSION_MASK	0x000000FF
+#define BRCMF_HOSTCAP_H2D_VALID_PHASE		0x00000100
+#define BRCMF_HOSTCAP_H2D_ENABLE_TRAP_ON_BADPHASE	0x00000200
+#define BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY	0x400
+#define BRCMF_HOSTCAP_DB0_TIMESTAMP		0x800
+#define BRCMF_HOSTCAP_DS_NO_OOB_DW		0x1000
+#define BRCMF_HOSTCAP_DS_INBAND_DW		0x2000
+#define BRCMF_HOSTCAP_H2D_IDMA			0x4000
+#define BRCMF_HOSTCAP_H2D_IFRM			0x8000
+#define BRCMF_HOSTCAP_H2D_DAR			0x10000
+#define BRCMF_HOSTCAP_EXTENDED_TRAP_DATA	0x20000
+#define BRCMF_HOSTCAP_TXSTATUS_METADATA		0x40000
+#define BRCMF_HOSTCAP_BT_LOGGING		0x80000
+#define BRCMF_HOSTCAP_SNAPSHOT_UPLOAD		0x100000
+#define BRCMF_HOSTCAP_FAST_DELETE_RING		0x200000
+#define BRCMF_HOSTCAP_PKT_TXSTATUS		0x400000
+#define BRCMF_HOSTCAP_UR_FW_NO_TRAP		0x800000
+#define BRCMF_HOSTCAP_HSCB			0x2000000
+#define BRCMF_HOSTCAP_EXT_TRAP_DBGBUF		0x4000000
+#define BRCMF_HOSTCAP_EDL_RING			0x10000000
+#define BRCMF_HOSTCAP_PKT_TIMESTAMP		0x20000000
+#define BRCMF_HOSTCAP_PKT_HP2P			0x40000000
+#define BRCMF_HOSTCAP_HWA			0x80000000
+#define BRCMF_HOSTCAP2_DURATION_SCALE_MASK	0x3F
+
+#define BRCMF_SHARED_FLAGS_OFFSET		0
 #define BRCMF_SHARED_MAX_RXBUFPOST_OFFSET	34
 #define BRCMF_SHARED_RING_BASE_OFFSET		52
 #define BRCMF_SHARED_RX_DATAOFFSET_OFFSET	36
@@ -231,6 +284,11 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 #define BRCMF_SHARED_DMA_SCRATCH_ADDR_OFFSET	56
 #define BRCMF_SHARED_DMA_RINGUPD_LEN_OFFSET	64
 #define BRCMF_SHARED_DMA_RINGUPD_ADDR_OFFSET	68
+#define BRCMF_SHARED_FLAGS2_OFFSET		80
+#define BRCMF_SHARED_HOST_CAP_OFFSET		84
+#define BRCMF_SHARED_FLAGS3_OFFSET		108
+#define BRCMF_SHARED_HOST_CAP2_OFFSET		112
+#define BRCMF_SHARED_HOST_CAP3_OFFSET		116
 
 #define BRCMF_RING_H2D_RING_COUNT_OFFSET	0
 #define BRCMF_RING_D2H_RING_COUNT_OFFSET	1

From 1cc1ece93de0d037ac7fa1915d3e4b945cb56852 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 22:02:10 +0900
Subject: [PATCH 0665/1027] wifi: brcmfmac: Handle PCIe MSI properly

On newer firmwares under at least certain conditions, MSI mode does not
leave interrupt flags set (they are cleared by the firmware). Handle
this by always checking for ring data when we get an MSI, regardless of
whether any IRQ flags were set.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 21 +++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 7548ab5b3ed602..6bf01c8548e08a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -401,6 +401,7 @@ struct brcmf_pciedev_info {
 	wait_queue_head_t mbdata_resp_wait;
 	bool mbdata_completed;
 	bool irq_allocated;
+	bool have_msi;
 	bool wowl_enabled;
 	u8 dma_idx_sz;
 	void *idxbuf;
@@ -985,6 +986,11 @@ static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg)
 		brcmf_dbg(PCIE, "Enter\n");
 		return IRQ_WAKE_THREAD;
 	}
+
+	/* mailboxint is cleared by the firmware in MSI mode */
+	if (devinfo->have_msi)
+		return IRQ_WAKE_THREAD;
+
 	return IRQ_NONE;
 }
 
@@ -1002,12 +1008,12 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg)
 				       status);
 		if (status & devinfo->reginfo->int_fn0)
 			brcmf_pcie_handle_mb_data(devinfo);
-		if (status & devinfo->reginfo->int_d2h_db) {
-			if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
-				brcmf_proto_msgbuf_rx_trigger(
-							&devinfo->pdev->dev);
-		}
 	}
+	if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) {
+		if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
+			brcmf_proto_msgbuf_rx_trigger(&devinfo->pdev->dev);
+	}
+
 	brcmf_pcie_bus_console_read(devinfo, false);
 	if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
 		brcmf_pcie_intr_enable(devinfo);
@@ -1025,7 +1031,10 @@ static int brcmf_pcie_request_irq(struct brcmf_pciedev_info *devinfo)
 
 	brcmf_dbg(PCIE, "Enter\n");
 
-	pci_enable_msi(pdev);
+	devinfo->have_msi = pci_enable_msi(pdev) >= 0;
+	if (devinfo->have_msi)
+		brcmf_dbg(PCIE, "MSI enabled\n");
+
 	if (request_threaded_irq(pdev->irq, brcmf_pcie_quick_check_isr,
 				 brcmf_pcie_isr_thread, IRQF_SHARED,
 				 "brcmf_pcie_intr", devinfo)) {

From f63efc7fe28430c5d7f2364c31dcaa109a8b8539 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 22:06:40 +0900
Subject: [PATCH 0666/1027] wifi: brcmfmac: Fix logic for deciding which
 doorbell registers to use

While the other >PCIe r64 registers (which are apparently called DAR
registers) are always used on newer revisions, which doorbell registers
should be used depends only on flags set by firmware. Take them out of
the reginfo struct and check the flag to decide instead.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 20 +++++++++----------
 1 file changed, 10 insertions(+), 10 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 6bf01c8548e08a..9846af00c5cbb0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -487,8 +487,6 @@ struct brcmf_pcie_reginfo {
 	u32 intmask;
 	u32 mailboxint;
 	u32 mailboxmask;
-	u32 h2d_mailbox_0;
-	u32 h2d_mailbox_1;
 	u32 int_d2h_db;
 	u32 int_fn0;
 };
@@ -497,8 +495,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_default = {
 	.intmask = BRCMF_PCIE_PCIE2REG_INTMASK,
 	.mailboxint = BRCMF_PCIE_PCIE2REG_MAILBOXINT,
 	.mailboxmask = BRCMF_PCIE_PCIE2REG_MAILBOXMASK,
-	.h2d_mailbox_0 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0,
-	.h2d_mailbox_1 = BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1,
 	.int_d2h_db = BRCMF_PCIE_MB_INT_D2H_DB,
 	.int_fn0 = BRCMF_PCIE_MB_INT_FN0,
 };
@@ -507,8 +503,6 @@ static const struct brcmf_pcie_reginfo brcmf_reginfo_64 = {
 	.intmask = BRCMF_PCIE_64_PCIE2REG_INTMASK,
 	.mailboxint = BRCMF_PCIE_64_PCIE2REG_MAILBOXINT,
 	.mailboxmask = BRCMF_PCIE_64_PCIE2REG_MAILBOXMASK,
-	.h2d_mailbox_0 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0,
-	.h2d_mailbox_1 = BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1,
 	.int_d2h_db = BRCMF_PCIE_64_MB_INT_D2H_DB,
 	.int_fn0 = 0,
 };
@@ -972,9 +966,12 @@ static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo)
 
 static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo)
 {
-	if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1)
-		brcmf_pcie_write_reg32(devinfo,
-				       devinfo->reginfo->h2d_mailbox_1, 1);
+	if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) {
+		if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
+			brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1);
+		else
+			brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1);
+	}
 }
 
 static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg)
@@ -1123,7 +1120,10 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx)
 
 	brcmf_dbg(PCIE, "RING !\n");
 	/* Any arbitrary value will do, lets use 1 */
-	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->h2d_mailbox_0, 1);
+	if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
+		brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1);
+	else
+		brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1);
 
 	return 0;
 }

From 04ca7f5bcca998f91973e2f5400fd7da6ab02e7f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 22:10:08 +0900
Subject: [PATCH 0667/1027] wifi: brcmfmac: Support v6+ flags and set host_cap
 properly

Interface versions 6 and above support having the host tell the dongle
about what it supports via a host_cap field (it seems that if it is set
to zero, some kind of unknown defaults are used). Explicitly support and
set this.

This also disables OOB deep sleep support; it doesn't look like deep
sleep is properly supported yet at all (it needs more logic than merely
acking requests, which is all pcie.c does right now).

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 35 ++++++++++++++++++-
 1 file changed, 34 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 9846af00c5cbb0..f17d40bc2d1b38 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -353,6 +353,8 @@ struct brcmf_pcie_console {
 struct brcmf_pcie_shared_info {
 	u32 tcm_base_address;
 	u32 flags;
+	u32 flags2;
+	u32 flags3;
 	struct brcmf_pcie_ringbuf *commonrings[BRCMF_NROF_COMMON_MSGRINGS];
 	struct brcmf_pcie_ringbuf *flowrings;
 	u16 max_rxbufpost;
@@ -1680,12 +1682,16 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 {
 	struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev);
 	struct brcmf_pcie_shared_info *shared;
+	u32 host_cap;
+	u32 host_cap2;
 	u32 addr;
 
 	shared = &devinfo->shared;
 	shared->tcm_base_address = sharedram_addr;
 
-	shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr);
+	shared->flags = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+	                                      BRCMF_SHARED_FLAGS_OFFSET);
+
 	shared->version = (u8)(shared->flags & BRCMF_PCIE_SHARED_VERSION_MASK);
 	brcmf_dbg(PCIE, "PCIe protocol version %d\n", shared->version);
 	if ((shared->version > BRCMF_PCIE_MAX_SHARED_VERSION) ||
@@ -1726,6 +1732,33 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 	brcmf_pcie_bus_console_init(devinfo);
 	brcmf_pcie_bus_console_read(devinfo, false);
 
+	/* Features added in revision 6 follow */
+	if (shared->version < 6)
+		return 0;
+
+	shared->flags2 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+	                                       BRCMF_SHARED_FLAGS2_OFFSET);
+	shared->flags3 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
+	                                       BRCMF_SHARED_FLAGS3_OFFSET);
+
+	/* Update host support flags */
+	host_cap = shared->version;
+	host_cap2 = 0;
+
+	if (shared->flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1)
+		host_cap |= BRCMF_HOSTCAP_H2D_ENABLE_HOSTRDY;
+
+	if (shared->flags & BRCMF_PCIE_SHARED_DAR)
+		host_cap |= BRCMF_HOSTCAP_H2D_DAR;
+
+	/* Disable DS: this is not currently properly supported */
+	host_cap |= BRCMF_HOSTCAP_DS_NO_OOB_DW;
+
+	brcmf_pcie_write_tcm32(devinfo, sharedram_addr +
+			       BRCMF_SHARED_HOST_CAP_OFFSET, host_cap);
+	brcmf_pcie_write_tcm32(devinfo, sharedram_addr +
+			       BRCMF_SHARED_HOST_CAP2_OFFSET, host_cap2);
+
 	return 0;
 }
 

From 67ad5d32c130960ba2d19b5990fb5ea8334cf261 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 21:55:52 +0900
Subject: [PATCH 0668/1027] wifi: brcmfmac: Add newer msgbuf packet types up to
 0x2e

There are many newer msgbuf packet types that are not yet listed in the
defines in msgbuf.c. Add them for future use.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/msgbuf.c      | 26 +++++++++++++++++++
 1 file changed, 26 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index 45fbcbdc7d9e4b..4405451b0c59a4 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -47,6 +47,32 @@
 #define MSGBUF_TYPE_RX_CMPLT			0x12
 #define MSGBUF_TYPE_LPBK_DMAXFER		0x13
 #define MSGBUF_TYPE_LPBK_DMAXFER_CMPLT		0x14
+#define MSGBUF_TYPE_FLOW_RING_RESUME		0x15
+#define MSGBUF_TYPE_FLOW_RING_RESUME_CMPLT	0x16
+#define MSGBUF_TYPE_FLOW_RING_SUSPEND		0x17
+#define MSGBUF_TYPE_FLOW_RING_SUSPEND_CMPLT	0x18
+#define MSGBUF_TYPE_INFO_BUF_POST		0x19
+#define MSGBUF_TYPE_INFO_BUF_CMPLT		0x1A
+#define MSGBUF_TYPE_H2D_RING_CREATE		0x1B
+#define MSGBUF_TYPE_D2H_RING_CREATE		0x1C
+#define MSGBUF_TYPE_H2D_RING_CREATE_CMPLT	0x1D
+#define MSGBUF_TYPE_D2H_RING_CREATE_CMPLT	0x1E
+#define MSGBUF_TYPE_H2D_RING_CONFIG		0x1F
+#define MSGBUF_TYPE_D2H_RING_CONFIG		0x20
+#define MSGBUF_TYPE_H2D_RING_CONFIG_CMPLT	0x21
+#define MSGBUF_TYPE_D2H_RING_CONFIG_CMPLT	0x22
+#define MSGBUF_TYPE_H2D_MAILBOX_DATA		0x23
+#define MSGBUF_TYPE_D2H_MAILBOX_DATA		0x24
+#define MSGBUF_TYPE_TIMSTAMP_BUFPOST		0x25
+#define MSGBUF_TYPE_HOSTTIMSTAMP		0x26
+#define MSGBUF_TYPE_HOSTTIMSTAMP_CMPLT		0x27
+#define MSGBUF_TYPE_FIRMWARE_TIMESTAMP		0x28
+#define MSGBUF_TYPE_SNAPSHOT_UPLOAD		0x29
+#define MSGBUF_TYPE_SNAPSHOT_CMPLT		0x2A
+#define MSGBUF_TYPE_H2D_RING_DELETE		0x2B
+#define MSGBUF_TYPE_D2H_RING_DELETE		0x2C
+#define MSGBUF_TYPE_H2D_RING_DELETE_CMPLT	0x2D
+#define MSGBUF_TYPE_D2H_RING_DELETE_CMPLT	0x2E
 
 #define NR_TX_PKTIDS				2048
 #define NR_RX_PKTIDS				1024

From 1e1546a71230f65c0b1410312bd545de5d9632c0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 21:56:53 +0900
Subject: [PATCH 0669/1027] wifi: brcmfmac: Add a new bus op for D2H mailbox
 message handling

Newer firmware versions use the common ring for sending mailbox messages
between the dongle and host, instead of the hardware mailboxes. This
needs the protocol driver to call back into the bus driver, so add a
callback for this to bus.h.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
index fe31051a9e11b1..5efd7f6d757a4c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/bus.h
@@ -107,6 +107,7 @@ struct brcmf_bus_ops {
 	void (*debugfs_create)(struct device *dev);
 	int (*reset)(struct device *dev);
 	void (*remove)(struct device *dev);
+	void (*d2h_mb_rx)(struct device *dev, u32 data);
 };
 
 
@@ -286,6 +287,15 @@ static inline void brcmf_bus_remove(struct brcmf_bus *bus)
 	bus->ops->remove(bus->dev);
 }
 
+static inline
+void brcmf_bus_d2h_mb_rx(struct brcmf_bus *bus, u32 data)
+{
+	if (!bus->ops->d2h_mb_rx)
+		return;
+
+	return bus->ops->d2h_mb_rx(bus->dev, data);
+}
+
 /*
  * interface functions from common layer
  */

From a5aa3bef1747a4c8ce0e4eebd13d8ecd17c0f1e6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 21:58:21 +0900
Subject: [PATCH 0670/1027] wifi: brcmfmac: Implement the H2D/D2H mailbox data
 commonring messages

Newer firmware versions use these to exchange mailbox data, instead of
the hardware mailbox registers. Add handling for them to msgbuf.c.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/msgbuf.c      | 59 +++++++++++++++++++
 .../broadcom/brcm80211/brcmfmac/msgbuf.h      |  1 +
 2 files changed, 60 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index 4405451b0c59a4..93206850373300 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -244,6 +244,19 @@ struct msgbuf_flowring_flush_resp {
 	__le32				rsvd0[3];
 };
 
+struct msgbuf_h2d_mailbox_data {
+	struct msgbuf_common_hdr	msg;
+	__le32				data;
+	__le32				rsvd0[7];
+};
+
+struct msgbuf_d2h_mailbox_data {
+	struct msgbuf_common_hdr	msg;
+	struct msgbuf_completion_hdr	compl_hdr;
+	__le32				data;
+	__le32				rsvd0[2];
+};
+
 struct brcmf_msgbuf_work_item {
 	struct list_head queue;
 	u32 flowid;
@@ -1311,6 +1324,16 @@ brcmf_msgbuf_process_flow_ring_delete_response(struct brcmf_msgbuf *msgbuf,
 }
 
 
+static void brcmf_msgbuf_process_d2h_mailbox_data(struct brcmf_msgbuf *msgbuf,
+						  void *buf)
+{
+	struct msgbuf_d2h_mailbox_data *d2h_mb_data = buf;
+	struct brcmf_pub *drvr = msgbuf->drvr;
+
+	brcmf_bus_d2h_mb_rx(drvr->bus_if, le32_to_cpu(d2h_mb_data->data));
+}
+
+
 static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf)
 {
 	struct brcmf_pub *drvr = msgbuf->drvr;
@@ -1353,6 +1376,10 @@ static void brcmf_msgbuf_process_msgtype(struct brcmf_msgbuf *msgbuf, void *buf)
 		brcmf_dbg(MSGBUF, "MSGBUF_TYPE_RX_CMPLT\n");
 		brcmf_msgbuf_process_rx_complete(msgbuf, buf);
 		break;
+	case MSGBUF_TYPE_D2H_MAILBOX_DATA:
+		brcmf_dbg(MSGBUF, "MSGBUF_TYPE_D2H_MAILBOX_DATA\n");
+		brcmf_msgbuf_process_d2h_mailbox_data(msgbuf, buf);
+		break;
 	default:
 		bphy_err(drvr, "Unsupported msgtype %d\n", msg->msgtype);
 		break;
@@ -1491,6 +1518,38 @@ void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid)
 	}
 }
 
+
+int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data)
+{
+	struct brcmf_msgbuf *msgbuf = (struct brcmf_msgbuf *)drvr->proto->pd;
+	struct brcmf_commonring *commonring;
+	struct msgbuf_h2d_mailbox_data *request;
+	void *ret_ptr;
+	int err;
+
+	commonring = msgbuf->commonrings[BRCMF_H2D_MSGRING_CONTROL_SUBMIT];
+	brcmf_commonring_lock(commonring);
+	ret_ptr = brcmf_commonring_reserve_for_write(commonring);
+	if (!ret_ptr) {
+		bphy_err(drvr, "Failed to reserve space in commonring\n");
+		brcmf_commonring_unlock(commonring);
+		return -ENOMEM;
+	}
+
+	request = (struct msgbuf_h2d_mailbox_data *)ret_ptr;
+	request->msg.msgtype = MSGBUF_TYPE_H2D_MAILBOX_DATA;
+	request->msg.ifidx = -1;
+	request->msg.flags = 0;
+	request->msg.request_id = 0;
+	request->data = data;
+
+	err = brcmf_commonring_write_complete(commonring);
+	brcmf_commonring_unlock(commonring);
+
+	return err;
+}
+
+
 #ifdef DEBUG
 static int brcmf_msgbuf_stats_read(struct seq_file *seq, void *data)
 {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
index 6a849f4a94dd7f..89b6b7f9ddb748 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
@@ -32,6 +32,7 @@ int brcmf_proto_msgbuf_rx_trigger(struct device *dev);
 void brcmf_msgbuf_delete_flowring(struct brcmf_pub *drvr, u16 flowid);
 int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr);
 void brcmf_proto_msgbuf_detach(struct brcmf_pub *drvr);
+int brcmf_msgbuf_h2d_mb_write(struct brcmf_pub *drvr, u32 data);
 #else
 static inline int brcmf_proto_msgbuf_attach(struct brcmf_pub *drvr)
 {

From 15208f078461ad7b169ab7852823e9dfa31dc8cc Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 18 Oct 2022 22:12:15 +0900
Subject: [PATCH 0671/1027] wifi: brcmfmac: Support exchanging power mailbox
 messages via commonring

Newer firmwares have switched from using the hardware mailbox to
commonring messages for power mailbox data. Implement this, which makes
D3 work on WiFi chipsets in Apple devices.

This is only enabled on v6 or newer, iff BRCMF_PCIE_SHARED_USE_MAILBOX
is not set in the flags.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 75 ++++++++++++++-----
 1 file changed, 55 insertions(+), 20 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index f17d40bc2d1b38..9261578ffe9a5f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -371,6 +371,7 @@ struct brcmf_pcie_shared_info {
 	void *ringupd;
 	dma_addr_t ringupd_dmahandle;
 	u8 version;
+	bool mb_via_ctl;
 };
 
 #define BRCMF_OTP_MAX_PARAM_LEN 16
@@ -817,6 +818,19 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
 	u32 i;
 
 	shared = &devinfo->shared;
+
+	if (shared->mb_via_ctl) {
+		struct pci_dev *pdev = devinfo->pdev;
+		struct brcmf_bus *bus = dev_get_drvdata(&pdev->dev);
+		int ret;
+
+		ret = brcmf_msgbuf_h2d_mb_write(bus->drvr, htod_mb_data);
+		if (ret < 0)
+			brcmf_err(bus, "Failed to send H2D mailbox data (%d)\n",
+				  ret);
+		return ret;
+	}
+
 	addr = shared->htod_mb_data_addr;
 	cur_htod_mb_data = brcmf_pcie_read_tcm32(devinfo, addr);
 
@@ -844,8 +858,29 @@ brcmf_pcie_send_mb_data(struct brcmf_pciedev_info *devinfo, u32 htod_mb_data)
 	return 0;
 }
 
+static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo, u32 data)
+{
+	brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", data);
+	if (data & BRCMF_D2H_DEV_DS_ENTER_REQ)  {
+		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n");
+		brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
+		brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n");
+	}
+	if (data & BRCMF_D2H_DEV_DS_EXIT_NOTE)
+		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n");
+	if (data & BRCMF_D2H_DEV_D3_ACK) {
+		brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n");
+		devinfo->mbdata_completed = true;
+		wake_up(&devinfo->mbdata_resp_wait);
+	}
+	if (data & BRCMF_D2H_DEV_FWHALT) {
+		brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n");
+		brcmf_fw_crashed(&devinfo->pdev->dev);
+	}
+}
+
 
-static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo)
+static void brcmf_pcie_poll_mb_data(struct brcmf_pciedev_info *devinfo)
 {
 	struct brcmf_pcie_shared_info *shared;
 	u32 addr;
@@ -860,23 +895,16 @@ static void brcmf_pcie_handle_mb_data(struct brcmf_pciedev_info *devinfo)
 
 	brcmf_pcie_write_tcm32(devinfo, addr, 0);
 
-	brcmf_dbg(PCIE, "D2H_MB_DATA: 0x%04x\n", dtoh_mb_data);
-	if (dtoh_mb_data & BRCMF_D2H_DEV_DS_ENTER_REQ)  {
-		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP REQ\n");
-		brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_DS_ACK);
-		brcmf_dbg(PCIE, "D2H_MB_DATA: sent DEEP SLEEP ACK\n");
-	}
-	if (dtoh_mb_data & BRCMF_D2H_DEV_DS_EXIT_NOTE)
-		brcmf_dbg(PCIE, "D2H_MB_DATA: DEEP SLEEP EXIT\n");
-	if (dtoh_mb_data & BRCMF_D2H_DEV_D3_ACK) {
-		brcmf_dbg(PCIE, "D2H_MB_DATA: D3 ACK\n");
-		devinfo->mbdata_completed = true;
-		wake_up(&devinfo->mbdata_resp_wait);
-	}
-	if (dtoh_mb_data & BRCMF_D2H_DEV_FWHALT) {
-		brcmf_dbg(PCIE, "D2H_MB_DATA: FW HALT\n");
-		brcmf_fw_crashed(&devinfo->pdev->dev);
-	}
+	brcmf_pcie_handle_mb_data(devinfo, dtoh_mb_data);
+}
+
+
+static void brcmf_pcie_d2h_mb_rx(struct device *dev, u32 data)
+{
+	struct brcmf_bus *bus_if = dev_get_drvdata(dev);
+	struct brcmf_pciedev *buspub = bus_if->bus_priv.pcie;
+
+	brcmf_pcie_handle_mb_data(buspub->devinfo, data);
 }
 
 
@@ -1006,7 +1034,7 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg)
 		brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint,
 				       status);
 		if (status & devinfo->reginfo->int_fn0)
-			brcmf_pcie_handle_mb_data(devinfo);
+			brcmf_pcie_poll_mb_data(devinfo);
 	}
 	if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) {
 		if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
@@ -1651,6 +1679,7 @@ static const struct brcmf_bus_ops brcmf_pcie_bus_ops = {
 	.get_blob = brcmf_pcie_get_blob,
 	.reset = brcmf_pcie_reset,
 	.debugfs_create = brcmf_pcie_debugfs_create,
+	.d2h_mb_rx = brcmf_pcie_d2h_mb_rx,
 };
 
 
@@ -1741,6 +1770,10 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 	shared->flags3 = brcmf_pcie_read_tcm32(devinfo, sharedram_addr +
 	                                       BRCMF_SHARED_FLAGS3_OFFSET);
 
+	/* Check which mailbox mechanism to use */
+	if (!(shared->flags & BRCMF_PCIE_SHARED_USE_MAILBOX))
+		shared->mb_via_ctl = true;
+
 	/* Update host support flags */
 	host_cap = shared->version;
 	host_cap2 = 0;
@@ -2718,10 +2751,11 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	/* Check if device is still up and running, if so we are ready */
 	if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) {
 		brcmf_dbg(PCIE, "Try to wakeup device....\n");
+		/* Set the device up, so we can write the MB data message in ring mode */
+		devinfo->state = BRCMFMAC_PCIE_STATE_UP;
 		if (brcmf_pcie_send_mb_data(devinfo, BRCMF_H2D_HOST_D0_INFORM))
 			goto cleanup;
 		brcmf_dbg(PCIE, "Hot resume, continue....\n");
-		devinfo->state = BRCMFMAC_PCIE_STATE_UP;
 		brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
 		brcmf_bus_change_state(bus, BRCMF_BUS_UP);
 		brcmf_pcie_intr_enable(devinfo);
@@ -2731,6 +2765,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	}
 
 cleanup:
+	devinfo->state = BRCMFMAC_PCIE_STATE_DOWN;
 	brcmf_chip_detach(devinfo->ci);
 	devinfo->ci = NULL;
 	pdev = devinfo->pdev;

From 60759f384c402307f40efafc39ac2ba4dd8ddfb5 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sat, 25 Mar 2023 15:23:04 +0900
Subject: [PATCH 0672/1027] wifi: brcmfmac: Shut up p2p unknown frame error

People keep complaining about this and think their wifi is broken for
some reason...

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
index 6e0c90f4718b58..543d3cba1c6156 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/p2p.c
@@ -1793,8 +1793,8 @@ bool brcmf_p2p_send_action_frame(struct brcmf_cfg80211_info *cfg,
 		/* do not configure anything. it will be */
 		/* sent with a default configuration     */
 	} else {
-		bphy_err(drvr, "Unknown Frame: category 0x%x, action 0x%x\n",
-			 category, action);
+		bphy_info_once(drvr, "Unknown Frame: category 0x%x, action 0x%x\n",
+			       category, action);
 		return false;
 	}
 

From f3c695b08ec0842f9859577c452f56de7a0b23db Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 6 Jun 2023 15:53:23 +0900
Subject: [PATCH 0673/1027] wifi: brcmfmac: Do not service msgbuf IRQs until
 ready in MSI mode

This is the counterpart to b50255c83b. In MSI mode we can still get MSIs
even with IRQs disabled, so add an explicit gate for it.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 9261578ffe9a5f..76c5fc361234e5 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -404,6 +404,7 @@ struct brcmf_pciedev_info {
 	wait_queue_head_t mbdata_resp_wait;
 	bool mbdata_completed;
 	bool irq_allocated;
+	bool irq_ready;
 	bool have_msi;
 	bool wowl_enabled;
 	u8 dma_idx_sz;
@@ -984,6 +985,8 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo,
 static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo)
 {
 	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0);
+
+	devinfo->irq_ready = false;
 }
 
 
@@ -992,6 +995,8 @@ static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo)
 	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask,
 			       devinfo->reginfo->int_d2h_db |
 			       devinfo->reginfo->int_fn0);
+
+	devinfo->irq_ready = true;
 }
 
 static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo)
@@ -1037,7 +1042,7 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg)
 			brcmf_pcie_poll_mb_data(devinfo);
 	}
 	if (devinfo->have_msi || status & devinfo->reginfo->int_d2h_db) {
-		if (devinfo->state == BRCMFMAC_PCIE_STATE_UP)
+		if (devinfo->state == BRCMFMAC_PCIE_STATE_UP && devinfo->irq_ready)
 			brcmf_proto_msgbuf_rx_trigger(&devinfo->pdev->dev);
 	}
 

From a85ff118b36c0153c8eda484767838ed3cd1af07 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 2 Oct 2023 22:55:08 +0900
Subject: [PATCH 0674/1027] wifi: brcmfmac: Add support for SYSMEM corerev >=
 12 & fix < 12

SYSMEM corerev 12+ uses different coreinfo masks for the ROM/RAM sizes.
The masks for cores <12 also look like they were wrong all along, since
the register layout is not the same as for SOCRAM (even though it was
sharing the defines). Plus we need to skip the ROM banks, which we
weren't doing.

So it looks like this was always wrong for SYSMEM chips. Fix it and add
support for the new revisions.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/chip.c        | 20 +++++++++++++++++--
 1 file changed, 18 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
index 2ef92ef25517e8..bbf7ce21c48456 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
@@ -162,6 +162,15 @@ struct sbconfig {
 #define	SRCI_SRBSZ_SHIFT	0
 #define SR_BSZ_BASE		14
 
+#define SYSMEM_SRCI_ROMNB_MASK		0x3e0
+#define SYSMEM_SRCI_ROMNB_SHIFT		5
+#define SYSMEM_SRCI_SRNB_MASK		0x1f
+#define SYSMEM_SRCI_SRNB_SHIFT		0
+#define SYSMEM_SRCI_NEW_ROMNB_MASK	0xff000000
+#define SYSMEM_SRCI_NEW_ROMNB_SHIFT	24
+#define SYSMEM_SRCI_NEW_SRNB_MASK	0xff0000
+#define SYSMEM_SRCI_NEW_SRNB_SHIFT	16
+
 struct sbsocramregs {
 	u32 coreinfo;
 	u32 bwalloc;
@@ -659,6 +668,7 @@ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem)
 	u32 memsize = 0;
 	u32 coreinfo;
 	u32 idx;
+	u32 nrb;
 	u32 nb;
 	u32 banksize;
 
@@ -666,10 +676,16 @@ static u32 brcmf_chip_sysmem_ramsize(struct brcmf_core_priv *sysmem)
 		brcmf_chip_resetcore(&sysmem->pub, 0, 0, 0);
 
 	coreinfo = brcmf_chip_core_read32(sysmem, SYSMEMREGOFFS(coreinfo));
-	nb = (coreinfo & SRCI_SRNB_MASK) >> SRCI_SRNB_SHIFT;
+	if (sysmem->pub.rev >= 12) {
+		nrb = (coreinfo & SYSMEM_SRCI_NEW_ROMNB_MASK) >> SYSMEM_SRCI_NEW_ROMNB_SHIFT;
+		nb = (coreinfo & SYSMEM_SRCI_NEW_SRNB_MASK) >> SYSMEM_SRCI_NEW_SRNB_SHIFT;
+	} else {
+		nrb = (coreinfo & SYSMEM_SRCI_ROMNB_MASK) >> SYSMEM_SRCI_ROMNB_SHIFT;
+		nb = (coreinfo & SYSMEM_SRCI_SRNB_MASK) >> SYSMEM_SRCI_SRNB_SHIFT;
+	}
 
 	for (idx = 0; idx < nb; idx++) {
-		brcmf_chip_socram_banksize(sysmem, idx, &banksize);
+		brcmf_chip_socram_banksize(sysmem, idx + nrb, &banksize);
 		memsize += banksize;
 	}
 

From 5b9c3f6cbc3b00ebf6521b569af1b8945e7eb78c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 3 Oct 2023 17:28:02 +0900
Subject: [PATCH 0675/1027] wifi: brcmfmac: Add support for firmware signatures

Beginning with BCM4388, Apple machines are using firmware signing. This
requires a new firmware blob (as the signature is provided out-of-band)
as well as an extension of the existing random seed upload mechanism to
populate the data structures required for signature verification by the
bootloader.

To implement this, refactor the existing random seed code to be more
generic, and use it to implement the signature upload.

Drive-by changes: Remove two unused members of brcmf_pciedev_info (which
are confusing as they are never initialized), and also zero out the
unused portion of TCM to make TCM dumps less noisy. With this, the TCM
contents are 1:1 identical to what the macOS driver ends up doing,
except for the NVRAM which has the injected macaddr property at the end
instead of at the start.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 199 +++++++++++++++---
 1 file changed, 169 insertions(+), 30 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 76c5fc361234e5..8e7d0eee9b0807 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -388,6 +388,7 @@ struct brcmf_pciedev_info {
 	bool in_irq;
 	struct pci_dev *pdev;
 	char fw_name[BRCMF_FW_NAME_LEN];
+	char sig_name[BRCMF_FW_NAME_LEN];
 	char nvram_name[BRCMF_FW_NAME_LEN];
 	char clm_name[BRCMF_FW_NAME_LEN];
 	char txcap_name[BRCMF_FW_NAME_LEN];
@@ -396,8 +397,7 @@ struct brcmf_pciedev_info {
 	const struct brcmf_pcie_reginfo *reginfo;
 	void __iomem *regs;
 	void __iomem *tcm;
-	u32 ram_base;
-	u32 ram_size;
+	u32 fw_size;
 	struct brcmf_chip *ci;
 	u32 coreid;
 	struct brcmf_pcie_shared_info shared;
@@ -1800,26 +1800,164 @@ brcmf_pcie_init_share_ram_info(struct brcmf_pciedev_info *devinfo,
 	return 0;
 }
 
-struct brcmf_random_seed_footer {
+struct brcmf_rtlv_footer {
 	__le32 length;
 	__le32 magic;
 };
 
+struct brcmf_fw_memmap {
+	u32 pad1[8];
+	u32 vstatus_start;
+	u32 vstatus_end;
+	u32 fw_start;
+	u32 fw_end;
+	u32 sig_start;
+	u32 sig_end;
+	u32 heap_start;
+	u32 heap_end;
+	u32 pad2[6];
+};
+
+
+#define BRCMF_BL_HEAP_START_GAP		0x1000
+#define BRCMF_BL_HEAP_SIZE		0x10000
 #define BRCMF_RANDOM_SEED_MAGIC		0xfeedc0de
 #define BRCMF_RANDOM_SEED_LENGTH	0x100
+#define BRCMF_SIG_MAGIC			0xfeedfe51
+#define BRCMF_VSTATUS_MAGIC		0xfeedfe54
+#define BRCMF_VSTATUS_SIZE		0x28
+#define BRCMF_MEMMAP_MAGIC		0xfeedfe53
+#define BRCMF_END_MAGIC			0xfeed0e2d
+
+static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, size_t length)
+{
+	struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev);
+	u32 boundary = devinfo->ci->rambase + devinfo->fw_size +
+		BRCMF_BL_HEAP_START_GAP + BRCMF_BL_HEAP_SIZE;
+	u32 start_addr;
+	struct brcmf_rtlv_footer footer = {
+		.magic = type,
+	};
+
+	length = ALIGN(length, 4);
+	start_addr = *address - length - sizeof(struct brcmf_rtlv_footer);
+
+	if (length > 0xffff || start_addr > *address || start_addr < boundary) {
+		brcmf_err(bus, "failed to allocate 0x%zx bytes for rTLV type 0x%x\n",
+			  length, type);
+		return -ENOMEM;
+	}
+
+	/* Random seed does not use the length check code */
+	if (type == BRCMF_RANDOM_SEED_MAGIC)
+		footer.length = length;
+	else
+		footer.length = length | ((length ^ 0xffff) << 16);
+
+	memcpy_toio(devinfo->tcm + *address - sizeof(struct brcmf_rtlv_footer),
+		    &footer, sizeof(struct brcmf_rtlv_footer));
+
+	*address = start_addr;
+
+	return 0;
+}
 
-static noinline_for_stack void
-brcmf_pcie_provide_random_bytes(struct brcmf_pciedev_info *devinfo, u32 address)
+static noinline_for_stack int
+brcmf_pcie_add_random_seed(struct brcmf_pciedev_info *devinfo, u32 *address)
 {
+	int err;
 	u8 randbuf[BRCMF_RANDOM_SEED_LENGTH];
 
+	err = brcmf_alloc_rtlv(devinfo, address,
+			       BRCMF_RANDOM_SEED_MAGIC, BRCMF_RANDOM_SEED_LENGTH);
+	if (err)
+		return err;
+
+	/* Some Apple chips/firmwares expect a buffer of random
+	 * data to be present before NVRAM
+	 */
+	brcmf_dbg(PCIE, "Download random seed\n");
+
 	get_random_bytes(randbuf, BRCMF_RANDOM_SEED_LENGTH);
 	memcpy_toio(devinfo->tcm + address, randbuf, BRCMF_RANDOM_SEED_LENGTH);
+
+	return 0;
+}
+
+static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo,
+				    u32 *address, const struct firmware *fwsig)
+{
+	int err;
+	struct brcmf_fw_memmap memmap;
+
+	brcmf_dbg(PCIE, "Download firmware signature\n");
+
+	memset(&memmap, 0, sizeof(memmap));
+
+	memmap.sig_end = *address;
+	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_SIG_MAGIC, fwsig->size);
+	if (err)
+		return err;
+	memmap.sig_start = *address;
+
+	memmap.vstatus_end = *address;
+	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_VSTATUS_MAGIC, BRCMF_VSTATUS_SIZE);
+	if (err)
+		return err;
+	memmap.vstatus_start = *address;
+
+	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_MEMMAP_MAGIC, sizeof(memmap));
+	if (err)
+		return err;
+
+	memmap.fw_start = devinfo->ci->rambase;
+	memmap.fw_end = memmap.fw_start + devinfo->fw_size;
+	memmap.heap_start = memmap.fw_end + BRCMF_BL_HEAP_START_GAP;
+	memmap.heap_end = memmap.heap_start + BRCMF_BL_HEAP_SIZE;
+
+	if (memmap.heap_end > *address)
+		return -ENOMEM;
+
+	memcpy_toio(devinfo->tcm + memmap.sig_start, fwsig->data, fwsig->size);
+	memset_io(devinfo->tcm + memmap.vstatus_start, 0, BRCMF_VSTATUS_SIZE);
+	memcpy_toio(devinfo->tcm + *address, &memmap, sizeof(memmap));
+
+	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_END_MAGIC, 0);
+	if (err)
+		return err;
+
+	return 0;
+}
+
+static int brcmf_pcie_populate_footers(struct brcmf_pciedev_info *devinfo,
+				       u32 *address, const struct firmware *fwsig)
+{
+	int err;
+
+	/* We only do this for Apple firmwares. If any other
+	 * production firmwares are found to need this, the condition
+	 * needs to be adjusted.
+	 */
+	if (!devinfo->otp.valid)
+		return 0;
+
+	err = brcmf_pcie_add_random_seed(devinfo, address);
+	if (err)
+		return err;
+
+	if (fwsig) {
+		err = brcmf_pcie_add_signature(devinfo, address, fwsig);
+		if (err)
+			return err;
+	}
+
+	return 0;
 }
 
 static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo,
-					const struct firmware *fw, void *nvram,
-					u32 nvram_len)
+					const struct firmware *fw,
+					const struct firmware *fwsig,
+					void *nvram, u32 nvram_len)
 {
 	struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev);
 	u32 sharedram_addr;
@@ -1839,6 +1977,7 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo,
 		    (void *)fw->data, fw->size);
 
 	resetintr = get_unaligned_le32(fw->data);
+	devinfo->fw_size = fw->size;
 	release_firmware(fw);
 
 	/* reset last 4 bytes of RAM address. to be used for shared
@@ -1846,37 +1985,31 @@ static int brcmf_pcie_download_fw_nvram(struct brcmf_pciedev_info *devinfo,
 	 */
 	brcmf_pcie_write_ram32(devinfo, devinfo->ci->ramsize - 4, 0);
 
+	address = devinfo->ci->rambase + devinfo->ci->ramsize;
+
 	if (nvram) {
 		brcmf_dbg(PCIE, "Download NVRAM %s\n", devinfo->nvram_name);
-		address = devinfo->ci->rambase + devinfo->ci->ramsize -
-			  nvram_len;
+		address -= nvram_len;
 		memcpy_toio(devinfo->tcm + address, nvram, nvram_len);
 		brcmf_fw_nvram_free(nvram);
 
-		if (devinfo->otp.valid) {
-			size_t rand_len = BRCMF_RANDOM_SEED_LENGTH;
-			struct brcmf_random_seed_footer footer = {
-				.length = cpu_to_le32(rand_len),
-				.magic = cpu_to_le32(BRCMF_RANDOM_SEED_MAGIC),
-			};
-
-			/* Some Apple chips/firmwares expect a buffer of random
-			 * data to be present before NVRAM
-			 */
-			brcmf_dbg(PCIE, "Download random seed\n");
-
-			address -= sizeof(footer);
-			memcpy_toio(devinfo->tcm + address, &footer,
-				    sizeof(footer));
-
-			address -= rand_len;
-			brcmf_pcie_provide_random_bytes(devinfo, address);
-		}
+		err = brcmf_pcie_populate_footers(devinfo, &address, fwsig);
+		if (err)
+			brcmf_err(bus, "failed to populate firmware footers err=%d\n", err);
 	} else {
 		brcmf_dbg(PCIE, "No matching NVRAM file found %s\n",
 			  devinfo->nvram_name);
 	}
 
+	release_firmware(fwsig);
+
+	/* Clear free TCM. This isn't really necessary, but it
+	 * makes debugging memory dumps a lot easier since we
+	 * don't get a bunch of junk filling up the free space.
+	 */
+	memset_io(devinfo->tcm + devinfo->ci->rambase + devinfo->fw_size,
+		  0, address - devinfo->fw_size - devinfo->ci->rambase);
+
 	sharedram_addr_written = brcmf_pcie_read_ram32(devinfo,
 						       devinfo->ci->ramsize -
 						       4);
@@ -2262,11 +2395,12 @@ static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo)
 #define BRCMF_PCIE_FW_NVRAM	1
 #define BRCMF_PCIE_FW_CLM	2
 #define BRCMF_PCIE_FW_TXCAP	3
+#define BRCMF_PCIE_FW_SIG	4
 
 static void brcmf_pcie_setup(struct device *dev, int ret,
 			     struct brcmf_fw_request *fwreq)
 {
-	const struct firmware *fw;
+	const struct firmware *fw, *fwsig;
 	void *nvram;
 	struct brcmf_bus *bus;
 	struct brcmf_pciedev *pcie_bus_dev;
@@ -2285,6 +2419,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 	brcmf_pcie_attach(devinfo);
 
 	fw = fwreq->items[BRCMF_PCIE_FW_CODE].binary;
+	fwsig = fwreq->items[BRCMF_PCIE_FW_SIG].binary;
 	nvram = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.data;
 	nvram_len = fwreq->items[BRCMF_PCIE_FW_NVRAM].nv_data.len;
 	devinfo->clm_fw = fwreq->items[BRCMF_PCIE_FW_CLM].binary;
@@ -2295,6 +2430,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 	if (ret) {
 		brcmf_err(bus, "Failed to get RAM info\n");
 		release_firmware(fw);
+		release_firmware(fwsig);
 		brcmf_fw_nvram_free(nvram);
 		goto fail;
 	}
@@ -2306,7 +2442,7 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 	 */
 	brcmf_pcie_adjust_ramsize(devinfo, (u8 *)fw->data, fw->size);
 
-	ret = brcmf_pcie_download_fw_nvram(devinfo, fw, nvram, nvram_len);
+	ret = brcmf_pcie_download_fw_nvram(devinfo, fw, fwsig, nvram, nvram_len);
 	if (ret)
 		goto fail;
 
@@ -2371,6 +2507,7 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo)
 		{ ".txt", devinfo->nvram_name },
 		{ ".clm_blob", devinfo->clm_name },
 		{ ".txcap_blob", devinfo->txcap_name },
+		{ ".sig", devinfo->sig_name },
 	};
 
 	fwreq = brcmf_fw_alloc_request(devinfo->ci->chip, devinfo->ci->chiprev,
@@ -2381,6 +2518,8 @@ brcmf_pcie_prepare_fw_request(struct brcmf_pciedev_info *devinfo)
 		return NULL;
 
 	fwreq->items[BRCMF_PCIE_FW_CODE].type = BRCMF_FW_TYPE_BINARY;
+	fwreq->items[BRCMF_PCIE_FW_SIG].type = BRCMF_FW_TYPE_BINARY;
+	fwreq->items[BRCMF_PCIE_FW_SIG].flags = BRCMF_FW_REQF_OPTIONAL;
 	fwreq->items[BRCMF_PCIE_FW_NVRAM].type = BRCMF_FW_TYPE_NVRAM;
 	fwreq->items[BRCMF_PCIE_FW_NVRAM].flags = BRCMF_FW_REQF_OPTIONAL;
 	fwreq->items[BRCMF_PCIE_FW_CLM].type = BRCMF_FW_TYPE_BINARY;

From a47cf78a70032dcd897c505e80dd68f5e036512e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 3 Oct 2023 18:33:36 +0900
Subject: [PATCH 0676/1027] wifi: brcmfmac: msgbuf: Increase RX ring sizes to
 2048

New chips, bigger rings again. BCM4388 Apple firmware posts more than
1024 RX buffers, so we need to bump this up again.

This also requires increasing the number of RX PKTIDs.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c | 2 +-
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
index 93206850373300..0e41d618486d39 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.c
@@ -75,7 +75,7 @@
 #define MSGBUF_TYPE_D2H_RING_DELETE_CMPLT	0x2E
 
 #define NR_TX_PKTIDS				2048
-#define NR_RX_PKTIDS				1024
+#define NR_RX_PKTIDS				2048
 
 #define BRCMF_IOCTL_REQ_PKTID			0xFFFE
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
index 89b6b7f9ddb748..0ed48cf13d93cf 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/msgbuf.h
@@ -8,10 +8,10 @@
 #ifdef CONFIG_BRCMFMAC_PROTO_MSGBUF
 
 #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_MAX_ITEM	64
-#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM	1024
+#define BRCMF_H2D_MSGRING_RXPOST_SUBMIT_MAX_ITEM	2048
 #define BRCMF_D2H_MSGRING_CONTROL_COMPLETE_MAX_ITEM	64
 #define BRCMF_D2H_MSGRING_TX_COMPLETE_MAX_ITEM		1024
-#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM		1024
+#define BRCMF_D2H_MSGRING_RX_COMPLETE_MAX_ITEM		2048
 #define BRCMF_H2D_TXFLOWRING_MAX_ITEM			512
 
 #define BRCMF_H2D_MSGRING_CONTROL_SUBMIT_ITEMSIZE	40

From 29282ce8c56c86cb2a1158d57a2ea24508c7292c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 04:55:02 +0900
Subject: [PATCH 0677/1027] wifi: brcmfmac: Increase bandlist size

BCM4388 supports more bands, so make space for them.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 815f6b3c79fc0f..28d93c15697cb0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7579,7 +7579,7 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 	struct ieee80211_supported_band *band;
 	u16 max_interfaces = 0;
 	bool gscan;
-	__le32 bandlist[3];
+	__le32 bandlist[16];
 	u32 n_bands;
 	int err, i;
 

From 03d5c3d95890628f0b77a0ab15db042b3c767b7c Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 04:57:34 +0900
Subject: [PATCH 0678/1027] wifi: brcmfmac: chip: ca7: Only disable D11 cores;
 handle an arbitrary number

This is the ca7 version of 3c7c07ca7ab1 ("wifi: brcmfmac: chip: Only disable
D11 cores; handle an arbitrary number"). Instead of the hack in
resetcore to handle multiple 80211 cores, let's just iterate in
set_passive.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/chip.c        | 46 +++----------------
 1 file changed, 6 insertions(+), 40 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
index bbf7ce21c48456..833ca6eb5c8eb3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
@@ -445,25 +445,11 @@ static void brcmf_chip_ai_resetcore(struct brcmf_core_priv *core, u32 prereset,
 {
 	struct brcmf_chip_priv *ci;
 	int count;
-	struct brcmf_core *d11core2 = NULL;
-	struct brcmf_core_priv *d11priv2 = NULL;
 
 	ci = core->chip;
 
-	/* special handle two D11 cores reset */
-	if (core->pub.id == BCMA_CORE_80211) {
-		d11core2 = brcmf_chip_get_d11core(&ci->pub, 1);
-		if (d11core2) {
-			brcmf_dbg(INFO, "found two d11 cores, reset both\n");
-			d11priv2 = container_of(d11core2,
-						struct brcmf_core_priv, pub);
-		}
-	}
-
 	/* must disable first to work for arbitrary current core state */
 	brcmf_chip_ai_coredisable(core, prereset, reset);
-	if (d11priv2)
-		brcmf_chip_ai_coredisable(d11priv2, prereset, reset);
 
 	count = 0;
 	while (ci->ops->read32(ci->ctx, core->wrapbase + BCMA_RESET_CTL) &
@@ -475,30 +461,9 @@ static void brcmf_chip_ai_resetcore(struct brcmf_core_priv *core, u32 prereset,
 		usleep_range(40, 60);
 	}
 
-	if (d11priv2) {
-		count = 0;
-		while (ci->ops->read32(ci->ctx,
-				       d11priv2->wrapbase + BCMA_RESET_CTL) &
-				       BCMA_RESET_CTL_RESET) {
-			ci->ops->write32(ci->ctx,
-					 d11priv2->wrapbase + BCMA_RESET_CTL,
-					 0);
-			count++;
-			if (count > 50)
-				break;
-			usleep_range(40, 60);
-		}
-	}
-
 	ci->ops->write32(ci->ctx, core->wrapbase + BCMA_IOCTL,
 			 postreset | BCMA_IOCTL_CLK);
 	ci->ops->read32(ci->ctx, core->wrapbase + BCMA_IOCTL);
-
-	if (d11priv2) {
-		ci->ops->write32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL,
-				 postreset | BCMA_IOCTL_CLK);
-		ci->ops->read32(ci->ctx, d11priv2->wrapbase + BCMA_IOCTL);
-	}
 }
 
 char *brcmf_chip_name(u32 id, u32 rev, char *buf, uint len)
@@ -1353,14 +1318,15 @@ static inline void
 brcmf_chip_ca7_set_passive(struct brcmf_chip_priv *chip)
 {
 	struct brcmf_core *core;
+	int i;
 
 	brcmf_chip_disable_arm(chip, BCMA_CORE_ARM_CA7);
 
-	core = brcmf_chip_get_core(&chip->pub, BCMA_CORE_80211);
-	brcmf_chip_resetcore(core, D11_BCMA_IOCTL_PHYRESET |
-				   D11_BCMA_IOCTL_PHYCLOCKEN,
-			     D11_BCMA_IOCTL_PHYCLOCKEN,
-			     D11_BCMA_IOCTL_PHYCLOCKEN);
+	/* Disable the cores only and let the firmware enable them. */
+	for (i = 0; (core = brcmf_chip_get_d11core(&chip->pub, i)); i++)
+		brcmf_chip_coredisable(core, D11_BCMA_IOCTL_PHYRESET |
+				       D11_BCMA_IOCTL_PHYCLOCKEN,
+				       D11_BCMA_IOCTL_PHYCLOCKEN);
 }
 
 static bool brcmf_chip_ca7_set_active(struct brcmf_chip_priv *chip, u32 rstvec)

From e766c7825a2f7e9ace4eca98e0dc085865b2fb69 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:00:34 +0900
Subject: [PATCH 0679/1027] wifi: brcmfmac: Handle watchdog properly in newer
 cores

On newer cores, we need to explicitly set the subsystems to reset via
the watchdog. Logic adapted from bcmdhd.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 26 +++++++++++++++++--
 .../broadcom/brcm80211/include/chipcommon.h   |  8 ++++++
 2 files changed, 32 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 8e7d0eee9b0807..224b4a8e3903a8 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -736,8 +736,30 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo)
 
 	/* Watchdog reset */
 	brcmf_pcie_select_core(devinfo, BCMA_CORE_CHIPCOMMON);
-	WRITECC32(devinfo, watchdog, 4);
-	msleep(100);
+	core = brcmf_chip_get_chipcommon(devinfo->ci);
+
+	if (core->rev >= 65) {
+		u32 mask = CC_WD_SSRESET_PCIE_F0_EN;
+
+		core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2);
+		if (core->rev < 66)
+			mask |= CC_WD_SSRESET_PCIE_ALL_FN_EN;
+
+		val = READCC32(devinfo, watchdog);
+		val &= ~CC_WD_ENABLE_MASK;
+		val |= mask;
+		WRITECC32(devinfo, watchdog, val);
+		val &= ~CC_WD_COUNTER_MASK;
+		val |= 4;
+		WRITECC32(devinfo, watchdog, val);
+		msleep(10);
+		val = READCC32(devinfo, intstatus);
+		val |= mask;
+		WRITECC32(devinfo, intstatus, val);
+	} else {
+		WRITECC32(devinfo, watchdog, 4);
+		msleep(100);
+	}
 
 	/* Restore ASPM */
 	brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
index 0340bba968688f..5c3b8fb41194ae 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/chipcommon.h
@@ -302,6 +302,14 @@ struct chipcregs {
 #define PMU_RCTL_LOGIC_DISABLE_MASK         (1 << 27)
 
 
+/* watchdog */
+#define CC_WD_SSRESET_PCIE_F0_EN	0x10000000
+#define CC_WD_SSRESET_PCIE_F1_EN	0x20000000
+#define CC_WD_SSRESET_PCIE_F2_EN	0x40000000
+#define CC_WD_SSRESET_PCIE_ALL_FN_EN	0x80000000
+#define CC_WD_COUNTER_MASK		0x0fffffff
+#define CC_WD_ENABLE_MASK		0xf0000000
+
 /*
 * Maximum delay for the PMU state transition in us.
 * This is an upper bound intended for spinwaits etc.

From 91eb19619cd74851ee59b5d2d1521b69df85a163 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:03:38 +0900
Subject: [PATCH 0680/1027] wifi: brcmfmac: pcie: Access pcie core registers
 via dedicated window

Currently the pcie code multiplexes all register accesses through a
single window. This isn't very efficient, and it creates race conditions
when we access registers from multiple paths (e.g. in the interrupt
handler). Since the chip has a dedicated window for the PCIe core
registers, we can use that instead, avoid all the gratuitous window
switching, and fix the IRQ race issues.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 53 ++++++++++++-------
 1 file changed, 33 insertions(+), 20 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 224b4a8e3903a8..be0e90b28c762f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -545,6 +545,19 @@ brcmf_pcie_write_reg32(struct brcmf_pciedev_info *devinfo, u32 reg_offset,
 	iowrite32(value, address);
 }
 
+static u32
+brcmf_pcie_read_pcie32(struct brcmf_pciedev_info *devinfo, u32 reg_offset)
+{
+	return brcmf_pcie_read_reg32(devinfo, 0x2000 + reg_offset);
+}
+
+
+static void
+brcmf_pcie_write_pcie32(struct brcmf_pciedev_info *devinfo, u32 reg_offset,
+		       u32 value)
+{
+	brcmf_pcie_write_reg32(devinfo, 0x2000 + reg_offset, value);
+}
 
 static u8
 brcmf_pcie_read_tcm8(struct brcmf_pciedev_info *devinfo, u32 mem_offset)
@@ -769,14 +782,14 @@ static void brcmf_pcie_reset_device(struct brcmf_pciedev_info *devinfo)
 	core = brcmf_chip_get_core(devinfo->ci, BCMA_CORE_PCIE2);
 	if (core->rev <= 13) {
 		for (i = 0; i < ARRAY_SIZE(cfg_offset); i++) {
-			brcmf_pcie_write_reg32(devinfo,
+			brcmf_pcie_write_pcie32(devinfo,
 					       BRCMF_PCIE_PCIE2REG_CONFIGADDR,
 					       cfg_offset[i]);
-			val = brcmf_pcie_read_reg32(devinfo,
+			val = brcmf_pcie_read_pcie32(devinfo,
 				BRCMF_PCIE_PCIE2REG_CONFIGDATA);
 			brcmf_dbg(PCIE, "config offset 0x%04x, value 0x%04x\n",
 				  cfg_offset[i], val);
-			brcmf_pcie_write_reg32(devinfo,
+			brcmf_pcie_write_pcie32(devinfo,
 					       BRCMF_PCIE_PCIE2REG_CONFIGDATA,
 					       val);
 		}
@@ -790,9 +803,9 @@ static void brcmf_pcie_attach(struct brcmf_pciedev_info *devinfo)
 
 	/* BAR1 window may not be sized properly */
 	brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
-	brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, 0x4e0);
-	config = brcmf_pcie_read_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA);
-	brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, config);
+	brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR, 0x4e0);
+	config = brcmf_pcie_read_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA);
+	brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA, config);
 
 	device_wakeup_enable(&devinfo->pdev->dev);
 }
@@ -1006,7 +1019,7 @@ static void brcmf_pcie_bus_console_read(struct brcmf_pciedev_info *devinfo,
 
 static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo)
 {
-	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask, 0);
+	brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask, 0);
 
 	devinfo->irq_ready = false;
 }
@@ -1014,7 +1027,7 @@ static void brcmf_pcie_intr_disable(struct brcmf_pciedev_info *devinfo)
 
 static void brcmf_pcie_intr_enable(struct brcmf_pciedev_info *devinfo)
 {
-	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxmask,
+	brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask,
 			       devinfo->reginfo->int_d2h_db |
 			       devinfo->reginfo->int_fn0);
 
@@ -1025,9 +1038,9 @@ static void brcmf_pcie_hostready(struct brcmf_pciedev_info *devinfo)
 {
 	if (devinfo->shared.flags & BRCMF_PCIE_SHARED_HOSTRDY_DB1) {
 		if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
-			brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1);
+			brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_1, 1);
 		else
-			brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1);
+			brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_1, 1);
 	}
 }
 
@@ -1035,7 +1048,7 @@ static irqreturn_t brcmf_pcie_quick_check_isr(int irq, void *arg)
 {
 	struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)arg;
 
-	if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint)) {
+	if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint)) {
 		brcmf_pcie_intr_disable(devinfo);
 		brcmf_dbg(PCIE, "Enter\n");
 		return IRQ_WAKE_THREAD;
@@ -1055,10 +1068,10 @@ static irqreturn_t brcmf_pcie_isr_thread(int irq, void *arg)
 	u32 status;
 
 	devinfo->in_irq = true;
-	status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint);
+	status = brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint);
 	brcmf_dbg(PCIE, "Enter %x\n", status);
 	if (status) {
-		brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint,
+		brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint,
 				       status);
 		if (status & devinfo->reginfo->int_fn0)
 			brcmf_pcie_poll_mb_data(devinfo);
@@ -1124,8 +1137,8 @@ static void brcmf_pcie_release_irq(struct brcmf_pciedev_info *devinfo)
 	if (devinfo->in_irq)
 		brcmf_err(bus, "Still in IRQ (processing) !!!\n");
 
-	status = brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->mailboxint);
-	brcmf_pcie_write_reg32(devinfo, devinfo->reginfo->mailboxint, status);
+	status = brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->mailboxint);
+	brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint, status);
 
 	devinfo->irq_allocated = false;
 }
@@ -1178,9 +1191,9 @@ static int brcmf_pcie_ring_mb_ring_bell(void *ctx)
 	brcmf_dbg(PCIE, "RING !\n");
 	/* Any arbitrary value will do, lets use 1 */
 	if (devinfo->shared.flags & BRCMF_PCIE_SHARED_DAR)
-		brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1);
+		brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_64_PCIE2REG_H2D_MAILBOX_0, 1);
 	else
-		brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1);
+		brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_H2D_MAILBOX_0, 1);
 
 	return 0;
 }
@@ -2175,9 +2188,9 @@ static int brcmf_pcie_buscore_reset(void *ctx, struct brcmf_chip *chip)
 	else
 		reg = BRCMF_PCIE_PCIE2REG_MAILBOXINT;
 
-	val = brcmf_pcie_read_reg32(devinfo, reg);
+	val = brcmf_pcie_read_pcie32(devinfo, reg);
 	if (val != 0xffffffff)
-		brcmf_pcie_write_reg32(devinfo, reg, val);
+		brcmf_pcie_write_pcie32(devinfo, reg, val);
 
 	return 0;
 }
@@ -2915,7 +2928,7 @@ static int brcmf_pcie_pm_leave_D3(struct device *dev)
 	brcmf_dbg(PCIE, "Enter, dev=%p, bus=%p\n", dev, bus);
 
 	/* Check if device is still up and running, if so we are ready */
-	if (brcmf_pcie_read_reg32(devinfo, devinfo->reginfo->intmask) != 0) {
+	if (brcmf_pcie_read_pcie32(devinfo, devinfo->reginfo->intmask) != 0) {
 		brcmf_dbg(PCIE, "Try to wakeup device....\n");
 		/* Set the device up, so we can write the MB data message in ring mode */
 		devinfo->state = BRCMFMAC_PCIE_STATE_UP;

From 16b0902dd6ddc349ade453f6d330a38a77067681 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:08:25 +0900
Subject: [PATCH 0681/1027] wifi: brcmfmac: pcie: Initialize IRQs before
 firmware boot

Newer firmwares notify the host of boot completion via an MSI, so let's
make sure that is initialized before booting the firmware.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c   | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index be0e90b28c762f..d2255d9770ed3f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -2477,6 +2477,14 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 	 */
 	brcmf_pcie_adjust_ramsize(devinfo, (u8 *)fw->data, fw->size);
 
+	/* Newer firmwares will signal firmware boot via MSI, so make sure we
+	 * initialize that upfront.
+	 */
+	brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
+	ret = brcmf_pcie_request_irq(devinfo);
+	if (ret)
+		goto fail;
+
 	ret = brcmf_pcie_download_fw_nvram(devinfo, fw, fwsig, nvram, nvram_len);
 	if (ret)
 		goto fail;
@@ -2492,9 +2500,6 @@ static void brcmf_pcie_setup(struct device *dev, int ret,
 		goto fail;
 
 	brcmf_pcie_select_core(devinfo, BCMA_CORE_PCIE2);
-	ret = brcmf_pcie_request_irq(devinfo);
-	if (ret)
-		goto fail;
 
 	/* hook the commonrings in the bus structure. */
 	for (i = 0; i < BRCMF_NROF_COMMON_MSGRINGS; i++)

From ba89c37179750732a379c7ff7288be4b2d27b2a1 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:21:22 +0900
Subject: [PATCH 0682/1027] wifi: brcmfmac: Do not set reset vector when
 signatures are in use

With secure boot, the vector is not accessible and trying to write it
triggers PCIe errors. Skip it in that case.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index d2255d9770ed3f..8aa407d997675f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -398,6 +398,7 @@ struct brcmf_pciedev_info {
 	void __iomem *regs;
 	void __iomem *tcm;
 	u32 fw_size;
+	bool skip_reset_vector;
 	struct brcmf_chip *ci;
 	u32 coreid;
 	struct brcmf_pcie_shared_info shared;
@@ -1961,6 +1962,8 @@ static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo,
 	if (err)
 		return err;
 
+	devinfo->skip_reset_vector = true;
+
 	return 0;
 }
 
@@ -2201,7 +2204,8 @@ static void brcmf_pcie_buscore_activate(void *ctx, struct brcmf_chip *chip,
 {
 	struct brcmf_pciedev_info *devinfo = (struct brcmf_pciedev_info *)ctx;
 
-	brcmf_pcie_write_tcm32(devinfo, 0, rstvec);
+	if (!devinfo->skip_reset_vector)
+		brcmf_pcie_write_tcm32(devinfo, 0, rstvec);
 }
 
 

From fd9564e48cc0d887a80bddb38fd941fc78b7b427 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:27:54 +0900
Subject: [PATCH 0683/1027] wifi: brcmfmac: Mask all IRQs before starting
 firmware

Make sure the firmware can't get any early notifications by masking all
IRQs explicitly before loading the firmware.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../wireless/broadcom/brcm80211/brcmfmac/pcie.c  | 16 ++++++++++++++++
 1 file changed, 16 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 8aa407d997675f..c0616cce7c8512 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -334,6 +334,7 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 #define BRCMF_PCIE_CFGREG_PML1_SUB_CTRL1	0x248
 #define BRCMF_PCIE_CFGREG_REG_BAR2_CONFIG	0x4E0
 #define BRCMF_PCIE_CFGREG_REG_BAR3_CONFIG	0x4F4
+#define BRCMF_PCIE_CFGREG_TLCNTRL_5		0x814
 #define BRCMF_PCIE_LINK_STATUS_CTRL_ASPM_ENAB	3
 
 /* Magic number at a magic location to find RAM size */
@@ -825,6 +826,21 @@ static int brcmf_pcie_enter_download_state(struct brcmf_pciedev_info *devinfo)
 		brcmf_pcie_write_reg32(devinfo, BRCMF_PCIE_ARMCR4REG_BANKPDA,
 				       0);
 	}
+
+	/* Ensure all IRQs are masked so the firmware doesn't get
+	 * a hostready notification too early.
+	 */
+
+	brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxmask, 0);
+	brcmf_pcie_write_pcie32(devinfo, devinfo->reginfo->mailboxint,
+				0xffffffff);
+
+	pci_write_config_dword(devinfo->pdev, BRCMF_PCIE_REG_INTMASK, 0);
+
+	brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGADDR,
+				BRCMF_PCIE_CFGREG_TLCNTRL_5);
+	brcmf_pcie_write_pcie32(devinfo, BRCMF_PCIE_PCIE2REG_CONFIGDATA,
+				0xffffffff);
 	return 0;
 }
 

From 600b2490121796bb0a70f6b6ce93e22444e7a17a Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 05:50:00 +0900
Subject: [PATCH 0684/1027] wifi: brcmfmac: Add support for SCAN_V3

This is essentially identical to SCAN_V2 with an extra field where we
had a padding byte, so don't bother duplicating the entire structure.
Just add the field and the logic to set the version properly.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 20 +++++++++++++------
 .../broadcom/brcm80211/brcmfmac/feature.c     | 16 ++++++++++++++-
 .../broadcom/brcm80211/brcmfmac/feature.h     |  1 +
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  | 15 +++++++++++++-
 4 files changed, 44 insertions(+), 8 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 28d93c15697cb0..e0e67689457ccf 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -1075,6 +1075,7 @@ static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2
 }
 
 static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
+			     struct brcmf_if *ifp,
 			     struct brcmf_scan_params_v2_le *params_le,
 			     struct cfg80211_scan_request *request)
 {
@@ -1091,8 +1092,13 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
 
 	length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE;
 
-	params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2);
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3))
+		params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3);
+	else
+		params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2);
+
 	params_le->bss_type = DOT11_BSSTYPE_ANY;
+	params_le->ssid_type = 0;
 	params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_ACTIVE);
 	params_le->channel_num = 0;
 	params_le->nprobes = cpu_to_le32(-1);
@@ -1186,7 +1192,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
 		/* Do a scan abort to stop the driver's scan engine */
 		brcmf_dbg(SCAN, "ABORT scan in firmware\n");
 
-		brcmf_escan_prep(cfg, &params_v2_le, NULL);
+		brcmf_escan_prep(cfg, ifp, &params_v2_le, NULL);
 
 		/* E-Scan (or anyother type) can be aborted by SCAN */
 		if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) {
@@ -1446,11 +1452,13 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
 		goto exit;
 	}
 	BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN);
-	brcmf_escan_prep(cfg, &params->params_v2_le, request);
+	brcmf_escan_prep(cfg, ifp, &params->params_v2_le, request);
 
-	params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2);
-
-	if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) {
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) {
+		params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V3);
+	} else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) {
+		params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2);
+	} else {
 		struct brcmf_escan_params_le *params_v1;
 
 		params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 0d9ae197fa1ec3..8d592ca30092e9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -288,6 +288,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
 void brcmf_feat_attach(struct brcmf_pub *drvr)
 {
 	struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
+	struct brcmf_wl_scan_version_le scan_ver;
 	struct brcmf_pno_macaddr_le pfn_mac;
 	struct brcmf_gscan_config gscan_cfg;
 	u32 wowl_cap;
@@ -338,7 +339,20 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 		ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_RANDOM_MAC);
 
 	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa");
-	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_SCAN_V2, "scan_ver");
+
+	err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver));
+	if (!err) {
+		int ver = le16_to_cpu(scan_ver.scan_ver_major);
+
+		if (ver == 2) {
+			ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2);
+		} else if (ver == 3) {
+			/* We consider SCAN_V3 a subtype of SCAN_V2 since the
+			 * structure is essentially the same.
+			 */
+			ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2) | BIT(BRCMF_FEAT_SCAN_V3);
+		}
+	}
 
 	brcmf_feat_wlc_version_overrides(drvr);
 	brcmf_feat_firmware_overrides(drvr);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 7f4f0b3e4a7b4a..3317e80c398a61 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -56,6 +56,7 @@
 	BRCMF_FEAT_DEF(FWAUTH) \
 	BRCMF_FEAT_DEF(DUMP_OBSS) \
 	BRCMF_FEAT_DEF(SCAN_V2) \
+	BRCMF_FEAT_DEF(SCAN_V3) \
 	BRCMF_FEAT_DEF(PMKID_V2) \
 	BRCMF_FEAT_DEF(PMKID_V3)
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index e74a23e11830c1..7ff6cf948e624d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -52,6 +52,7 @@
 
 /* version of brcmf_scan_params structure */
 #define BRCMF_SCAN_PARAMS_VERSION_V2	2
+#define BRCMF_SCAN_PARAMS_VERSION_V3	3
 
 /* masks for channel and ssid count */
 #define BRCMF_SCAN_PARAMS_COUNT_MASK	0x0000ffff
@@ -72,6 +73,7 @@
 #define DOT11_BSSTYPE_ANY		2
 #define BRCMF_ESCAN_REQ_VERSION		1
 #define BRCMF_ESCAN_REQ_VERSION_V2	2
+#define BRCMF_ESCAN_REQ_VERSION_V3	3
 
 #define BRCMF_MAXRATES_IN_SET		16	/* max # of rates in rateset */
 
@@ -414,7 +416,7 @@ struct brcmf_scan_params_v2_le {
 	s8 bss_type;		/* default: any,
 				 * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
 				 */
-	u8 pad;
+	u8 ssid_type;		/* v3 only */
 	__le32 scan_type;	/* flags, 0 use default */
 	__le32 nprobes;		/* -1 use default, number of probes per channel */
 	__le32 active_time;	/* -1 use default, dwell time per channel for
@@ -833,6 +835,17 @@ struct brcmf_wlc_version_le {
 	__le16 wlc_ver_minor;
 };
 
+/**
+ * struct brcmf_wl_scan_version_le - scan interface version
+ */
+struct brcmf_wl_scan_version_le {
+        __le16  version;
+        __le16  length;
+        __le16  scan_ver_major;
+};
+
+#define BRCMF_WL_SCAN_VERSION_VERSION 1
+
 /**
  * struct brcmf_assoclist_le - request assoc list.
  *

From 201cac98d401303916f89dabb687d55b225bdca5 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Wed, 4 Oct 2023 23:54:19 +0900
Subject: [PATCH 0685/1027] wifi: brcmfmac: Implement event_msgs_ext

This extended command supports bit set/clear operations, but we just use
it like the old full mask set command.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/common.c      |  23 +---
 .../broadcom/brcm80211/brcmfmac/core.c        |   5 +
 .../broadcom/brcm80211/brcmfmac/feature.c     |   1 +
 .../broadcom/brcm80211/brcmfmac/feature.h     |   3 +-
 .../broadcom/brcm80211/brcmfmac/fweh.c        | 101 +++++++++++++++---
 .../broadcom/brcm80211/brcmfmac/fweh.h        |   1 +
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  27 +++++
 7 files changed, 126 insertions(+), 35 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
index b24faae35873dc..0482bbbb641a35 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/common.c
@@ -13,6 +13,7 @@
 #include "core.h"
 #include "bus.h"
 #include "debug.h"
+#include "fweh.h"
 #include "fwil.h"
 #include "fwil_types.h"
 #include "tracepoint.h"
@@ -266,7 +267,6 @@ static int brcmf_c_process_cal_blob(struct brcmf_if *ifp)
 int brcmf_c_preinit_dcmds(struct brcmf_if *ifp)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
-	struct brcmf_fweh_info *fweh = drvr->fweh;
 	u8 buf[BRCMF_DCMD_SMLEN];
 	struct brcmf_bus *bus;
 	struct brcmf_rev_info_le revinfo;
@@ -412,27 +412,6 @@ int brcmf_c_preinit_dcmds(struct brcmf_if *ifp)
 
 	brcmf_c_set_joinpref_default(ifp);
 
-	/* Setup event_msgs, enable E_IF */
-	err = brcmf_fil_iovar_data_get(ifp, "event_msgs", fweh->event_mask,
-				       fweh->event_mask_len);
-	if (err) {
-		bphy_err(drvr, "Get event_msgs error (%d)\n", err);
-		goto done;
-	}
-	/*
-	 * BRCMF_E_IF can safely be used to set the appropriate bit
-	 * in the event_mask as the firmware event code is guaranteed
-	 * to match the value of BRCMF_E_IF because it is old cruft
-	 * that all vendors have.
-	 */
-	setbit(fweh->event_mask, BRCMF_E_IF);
-	err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask,
-				       fweh->event_mask_len);
-	if (err) {
-		bphy_err(drvr, "Set event_msgs error (%d)\n", err);
-		goto done;
-	}
-
 	/* Setup default scan channel time */
 	err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_SCAN_CHANNEL_TIME,
 				    BRCMF_DEFAULT_SCAN_CHANNEL_TIME);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index df53dd1d7e748a..abcd46e7120faa 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -1220,6 +1220,11 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops)
 
 	brcmf_feat_attach(drvr);
 
+	/* Setup event_msgs, enable E_IF */
+	ret = brcmf_fweh_init_events(ifp);
+	if (ret)
+		goto fail;
+
 	ret = brcmf_proto_init_done(drvr);
 	if (ret < 0)
 		goto fail;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 8d592ca30092e9..4e4b37b83347f0 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -331,6 +331,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_TDLS, "tdls_enable");
 	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_MFP, "mfp");
 	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_DUMP_OBSS, "dump_obss");
+	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_EVENT_MSGS_EXT, "event_msgs_ext");
 
 	pfn_mac.version = BRCMF_PFN_MACADDR_CFG_VER;
 	err = brcmf_fil_iovar_data_get(ifp, "pfn_macaddr", &pfn_mac,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 3317e80c398a61..622b01d3f86c8d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -58,7 +58,8 @@
 	BRCMF_FEAT_DEF(SCAN_V2) \
 	BRCMF_FEAT_DEF(SCAN_V3) \
 	BRCMF_FEAT_DEF(PMKID_V2) \
-	BRCMF_FEAT_DEF(PMKID_V3)
+	BRCMF_FEAT_DEF(PMKID_V3) \
+	BRCMF_FEAT_DEF(EVENT_MSGS_EXT) \
 
 /*
  * Quirks:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
index f0b6a7607f1604..4ad83ea9ba165b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.c
@@ -11,8 +11,10 @@
 #include "core.h"
 #include "debug.h"
 #include "tracepoint.h"
+#include "feature.h"
 #include "fweh.h"
 #include "fwil.h"
+#include "fwil_types.h"
 #include "proto.h"
 #include "bus.h"
 #include "fwvid.h"
@@ -423,6 +425,67 @@ void brcmf_fweh_unregister(struct brcmf_pub *drvr,
 	drvr->fweh->evt_handler[evt_handler_idx] = NULL;
 }
 
+/**
+ * brcmf_fweh_init_events() - initialize event handling.
+ *
+ * @ifp: primary interface object.
+ */
+int brcmf_fweh_init_events(struct brcmf_if *ifp)
+{
+	struct brcmf_pub *drvr = ifp->drvr;
+	struct brcmf_eventmsgs_ext_le *eventmsgs;
+	size_t size = sizeof(*eventmsgs) + drvr->fweh->event_mask_len;
+	int err;
+
+	eventmsgs = kzalloc(size, GFP_KERNEL);
+	if(!eventmsgs)
+		return -ENOMEM;
+
+	eventmsgs->version = EVENTMSGS_VER;
+	eventmsgs->command = EVENTMSGS_NONE;
+	eventmsgs->len = drvr->fweh->event_mask_len;
+	eventmsgs->maxgetsize = drvr->fweh->event_mask_len;
+
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT))
+		err = brcmf_fil_iovar_data_get(ifp, "event_msgs_ext",
+					       eventmsgs, size);
+	else
+		err = brcmf_fil_iovar_data_get(ifp, "event_msgs",
+					       drvr->fweh->event_mask,
+					       drvr->fweh->event_mask_len);
+
+	if (err) {
+		bphy_err(drvr, "Get event_msgs error (%d)\n", err);
+		kfree(eventmsgs);
+		return err;
+	}
+
+	brcmf_dbg(EVENT, "Event mask len: driver=%d fw=%d\n",
+		  drvr->fweh->event_mask_len, eventmsgs->len);
+
+	/* want to handle IF event as well */
+	brcmf_dbg(EVENT, "enable event IF\n");
+	setbit(eventmsgs->mask, BRCMF_E_IF);
+
+	eventmsgs->version = EVENTMSGS_VER;
+	eventmsgs->command = EVENTMSGS_SET_MASK;
+	eventmsgs->len = drvr->fweh->event_mask_len;
+
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT))
+		err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext",
+					       eventmsgs, size);
+	else
+		err = brcmf_fil_iovar_data_set(ifp, "event_msgs",
+					       drvr->fweh->event_mask,
+					       drvr->fweh->event_mask_len);
+
+	if (err)
+		bphy_err(drvr, "Set event_msgs error (%d)\n", err);
+
+	kfree(eventmsgs);
+	return err;
+}
+
 /**
  * brcmf_fweh_activate_events() - enables firmware events registered.
  *
@@ -430,29 +493,43 @@ void brcmf_fweh_unregister(struct brcmf_pub *drvr,
  */
 int brcmf_fweh_activate_events(struct brcmf_if *ifp)
 {
-	struct brcmf_fweh_info *fweh = ifp->drvr->fweh;
-	enum brcmf_fweh_event_code code;
+	struct brcmf_pub *drvr = ifp->drvr;
+	struct brcmf_eventmsgs_ext_le *eventmsgs;
+	size_t size = sizeof(*eventmsgs) + drvr->fweh->event_mask_len;
 	int i, err;
 
-	memset(fweh->event_mask, 0, fweh->event_mask_len);
-	for (i = 0; i < fweh->num_event_codes; i++) {
-		if (fweh->evt_handler[i]) {
-			brcmf_fweh_map_fwevt_code(fweh, i, &code);
+	eventmsgs = kzalloc(size, GFP_KERNEL);
+	if(!eventmsgs)
+		return -ENOMEM;
+
+	for (i = 0; i < drvr->fweh->num_event_codes; i++) {
+		if (drvr->fweh->evt_handler[i]) {
 			brcmf_dbg(EVENT, "enable event %s\n",
-				  brcmf_fweh_event_name(code));
-			setbit(fweh->event_mask, i);
+				  brcmf_fweh_event_name(i));
+			setbit(eventmsgs->mask, i);
 		}
 	}
 
 	/* want to handle IF event as well */
 	brcmf_dbg(EVENT, "enable event IF\n");
-	setbit(fweh->event_mask, BRCMF_E_IF);
+	setbit(eventmsgs->mask, BRCMF_E_IF);
+
+	eventmsgs->version = EVENTMSGS_VER;
+	eventmsgs->command = EVENTMSGS_SET_MASK;
+	eventmsgs->len = drvr->fweh->event_mask_len;
+
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_EVENT_MSGS_EXT))
+		err = brcmf_fil_iovar_data_set(ifp, "event_msgs_ext",
+					       eventmsgs, size);
+	else
+		err = brcmf_fil_iovar_data_set(ifp, "event_msgs",
+					       drvr->fweh->event_mask,
+					       drvr->fweh->event_mask_len);
 
-	err = brcmf_fil_iovar_data_set(ifp, "event_msgs", fweh->event_mask,
-				       fweh->event_mask_len);
 	if (err)
-		bphy_err(fweh->drvr, "Set event_msgs error (%d)\n", err);
+		bphy_err(drvr, "Set event_msgs error (%d)\n", err);
 
+	kfree(eventmsgs);
 	return err;
 }
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
index 9ca1b2aadcb537..87b508c83a68f3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fweh.h
@@ -352,6 +352,7 @@ int brcmf_fweh_register(struct brcmf_pub *drvr, enum brcmf_fweh_event_code code,
 				       void *data));
 void brcmf_fweh_unregister(struct brcmf_pub *drvr,
 			   enum brcmf_fweh_event_code code);
+int brcmf_fweh_init_events(struct brcmf_if *ifp);
 int brcmf_fweh_activate_events(struct brcmf_if *ifp);
 void brcmf_fweh_process_event(struct brcmf_pub *drvr,
 			      struct brcmf_event *event_packet,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 7ff6cf948e624d..74f4c7a72596ec 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1249,4 +1249,31 @@ struct brcmf_mkeep_alive_pkt_le {
 	u8   data[];
 } __packed;
 
+enum event_msgs_ext_command {
+	EVENTMSGS_NONE		=	0,
+	EVENTMSGS_SET_BIT	=	1,
+	EVENTMSGS_RESET_BIT	=	2,
+	EVENTMSGS_SET_MASK	=	3
+};
+
+#define EVENTMSGS_VER 1
+
+/**
+ * struct brcmf_eventmsgs_ext_le - new event message mask commands
+ *
+ * @version: EVENTMSGS_VER
+ * @command: one of enum event_msgs_ext_command
+ * @len: for set, the mask size from the application to the firmware.
+ *       for get, the actual firmware mask size.
+ * @maxgetsize: for get, the max size that the application can read from
+ *              the firmware.
+ */
+struct brcmf_eventmsgs_ext_le {
+	u8	version;
+	u8	command;
+	u8	len;
+	u8	maxgetsize;
+	u8	mask[];
+};
+
 #endif /* FWIL_TYPES_H_ */

From 9918be0ec9e5af4c521069430f3704d9fe981aaf Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 00:34:04 +0900
Subject: [PATCH 0686/1027] wifi: brcmfmac: Support bss_info up to v112

The structures are compatible and just add fields, so we can just treat
it as always v112. If we start using new fields, that will have to be
gated on the version.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    |  5 ++-
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  | 37 +++++++++++++++++--
 2 files changed, 36 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index e0e67689457ccf..254b3946c0e25d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -3410,8 +3410,9 @@ static s32 brcmf_inform_bss(struct brcmf_cfg80211_info *cfg)
 
 	bss_list = (struct brcmf_scan_results *)cfg->escan_info.escan_buf;
 	if (bss_list->count != 0 &&
-	    bss_list->version != BRCMF_BSS_INFO_VERSION) {
-		bphy_err(drvr, "Version %d != WL_BSS_INFO_VERSION\n",
+	    (bss_list->version < BRCMF_BSS_INFO_MIN_VERSION ||
+	    bss_list->version > BRCMF_BSS_INFO_MAX_VERSION)) {
+		bphy_err(drvr, "BSS info version %d unsupported\n",
 			 bss_list->version);
 		return -EOPNOTSUPP;
 	}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 74f4c7a72596ec..cd7057e6b13adb 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -18,7 +18,8 @@
 #define BRCMF_ARP_OL_HOST_AUTO_REPLY	0x00000004
 #define BRCMF_ARP_OL_PEER_AUTO_REPLY	0x00000008
 
-#define	BRCMF_BSS_INFO_VERSION	109 /* curr ver of brcmf_bss_info_le struct */
+#define	BRCMF_BSS_INFO_MIN_VERSION	109 /* min ver of brcmf_bss_info_le struct */
+#define	BRCMF_BSS_INFO_MAX_VERSION	112 /* max ver of brcmf_bss_info_le struct */
 #define BRCMF_BSS_RSSI_ON_CHANNEL	0x0004
 
 #define BRCMF_STA_BRCM			0x00000001	/* Running a Broadcom driver */
@@ -323,28 +324,56 @@ struct brcmf_bss_info_le {
 	__le16 capability;	/* Capability information */
 	u8 SSID_len;
 	u8 SSID[32];
+	u8 bcnflags;		/* additional flags w.r.t. beacon */
 	struct {
 		__le32 count;   /* # rates in this set */
 		u8 rates[16]; /* rates in 500kbps units w/hi bit set if basic */
 	} rateset;		/* supported rates */
 	__le16 chanspec;	/* chanspec for bss */
 	__le16 atim_window;	/* units are Kusec */
-	u8 dtim_period;	/* DTIM period */
+	u8 dtim_period;		/* DTIM period */
+	u8 accessnet;		/* from beacon interwork IE (if bcnflags) */
 	__le16 RSSI;		/* receive signal strength (in dBm) */
 	s8 phy_noise;		/* noise (in dBm) */
 
 	u8 n_cap;		/* BSS is 802.11N Capable */
+	u8 he_cap;		/* BSS is he capable */
+	u8 load;		/* BSS Load from QBSS load IE if available */
 	/* 802.11N BSS Capabilities (based on HT_CAP_*): */
 	__le32 nbss_cap;
 	u8 ctl_ch;		/* 802.11N BSS control channel number */
-	__le32 reserved32[1];	/* Reserved for expansion of BSS properties */
+	u8 reserved1[3];	/* Reserved for expansion of BSS properties */
+	__le16 vht_rxmcsmap;	/* VHT rx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */
+	__le16 vht_txmcsmap;	/* VHT tx mcs map (802.11ac IE, VHT_CAP_MCS_MAP_*) */
 	u8 flags;		/* flags */
-	u8 reserved[3];	/* Reserved for expansion of BSS properties */
+	u8 vht_cap;		/* BSS is vht capable */
+	u8 reserved2[2];	/* Reserved for expansion of BSS properties */
 	u8 basic_mcs[BRCMF_MCSSET_LEN];	/* 802.11N BSS required MCS set */
 
 	__le16 ie_offset;	/* offset at which IEs start, from beginning */
+	u8 reserved3[2];	/* Reserved for expansion of BSS properties */
 	__le32 ie_length;	/* byte length of Information Elements */
 	__le16 SNR;		/* average SNR of during frame reception */
+	__le16		vht_mcsmap;		/**< STA's Associated vhtmcsmap */
+	__le16		vht_mcsmap_prop;	/**< STA's Associated prop vhtmcsmap */
+	__le16		vht_txmcsmap_prop;	/**< prop VHT tx mcs prop */
+	__le32		he_mcsmap;	/**< STA's Associated hemcsmap */
+	__le32		he_rxmcsmap;	/**< HE rx mcs map (802.11ax IE, HE_CAP_MCS_MAP_*) */
+	__le32		he_txmcsmap;	/**< HE tx mcs map (802.11ax IE, HE_CAP_MCS_MAP_*) */
+	__le32		timestamp[2];  /* Beacon Timestamp for FAKEAP req */
+	/* V112 fields follow */
+	u8		eht_cap;		/* BSS is EHT capable */
+	u8		reserved4[3];	/* Reserved for expansion of BSS properties */
+	/* by the spec. it is maximum 16 streams hence all mcs code for all nss may not fit
+	 * in a 32 bit mcs nss map but since this field only reflects the common mcs nss map
+	 * between that of the peer and our device so it's probably ok to make it 32 bit and
+	 * allow only a limited number of nss e.g. upto 8 of them in the map given the fact
+	 * that our device probably won't exceed 4 streams anyway...
+	 */
+	__le32		eht_mcsmap;		/* STA's associated EHT mcs code map */
+	/* FIXME: change the following mcs code map to uint32 if all mcs+nss can fit in */
+	u8		eht_rxmcsmap[6];	/* EHT rx mcs code map */
+	u8		eht_txmcsmap[6];	/* EHT tx mcs code map */
 	/* Add new fields here */
 	/* variable length Information Elements */
 };

From e41cd46d7843aa2c4155b406fb2a2d1d697dfb77 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 00:34:44 +0900
Subject: [PATCH 0687/1027] wifi: brcmfmac: Extend brcmf_wsec_pmk_le

New firmware wants extra fields, hopefully old firmware ignores them.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h   | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index cd7057e6b13adb..a4ec3808a5c84c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -67,7 +67,7 @@
 #define BRCMF_WSEC_MAX_PSK_LEN		32
 #define	BRCMF_WSEC_PASSPHRASE		BIT(0)
 
-#define BRCMF_WSEC_MAX_SAE_PASSWORD_LEN 128
+#define BRCMF_WSEC_MAX_SAE_PASSWORD_LEN	256
 
 /* primary (ie tx) key */
 #define BRCMF_PRIMARY_KEY		(1 << 1)
@@ -611,11 +611,15 @@ struct brcmf_wsec_key_le {
  * @key_len: number of octets in key material.
  * @flags: key handling qualifiers.
  * @key: PMK key material.
+ * @opt_len: optional field length
+ * @opt_tlvs: optional fields in TLV format
  */
 struct brcmf_wsec_pmk_le {
 	__le16  key_len;
 	__le16  flags;
 	u8 key[BRCMF_WSEC_MAX_SAE_PASSWORD_LEN];
+	__le16  opt_len;
+	u8   opt_tlvs[];
 };
 
 /**

From 602852f363dbd5e92cd6e799010a640825d2bbdd Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 2 Oct 2023 22:26:16 +0900
Subject: [PATCH 0688/1027] wifi: brcmfmac: Add BCM4388 support

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 .../net/wireless/broadcom/brcm80211/brcmfmac/chip.c    |  1 +
 .../net/wireless/broadcom/brcm80211/brcmfmac/pcie.c    | 10 ++++++++++
 .../wireless/broadcom/brcm80211/include/brcm_hw_ids.h  |  2 ++
 3 files changed, 13 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
index 833ca6eb5c8eb3..9d7d69e5f99340 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/chip.c
@@ -712,6 +712,7 @@ static u32 brcmf_chip_tcm_rambase(struct brcmf_chip_priv *ci)
 	case BRCM_CC_4366_CHIP_ID:
 	case BRCM_CC_43664_CHIP_ID:
 	case BRCM_CC_43666_CHIP_ID:
+	case BRCM_CC_4388_CHIP_ID:
 		return 0x200000;
 	case BRCM_CC_4355_CHIP_ID:
 	case BRCM_CC_4359_CHIP_ID:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index c0616cce7c8512..4159b9c1529cee 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -70,6 +70,8 @@ BRCMF_FW_CLM_DEF(4377B3, "brcmfmac4377b3-pcie");
 BRCMF_FW_CLM_DEF(4378B1, "brcmfmac4378b1-pcie");
 BRCMF_FW_CLM_DEF(4378B3, "brcmfmac4378b3-pcie");
 BRCMF_FW_CLM_DEF(4387C2, "brcmfmac4387c2-pcie");
+BRCMF_FW_CLM_DEF(4388B0, "brcmfmac4388b0-pcie");
+BRCMF_FW_CLM_DEF(4388C0, "brcmfmac4388c0-pcie");
 
 /* firmware config files */
 MODULE_FIRMWARE(BRCMF_FW_DEFAULT_PATH "brcmfmac*-pcie.txt");
@@ -108,6 +110,8 @@ static const struct brcmf_firmware_mapping brcmf_pcie_fwnames[] = {
 	BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0x0000000F, 4378B1), /* revision ID 3 */
 	BRCMF_FW_ENTRY(BRCM_CC_4378_CHIP_ID, 0xFFFFFFE0, 4378B3), /* revision ID 5 */
 	BRCMF_FW_ENTRY(BRCM_CC_4387_CHIP_ID, 0xFFFFFFFF, 4387C2), /* revision ID 7 */
+	BRCMF_FW_ENTRY(BRCM_CC_4388_CHIP_ID, 0x0000000F, 4388B0),
+	BRCMF_FW_ENTRY(BRCM_CC_4388_CHIP_ID, 0xFFFFFFF0, 4388C0), /* revision ID 4 */
 };
 
 #define BRCMF_PCIE_FW_UP_TIMEOUT		5000 /* msec */
@@ -2392,6 +2396,11 @@ static int brcmf_pcie_read_otp(struct brcmf_pciedev_info *devinfo)
 		base = 0x113c;
 		words = 0x170;
 		break;
+	case BRCM_CC_4388_CHIP_ID:
+		coreid = BCMA_CORE_GCI;
+		base = 0x115c;
+		words = 0x150;
+		break;
 	default:
 		/* OTP not supported on this chip */
 		return 0;
@@ -3037,6 +3046,7 @@ static const struct pci_device_id brcmf_pcie_devid_table[] = {
 	BRCMF_PCIE_DEVICE(BRCM_PCIE_4377_DEVICE_ID, WCC),
 	BRCMF_PCIE_DEVICE(BRCM_PCIE_4378_DEVICE_ID, WCC),
 	BRCMF_PCIE_DEVICE(BRCM_PCIE_4387_DEVICE_ID, WCC),
+	BRCMF_PCIE_DEVICE(BRCM_PCIE_4388_DEVICE_ID, WCC),
 
 	{ /* end: all zeroes */ }
 };
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h
index 44684bf1b9acc6..eae7d04f3bdcc4 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcm_hw_ids.h
@@ -55,6 +55,7 @@
 #define BRCM_CC_4377_CHIP_ID		0x4377
 #define BRCM_CC_4378_CHIP_ID		0x4378
 #define BRCM_CC_4387_CHIP_ID		0x4387
+#define BRCM_CC_4388_CHIP_ID		0x4388
 #define CY_CC_4373_CHIP_ID		0x4373
 #define CY_CC_43012_CHIP_ID		43012
 #define CY_CC_43439_CHIP_ID		43439
@@ -97,6 +98,7 @@
 #define BRCM_PCIE_4377_DEVICE_ID	0x4488
 #define BRCM_PCIE_4378_DEVICE_ID	0x4425
 #define BRCM_PCIE_4387_DEVICE_ID	0x4433
+#define BRCM_PCIE_4388_DEVICE_ID	0x4434
 
 /* brcmsmac IDs */
 #define BCM4313_D11N2G_ID	0x4727	/* 4313 802.11n 2.4G device */

From 17bd762f25149c5fdd1360063495d717fccf7463 Mon Sep 17 00:00:00 2001
From: Patrick Blass <patrickblass.dev@gmail.com>
Date: Sun, 3 Sep 2023 15:34:06 +0200
Subject: [PATCH 0689/1027] brcmfmac: Fix AP mode

Fix access point mode by bringing firmware into appropriate state before setting up the device.

Signed-off-by: Patrick Blass <patrickblass.dev@gmail.com>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 19 +++++++++++++++++++
 .../broadcom/brcm80211/include/brcmu_wifi.h   |  2 ++
 2 files changed, 21 insertions(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 254b3946c0e25d..2b9562675f9993 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -5132,6 +5132,25 @@ brcmf_cfg80211_start_ap(struct wiphy *wiphy, struct net_device *ndev,
 		  settings->inactivity_timeout);
 	dev_role = ifp->vif->wdev.iftype;
 	mbss = ifp->vif->mbss;
+	/* Bring firmware into correct state for AP mode*/
+	if (dev_role == NL80211_IFTYPE_AP) {
+		brcmf_dbg(TRACE, "set AP mode\n");
+		err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_AP, 1);
+		if (err < 0) {
+			bphy_err(drvr, "setting AP mode failed %d\n",
+				err);
+			goto exit;
+		}
+
+		bss_enable.bsscfgidx = cpu_to_le32(ifp->bsscfgidx);
+		bss_enable.enable = cpu_to_le32(WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE);
+		err = brcmf_fil_iovar_data_set(ifp, "bss", &bss_enable,
+							sizeof(bss_enable));
+		if (err < 0) {
+			bphy_err(drvr, "AP role set error, %d\n", err);
+			goto exit;
+		}
+	}
 
 	/* store current 11d setting */
 	if (brcmf_fil_cmd_int_get(ifp, BRCMF_C_GET_REGULATORY,
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
index 7552bdb91991ce..889dc7343899cf 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
@@ -94,6 +94,8 @@
 #define	WLC_BAND_2G			2	/* 2.4 Ghz */
 #define	WLC_BAND_ALL			3	/* all bands */
 
+#define WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE	2
+
 #define CHSPEC_CHANNEL(chspec)	((u8)((chspec) & WL_CHANSPEC_CHAN_MASK))
 #define CHSPEC_BAND(chspec)	((chspec) & WL_CHANSPEC_BAND_MASK)
 

From dbe23a887e7755d4cb06d5863c5c389dfa99ea0d Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Tue, 24 Oct 2023 09:49:40 -0400
Subject: [PATCH 0690/1027] [brcmfmac] Finish firmware mem map, fix heap start
 calculation bug.

This patch fixes the firmware memory map structure to be complete.
Along the way, we fix a failure to align the heap memory start address,
which causes failures with the newest apple wifi firmware.

With this patch, we can load the latest (sonoma 14.0 as of right now) apple wifi firmware.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/pcie.c        | 83 ++++++++++++-------
 1 file changed, 53 insertions(+), 30 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 4159b9c1529cee..2a8b5b83e03300 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -1861,35 +1861,58 @@ struct brcmf_rtlv_footer {
 	__le32 magic;
 };
 
-struct brcmf_fw_memmap {
-	u32 pad1[8];
-	u32 vstatus_start;
-	u32 vstatus_end;
-	u32 fw_start;
-	u32 fw_end;
-	u32 sig_start;
-	u32 sig_end;
-	u32 heap_start;
-	u32 heap_end;
-	u32 pad2[6];
+/** struct brcmf_fw_memmap_region - start/end of memory regions for chip
+ */
+struct brcmf_fw_memmap_region {
+	u32 start;
+	u32 end;
 };
 
+/** struct brcmf_fw_memmap
+ *
+ * @reset_vec - Reset vector - read only
+ * @int_vec - copied from ram, jumps here on success
+ * @rom - bootloader at rom start
+ * @mmap - struct/memory map written by host
+ * @vstatus - verification status
+ * @fw - firmware
+ * @sig - firwmare signature
+ * @heap - region for heap allocations
+ * @stack - region for stack allocations
+ * @prng - PRNG data, may be 0 length
+ * @nvram - NVRAM data
+ */
+struct brcmf_fw_memmap {
+	struct brcmf_fw_memmap_region reset_vec;
+	struct brcmf_fw_memmap_region int_vec;
+	struct brcmf_fw_memmap_region rom;
+	struct brcmf_fw_memmap_region mmap;
+	struct brcmf_fw_memmap_region vstatus;
+	struct brcmf_fw_memmap_region fw;
+	struct brcmf_fw_memmap_region sig;
+	struct brcmf_fw_memmap_region heap;
+	struct brcmf_fw_memmap_region stack;
+	struct brcmf_fw_memmap_region prng;
+	struct brcmf_fw_memmap_region nvram;
+};
 
 #define BRCMF_BL_HEAP_START_GAP		0x1000
 #define BRCMF_BL_HEAP_SIZE		0x10000
 #define BRCMF_RANDOM_SEED_MAGIC		0xfeedc0de
 #define BRCMF_RANDOM_SEED_LENGTH	0x100
-#define BRCMF_SIG_MAGIC			0xfeedfe51
+#define BRCMF_FW_SIG_MAGIC		0xfeedfe51
+#define BRCMF_NVRAM_SIG_MAGIC		0xfeedfe52
+#define BRCMF_MEMMAP_MAGIC		0xfeedfe53
 #define BRCMF_VSTATUS_MAGIC		0xfeedfe54
 #define BRCMF_VSTATUS_SIZE		0x28
-#define BRCMF_MEMMAP_MAGIC		0xfeedfe53
 #define BRCMF_END_MAGIC			0xfeed0e2d
 
-static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, size_t length)
+static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u32 type, u32 length)
 {
 	struct brcmf_bus *bus = dev_get_drvdata(&devinfo->pdev->dev);
-	u32 boundary = devinfo->ci->rambase + devinfo->fw_size +
-		BRCMF_BL_HEAP_START_GAP + BRCMF_BL_HEAP_SIZE;
+	u32 fw_top = devinfo->ci->rambase + devinfo->fw_size;
+	u32 ram_start = ALIGN(fw_top + BRCMF_BL_HEAP_START_GAP, 4);
+	u32 ram_end = ram_start + BRCMF_BL_HEAP_SIZE;
 	u32 start_addr;
 	struct brcmf_rtlv_footer footer = {
 		.magic = type,
@@ -1898,8 +1921,8 @@ static int brcmf_alloc_rtlv(struct brcmf_pciedev_info *devinfo, u32 *address, u3
 	length = ALIGN(length, 4);
 	start_addr = *address - length - sizeof(struct brcmf_rtlv_footer);
 
-	if (length > 0xffff || start_addr > *address || start_addr < boundary) {
-		brcmf_err(bus, "failed to allocate 0x%zx bytes for rTLV type 0x%x\n",
+	if (length > 0xffff || start_addr > *address || start_addr < ram_end) {
+		brcmf_err(bus, "failed to allocate 0x%x bytes for rTLV type 0x%x\n",
 			  length, type);
 		return -ENOMEM;
 	}
@@ -1950,32 +1973,32 @@ static int brcmf_pcie_add_signature(struct brcmf_pciedev_info *devinfo,
 
 	memset(&memmap, 0, sizeof(memmap));
 
-	memmap.sig_end = *address;
-	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_SIG_MAGIC, fwsig->size);
+	memmap.sig.end = *address;
+	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_FW_SIG_MAGIC, fwsig->size);
 	if (err)
 		return err;
-	memmap.sig_start = *address;
+	memmap.sig.start = *address;
 
-	memmap.vstatus_end = *address;
+	memmap.vstatus.end = *address;
 	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_VSTATUS_MAGIC, BRCMF_VSTATUS_SIZE);
 	if (err)
 		return err;
-	memmap.vstatus_start = *address;
+	memmap.vstatus.start = *address;
 
 	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_MEMMAP_MAGIC, sizeof(memmap));
 	if (err)
 		return err;
 
-	memmap.fw_start = devinfo->ci->rambase;
-	memmap.fw_end = memmap.fw_start + devinfo->fw_size;
-	memmap.heap_start = memmap.fw_end + BRCMF_BL_HEAP_START_GAP;
-	memmap.heap_end = memmap.heap_start + BRCMF_BL_HEAP_SIZE;
+	memmap.fw.start = devinfo->ci->rambase;
+	memmap.fw.end = memmap.fw.start + devinfo->fw_size;
+	memmap.heap.start = ALIGN(memmap.fw.end + BRCMF_BL_HEAP_START_GAP, 4);
+	memmap.heap.end = memmap.heap.start + BRCMF_BL_HEAP_SIZE;
 
-	if (memmap.heap_end > *address)
+	if (memmap.heap.end > *address)
 		return -ENOMEM;
 
-	memcpy_toio(devinfo->tcm + memmap.sig_start, fwsig->data, fwsig->size);
-	memset_io(devinfo->tcm + memmap.vstatus_start, 0, BRCMF_VSTATUS_SIZE);
+	memcpy_toio(devinfo->tcm + memmap.sig.start, fwsig->data, fwsig->size);
+	memset_io(devinfo->tcm + memmap.vstatus.start, 0, BRCMF_VSTATUS_SIZE);
 	memcpy_toio(devinfo->tcm + *address, &memmap, sizeof(memmap));
 
 	err = brcmf_alloc_rtlv(devinfo, address, BRCMF_END_MAGIC, 0);

From 7a1875df9170021b35251ef9a8f5a3a5911b2441 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sat, 14 Oct 2023 11:38:13 -0400
Subject: [PATCH 0691/1027] [brcmfmac] Add support for encoding/decoding 6g
 chanspecs

This patch adds support for 6G chanspecs, as part of adding 6G and
802.11ax support.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    |  1 -
 .../broadcom/brcm80211/brcmutil/d11.c         | 46 +++++++++++++++----
 .../broadcom/brcm80211/include/brcmu_d11.h    | 46 +++++++++++++------
 .../broadcom/brcm80211/include/brcmu_wifi.h   | 27 ++++++++---
 4 files changed, 89 insertions(+), 31 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 2b9562675f9993..31ea3f29a5c637 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -6997,7 +6997,6 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
 	if (band)
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
-
 	total = le32_to_cpu(list->count);
 	if (total > BRCMF_MAX_CHANSPEC_LIST) {
 		bphy_err(drvr, "Invalid count of channel Spec. (%u)\n",
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c
index 1e2b1e487eb76e..faf7eeeeb2d57e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmutil/d11.c
@@ -87,10 +87,20 @@ static void brcmu_d11ac_encchspec(struct brcmu_chan *ch)
 			0, d11ac_bw(ch->bw));
 
 	ch->chspec &= ~BRCMU_CHSPEC_D11AC_BND_MASK;
-	if (ch->chnum <= CH_MAX_2G_CHANNEL)
-		ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G;
-	else
+	switch (ch->band) {
+	case BRCMU_CHAN_BAND_6G:
+		ch->chspec |= BRCMU_CHSPEC_D11AC_BND_6G;
+		break;
+	case BRCMU_CHAN_BAND_5G:
 		ch->chspec |= BRCMU_CHSPEC_D11AC_BND_5G;
+		break;
+	case BRCMU_CHAN_BAND_2G:
+		ch->chspec |= BRCMU_CHSPEC_D11AC_BND_2G;
+		break;
+	default:
+		WARN_ONCE(1, "Invalid band 0x%04x\n", ch->band);
+		break;
+	}
 }
 
 static void brcmu_d11n_decchspec(struct brcmu_chan *ch)
@@ -117,7 +127,9 @@ static void brcmu_d11n_decchspec(struct brcmu_chan *ch)
 		}
 		break;
 	default:
-		WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+		WARN_ONCE(1,
+			  "Invalid chanspec - unknown 11n bandwidth 0x%04x\n",
+			  ch->chspec);
 		break;
 	}
 
@@ -129,7 +141,8 @@ static void brcmu_d11n_decchspec(struct brcmu_chan *ch)
 		ch->band = BRCMU_CHAN_BAND_2G;
 		break;
 	default:
-		WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+		WARN_ONCE(1, "Invalid chanspec - unknown 11n band 0x%04x\n",
+			  ch->chspec);
 		break;
 	}
 }
@@ -156,7 +169,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch)
 			ch->sb = BRCMU_CHAN_SB_U;
 			ch->control_ch_num += CH_10MHZ_APART;
 		} else {
-			WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+			WARN_ONCE(1,
+				  "Invalid chanspec - unknown 11ac channel distance 0x%04x\n",
+				  ch->chspec);
 		}
 		break;
 	case BRCMU_CHSPEC_D11AC_BW_80:
@@ -177,7 +192,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch)
 			ch->control_ch_num += CH_30MHZ_APART;
 			break;
 		default:
-			WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+			WARN_ONCE(1,
+				  "Invalid chanspec - unknown 11ac channel distance 0x%04x\n",
+				  ch->chspec);
 			break;
 		}
 		break;
@@ -211,17 +228,24 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch)
 			ch->control_ch_num += CH_70MHZ_APART;
 			break;
 		default:
-			WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+			WARN_ONCE(1,
+				  "Invalid chanspec - unknown 11ac channel distance 0x%04x\n",
+				  ch->chspec);
 			break;
 		}
 		break;
 	case BRCMU_CHSPEC_D11AC_BW_8080:
 	default:
-		WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+		WARN_ONCE(1,
+			  "Invalid chanspec - unknown 11ac channel bandwidth 0x%04x\n",
+			  ch->chspec);
 		break;
 	}
 
 	switch (ch->chspec & BRCMU_CHSPEC_D11AC_BND_MASK) {
+	case BRCMU_CHSPEC_D11AC_BND_6G:
+		ch->band = BRCMU_CHAN_BAND_6G;
+		break;
 	case BRCMU_CHSPEC_D11AC_BND_5G:
 		ch->band = BRCMU_CHAN_BAND_5G;
 		break;
@@ -229,7 +253,9 @@ static void brcmu_d11ac_decchspec(struct brcmu_chan *ch)
 		ch->band = BRCMU_CHAN_BAND_2G;
 		break;
 	default:
-		WARN_ONCE(1, "Invalid chanspec 0x%04x\n", ch->chspec);
+		WARN_ONCE(1,
+			  "Invalid chanspec - unknown 11ac channel band 0x%04x\n",
+			  ch->chspec);
 		break;
 	}
 }
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h
index f6344023855c36..bb48b744206223 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_d11.h
@@ -69,24 +69,44 @@
 #define  BRCMU_CHSPEC_D11AC_SB_UU	BRCMU_CHSPEC_D11AC_SB_LUU
 #define  BRCMU_CHSPEC_D11AC_SB_L	BRCMU_CHSPEC_D11AC_SB_LLL
 #define  BRCMU_CHSPEC_D11AC_SB_U	BRCMU_CHSPEC_D11AC_SB_LLU
+/* channel sideband indication for frequency >= 240MHz */
+#define BRCMU_CHSPEC_D11AC_320_SB_MASK	0x0780
+#define BRCMU_CHSPEC_D11AC_320_SB_SHIFT	7
+#define BRCMU_CHSPEC_D11AC_SB_LLLL	0x0000
+#define BRCMU_CHSPEC_D11AC_SB_LLLU	0x0080
+#define BRCMU_CHSPEC_D11AC_SB_LLUL	0x0100
+#define BRCMU_CHSPEC_D11AC_SB_LLUU	0x0180
+#define BRCMU_CHSPEC_D11AC_SB_LULL	0x0200
+#define BRCMU_CHSPEC_D11AC_SB_LULU	0x0280
+#define BRCMU_CHSPEC_D11AC_SB_LUUL	0x0300
+#define BRCMU_CHSPEC_D11AC_SB_LUUU	0x0380
+#define BRCMU_CHSPEC_D11AC_SB_ULLL	0x0400
+#define BRCMU_CHSPEC_D11AC_SB_ULLU	0x0480
+#define BRCMU_CHSPEC_D11AC_SB_ULUL	0x0500
+#define BRCMU_CHSPEC_D11AC_SB_ULUU	0x0580
+#define BRCMU_CHSPEC_D11AC_SB_UULL	0x0600
+#define BRCMU_CHSPEC_D11AC_SB_UULU	0x0680
+#define BRCMU_CHSPEC_D11AC_SB_UUUL	0x0700
+#define BRCMU_CHSPEC_D11AC_SB_UUUU	0x0780
 #define BRCMU_CHSPEC_D11AC_BW_MASK	0x3800
 #define BRCMU_CHSPEC_D11AC_BW_SHIFT	11
-#define  BRCMU_CHSPEC_D11AC_BW_5	0x0000
-#define  BRCMU_CHSPEC_D11AC_BW_10	0x0800
-#define  BRCMU_CHSPEC_D11AC_BW_20	0x1000
-#define  BRCMU_CHSPEC_D11AC_BW_40	0x1800
-#define  BRCMU_CHSPEC_D11AC_BW_80	0x2000
-#define  BRCMU_CHSPEC_D11AC_BW_160	0x2800
-#define  BRCMU_CHSPEC_D11AC_BW_8080	0x3000
-#define BRCMU_CHSPEC_D11AC_BND_MASK	0xc000
-#define BRCMU_CHSPEC_D11AC_BND_SHIFT	14
-#define  BRCMU_CHSPEC_D11AC_BND_2G	0x0000
-#define  BRCMU_CHSPEC_D11AC_BND_3G	0x4000
-#define  BRCMU_CHSPEC_D11AC_BND_4G	0x8000
-#define  BRCMU_CHSPEC_D11AC_BND_5G	0xc000
+#define BRCMU_CHSPEC_D11AC_BW_10    0x0800
+#define BRCMU_CHSPEC_D11AC_BW_20    0x1000
+#define BRCMU_CHSPEC_D11AC_BW_40    0x1800
+#define BRCMU_CHSPEC_D11AC_BW_80    0x2000
+#define BRCMU_CHSPEC_D11AC_BW_160   0x2800
+#define BRCMU_CHSPEC_D11AC_BW_320   0x0000
+#define BRCMU_CHSPEC_D11AC_BW_8080  0x3000
+#define BRCMU_CHSPEC_D11AC_BND_MASK 0xc000
+#define BRCMU_CHSPEC_D11AC_BND_SHIFT 14
+#define BRCMU_CHSPEC_D11AC_BND_2G   0x0000
+#define BRCMU_CHSPEC_D11AC_BND_4G   0x8000
+#define BRCMU_CHSPEC_D11AC_BND_5G   0xc000
+#define BRCMU_CHSPEC_D11AC_BND_6G   0x4000
 
 #define BRCMU_CHAN_BAND_2G		0
 #define BRCMU_CHAN_BAND_5G		1
+#define BRCMU_CHAN_BAND_6G		2
 
 enum brcmu_chan_bw {
 	BRCMU_CHAN_BW_20,
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
index 889dc7343899cf..e054b84443563e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
@@ -31,6 +31,7 @@
 /* bandstate array indices */
 #define BAND_2G_INDEX		0	/* wlc->bandstate[x] index */
 #define BAND_5G_INDEX		1	/* wlc->bandstate[x] index */
+#define BAND_6G_INDEX		2	/* wlc->bandstate[x] index */
 
 /*
  * max # supported channels. The max channel no is 216, this is that + 1
@@ -48,17 +49,22 @@
 #define WL_CHANSPEC_CTL_SB_UPPER	0x0200
 #define WL_CHANSPEC_CTL_SB_NONE		0x0300
 
-#define WL_CHANSPEC_BW_MASK		0x0C00
-#define WL_CHANSPEC_BW_SHIFT		    10
+#define WL_CHANSPEC_BW_MASK		0x3800
+#define WL_CHANSPEC_BW_SHIFT	11
 #define WL_CHANSPEC_BW_10		0x0400
 #define WL_CHANSPEC_BW_20		0x0800
 #define WL_CHANSPEC_BW_40		0x0C00
 #define WL_CHANSPEC_BW_80		0x2000
-
-#define WL_CHANSPEC_BAND_MASK		0xf000
-#define WL_CHANSPEC_BAND_SHIFT		12
-#define WL_CHANSPEC_BAND_5G		0x1000
-#define WL_CHANSPEC_BAND_2G		0x2000
+#define WL_CHANSPEC_BW_160	0x2800
+#define WL_CHANSPEC_BW_8080 0x3000
+#define WL_CHANSPEC_BW_320  0x0000
+
+#define WL_CHANSPEC_BAND_MASK		0xc000
+#define WL_CHANSPEC_BAND_SHIFT		14
+#define WL_CHANSPEC_BAND_2G		0x0000
+#define WL_CHANSPEC_BAND_4G		0x8000
+#define WL_CHANSPEC_BAND_5G		0xc000
+#define WL_CHANSPEC_BAND_6G		0x4000
 #define INVCHANSPEC			255
 
 #define WL_CHAN_VALID_HW		(1 << 0) /* valid with current HW */
@@ -93,6 +99,7 @@
 #define	WLC_BAND_5G			1	/* 5 Ghz */
 #define	WLC_BAND_2G			2	/* 2.4 Ghz */
 #define	WLC_BAND_ALL			3	/* all bands */
+#define WLC_BAND_6G			4	/* 6 Ghz */
 
 #define WLC_AP_IOV_OP_MANUAL_AP_BSSCFG_CREATE	2
 
@@ -114,6 +121,12 @@
 #define CHSPEC_IS80(chspec) \
 	(((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_80)
 
+#define CHSPEC_IS160(chspec) \
+	(((chspec) & WL_CHANSPEC_BW_MASK) == WL_CHANSPEC_BW_160)
+
+#define CHSPEC_IS6G(chspec) \
+	(((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_6G)
+
 #define CHSPEC_IS5G(chspec) \
 	(((chspec) & WL_CHANSPEC_BAND_MASK) == WL_CHANSPEC_BAND_5G)
 

From db9c02f6753011329e4865a6e27660cb16631ee3 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Mon, 9 Oct 2023 14:04:16 -0400
Subject: [PATCH 0692/1027] [brcmfmac] Dynamically configure VHT settings to
 match firmware

1. Correct VHT MCS settings to support as many tx/rx streams as chip
   does.

2. Correct VHT capabilities to support what all chips do.

3. Correct max AMPDU capabilities for VHT.

4. Support LDPC and STBC in VHT where available.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 50 +++++++++++++++----
 1 file changed, 41 insertions(+), 9 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 31ea3f29a5c637..9777b9118f89c3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7235,20 +7235,22 @@ static void brcmf_update_ht_cap(struct ieee80211_supported_band *band,
 	band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
 }
 
-static __le16 brcmf_get_mcs_map(u32 nchain, enum ieee80211_vht_mcs_support supp)
+static __le16 brcmf_get_mcs_map(u32 nstreams,
+				enum ieee80211_vht_mcs_support supp)
 {
 	u16 mcs_map;
 	int i;
 
-	for (i = 0, mcs_map = 0xFFFF; i < nchain; i++)
+	for (i = 0, mcs_map = 0xFFFF; i < nstreams; i++)
 		mcs_map = (mcs_map << 2) | supp;
 
 	return cpu_to_le16(mcs_map);
 }
 
 static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
-				 u32 bw_cap[2], u32 nchain, u32 txstreams,
-				 u32 txbf_bfe_cap, u32 txbf_bfr_cap)
+				 u32 bw_cap[2], u32 txstreams, u32 rxstreams,
+				 u32 txbf_bfe_cap, u32 txbf_bfr_cap,
+				 u32 ldpc_cap, u32 stbc_rx, u32 stbc_tx)
 {
 	__le16 mcs_map;
 
@@ -7257,6 +7259,21 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
 		return;
 
 	band->vht_cap.vht_supported = true;
+	band->vht_cap.vht_mcs.tx_highest = cpu_to_le16(433 * txstreams);
+	band->vht_cap.vht_mcs.rx_highest = cpu_to_le16(433 * rxstreams);
+
+	band->vht_cap.cap |= IEEE80211_VHT_CAP_RX_ANTENNA_PATTERN |
+			     IEEE80211_VHT_CAP_TX_ANTENNA_PATTERN;
+
+	if (ldpc_cap)
+		band->vht_cap.cap |= IEEE80211_VHT_CAP_RXLDPC;
+	if (stbc_tx)
+		band->vht_cap.cap |= IEEE80211_VHT_CAP_TXSTBC;
+
+	if (stbc_rx)
+		band->vht_cap.cap |=
+			(stbc_rx << IEEE80211_VHT_CAP_RXSTBC_SHIFT);
+
 	/* 80MHz is mandatory */
 	band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_80;
 	if (bw_cap[band->band] & WLC_BW_160MHZ_BIT) {
@@ -7264,8 +7281,10 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
 		band->vht_cap.cap |= IEEE80211_VHT_CAP_SHORT_GI_160;
 	}
 	/* all support 256-QAM */
-	mcs_map = brcmf_get_mcs_map(nchain, IEEE80211_VHT_MCS_SUPPORT_0_9);
+	mcs_map = brcmf_get_mcs_map(rxstreams, IEEE80211_VHT_MCS_SUPPORT_0_9);
 	band->vht_cap.vht_mcs.rx_mcs_map = mcs_map;
+	mcs_map = brcmf_get_mcs_map(txstreams, IEEE80211_VHT_MCS_SUPPORT_0_9);
+
 	band->vht_cap.vht_mcs.tx_mcs_map = mcs_map;
 
 	/* Beamforming support information */
@@ -7281,11 +7300,15 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
 	if ((txbf_bfe_cap || txbf_bfr_cap) && (txstreams > 1)) {
 		band->vht_cap.cap |=
 			(2 << IEEE80211_VHT_CAP_BEAMFORMEE_STS_SHIFT);
-		band->vht_cap.cap |= ((txstreams - 1) <<
-				IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT);
+		band->vht_cap.cap |=
+			((txstreams - 1)
+			 << IEEE80211_VHT_CAP_SOUNDING_DIMENSIONS_SHIFT);
 		band->vht_cap.cap |=
 			IEEE80211_VHT_CAP_VHT_LINK_ADAPTATION_VHT_MRQ_MFB;
 	}
+	/* AMPDU length limit, support max 1MB (2 ^ (13 + 7)) */
+	band->vht_cap.cap |=
+		(7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT);
 }
 
 static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
@@ -7302,10 +7325,17 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	s32 i;
 	struct ieee80211_supported_band *band;
 	u32 txstreams = 0;
+	u32 rxstreams = 0;
 	u32 txbf_bfe_cap = 0;
 	u32 txbf_bfr_cap = 0;
+	u32 ldpc_cap = 0;
+	u32 stbc_rx = 0;
+	u32 stbc_tx = 0;
 
 	(void)brcmf_fil_iovar_int_get(ifp, "vhtmode", &vhtmode);
+	(void)brcmf_fil_iovar_int_get(ifp, "ldpc_cap", &ldpc_cap);
+	(void)brcmf_fil_iovar_int_get(ifp, "stbc_rx", &stbc_rx);
+	(void)brcmf_fil_iovar_int_get(ifp, "stbc_tx", &stbc_tx);
 	err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode);
 	if (err) {
 		bphy_err(drvr, "nmode error (%d)\n", err);
@@ -7338,6 +7368,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	}
 
 	if (vhtmode) {
+		(void)brcmf_fil_iovar_int_get(ifp, "rxstreams", &rxstreams);
 		(void)brcmf_fil_iovar_int_get(ifp, "txstreams", &txstreams);
 		(void)brcmf_fil_iovar_int_get(ifp, "txbf_bfe_cap",
 					      &txbf_bfe_cap);
@@ -7353,8 +7384,9 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 		if (nmode)
 			brcmf_update_ht_cap(band, bw_cap, nchain);
 		if (vhtmode)
-			brcmf_update_vht_cap(band, bw_cap, nchain, txstreams,
-					     txbf_bfe_cap, txbf_bfr_cap);
+			brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams,
+					     txbf_bfe_cap, txbf_bfr_cap,
+					     ldpc_cap, stbc_rx, stbc_tx);
 	}
 
 	return 0;

From 6f139bb14f215c94eb3f5cc3068ceaa1af5f15cf Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Mon, 9 Oct 2023 19:19:45 -0400
Subject: [PATCH 0693/1027] [brcmfmac] Compute number of available antennas and
 set it in wiphy structure.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 35 +++++++++++++++----
 1 file changed, 28 insertions(+), 7 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 9777b9118f89c3..37a322d72ce8fe 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7220,7 +7220,7 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[])
 }
 
 static void brcmf_update_ht_cap(struct ieee80211_supported_band *band,
-				u32 bw_cap[2], u32 nchain)
+				u32 bw_cap[2], u32 nrxchain)
 {
 	band->ht_cap.ht_supported = true;
 	if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) {
@@ -7231,7 +7231,7 @@ static void brcmf_update_ht_cap(struct ieee80211_supported_band *band,
 	band->ht_cap.cap |= IEEE80211_HT_CAP_DSSSCCK40;
 	band->ht_cap.ampdu_factor = IEEE80211_HT_MAX_AMPDU_64K;
 	band->ht_cap.ampdu_density = IEEE80211_HT_MPDU_DENSITY_16;
-	memset(band->ht_cap.mcs.rx_mask, 0xff, nchain);
+	memset(band->ht_cap.mcs.rx_mask, 0xff, nrxchain);
 	band->ht_cap.mcs.tx_params = IEEE80211_HT_MCS_TX_DEFINED;
 }
 
@@ -7320,7 +7320,9 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	u32 vhtmode = 0;
 	u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT };
 	u32 rxchain;
-	u32 nchain;
+	u32 txchain;
+	u32 nrxchain;
+	u32 ntxchain;
 	int err;
 	s32 i;
 	struct ieee80211_supported_band *band;
@@ -7354,12 +7356,31 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 		else
 			bphy_err(drvr, "rxchain error (%d)\n", err);
 
-		nchain = 1;
+		nrxchain = 1;
+		rxchain = 1;
 	} else {
-		for (nchain = 0; rxchain; nchain++)
+		for (nrxchain = 0; rxchain; nrxchain++)
 			rxchain = rxchain & (rxchain - 1);
 	}
-	brcmf_dbg(INFO, "nchain=%d\n", nchain);
+	brcmf_dbg(INFO, "nrxchain=%d\n", nrxchain);
+	err = brcmf_fil_iovar_int_get(ifp, "txchain", &txchain);
+	if (err) {
+		/* rxchain unsupported by firmware of older chips */
+		if (err == -EBADE)
+			bphy_info_once(drvr, "rxchain unsupported\n");
+		else
+			bphy_err(drvr, "rxchain error (%d)\n", err);
+
+		ntxchain = 1;
+		txchain = 1;
+	} else {
+		for (ntxchain = 0; txchain; ntxchain++)
+			txchain = txchain & (txchain - 1);
+	}
+	brcmf_dbg(INFO, "ntxchain=%d\n", ntxchain);
+
+	wiphy->available_antennas_rx = nrxchain;
+	wiphy->available_antennas_tx = ntxchain;
 
 	err = brcmf_construct_chaninfo(cfg, bw_cap);
 	if (err) {
@@ -7382,7 +7403,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 			continue;
 
 		if (nmode)
-			brcmf_update_ht_cap(band, bw_cap, nchain);
+			brcmf_update_ht_cap(band, bw_cap, nrxchain);
 		if (vhtmode)
 			brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams,
 					     txbf_bfe_cap, txbf_bfr_cap,

From f80f7ff1156c8a93f4af853c3f4469185bc9f243 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Tue, 10 Oct 2023 09:42:36 -0400
Subject: [PATCH 0694/1027] [brcmfmac] Support GCMP cipher suite, used by WPA3.

This patch adds support for using GCMP/etc during offload
where supported by the firmware.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 132 +++++++++++++++++-
 .../broadcom/brcm80211/brcmfmac/feature.c     |   1 +
 .../broadcom/brcm80211/brcmfmac/feature.h     |   6 +
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  18 +++
 .../broadcom/brcm80211/include/brcmu_wifi.h   |   7 +
 5 files changed, 160 insertions(+), 4 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 37a322d72ce8fe..8c9f76b99892e3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -32,7 +32,9 @@
 #include "vendor.h"
 #include "bus.h"
 #include "common.h"
+#include "feature.h"
 #include "fwvid.h"
+#include "xtlv.h"
 
 #define BRCMF_SCAN_IE_LEN_MAX		2048
 
@@ -125,6 +127,13 @@ struct cca_msrmnt_query {
 	u32 time_req;
 };
 
+/* algo bit vector */
+#define KEY_ALGO_MASK(_algo)	(1 << (_algo))
+
+/* start enum value for BSS properties */
+#define WL_WSEC_INFO_BSS_BASE 0x0100
+#define WL_WSEC_INFO_BSS_ALGOS (WL_WSEC_INFO_BSS_BASE + 6)
+
 static bool check_vif_up(struct brcmf_cfg80211_vif *vif)
 {
 	if (!test_bit(BRCMF_VIF_STATUS_READY, &vif->sme_state)) {
@@ -237,16 +246,22 @@ static const struct ieee80211_regdomain brcmf_regdom = {
 /* Note: brcmf_cipher_suites is an array of int defining which cipher suites
  * are supported. A pointer to this array and the number of entries is passed
  * on to upper layers. AES_CMAC defines whether or not the driver supports MFP.
- * So the cipher suite AES_CMAC has to be the last one in the array, and when
- * device does not support MFP then the number of suites will be decreased by 1
+ * MFP support includes a few other suites,  so if MFP is not supported,
+ * then the number of suites will be decreased by 4
  */
 static const u32 brcmf_cipher_suites[] = {
 	WLAN_CIPHER_SUITE_WEP40,
 	WLAN_CIPHER_SUITE_WEP104,
 	WLAN_CIPHER_SUITE_TKIP,
 	WLAN_CIPHER_SUITE_CCMP,
-	/* Keep as last entry: */
-	WLAN_CIPHER_SUITE_AES_CMAC
+	WLAN_CIPHER_SUITE_CCMP_256,
+	WLAN_CIPHER_SUITE_GCMP,
+	WLAN_CIPHER_SUITE_GCMP_256,
+	/* Keep as last 4 entries: */
+	WLAN_CIPHER_SUITE_AES_CMAC,
+	WLAN_CIPHER_SUITE_BIP_CMAC_256,
+	WLAN_CIPHER_SUITE_BIP_GMAC_128,
+	WLAN_CIPHER_SUITE_BIP_GMAC_256
 };
 
 /* Vendor specific ie. id = 221, oui and type defines exact ie */
@@ -2010,6 +2025,48 @@ static s32 brcmf_set_auth_type(struct net_device *ndev,
 	return err;
 }
 
+static s32 brcmf_set_wsec_info_algos(struct brcmf_if *ifp, u32 algos, u32 mask)
+{
+	struct brcmf_pub *drvr = ifp->drvr;
+	s32 err = 0;
+	struct brcmf_wsec_info *wsec_info;
+	struct brcmf_xtlv *wsec_info_tlv;
+	u16 tlv_data_len;
+	u8 tlv_data[8];
+	u32 param_len;
+	u8 *buf;
+
+	brcmf_dbg(TRACE, "Enter\n");
+
+	buf = kzalloc(sizeof(struct brcmf_wsec_info) + sizeof(tlv_data),
+		      GFP_KERNEL);
+	if (!buf) {
+		bphy_err(drvr, "unable to allocate.\n");
+		return -ENOMEM;
+	}
+	wsec_info = (struct brcmf_wsec_info *)buf;
+	wsec_info->version = BRCMF_WSEC_INFO_VER;
+	wsec_info_tlv =
+		(struct brcmf_xtlv *)(buf +
+				      offsetof(struct brcmf_wsec_info, tlvs));
+	wsec_info->num_tlvs++;
+	tlv_data_len = sizeof(tlv_data);
+	memcpy(tlv_data, &algos, sizeof(algos));
+	memcpy(tlv_data + sizeof(algos), &mask, sizeof(mask));
+	brcmf_xtlv_pack_header(wsec_info_tlv, WL_WSEC_INFO_BSS_ALGOS,
+			       tlv_data_len, tlv_data, 0);
+
+	param_len = offsetof(struct brcmf_wsec_info, tlvs) +
+		    offsetof(struct brcmf_wsec_info_tlv, data) + tlv_data_len;
+
+	err = brcmf_fil_bsscfg_data_set(ifp, "wsec_info", buf, param_len);
+	if (err)
+		brcmf_err("set wsec_info_error:%d\n", err);
+
+	kfree(buf);
+	return err;
+}
+
 static s32
 brcmf_set_wsec_mode(struct net_device *ndev,
 		    struct cfg80211_connect_params *sme)
@@ -2022,6 +2079,8 @@ brcmf_set_wsec_mode(struct net_device *ndev,
 	s32 gval = 0;
 	s32 wsec;
 	s32 err = 0;
+	u32 algos = 0;
+	u32 mask = 0;
 
 	if (sme->crypto.n_ciphers_pairwise) {
 		switch (sme->crypto.ciphers_pairwise[0]) {
@@ -2038,6 +2097,15 @@ brcmf_set_wsec_mode(struct net_device *ndev,
 		case WLAN_CIPHER_SUITE_AES_CMAC:
 			pval = AES_ENABLED;
 			break;
+		case WLAN_CIPHER_SUITE_GCMP_256:
+			if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+				brcmf_err("This chip does not support GCMP\n");
+				return -EOPNOTSUPP;
+			}
+			pval = AES_ENABLED;
+			algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256);
+			mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM);
+			break;
 		default:
 			bphy_err(drvr, "invalid cipher pairwise (%d)\n",
 				 sme->crypto.ciphers_pairwise[0]);
@@ -2059,6 +2127,15 @@ brcmf_set_wsec_mode(struct net_device *ndev,
 		case WLAN_CIPHER_SUITE_AES_CMAC:
 			gval = AES_ENABLED;
 			break;
+		case WLAN_CIPHER_SUITE_GCMP_256:
+			if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+				brcmf_err("This chip does not support GCMP\n");
+				return -EOPNOTSUPP;
+			}
+			gval = AES_ENABLED;
+			algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256);
+			mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM);
+			break;
 		default:
 			bphy_err(drvr, "invalid cipher group (%d)\n",
 				 sme->crypto.cipher_group);
@@ -2067,6 +2144,7 @@ brcmf_set_wsec_mode(struct net_device *ndev,
 	}
 
 	brcmf_dbg(CONN, "pval (%d) gval (%d)\n", pval, gval);
+	brcmf_dbg(CONN, "algos (0x%x) mask (0x%x)\n", algos, mask);
 	/* In case of privacy, but no security and WPS then simulate */
 	/* setting AES. WPS-2.0 allows no security                   */
 	if (brcmf_find_wpsie(sme->ie, sme->ie_len) && !pval && !gval &&
@@ -2079,6 +2157,15 @@ brcmf_set_wsec_mode(struct net_device *ndev,
 		bphy_err(drvr, "error (%d)\n", err);
 		return err;
 	}
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+		brcmf_dbg(CONN, "set_wsec_info algos (0x%x) mask (0x%x)\n",
+			  algos, mask);
+		err = brcmf_set_wsec_info_algos(ifp, algos, mask);
+		if (err) {
+			brcmf_err("set wsec_info error (%d)\n", err);
+			return err;
+		}
+	}
 
 	sec = &profile->sec;
 	sec->cipher_pairwise = sme->crypto.ciphers_pairwise[0];
@@ -2793,6 +2880,8 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
 	s32 val;
 	s32 wsec;
 	s32 err;
+	u32 algos = 0;
+	u32 mask = 0;
 	u8 keybuf[8];
 	bool ext_key;
 
@@ -2876,6 +2965,30 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
 		val = AES_ENABLED;
 		brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_CCMP\n");
 		break;
+	case WLAN_CIPHER_SUITE_GCMP_256:
+		if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+			brcmf_err("the low layer not support GCMP\n");
+			err = -EOPNOTSUPP;
+			goto done;
+		}
+		key->algo = CRYPTO_ALGO_AES_GCM256;
+		val = AES_ENABLED;
+		brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_GCMP_256\n");
+		algos = KEY_ALGO_MASK(CRYPTO_ALGO_AES_GCM256);
+		mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM);
+		break;
+	case WLAN_CIPHER_SUITE_BIP_GMAC_256:
+		if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+			brcmf_err("the low layer not support GCMP\n");
+			err = -EOPNOTSUPP;
+			goto done;
+		}
+		key->algo = CRYPTO_ALGO_BIP_GMAC256;
+		val = AES_ENABLED;
+		algos = KEY_ALGO_MASK(CRYPTO_ALGO_BIP_GMAC256);
+		mask = algos | KEY_ALGO_MASK(CRYPTO_ALGO_AES_CCM);
+		brcmf_dbg(CONN, "WLAN_CIPHER_SUITE_BIP_GMAC_256\n");
+		break;
 	default:
 		bphy_err(drvr, "Invalid cipher (0x%x)\n", params->cipher);
 		err = -EINVAL;
@@ -2897,6 +3010,17 @@ brcmf_cfg80211_add_key(struct wiphy *wiphy, struct net_device *ndev,
 		bphy_err(drvr, "set wsec error (%d)\n", err);
 		goto done;
 	}
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_GCMP)) {
+		brcmf_dbg(CONN,
+			  "set_wsdec_info algos (0x%x) mask (0x%x)\n",
+			  algos, mask);
+		err = brcmf_set_wsec_info_algos(ifp, algos, mask);
+		if (err) {
+			brcmf_err("set wsec_info error (%d)\n", err);
+			return err;
+		}
+	}
+
 
 done:
 	brcmf_dbg(TRACE, "Exit\n");
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 4e4b37b83347f0..64af71d768a89c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -44,6 +44,7 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = {
 	{ BRCMF_FEAT_DOT11H, "802.11h" },
 	{ BRCMF_FEAT_SAE, "sae" },
 	{ BRCMF_FEAT_FWAUTH, "idauth" },
+	{ BRCMF_FEAT_GCMP, "gcmp"}
 };
 
 #ifdef DEBUG
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 622b01d3f86c8d..0712794f91ec09 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -31,6 +31,11 @@
  * FWAUTH: Firmware authenticator
  * DUMP_OBSS: Firmware has capable to dump obss info to support ACS
  * SCAN_V2: Version 2 scan params
+ * SCAN_v3: Version 3 scan params
+ * PMKID_V2: Version 2 PMKID
+ * PMKID_V3: Version 3 PMKID
+ * JOIN_V1: Version 1 join struct
+ * GCMP: GCMP Cipher suite support
  */
 #define BRCMF_FEAT_LIST \
 	BRCMF_FEAT_DEF(MBSS) \
@@ -60,6 +65,7 @@
 	BRCMF_FEAT_DEF(PMKID_V2) \
 	BRCMF_FEAT_DEF(PMKID_V3) \
 	BRCMF_FEAT_DEF(EVENT_MSGS_EXT) \
+	BRCMF_FEAT_DEF(GCMP)
 
 /*
  * Quirks:
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index a4ec3808a5c84c..27ec9a41433896 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1309,4 +1309,22 @@ struct brcmf_eventmsgs_ext_le {
 	u8	mask[];
 };
 
+/* version of the brcmf_wl_wsec_info structure */
+#define BRCMF_WSEC_INFO_VER 1
+
+/* tlv used to return wl_wsec_info properties */
+struct brcmf_wsec_info_tlv {
+	u16 type;
+	u16 len; /* data length */
+	u8 data[1]; /* data follows */
+};
+
+/* input/output data type for wsec_info iovar */
+struct brcmf_wsec_info {
+	u8 version; /* structure version */
+	u8 pad[2];
+	u8 num_tlvs;
+	struct brcmf_wsec_info_tlv tlvs[1]; /* tlv data follows */
+};
+
 #endif /* FWIL_TYPES_H_ */
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
index e054b84443563e..0ab1b95318e581 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
@@ -215,6 +215,13 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec)
 #define CRYPTO_ALGO_AES_RESERVED1	5
 #define CRYPTO_ALGO_AES_RESERVED2	6
 #define CRYPTO_ALGO_NALG		7
+#define CRYPTO_ALGO_AES_GCM     14  /* 128 bit GCM */
+#define CRYPTO_ALGO_AES_CCM256  15  /* 256 bit CCM */
+#define CRYPTO_ALGO_AES_GCM256  16  /* 256 bit GCM */
+#define CRYPTO_ALGO_BIP_CMAC256 17  /* 256 bit BIP CMAC */
+#define CRYPTO_ALGO_BIP_GMAC    18  /* 128 bit BIP GMAC */
+#define CRYPTO_ALGO_BIP_GMAC256 19  /* 256 bit BIP GMAC */
+
 
 /* wireless security bitvec */
 

From caebdc0753550f5e9349a8cb84ff830b9d7f8068 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 15 Oct 2023 11:11:26 -0400
Subject: [PATCH 0695/1027] [brcmfmac] Don't issue wrong insufficient headroom
 warning

We may just have had to clone a packet, and not actually
have run out of headroom.  Only issue warning about headroom
when we actually ran out of headroom.
This removes useless spam about needing 0 more bytes of headroom.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index abcd46e7120faa..1e0196c0e521fa 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -326,9 +326,11 @@ static netdev_tx_t brcmf_netdev_start_xmit(struct sk_buff *skb,
 	/* Make sure there's enough writeable headroom */
 	if (skb_headroom(skb) < drvr->hdrlen || skb_header_cloned(skb)) {
 		head_delta = max_t(int, drvr->hdrlen - skb_headroom(skb), 0);
-
-		brcmf_dbg(INFO, "%s: insufficient headroom (%d)\n",
-			  brcmf_ifname(ifp), head_delta);
+		/* Don't warn unless we actually ran out of headroom vs
+		   had to clone.*/
+		if (head_delta != 0)
+			brcmf_dbg(INFO, "%s: insufficient headroom (%d)\n",
+				  brcmf_ifname(ifp), head_delta);
 		atomic_inc(&drvr->bus_if->stats.pktcowed);
 		ret = pskb_expand_head(skb, ALIGN(head_delta, NET_SKB_PAD), 0,
 				       GFP_ATOMIC);

From 5e742ed312b2f25e7693e88703da22ef4052c862 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Wed, 18 Oct 2023 19:03:58 -0400
Subject: [PATCH 0696/1027] [brcmfmac] Support high power/low power/etc scan
 flags

This patch adds support for handling the scan flags that come from the
802.11 stack.  This enables the stack to control whether we are doing
high/low power scans, as well as other options.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 44 ++++++++++++++++++-
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  9 ++++
 2 files changed, 51 insertions(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 8c9f76b99892e3..6f68be0af84f03 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -1089,6 +1089,28 @@ static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2
 	       &params_v2_le->channel_list[0], params_size);
 }
 
+static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags)
+{
+	u32 scan_flags = 0;
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN;
+		brcmf_dbg(SCAN, "requested low span scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) {
+		scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY;
+		brcmf_dbg(SCAN, "requested high accuracy scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_POWER;
+		brcmf_dbg(SCAN, "requested low power scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO;
+		brcmf_dbg(SCAN, "requested low priority scan\n");
+	}
+	return scan_flags;
+}
+
 static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
 			     struct brcmf_if *ifp,
 			     struct brcmf_scan_params_v2_le *params_le,
@@ -1102,6 +1124,7 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
 	char *ptr;
 	int length;
 	struct brcmf_ssid_le ssid_le;
+	u32 scan_type = BRCMF_SCANTYPE_ACTIVE;
 
 	eth_broadcast_addr(params_le->bssid);
 
@@ -1114,7 +1137,6 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
 
 	params_le->bss_type = DOT11_BSSTYPE_ANY;
 	params_le->ssid_type = 0;
-	params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_ACTIVE);
 	params_le->channel_num = 0;
 	params_le->nprobes = cpu_to_le32(-1);
 	params_le->active_time = cpu_to_le32(-1);
@@ -1174,9 +1196,17 @@ static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
 		}
 	} else {
 		brcmf_dbg(SCAN, "Performing passive scan\n");
-		params_le->scan_type = cpu_to_le32(BRCMF_SCANTYPE_PASSIVE);
+		scan_type = BRCMF_SCANTYPE_PASSIVE;
 	}
+	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
+	params_le->scan_type = cpu_to_le32(scan_type);
 	params_le->length = cpu_to_le16(length);
+
+	/* Include RNR results if requested */
+	if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) {
+		params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR;
+	}
+
 	/* Adding mask to channel numbers */
 	params_le->channel_num =
 		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
@@ -7847,6 +7877,16 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 			wiphy_ext_feature_set(wiphy,
 					      NL80211_EXT_FEATURE_SAE_OFFLOAD_AP);
 	}
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) {
+		wiphy->features |= NL80211_FEATURE_SAE;
+	}
+
+	/* High accuracy and low power scans are always supported. */
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_HIGH_ACCURACY_SCAN);
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_LOW_POWER_SCAN);
+	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_LOW_SPAN_SCAN);
+	wiphy->features |= NL80211_FEATURE_LOW_PRIORITY_SCAN;
+
 	wiphy->mgmt_stypes = brcmf_txrx_stypes;
 	wiphy->max_remain_on_channel_duration = 5000;
 	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_PNO)) {
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 27ec9a41433896..70deae79286083 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -64,6 +64,15 @@
 #define BRCMF_SCANTYPE_ACTIVE		0
 #define BRCMF_SCANTYPE_PASSIVE		1
 
+/* Additional scanning flags */
+#define BRCMF_SCANFLAGS_LOW_PRIO 	0x2
+#define BRCMF_SCANFLAGS_LOW_POWER	0x1000
+#define BRCMF_SCANFLAGS_HIGH_ACCURACY	0x2000
+#define BRCMF_SCANFLAGS_LOW_SPAN	0x4000
+
+/* scan ssid_type flags */
+#define BRCMF_SCANSSID_INC_RNR		0x02 /* Include RNR channels*/
+
 #define BRCMF_WSEC_MAX_PSK_LEN		32
 #define	BRCMF_WSEC_PASSPHRASE		BIT(0)
 

From d5b808fd4dc3d5f014ff9d3650ed1ec43ba8b1e9 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 15 Oct 2023 08:59:44 -0400
Subject: [PATCH 0697/1027] [brcmfmac] Add support for 6G bands and HE

This patch adds support for 6G bands, along with HE capabilities,
as they are required to register 6G bands with wiphy.
This in turn, enables 802.11ax support for the other bands.

Scanning is not updated in this patch, so the bands are unused
except to be able to process what the firmware tells us.

Existing code is updated to handle all the bands rather than just 2g and
5g channels.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 373 +++++++++++++++---
 .../broadcom/brcm80211/brcmfmac/debug.h       |   2 +
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  92 +++++
 3 files changed, 414 insertions(+), 53 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 6f68be0af84f03..b047325dc8f91c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -188,6 +188,15 @@ static struct ieee80211_rate __wl_rates[] = {
 	.max_power		= 30,				\
 }
 
+#define CHAN6G(_channel) {					\
+	.band			= NL80211_BAND_6GHZ,		\
+	.center_freq		= ((_channel == 2) ? 5935 : 5950 + (5 * (_channel))),	\
+	.hw_value		= (_channel),			\
+	.max_antenna_gain	= 0,				\
+	.max_power		= 30,				\
+}
+
+
 static struct ieee80211_channel __wl_2ghz_channels[] = {
 	CHAN2G(1, 2412), CHAN2G(2, 2417), CHAN2G(3, 2422), CHAN2G(4, 2427),
 	CHAN2G(5, 2432), CHAN2G(6, 2437), CHAN2G(7, 2442), CHAN2G(8, 2447),
@@ -204,6 +213,23 @@ static struct ieee80211_channel __wl_5ghz_channels[] = {
 	CHAN5G(153), CHAN5G(157), CHAN5G(161), CHAN5G(165)
 };
 
+static struct ieee80211_channel __wl_6ghz_channels[] = {
+	CHAN6G(1),	CHAN6G(2),   CHAN6G(5),	  CHAN6G(9),   CHAN6G(13),
+	CHAN6G(17),	CHAN6G(21),  CHAN6G(25),  CHAN6G(29),  CHAN6G(33),
+	CHAN6G(37),	CHAN6G(41),  CHAN6G(45),  CHAN6G(49),  CHAN6G(53),
+	CHAN6G(57),	CHAN6G(61),  CHAN6G(65),  CHAN6G(69),  CHAN6G(73),
+	CHAN6G(77),	CHAN6G(81),  CHAN6G(85),  CHAN6G(89),  CHAN6G(93),
+	CHAN6G(97),	CHAN6G(101), CHAN6G(105), CHAN6G(109), CHAN6G(113),
+	CHAN6G(117),	CHAN6G(121), CHAN6G(125), CHAN6G(129), CHAN6G(133),
+	CHAN6G(137),	CHAN6G(141), CHAN6G(145), CHAN6G(149), CHAN6G(153),
+	CHAN6G(157),	CHAN6G(161), CHAN6G(165), CHAN6G(169), CHAN6G(173),
+	CHAN6G(177),	CHAN6G(181), CHAN6G(185), CHAN6G(189), CHAN6G(193),
+	CHAN6G(197),	CHAN6G(201), CHAN6G(205), CHAN6G(209), CHAN6G(213),
+	CHAN6G(217),	CHAN6G(221), CHAN6G(225), CHAN6G(229), CHAN6G(233),
+};
+
+struct ieee80211_sband_iftype_data sdata[NUM_NL80211_BANDS];
+
 /* Band templates duplicated per wiphy. The channel info
  * above is added to the band during setup.
  */
@@ -219,6 +245,12 @@ static const struct ieee80211_supported_band __wl_band_5ghz = {
 	.n_bitrates = wl_a_rates_size,
 };
 
+static const struct ieee80211_supported_band __wl_band_6ghz = {
+	.band = NL80211_BAND_6GHZ,
+	.bitrates = wl_a_rates,
+	.n_bitrates = wl_a_rates_size,
+};
+
 /* This is to override regulatory domains defined in cfg80211 module (reg.c)
  * By default world regulatory domain defined in reg.c puts the flags
  * NL80211_RRF_NO_IR for 5GHz channels (for * 36..48 and 149..165).
@@ -227,20 +259,22 @@ static const struct ieee80211_supported_band __wl_band_5ghz = {
  * domain are to be done here.
  */
 static const struct ieee80211_regdomain brcmf_regdom = {
-	.n_reg_rules = 4,
+	.n_reg_rules = 5,
 	.alpha2 =  "99",
 	.reg_rules = {
 		/* IEEE 802.11b/g, channels 1..11 */
-		REG_RULE(2412-10, 2472+10, 40, 6, 20, 0),
+		REG_RULE(2412 - 10, 2472 + 10, 40, 6, 20, 0),
 		/* If any */
 		/* IEEE 802.11 channel 14 - Only JP enables
 		 * this and for 802.11b only
 		 */
-		REG_RULE(2484-10, 2484+10, 20, 6, 20, 0),
+		REG_RULE(2484 - 10, 2484 + 10, 20, 6, 20, 0),
 		/* IEEE 802.11a, channel 36..64 */
-		REG_RULE(5150-10, 5350+10, 160, 6, 20, 0),
+		REG_RULE(5150 - 10, 5350 + 10, 160, 6, 20, 0),
 		/* IEEE 802.11a, channel 100..165 */
-		REG_RULE(5470-10, 5850+10, 160, 6, 20, 0), }
+		REG_RULE(5470 - 10, 5850 + 10, 160, 6, 20, 0),
+		/* IEEE 802.11ax, 6E */
+		REG_RULE(5935 - 10, 7115 + 10, 160, 6, 20, 0), }
 };
 
 /* Note: brcmf_cipher_suites is an array of int defining which cipher suites
@@ -332,6 +366,8 @@ static u8 nl80211_band_to_fwil(enum nl80211_band band)
 		return WLC_BAND_2G;
 	case NL80211_BAND_5GHZ:
 		return WLC_BAND_5G;
+	case NL80211_BAND_6GHZ:
+		return WLC_BAND_6G;
 	default:
 		WARN_ON(1);
 		break;
@@ -339,6 +375,23 @@ static u8 nl80211_band_to_fwil(enum nl80211_band band)
 	return 0;
 }
 
+static int nl80211_band_to_chanspec_band(enum nl80211_band band)
+{
+	switch (band) {
+	case NL80211_BAND_2GHZ:
+		return BRCMU_CHAN_BAND_2G;
+	case NL80211_BAND_5GHZ:
+		return BRCMU_CHAN_BAND_5G;
+	case NL80211_BAND_6GHZ:
+		return BRCMU_CHAN_BAND_6G;
+	case NL80211_BAND_60GHZ:
+	default:
+		WARN_ON_ONCE(1);
+		// Choose a safe default
+		return BRCMU_CHAN_BAND_2G;
+	}
+}
+
 static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
 			       struct cfg80211_chan_def *ch)
 {
@@ -398,17 +451,7 @@ static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
 	default:
 		WARN_ON_ONCE(1);
 	}
-	switch (ch->chan->band) {
-	case NL80211_BAND_2GHZ:
-		ch_inf.band = BRCMU_CHAN_BAND_2G;
-		break;
-	case NL80211_BAND_5GHZ:
-		ch_inf.band = BRCMU_CHAN_BAND_5G;
-		break;
-	case NL80211_BAND_60GHZ:
-	default:
-		WARN_ON_ONCE(1);
-	}
+	ch_inf.band = nl80211_band_to_chanspec_band(ch->chan->band);
 	d11inf->encchspec(&ch_inf);
 
 	brcmf_dbg(TRACE, "chanspec: 0x%x\n", ch_inf.chspec);
@@ -420,6 +463,7 @@ u16 channel_to_chanspec(struct brcmu_d11inf *d11inf,
 {
 	struct brcmu_chan ch_inf;
 
+	ch_inf.band = nl80211_band_to_chanspec_band(ch->band);
 	ch_inf.chnum = ieee80211_frequency_to_channel(ch->center_freq);
 	ch_inf.bw = BRCMU_CHAN_BW_20;
 	d11inf->encchspec(&ch_inf);
@@ -3489,6 +3533,7 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg,
 	struct cfg80211_bss *bss;
 	enum nl80211_band band;
 	struct brcmu_chan ch;
+	u16 chanspec;
 	u16 channel;
 	u32 freq;
 	u16 notify_capability;
@@ -3502,20 +3547,41 @@ static s32 brcmf_inform_single_bss(struct brcmf_cfg80211_info *cfg,
 		return -EINVAL;
 	}
 
+	chanspec = le16_to_cpu(bi->chanspec);
 	if (!bi->ctl_ch) {
-		ch.chspec = le16_to_cpu(bi->chanspec);
+		ch.chspec = chanspec;
 		cfg->d11inf.decchspec(&ch);
 		bi->ctl_ch = ch.control_ch_num;
 	}
 	channel = bi->ctl_ch;
 
-	if (channel <= CH_MAX_2G_CHANNEL)
-		band = NL80211_BAND_2GHZ;
-	else
+	if (CHSPEC_IS6G(chanspec))
+		band = NL80211_BAND_6GHZ;
+	else if (CHSPEC_IS5G(chanspec))
 		band = NL80211_BAND_5GHZ;
+	else
+		band = NL80211_BAND_2GHZ;
 
 	freq = ieee80211_channel_to_frequency(channel, band);
+	if (!freq) {
+		brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n",
+			  freq, channel, band, bi->chanspec);
+
+		/* We ignore this BSS ID rather than try to continue on.
+		 * Otherwise we will cause an OOPs because our frequency is 0.
+		 * The main case this occurs is some new frequency band
+		 * we have not seen before, and if we return an error,
+		 * we will cause the scan to fail.  It seems better to
+		 * report the error, skip this BSS, and move on.
+		 */
+		return 0;
+	}
 	bss_data.chan = ieee80211_get_channel(wiphy, freq);
+	if (!bss_data.chan) {
+		brcmf_err("Could not convert frequency into channel for channel %d, band %d, chanspec was %04x\n",
+			  channel, band, bi->chanspec);
+		return 0;
+	}
 	bss_data.boottime_ns = ktime_to_ns(ktime_get_boottime());
 
 	notify_capability = le16_to_cpu(bi->capability);
@@ -3604,7 +3670,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg,
 	buf = kzalloc(WL_BSS_INFO_MAX, GFP_KERNEL);
 	if (buf == NULL) {
 		err = -ENOMEM;
-		goto CleanUp;
+		goto cleanup;
 	}
 
 	*(__le32 *)buf = cpu_to_le32(WL_BSS_INFO_MAX);
@@ -3613,7 +3679,7 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg,
 				     buf, WL_BSS_INFO_MAX);
 	if (err) {
 		bphy_err(drvr, "WLC_GET_BSS_INFO failed: %d\n", err);
-		goto CleanUp;
+		goto cleanup;
 	}
 
 	bi = (struct brcmf_bss_info_le *)(buf + 4);
@@ -3623,10 +3689,18 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg,
 
 	if (ch.band == BRCMU_CHAN_BAND_2G)
 		band = wiphy->bands[NL80211_BAND_2GHZ];
-	else
+	else if (ch.band == BRCMU_CHAN_BAND_5G)
 		band = wiphy->bands[NL80211_BAND_5GHZ];
+	else
+		band = wiphy->bands[NL80211_BAND_6GHZ];
 
 	freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band);
+	if (freq == 0) {
+		brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n",
+			  freq, ch.control_ch_num, ch.band, bi->chanspec);
+		goto cleanup;
+	}
+
 	cfg->channel = freq;
 	notify_channel = ieee80211_get_channel(wiphy, freq);
 
@@ -3649,12 +3723,12 @@ static s32 brcmf_inform_ibss(struct brcmf_cfg80211_info *cfg,
 
 	if (!bss) {
 		err = -ENOMEM;
-		goto CleanUp;
+		goto cleanup;
 	}
 
 	cfg80211_put_bss(wiphy, bss);
 
-CleanUp:
+cleanup:
 
 	kfree(buf);
 
@@ -5890,6 +5964,9 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy,
 	case BRCMU_CHAN_BAND_5G:
 		band = NL80211_BAND_5GHZ;
 		break;
+	case BRCMU_CHAN_BAND_6G:
+		band = NL80211_BAND_6GHZ;
+		break;
 	}
 
 	switch (ch.bw) {
@@ -5911,9 +5988,19 @@ static int brcmf_cfg80211_get_channel(struct wiphy *wiphy,
 	}
 
 	freq = ieee80211_channel_to_frequency(ch.control_ch_num, band);
+	if (freq == 0) {
+		brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n",
+			  freq, ch.control_ch_num, ch.band, chanspec);
+		return -EINVAL;
+	}
 	chandef->chan = ieee80211_get_channel(wiphy, freq);
 	chandef->width = width;
 	chandef->center_freq1 = ieee80211_channel_to_frequency(ch.chnum, band);
+	if (chandef->center_freq1 == 0) {
+		brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n",
+			  freq, ch.chnum, ch.band, chanspec);
+		return -EINVAL;
+	}
 	chandef->center_freq2 = 0;
 
 	return 0;
@@ -6569,10 +6656,17 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg,
 
 	if (ch.band == BRCMU_CHAN_BAND_2G)
 		band = wiphy->bands[NL80211_BAND_2GHZ];
-	else
+	else if (ch.band == BRCMU_CHAN_BAND_5G)
 		band = wiphy->bands[NL80211_BAND_5GHZ];
+	else
+		band = wiphy->bands[NL80211_BAND_6GHZ];
 
 	freq = ieee80211_channel_to_frequency(ch.control_ch_num, band->band);
+	if (freq == 0) {
+		brcmf_err("Invalid frequency %d returned for channel %d, band %d. chanspec was %04x\n",
+			  freq, ch.control_ch_num, ch.band, bi->chanspec);
+		goto done;
+	}
 	notify_channel = ieee80211_get_channel(wiphy, freq);
 
 done:
@@ -7148,6 +7242,10 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
 	band = wiphy->bands[NL80211_BAND_5GHZ];
+	if (band)
+		for (i = 0; i < band->n_channels; i++)
+			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+	band = wiphy->bands[NL80211_BAND_6GHZ];
 	if (band)
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
@@ -7167,6 +7265,8 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
 			band = wiphy->bands[NL80211_BAND_2GHZ];
 		} else if (ch.band == BRCMU_CHAN_BAND_5G) {
 			band = wiphy->bands[NL80211_BAND_5GHZ];
+		} else if (ch.band == BRCMU_CHAN_BAND_6G) {
+			band = wiphy->bands[NL80211_BAND_6GHZ];
 		} else {
 			bphy_err(drvr, "Invalid channel Spec. 0x%x.\n",
 				 ch.chspec);
@@ -7332,7 +7432,7 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg)
 	return err;
 }
 
-static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[])
+static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
 	u32 band, mimo_bwcap;
@@ -7340,17 +7440,29 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[])
 
 	band = WLC_BAND_2G;
 	err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band);
-	if (!err) {
-		bw_cap[NL80211_BAND_2GHZ] = band;
-		band = WLC_BAND_5G;
-		err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &band);
-		if (!err) {
-			bw_cap[NL80211_BAND_5GHZ] = band;
-			return;
-		}
-		WARN_ON(1);
+	if (err)
+		goto fallback;
+	bw_cap[NL80211_BAND_2GHZ] = band;
+	band = WLC_BAND_5G;
+	err |= brcmf_fil_iovar_int_query(ifp, "bw_cap", &band);
+	if (err)
+		goto fallback;
+	bw_cap[NL80211_BAND_5GHZ] = band;
+	if (!has_6g)
 		return;
-	}
+	band = WLC_BAND_6G;
+	err |= brcmf_fil_iovar_int_query(ifp, "bw_cap", &band);
+	/* Prior to the introduction of 6g, this function only
+	 * did fallback in the case of 2g and 5g -failing.
+	 * As mimo_bwcap does not have 6g bwcap info anyway,
+	 * we keep that behavior.
+	 */
+	if (err)
+		return;
+	bw_cap[NL80211_BAND_6GHZ] = band;
+	return;
+fallback:
+
 	brcmf_dbg(INFO, "fallback to mimo_bw_cap info\n");
 	err = brcmf_fil_iovar_int_get(ifp, "mimo_bw_cap", &mimo_bwcap);
 	if (err)
@@ -7376,6 +7488,9 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[])
 static void brcmf_update_ht_cap(struct ieee80211_supported_band *band,
 				u32 bw_cap[2], u32 nrxchain)
 {
+	/* Not supported in 6G band */
+	if (band->band == NL80211_BAND_6GHZ)
+		return;
 	band->ht_cap.ht_supported = true;
 	if (bw_cap[band->band] & WLC_BW_40MHZ_BIT) {
 		band->ht_cap.cap |= IEEE80211_HT_CAP_SGI_40;
@@ -7408,8 +7523,8 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
 {
 	__le16 mcs_map;
 
-	/* not allowed in 2.4G band */
-	if (band->band == NL80211_BAND_2GHZ)
+	/* not allowed in 2.4G or 6G band */
+	if (band->band == NL80211_BAND_2GHZ || band->band == NL80211_BAND_6GHZ)
 		return;
 
 	band->vht_cap.vht_supported = true;
@@ -7465,6 +7580,120 @@ static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
 		(7 << IEEE80211_VHT_CAP_MAX_A_MPDU_LENGTH_EXPONENT_SHIFT);
 }
 
+static void brcmf_update_he_cap(struct ieee80211_supported_band *band,
+				struct ieee80211_sband_iftype_data *data)
+{
+	int idx = 1;
+	struct ieee80211_sta_he_cap *he_cap = &data->he_cap;
+	struct ieee80211_he_cap_elem *he_cap_elem = &he_cap->he_cap_elem;
+	struct ieee80211_he_mcs_nss_supp *he_mcs = &he_cap->he_mcs_nss_supp;
+	struct ieee80211_he_6ghz_capa *he_6ghz_capa = &data->he_6ghz_capa;
+
+	if (!data) {
+		brcmf_err("failed to allocate sdata\n");
+		return;
+	}
+
+	data->types_mask = BIT(NL80211_IFTYPE_STATION);
+	he_cap->has_he = true;
+
+	/* HE MAC Capabilities Information */
+	he_cap_elem->mac_cap_info[0] = IEEE80211_HE_MAC_CAP0_HTC_HE |
+				       IEEE80211_HE_MAC_CAP0_TWT_REQ |
+				       IEEE80211_HE_MAC_CAP0_TWT_RES;
+
+	he_cap_elem->mac_cap_info[1] =
+		IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_8US |
+		IEEE80211_HE_MAC_CAP1_TF_MAC_PAD_DUR_16US;
+
+	he_cap_elem->mac_cap_info[2] = IEEE80211_HE_MAC_CAP2_BSR |
+				       IEEE80211_HE_MAC_CAP2_BCAST_TWT;
+
+	he_cap_elem->mac_cap_info[3] =
+		IEEE80211_HE_MAC_CAP3_OMI_CONTROL |
+		IEEE80211_HE_MAC_CAP3_MAX_AMPDU_LEN_EXP_EXT_1 |
+		IEEE80211_HE_MAC_CAP3_FLEX_TWT_SCHED;
+
+	he_cap_elem->mac_cap_info[4] = IEEE80211_HE_MAC_CAP4_AMSDU_IN_AMPDU;
+
+	/* HE PHY Capabilities Information */
+	he_cap_elem->phy_cap_info[0] =
+		IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_IN_2G |
+		IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_40MHZ_80MHZ_IN_5G |
+		IEEE80211_HE_PHY_CAP0_CHANNEL_WIDTH_SET_160MHZ_IN_5G;
+	;
+
+	he_cap_elem->phy_cap_info[1] =
+		IEEE80211_HE_PHY_CAP1_LDPC_CODING_IN_PAYLOAD;
+
+	he_cap_elem->phy_cap_info[2] =
+		IEEE80211_HE_PHY_CAP2_NDP_4x_LTF_AND_3_2US |
+		IEEE80211_HE_PHY_CAP2_UL_MU_FULL_MU_MIMO |
+		IEEE80211_HE_PHY_CAP2_UL_MU_PARTIAL_MU_MIMO;
+
+	he_cap_elem->phy_cap_info[3] =
+		IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_TX_QPSK |
+		IEEE80211_HE_PHY_CAP3_DCM_MAX_TX_NSS_2 |
+		IEEE80211_HE_PHY_CAP3_DCM_MAX_CONST_RX_16_QAM |
+		IEEE80211_HE_PHY_CAP3_SU_BEAMFORMER;
+
+	he_cap_elem->phy_cap_info[4] =
+		IEEE80211_HE_PHY_CAP4_SU_BEAMFORMEE |
+		IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_MASK |
+		IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_ABOVE_80MHZ_4 |
+		IEEE80211_HE_PHY_CAP4_BEAMFORMEE_MAX_STS_UNDER_80MHZ_8;
+
+	he_cap_elem->phy_cap_info[5] =
+		IEEE80211_HE_PHY_CAP5_NG16_SU_FEEDBACK |
+		IEEE80211_HE_PHY_CAP5_NG16_MU_FEEDBACK |
+		IEEE80211_HE_PHY_CAP5_BEAMFORMEE_NUM_SND_DIM_UNDER_80MHZ_2;
+
+	he_cap_elem->phy_cap_info[6] =
+		IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_42_SU |
+		IEEE80211_HE_PHY_CAP6_CODEBOOK_SIZE_75_MU |
+		IEEE80211_HE_PHY_CAP6_TRIG_SU_BEAMFORMING_FB |
+		IEEE80211_HE_PHY_CAP6_TRIG_MU_BEAMFORMING_PARTIAL_BW_FB |
+		IEEE80211_HE_PHY_CAP6_TRIG_CQI_FB |
+		IEEE80211_HE_PHY_CAP6_PARTIAL_BW_EXT_RANGE |
+		IEEE80211_HE_PHY_CAP6_PPE_THRESHOLD_PRESENT;
+
+	he_cap_elem->phy_cap_info[7] =
+		IEEE80211_HE_PHY_CAP7_HE_SU_MU_PPDU_4XLTF_AND_08_US_GI |
+		IEEE80211_HE_PHY_CAP7_MAX_NC_1;
+
+	he_cap_elem->phy_cap_info[8] =
+		IEEE80211_HE_PHY_CAP8_HE_ER_SU_PPDU_4XLTF_AND_08_US_GI |
+		IEEE80211_HE_PHY_CAP8_20MHZ_IN_40MHZ_HE_PPDU_IN_2G |
+		IEEE80211_HE_PHY_CAP8_20MHZ_IN_160MHZ_HE_PPDU |
+		IEEE80211_HE_PHY_CAP8_80MHZ_IN_160MHZ_HE_PPDU;
+
+	he_cap_elem->phy_cap_info[9] =
+		IEEE80211_HE_PHY_CAP9_TX_1024_QAM_LESS_THAN_242_TONE_RU |
+		IEEE80211_HE_PHY_CAP9_RX_1024_QAM_LESS_THAN_242_TONE_RU |
+		IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_COMP_SIGB |
+		IEEE80211_HE_PHY_CAP9_RX_FULL_BW_SU_USING_MU_WITH_NON_COMP_SIGB;
+
+	/* HE Supported MCS and NSS Set */
+	he_mcs->rx_mcs_80 = cpu_to_le16(0xfffa);
+	he_mcs->tx_mcs_80 = cpu_to_le16(0xfffa);
+	he_mcs->rx_mcs_160 = cpu_to_le16(0xfffa);
+	he_mcs->tx_mcs_160 = cpu_to_le16(0xfffa);
+	/* HE 6 GHz band capabilities */
+	if (band->band == NL80211_BAND_6GHZ) {
+		u16 capa = 0;
+
+		capa = FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MIN_MPDU_START,
+				  IEEE80211_HT_MPDU_DENSITY_8) |
+		       FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_AMPDU_LEN_EXP,
+				  IEEE80211_VHT_MAX_AMPDU_1024K) |
+		       FIELD_PREP(IEEE80211_HE_6GHZ_CAP_MAX_MPDU_LEN,
+				  IEEE80211_VHT_CAP_MAX_MPDU_LENGTH_11454);
+		he_6ghz_capa->capa = cpu_to_le16(capa);
+	}
+	band->n_iftype_data = idx;
+	band->iftype_data = data;
+}
+
 static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 {
 	struct brcmf_pub *drvr = cfg->pub;
@@ -7472,7 +7701,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	struct wiphy *wiphy = cfg_to_wiphy(cfg);
 	u32 nmode;
 	u32 vhtmode = 0;
-	u32 bw_cap[2] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT };
+	/* 2GHZ, 5GHZ, 60GHZ, 6GHZ */
+	u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT, 0, 0 };
 	u32 rxchain;
 	u32 txchain;
 	u32 nrxchain;
@@ -7484,6 +7714,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	u32 rxstreams = 0;
 	u32 txbf_bfe_cap = 0;
 	u32 txbf_bfr_cap = 0;
+	u8 he_enable;
+	struct brcmf_he_defcap he_cap;
 	u32 ldpc_cap = 0;
 	u32 stbc_rx = 0;
 	u32 stbc_tx = 0;
@@ -7492,15 +7724,26 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	(void)brcmf_fil_iovar_int_get(ifp, "ldpc_cap", &ldpc_cap);
 	(void)brcmf_fil_iovar_int_get(ifp, "stbc_rx", &stbc_rx);
 	(void)brcmf_fil_iovar_int_get(ifp, "stbc_tx", &stbc_tx);
+	err = brcmf_fil_xtlv_int8_get(ifp, "he", BRCMF_HE_CMD_ENABLE,
+				      &he_enable);
+	if (!err && he_enable) {
+		brcmf_fil_xtlv_data_get(ifp, "he", BRCMF_HE_CMD_DEFCAP, &he_cap,
+					sizeof(he_cap));
+		brcmf_dbg_hex_dump(BRCMF_INFO_ON(), he_cap.mac_cap, 6,
+				   "default HE mac cap\n");
+		brcmf_dbg_hex_dump(BRCMF_INFO_ON(), he_cap.phy_cap, 11,
+				   "default HE phy cap\n");
+	}
 	err = brcmf_fil_iovar_int_get(ifp, "nmode", &nmode);
 	if (err) {
 		bphy_err(drvr, "nmode error (%d)\n", err);
-	} else {
-		brcmf_get_bwcap(ifp, bw_cap);
 	}
-	brcmf_dbg(INFO, "nmode=%d, vhtmode=%d, bw_cap=(%d, %d)\n",
+	brcmf_get_bwcap(ifp, bw_cap, he_enable != 0);
+	brcmf_dbg(INFO,
+		  "nmode=%d, vhtmode=%d, bw_cap=(%d, %d, %d), he_enable=%d\n",
 		  nmode, vhtmode, bw_cap[NL80211_BAND_2GHZ],
-		  bw_cap[NL80211_BAND_5GHZ]);
+		  bw_cap[NL80211_BAND_5GHZ], bw_cap[NL80211_BAND_6GHZ],
+		  he_enable);
 
 	err = brcmf_fil_iovar_int_get(ifp, "rxchain", &rxchain);
 	if (err) {
@@ -7562,6 +7805,8 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 			brcmf_update_vht_cap(band, bw_cap, txstreams, rxstreams,
 					     txbf_bfe_cap, txbf_bfr_cap,
 					     ldpc_cap, stbc_rx, stbc_tx);
+		if (he_enable)
+			brcmf_update_he_cap(band, &sdata[band->band]);
 	}
 
 	return 0;
@@ -7942,12 +8187,27 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 			band->n_channels = ARRAY_SIZE(__wl_5ghz_channels);
 			wiphy->bands[NL80211_BAND_5GHZ] = band;
 		}
-	}
+		if (bandlist[i] == cpu_to_le32(WLC_BAND_6G)) {
+			band = kmemdup(&__wl_band_6ghz, sizeof(__wl_band_6ghz),
+				       GFP_KERNEL);
+			if (!band)
+				return -ENOMEM;
 
+			band->channels = kmemdup(&__wl_6ghz_channels,
+						 sizeof(__wl_6ghz_channels),
+						 GFP_KERNEL);
+			if (!band->channels) {
+				kfree(band);
+				return -ENOMEM;
+			}
+
+			band->n_channels = ARRAY_SIZE(__wl_6ghz_channels);
+			wiphy->bands[NL80211_BAND_6GHZ] = band;
+		}
+	}
 	if (wiphy->bands[NL80211_BAND_5GHZ] &&
 	    brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DOT11H))
-		wiphy_ext_feature_set(wiphy,
-				      NL80211_EXT_FEATURE_DFS_OFFLOAD);
+		wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_DFS_OFFLOAD);
 
 	wiphy_ext_feature_set(wiphy, NL80211_EXT_FEATURE_CQM_RSSI_LIST);
 
@@ -8484,6 +8744,10 @@ static void brcmf_free_wiphy(struct wiphy *wiphy)
 		kfree(wiphy->bands[NL80211_BAND_5GHZ]->channels);
 		kfree(wiphy->bands[NL80211_BAND_5GHZ]);
 	}
+	if (wiphy->bands[NL80211_BAND_6GHZ]) {
+		kfree(wiphy->bands[NL80211_BAND_6GHZ]->channels);
+		kfree(wiphy->bands[NL80211_BAND_6GHZ]);
+	}
 #if IS_ENABLED(CONFIG_PM)
 	if (wiphy->wowlan != &brcmf_wowlan_support)
 		kfree(wiphy->wowlan);
@@ -8575,18 +8839,21 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
 	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_DUMP_OBSS))
 		ops->dump_survey = brcmf_cfg80211_dump_survey;
 
-	err = wiphy_register(wiphy);
-	if (err < 0) {
-		bphy_err(drvr, "Could not register wiphy device (%d)\n", err);
-		goto priv_out;
-	}
-
+	/* We have to configure the bands before we register the wiphy device
+	 * because it requires that band capabilities be correct.
+	 */
 	err = brcmf_setup_wiphybands(cfg);
 	if (err) {
 		bphy_err(drvr, "Setting wiphy bands failed (%d)\n", err);
 		goto wiphy_unreg_out;
 	}
 
+	err = wiphy_register(wiphy);
+	if (err < 0) {
+		bphy_err(drvr, "Could not register wiphy device (%d)\n", err);
+		goto priv_out;
+	}
+
 	/* If cfg80211 didn't disable 40MHz HT CAP in wiphy_register(),
 	 * setup 40MHz in 2GHz band and enable OBSS scanning.
 	 */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
index 9bb5f709d41a27..432d93ae8fb854 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/debug.h
@@ -85,6 +85,7 @@ do {								\
 #define BRCMF_FIL_ON()		(brcmf_msg_level & BRCMF_FIL_VAL)
 #define BRCMF_FWCON_ON()	(brcmf_msg_level & BRCMF_FWCON_VAL)
 #define BRCMF_SCAN_ON()		(brcmf_msg_level & BRCMF_SCAN_VAL)
+#define BRCMF_INFO_ON()		(brcmf_msg_level & BRCMF_INFO_VAL)
 
 #else /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */
 
@@ -104,6 +105,7 @@ do {								\
 #define BRCMF_FIL_ON()		0
 #define BRCMF_FWCON_ON()	0
 #define BRCMF_SCAN_ON()		0
+#define BRCMF_INFO_ON()		0
 
 #endif /* defined(DEBUG) || defined(CONFIG_BRCM_TRACING) */
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 70deae79286083..d8f8101c625258 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1336,4 +1336,96 @@ struct brcmf_wsec_info {
 	struct brcmf_wsec_info_tlv tlvs[1]; /* tlv data follows */
 };
 
+/* HE top level command IDs */
+enum {
+	BRCMF_HE_CMD_ENABLE = 0,
+	BRCMF_HE_CMD_FEATURES = 1,
+	BRCMF_HE_CMD_SR = 2,
+	BRCMF_HE_CMD_TESTBED = 3,
+	BRCMF_HE_CMD_BSR_SUPPORT = 4,
+	BRCMF_HE_CMD_BSSCOLOR = 5,
+	BRCMF_HE_CMD_PARTIAL_BSSCOLOR = 6,
+	BRCMF_HE_CMD_CAP = 7,
+	BRCMF_HE_CMD_OMI = 8,
+	BRCMF_HE_CMD_RANGE_EXT = 9,
+	BRCMF_HE_CMD_RTSDURTHRESH = 10,
+	BRCMF_HE_CMD_PEDURATION = 11,
+	BRCMF_HE_CMD_MUEDCA = 12,
+	BRCMF_HE_CMD_DYNFRAG = 13,
+	BRCMF_HE_CMD_PPET = 14,
+	BRCMF_HE_CMD_HTC = 15,
+	BRCMF_HE_CMD_AXMODE = 16,
+	BRCMF_HE_CMD_FRAGTX = 17,
+	BRCMF_HE_CMD_DEFCAP = 18,
+};
+
+#define BRCMF_HE_VER_1 1
+
+struct brcmf_he_bsscolor {
+	u8 color; /* 1..63, on get returns currently in use color */
+	u8 disabled; /* 0/1, 0 means disabled is false, so coloring is enabled */
+	u8 switch_count; /* 0, immediate programming, 1 .. 255 beacon count down */
+	u8 PAD;
+};
+
+struct brcmf_he_omi {
+	u8 peer[ETH_ALEN]; /* leave it all 0s' for non-AP */
+	u8 rx_nss; /* 0..7 */
+	u8 channel_width; /* 0:20, 1:40, 2:80, 3:160 */
+	u8 ul_mu_disable; /* 0|1 */
+	u8 tx_nsts; /* 0..7 */
+	u8 er_su_disable; /* 0|1 */
+	u8 dl_mumimo_resound; /* 0|1 */
+	u8 ul_mu_data_disable; /* 0|1 */
+	u8 tx_override; /* 0, only used for testbed AP */
+	u8 PAD[2];
+};
+
+struct brcmf_he_edca_v1 {
+	u8 aci_aifsn;
+	u8 ecw_min_max;
+	u8 muedca_timer;
+	u8 PAD;
+};
+
+#define BRCMF_AC_COUNT 4
+struct brcmf_he_muedca_v1 {
+	/* structure control */
+	__le16 version; /* structure version */
+	__le16 length; /* data length (starting after this field) */
+	struct brcmf_he_edca_v1 ac_param_ap[BRCMF_AC_COUNT];
+	struct brcmf_he_edca_v1 ac_param_sta[BRCMF_AC_COUNT];
+};
+
+#define BRCMF_HE_SR_VER_1 1
+
+#define SRC_PSR_DIS 0x01
+#define SRC_NON_SRG_OBSS_PD_SR_DIS 0x02
+#define SRC_NON_SRG_OFFSET_PRESENT 0x04
+#define SRC_SRG_INFORMATION_PRESENT 0x08
+#define SRC_HESIGA_SPATIAL_REUSE_VALUE15_ALLOWED 0x10
+
+#define HE_SR_SRG_INFO_LEN 18
+
+struct brcmf_he_sr_v1 {
+	/* structure control */
+	__le16 version; /* structure version */
+	__le16 length; /* data length (starting after this field) */
+	u8 enabled;
+	u8 src; /* SR control, see above defines. */
+	u8 non_srg_offset; /* Non-SRG Offset */
+	u8 srg[HE_SR_SRG_INFO_LEN]; /* SRG Information */
+};
+
+#define BRCMF_HE_DEFCAP_VER_1 1
+
+struct brcmf_he_defcap {
+	__le16 version; /* structure version */
+	__le16 length; /* data length (starting after this field) */
+	u8 bsscfg_type;
+	u8 bsscfg_subtype;
+	u8 mac_cap[6];
+	u8 phy_cap[11];
+};
+
 #endif /* FWIL_TYPES_H_ */

From dde25bf3758f77c073471272fbfcc2583efda250 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Wed, 18 Oct 2023 23:30:49 -0400
Subject: [PATCH 0698/1027] [brcmfmac] Fix regulatory domain handling to reset
 bands properly

Currently, we ignore the default country in the reg notifier.
We also register a custom regulatory domain, which is set
as the default.
As a result, the chip is likely to be set to the correct country,
but the regulatory domain will not match it.

When the regulatory notifier is then called, we see the countries
are the same and do not change anything, even though the domain
is wrong.

This patch forces us to reset the bands on the first country change
even if the chip is already set to that country.

We also restore the original band info before reconstructing channel
info, as the new regdom power limits may be higher than what is
currently set.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 37 ++++++++++++++++---
 .../broadcom/brcm80211/brcmfmac/cfg80211.h    |  2 +
 2 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index b047325dc8f91c..8c4e4c69630d58 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7237,18 +7237,34 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
 		goto fail_pbuf;
 	}
 
+	/* Changing regulatory domain may change power limits upwards.
+	 * To ensure that we correctly set the new band info, copy the original
+	 * info first.
+	 */
 	band = wiphy->bands[NL80211_BAND_2GHZ];
-	if (band)
+	if (band) {
+		memcpy(band->channels, &__wl_2ghz_channels,
+		       sizeof(__wl_2ghz_channels));
+		band->n_channels = ARRAY_SIZE(__wl_2ghz_channels);
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+	}
 	band = wiphy->bands[NL80211_BAND_5GHZ];
-	if (band)
+	if (band) {
+		memcpy(band->channels, &__wl_5ghz_channels,
+		       sizeof(__wl_5ghz_channels));
+		band->n_channels = ARRAY_SIZE(__wl_5ghz_channels);
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+	}
 	band = wiphy->bands[NL80211_BAND_6GHZ];
-	if (band)
+	if (band) {
+		memcpy(band->channels, &__wl_6ghz_channels,
+		       sizeof(__wl_6ghz_channels));
+		band->n_channels = ARRAY_SIZE(__wl_6ghz_channels);
 		for (i = 0; i < band->n_channels; i++)
 			band->channels[i].flags = IEEE80211_CHAN_DISABLED;
+	}
 	total = le32_to_cpu(list->count);
 	if (total > BRCMF_MAX_CHANSPEC_LIST) {
 		bphy_err(drvr, "Invalid count of channel Spec. (%u)\n",
@@ -8713,9 +8729,17 @@ static void brcmf_cfg80211_reg_notifier(struct wiphy *wiphy,
 	}
 
 	err = brcmf_translate_country_code(ifp->drvr, req->alpha2, &ccreq);
-	if (err)
-		return;
-
+	if (err) {
+		/* Because we ignore the default country code above,
+		 * we will start out in our custom reg domain, but the chip
+		 * may already be set to the right country.
+		 * As such, we force the bands to be re-set the first
+		 * time we try to set a country for real.
+		 */
+		if (err != -EAGAIN || !cfg->force_band_setup)
+			return;
+	}
+	cfg->force_band_setup = false;
 	err = brcmf_fil_iovar_data_set(ifp, "country", &ccreq, sizeof(ccreq));
 	if (err) {
 		bphy_err(drvr, "Firmware rejected country setting\n");
@@ -8782,6 +8806,7 @@ struct brcmf_cfg80211_info *brcmf_cfg80211_attach(struct brcmf_pub *drvr,
 	cfg->pub = drvr;
 	init_vif_event(&cfg->vif_event);
 	INIT_LIST_HEAD(&cfg->vif_list);
+	cfg->force_band_setup = true;
 
 	vif = brcmf_alloc_vif(cfg, NL80211_IFTYPE_STATION);
 	if (IS_ERR(vif))
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index dc3a6a537507d1..b54ad4b524d395 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -327,6 +327,7 @@ struct brcmf_cfg80211_wowl {
  * @dongle_up: indicate whether dongle up or not.
  * @roam_on: on/off switch for dongle self-roaming.
  * @scan_tried: indicates if first scan attempted.
+ * @force_band_setup: indicates if we should force band setup
  * @dcmd_buf: dcmd buffer.
  * @extra_buf: mainly to grab assoc information.
  * @debugfsdir: debugfs folder for this device.
@@ -357,6 +358,7 @@ struct brcmf_cfg80211_info {
 	bool pwr_save;
 	bool dongle_up;
 	bool scan_tried;
+	bool force_band_setup;
 	u8 *dcmd_buf;
 	u8 *extra_buf;
 	struct dentry *debugfsdir;

From da201c6016dfbdf76080ed95bfa140471423036e Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 12 Nov 2023 15:49:54 -0500
Subject: [PATCH 0699/1027] fixup! fix FWIL definition to use SSID length
 constant

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index d8f8101c625258..b8376ec39e4340 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -332,7 +332,7 @@ struct brcmf_bss_info_le {
 	__le16 beacon_period;	/* units are Kusec */
 	__le16 capability;	/* Capability information */
 	u8 SSID_len;
-	u8 SSID[32];
+	u8 SSID[IEEE80211_MAX_SSID_LEN];
 	u8 bcnflags;		/* additional flags w.r.t. beacon */
 	struct {
 		__le32 count;   /* # rates in this set */

From eeded0551c57b18d4a6d2721dbe6e0dd4189aab2 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 12 Nov 2023 15:50:57 -0500
Subject: [PATCH 0700/1027] fixup! define missing event message extension

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 0712794f91ec09..99867f76df376f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -34,6 +34,7 @@
  * SCAN_v3: Version 3 scan params
  * PMKID_V2: Version 2 PMKID
  * PMKID_V3: Version 3 PMKID
+ * EVENT_MSGS_EXT: Event messages extension
  * JOIN_V1: Version 1 join struct
  * GCMP: GCMP Cipher suite support
  */

From d7842ec24051146a04653879996bea70f430e169 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 12 Nov 2023 15:46:08 -0500
Subject: [PATCH 0701/1027] [brcmfmac] Structurize PNF scan and add support for
 latest version

This patch structurizes PNF scan handling, adding support for
netinfo v3 and PNO v3 structures.

This in turn, enables the chip to tell us about 6G scan results,
as the results contain chanspecs and not just channels.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 123 +++-----
 .../broadcom/brcm80211/brcmfmac/cfg80211.h    |  17 +
 .../broadcom/brcm80211/brcmfmac/core.h        |  20 ++
 .../broadcom/brcm80211/brcmfmac/feature.c     |  12 +
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  70 +++++
 .../broadcom/brcm80211/brcmfmac/pno.c         | 294 +++++++++++++++++-
 .../broadcom/brcm80211/brcmfmac/pno.h         |  10 +-
 7 files changed, 456 insertions(+), 90 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 8c4e4c69630d58..470ed48f55fe9b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -3980,17 +3980,11 @@ brcmf_alloc_internal_escan_request(struct wiphy *wiphy, u32 n_netinfo) {
 }
 
 static int brcmf_internal_escan_add_info(struct cfg80211_scan_request *req,
-					 u8 *ssid, u8 ssid_len, u8 channel)
+					 u8 *ssid, u8 ssid_len, u8 channel, enum nl80211_band band)
 {
 	struct ieee80211_channel *chan;
-	enum nl80211_band band;
 	int freq, i;
 
-	if (channel <= CH_MAX_2G_CHANNEL)
-		band = NL80211_BAND_2GHZ;
-	else
-		band = NL80211_BAND_5GHZ;
-
 	freq = ieee80211_channel_to_frequency(channel, band);
 	if (!freq)
 		return -EINVAL;
@@ -4046,53 +4040,30 @@ static int brcmf_start_internal_escan(struct brcmf_if *ifp, u32 fwmap,
 	return 0;
 }
 
-static struct brcmf_pno_net_info_le *
-brcmf_get_netinfo_array(struct brcmf_pno_scanresults_le *pfn_v1)
-{
-	struct brcmf_pno_scanresults_v2_le *pfn_v2;
-	struct brcmf_pno_net_info_le *netinfo;
-
-	switch (pfn_v1->version) {
-	default:
-		WARN_ON(1);
-		fallthrough;
-	case cpu_to_le32(1):
-		netinfo = (struct brcmf_pno_net_info_le *)(pfn_v1 + 1);
-		break;
-	case cpu_to_le32(2):
-		pfn_v2 = (struct brcmf_pno_scanresults_v2_le *)pfn_v1;
-		netinfo = (struct brcmf_pno_net_info_le *)(pfn_v2 + 1);
-		break;
-	}
-
-	return netinfo;
-}
-
 /* PFN result doesn't have all the info which are required by the supplicant
  * (For e.g IEs) Do a target Escan so that sched scan results are reported
  * via wl_inform_single_bss in the required format. Escan does require the
  * scan request in the form of cfg80211_scan_request. For timebeing, create
  * cfg80211_scan_request one out of the received PNO event.
  */
-static s32
-brcmf_notify_sched_scan_results(struct brcmf_if *ifp,
-				const struct brcmf_event_msg *e, void *data)
+static s32 brcmf_notify_sched_scan_results(struct brcmf_if *ifp,
+					   const struct brcmf_event_msg *e,
+					   void *data)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
 	struct brcmf_cfg80211_info *cfg = drvr->config;
-	struct brcmf_pno_net_info_le *netinfo, *netinfo_start;
 	struct cfg80211_scan_request *request = NULL;
 	struct wiphy *wiphy = cfg_to_wiphy(cfg);
 	int i, err = 0;
-	struct brcmf_pno_scanresults_le *pfn_result;
 	u32 bucket_map;
 	u32 result_count;
 	u32 status;
-	u32 datalen;
+	u32 min_data_len;
 
 	brcmf_dbg(SCAN, "Enter\n");
+	min_data_len = drvr->pno_handler.get_min_data_len();
 
-	if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) {
+	if (e->datalen < min_data_len) {
 		brcmf_dbg(SCAN, "Event data to small. Ignore\n");
 		return 0;
 	}
@@ -4102,9 +4073,8 @@ brcmf_notify_sched_scan_results(struct brcmf_if *ifp,
 		return 0;
 	}
 
-	pfn_result = (struct brcmf_pno_scanresults_le *)data;
-	result_count = le32_to_cpu(pfn_result->count);
-	status = le32_to_cpu(pfn_result->status);
+	result_count = drvr->pno_handler.get_result_count(data);
+	status = drvr->pno_handler.get_result_status(data);
 
 	/* PFN event is limited to fit 512 bytes so we may get
 	 * multiple NET_FOUND events. For now place a warning here.
@@ -4115,38 +4085,33 @@ brcmf_notify_sched_scan_results(struct brcmf_if *ifp,
 		bphy_err(drvr, "FALSE PNO Event. (pfn_count == 0)\n");
 		goto out_err;
 	}
-
-	netinfo_start = brcmf_get_netinfo_array(pfn_result);
-	datalen = e->datalen - ((void *)netinfo_start - (void *)pfn_result);
-	if (datalen < result_count * sizeof(*netinfo)) {
-		bphy_err(drvr, "insufficient event data\n");
+	err = drvr->pno_handler.validate_pfn_results(data, e->datalen);
+	if (err) {
+		bphy_err(drvr, "Invalid escan results (%d)", err);
 		goto out_err;
 	}
-
-	request = brcmf_alloc_internal_escan_request(wiphy,
-						     result_count);
+	request = brcmf_alloc_internal_escan_request(wiphy, result_count);
 	if (!request) {
 		err = -ENOMEM;
 		goto out_err;
 	}
-
 	bucket_map = 0;
 	for (i = 0; i < result_count; i++) {
-		netinfo = &netinfo_start[i];
-
-		if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN)
-			netinfo->SSID_len = IEEE80211_MAX_SSID_LEN;
-		brcmf_dbg(SCAN, "SSID:%.32s Channel:%d\n",
-			  netinfo->SSID, netinfo->channel);
-		bucket_map |= brcmf_pno_get_bucket_map(cfg->pno, netinfo);
-		err = brcmf_internal_escan_add_info(request,
-						    netinfo->SSID,
-						    netinfo->SSID_len,
-						    netinfo->channel);
+		u8 channel;
+		enum nl80211_band band;
+		u8 ssid[IEEE80211_MAX_SSID_LEN];
+		u8 ssid_len;
+
+		drvr->pno_handler.get_result_info(data, i, &ssid, &ssid_len,
+						 &channel, &band);
+		brcmf_dbg(SCAN, "SSID:%.32s Channel:%d Band:%d\n", ssid,
+			  channel, band);
+		bucket_map |= drvr->pno_handler.get_bucket_map(data, i, cfg->pno);
+		err = brcmf_internal_escan_add_info(request, ssid, ssid_len,
+						    channel, band);
 		if (err)
 			goto out_err;
 	}
-
 	if (!bucket_map)
 		goto free_req;
 
@@ -4249,48 +4214,50 @@ static s32 brcmf_config_wowl_pattern(struct brcmf_if *ifp, u8 cmd[4],
 	return ret;
 }
 
-static s32
-brcmf_wowl_nd_results(struct brcmf_if *ifp, const struct brcmf_event_msg *e,
-		      void *data)
+static s32 brcmf_wowl_nd_results(struct brcmf_if *ifp,
+				 const struct brcmf_event_msg *e, void *data)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
 	struct brcmf_cfg80211_info *cfg = drvr->config;
-	struct brcmf_pno_scanresults_le *pfn_result;
-	struct brcmf_pno_net_info_le *netinfo;
+	u32 min_data_len;
+	u8 channel;
+	enum nl80211_band band;
+	u8 ssid[IEEE80211_MAX_SSID_LEN];
+	u8 ssid_len;
+	u32 result_count;
 
 	brcmf_dbg(SCAN, "Enter\n");
 
-	if (e->datalen < (sizeof(*pfn_result) + sizeof(*netinfo))) {
+	min_data_len = drvr->pno_handler.get_min_data_len();
+
+	if (e->datalen < min_data_len) {
 		brcmf_dbg(SCAN, "Event data to small. Ignore\n");
 		return 0;
 	}
 
-	pfn_result = (struct brcmf_pno_scanresults_le *)data;
 
 	if (e->event_code == BRCMF_E_PFN_NET_LOST) {
 		brcmf_dbg(SCAN, "PFN NET LOST event. Ignore\n");
 		return 0;
 	}
 
-	if (le32_to_cpu(pfn_result->count) < 1) {
+	result_count = drvr->pno_handler.get_result_count(data);
+	if (result_count < 1) {
 		bphy_err(drvr, "Invalid result count, expected 1 (%d)\n",
-			 le32_to_cpu(pfn_result->count));
+			 result_count);
 		return -EINVAL;
 	}
 
-	netinfo = brcmf_get_netinfo_array(pfn_result);
-	if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN)
-		netinfo->SSID_len = IEEE80211_MAX_SSID_LEN;
-	memcpy(cfg->wowl.nd->ssid.ssid, netinfo->SSID, netinfo->SSID_len);
-	cfg->wowl.nd->ssid.ssid_len = netinfo->SSID_len;
+	drvr->pno_handler.get_result_info(data, 0, &ssid, &ssid_len, &channel,
+					 &band);
+	memcpy(cfg->wowl.nd->ssid.ssid, ssid, ssid_len);
+	cfg->wowl.nd->ssid.ssid_len = ssid_len;
 	cfg->wowl.nd->n_channels = 1;
 	cfg->wowl.nd->channels[0] =
-		ieee80211_channel_to_frequency(netinfo->channel,
-			netinfo->channel <= CH_MAX_2G_CHANNEL ?
-					NL80211_BAND_2GHZ : NL80211_BAND_5GHZ);
+		ieee80211_channel_to_frequency(channel, band);
+
 	cfg->wowl.nd_info->n_matches = 1;
 	cfg->wowl.nd_info->matches[0] = cfg->wowl.nd;
-
 	/* Inform (the resume task) that the net detect information was recvd */
 	cfg->wowl.nd_data_completed = true;
 	wake_up(&cfg->wowl.nd_data_wait);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index b54ad4b524d395..ff7db073731da3 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -8,6 +8,7 @@
 
 /* for brcmu_d11inf */
 #include <brcmu_d11.h>
+#include <brcmu_wifi.h>
 
 #include "core.h"
 #include "fwil_types.h"
@@ -388,6 +389,22 @@ struct brcmf_tlv {
 	u8 data[];
 };
 
+static inline enum nl80211_band fwil_band_to_nl80211(u16 band)
+{
+	switch (band) {
+	case WLC_BAND_2G:
+		return NL80211_BAND_2GHZ;
+	case WLC_BAND_5G:
+		return NL80211_BAND_5GHZ;
+	case WLC_BAND_6G:
+		return NL80211_BAND_6GHZ;
+	default:
+		WARN_ON(1);
+		break;
+	}
+	return 0;
+}
+
 static inline struct wiphy *cfg_to_wiphy(struct brcmf_cfg80211_info *cfg)
 {
 	return cfg->wiphy;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index ea76b8d334019a..772778b2d30efb 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -97,6 +97,24 @@ struct brcmf_rev_info {
 	u32 nvramrev;
 };
 
+struct brcmf_pno_info;
+/**
+ * struct pno_struct_handler
+ */
+struct pno_struct_handler {
+	u8 version;
+	int (*pno_config)(struct brcmf_if *ifp, u32 scan_freq, u32 mscan,
+			  u32 bestn);
+	u32 (*get_min_data_len)(void);
+	u32 (*get_result_count)(void *data);
+	u32 (*get_result_status)(void *data);
+	int (*validate_pfn_results)(void *data, u32 event_datalen);
+	u32 (*get_bucket_map)(void *data, int idx, struct brcmf_pno_info *pi);
+	int (*get_result_info)(void *data, int result_idx,
+			       u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len,
+			       u8 *channel, enum nl80211_band *band);
+};
+
 /* Common structure for module and instance linkage */
 struct brcmf_pub {
 	/* Linkage ponters */
@@ -145,6 +163,8 @@ struct brcmf_pub {
 	u8 sta_mac_idx;
 	const struct brcmf_fwvid_ops *vops;
 	void *vdata;
+	u16 cnt_ver;
+	struct pno_struct_handler pno_handler;
 };
 
 /* forward declarations */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 64af71d768a89c..36a7f1408fa85d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -16,6 +16,7 @@
 #include "fwvid.h"
 #include "feature.h"
 #include "common.h"
+#include "pno.h"
 
 #define BRCMF_FW_UNSUPPORTED	23
 
@@ -290,6 +291,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 {
 	struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
 	struct brcmf_wl_scan_version_le scan_ver;
+	struct brcmf_pno_param_v3_le pno_params;
 	struct brcmf_pno_macaddr_le pfn_mac;
 	struct brcmf_gscan_config gscan_cfg;
 	u32 wowl_cap;
@@ -356,6 +358,16 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 		}
 	}
 
+	/* See what version of PFN scan is supported*/
+	err = brcmf_fil_iovar_data_get(ifp, "pno_set", &pno_params,
+				       sizeof(pno_params));
+	if (!err) {
+		brcmf_pno_setup_for_version(drvr, le16_to_cpu(pno_params.version));
+	} else {
+		/* Default to version 2, supported by all chips we support. */
+		brcmf_pno_setup_for_version(drvr, 2);
+	}
+
 	brcmf_feat_wlc_version_overrides(drvr);
 	brcmf_feat_firmware_overrides(drvr);
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index b8376ec39e4340..151cef2c2e3196 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -1064,6 +1064,46 @@ struct brcmf_pno_param_le {
 	__le32 slow_freq;
 };
 
+/**
+ * struct brcmf_pno_param_le - PNO scan configuration parameters
+ *
+ * @version: PNO parameters version.
+ * @length: Length of PNO structure
+ * @scan_freq: scan frequency.
+ * @lost_network_timeout: #sec. to declare discovered network as lost.
+ * @flags: Bit field to control features of PFN such as sort criteria auto
+ *	enable switch and background scan.
+ * @rssi_margin: Margin to avoid jitter for choosing a PFN based on RSSI sort
+ *	criteria.
+ * @bestn: number of best networks in each scan.
+ * @mscan: number of scans recorded.
+ * @repeat: minimum number of scan intervals before scan frequency changes
+ *	in adaptive scan.
+ * @exp: exponent of 2 for maximum scan interval.
+ * @slow_freq: slow scan period.
+ * @min_bound: min bound for scan time randomization
+ * @max_bound: max bound for scan time randomization
+ * @pfn_lp_scan_disable: unused
+ * @pfn_lp_scan_cnt: allow interleaving lp scan with hp scan
+ */
+struct brcmf_pno_param_v3_le {
+	__le16 version;
+	__le16 length;
+	__le32 scan_freq;
+	__le32 lost_network_timeout;
+	__le16 flags;
+	__le16 rssi_margin;
+	u8 bestn;
+	u8 mscan;
+	u8 repeat;
+	u8 exp;
+	__le32 slow_freq;
+	u8 min_bound;
+	u8 max_bound;
+	u8 pfn_lp_scan_disable;
+	u8 pfn_lp_scan_cnt;
+};
+
 /**
  * struct brcmf_pno_config_le - PNO channel configuration.
  *
@@ -1117,6 +1157,28 @@ struct brcmf_pno_net_info_le {
 	__le16	timestamp;
 };
 
+/**
+ * struct brcmf_pno_net_info_v3_le - information per found network.
+ *
+ * @bssid: BSS network identifier.
+ * @chanspec: channel spec.
+ * @SSID_len: length of ssid.
+ * @SSID: ssid characters.
+ * @flags: flags
+ * @RSSI: receive signal strength (in dBm).
+ * @timestamp: age in seconds.
+ */
+struct brcmf_pno_net_info_v3_le {
+	u8 bssid[6];
+	u16 chanspec;
+	u8 SSID_len;
+	u8 padding;
+	u16 flags;
+	u8 SSID[32];
+	__le16 RSSI;
+	__le16 timestamp;
+};
+
 /**
  * struct brcmf_pno_scanresults_le - result returned in PNO NET FOUND event.
  *
@@ -1137,6 +1199,14 @@ struct brcmf_pno_scanresults_v2_le {
 	__le32 scan_ch_bucket;
 };
 
+/* V2 and V3 structs are the same */
+struct brcmf_pno_scanresults_v3_le {
+	__le32 version;
+	__le32 status;
+	__le32 count;
+	__le32 scan_ch_bucket;
+};
+
 /**
  * struct brcmf_pno_macaddr_le - to configure PNO macaddr randomization.
  *
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
index 05f66ab13bed6d..dbeeaef75b165a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.c
@@ -12,8 +12,10 @@
 #include "fwil_types.h"
 #include "cfg80211.h"
 #include "pno.h"
+#include "feature.h"
 
-#define BRCMF_PNO_VERSION		2
+#define BRCMF_PNO_VERSION_2		2
+#define BRCMF_PNO_VERSION_3		3
 #define BRCMF_PNO_REPEAT		4
 #define BRCMF_PNO_FREQ_EXPO_MAX		3
 #define BRCMF_PNO_IMMEDIATE_SCAN_BIT	3
@@ -99,8 +101,62 @@ static int brcmf_pno_channel_config(struct brcmf_if *ifp,
 	return brcmf_fil_iovar_data_set(ifp, "pfn_cfg", cfg, sizeof(*cfg));
 }
 
-static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq,
-			    u32 mscan, u32 bestn)
+static int brcmf_pno_config_v3(struct brcmf_if *ifp, u32 scan_freq, u32 mscan,
+			       u32 bestn)
+{
+	struct brcmf_pub *drvr = ifp->drvr;
+	struct brcmf_pno_param_v3_le pfn_param;
+	u16 flags;
+	u32 pfnmem;
+	s32 err;
+
+	memset(&pfn_param, 0, sizeof(pfn_param));
+	pfn_param.version = cpu_to_le16(BRCMF_PNO_VERSION_3);
+	pfn_param.length = cpu_to_le16(sizeof(struct brcmf_pno_param_v3_le));
+
+	/* set extra pno params */
+	flags = BIT(BRCMF_PNO_IMMEDIATE_SCAN_BIT) |
+		BIT(BRCMF_PNO_ENABLE_ADAPTSCAN_BIT);
+	pfn_param.repeat = BRCMF_PNO_REPEAT;
+	pfn_param.exp = BRCMF_PNO_FREQ_EXPO_MAX;
+
+	/* set up pno scan fr */
+	pfn_param.scan_freq = cpu_to_le32(scan_freq);
+
+	if (mscan) {
+		pfnmem = bestn;
+
+		/* set bestn in firmware */
+		err = brcmf_fil_iovar_int_set(ifp, "pfnmem", pfnmem);
+		if (err < 0) {
+			bphy_err(drvr, "failed to set pfnmem\n");
+			goto exit;
+		}
+		/* get max mscan which the firmware supports */
+		err = brcmf_fil_iovar_int_get(ifp, "pfnmem", &pfnmem);
+		if (err < 0) {
+			bphy_err(drvr, "failed to get pfnmem\n");
+			goto exit;
+		}
+		mscan = min_t(u32, mscan, pfnmem);
+		pfn_param.mscan = mscan;
+		pfn_param.bestn = bestn;
+		flags |= BIT(BRCMF_PNO_ENABLE_BD_SCAN_BIT);
+		brcmf_dbg(INFO, "mscan=%d, bestn=%d\n", mscan, bestn);
+	}
+
+	pfn_param.flags = cpu_to_le16(flags);
+	err = brcmf_fil_iovar_data_set(ifp, "pfn_set", &pfn_param,
+				       sizeof(pfn_param));
+	if (err)
+		bphy_err(drvr, "pfn_set failed, err=%d\n", err);
+
+exit:
+	return err;
+}
+
+static int brcmf_pno_config_v2(struct brcmf_if *ifp, u32 scan_freq, u32 mscan,
+			       u32 bestn)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
 	struct brcmf_pno_param_le pfn_param;
@@ -109,7 +165,7 @@ static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq,
 	s32 err;
 
 	memset(&pfn_param, 0, sizeof(pfn_param));
-	pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION);
+	pfn_param.version = cpu_to_le32(BRCMF_PNO_VERSION_2);
 
 	/* set extra pno params */
 	flags = BIT(BRCMF_PNO_IMMEDIATE_SCAN_BIT) |
@@ -152,6 +208,12 @@ static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq,
 	return err;
 }
 
+static int brcmf_pno_config(struct brcmf_if *ifp, u32 scan_freq, u32 mscan,
+			    u32 bestn)
+{
+	return ifp->drvr->pno_handler.pno_config(ifp, scan_freq, mscan, bestn);
+}
+
 static int brcmf_pno_set_random(struct brcmf_if *ifp, struct brcmf_pno_info *pi)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
@@ -275,7 +337,7 @@ static int brcmf_pno_get_bucket_channels(struct cfg80211_sched_scan_request *r,
 {
 	u32 n_chan = le32_to_cpu(pno_cfg->channel_num);
 	u16 chan;
-	int i, err = 0;
+	int i, err;
 
 	for (i = 0; i < r->n_channels; i++) {
 		if (n_chan >= BRCMF_NUMCHANNELS) {
@@ -562,9 +624,82 @@ u64 brcmf_pno_find_reqid_by_bucket(struct brcmf_pno_info *pi, u32 bucket)
 	return reqid;
 }
 
-u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi,
-			     struct brcmf_pno_net_info_le *ni)
+
+static struct brcmf_pno_net_info_le *
+brcmf_get_netinfo_array(void *pfn_v1_data)
+{
+	struct brcmf_pno_scanresults_le *pfn_v1 =
+		(struct brcmf_pno_scanresults_le *)pfn_v1_data;
+	struct brcmf_pno_scanresults_v2_le *pfn_v2;
+	struct brcmf_pno_net_info_le *netinfo = NULL;
+
+	switch (pfn_v1->version) {
+	default:
+		WARN_ON(1);
+		fallthrough;
+	case cpu_to_le32(1):
+		netinfo = (struct brcmf_pno_net_info_le *)(pfn_v1 + 1);
+		break;
+	case cpu_to_le32(2):
+		pfn_v2 = (struct brcmf_pno_scanresults_v2_le *)pfn_v1;
+		netinfo = (struct brcmf_pno_net_info_le *)(pfn_v2 + 1);
+		break;
+	case cpu_to_le32(3):
+		brcmf_err("Need to use brcmf_get_netinfo_v3_array\n");
+		break;
+	}
+
+	return netinfo;
+}
+
+static struct brcmf_pno_net_info_v3_le *
+brcmf_get_netinfo_v3_array(void*pfn_v3_data)
+{
+	struct brcmf_pno_scanresults_v3_le *pfn_v3 =
+		(struct brcmf_pno_scanresults_v3_le *)pfn_v3_data;
+	return (struct brcmf_pno_net_info_v3_le *) (pfn_v3 + 1);
+}
+
+static u32 brcmf_pno_get_bucket_map(void *data, int idx, struct brcmf_pno_info *pi)
+{
+
+	struct brcmf_pno_net_info_le *netinfo_start =
+		brcmf_get_netinfo_array(data);
+	struct brcmf_pno_net_info_le *ni = &netinfo_start[idx];
+	struct cfg80211_sched_scan_request *req;
+	struct cfg80211_match_set *ms;
+	u32 bucket_map = 0;
+	int i, j;
+
+	mutex_lock(&pi->req_lock);
+	for (i = 0; i < pi->n_reqs; i++) {
+		req = pi->reqs[i];
+
+		if (!req->n_match_sets)
+			continue;
+		for (j = 0; j < req->n_match_sets; j++) {
+			ms = &req->match_sets[j];
+			if (ms->ssid.ssid_len == ni->SSID_len &&
+			    !memcmp(ms->ssid.ssid, ni->SSID, ni->SSID_len)) {
+				bucket_map |= BIT(i);
+				break;
+			}
+			if (is_valid_ether_addr(ms->bssid) &&
+			    !memcmp(ms->bssid, ni->bssid, ETH_ALEN)) {
+				bucket_map |= BIT(i);
+				break;
+			}
+		}
+	}
+	mutex_unlock(&pi->req_lock);
+	return bucket_map;
+}
+
+static u32 brcmf_pno_get_bucket_map_v3(void *data, int idx, struct brcmf_pno_info *pi)
 {
+	struct brcmf_pno_net_info_v3_le *netinfo_v3_start =
+		brcmf_get_netinfo_v3_array(data);
+	struct brcmf_pno_net_info_v3_le *ni = &netinfo_v3_start[idx];
 	struct cfg80211_sched_scan_request *req;
 	struct cfg80211_match_set *ms;
 	u32 bucket_map = 0;
@@ -593,3 +728,148 @@ u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi,
 	mutex_unlock(&pi->req_lock);
 	return bucket_map;
 }
+
+static u32 brcmf_pno_min_data_len(void)
+{
+	return sizeof(struct brcmf_pno_scanresults_le) +
+	       sizeof(struct brcmf_pno_net_info_le);
+}
+static u32 brcmf_pno_min_data_len_v3(void)
+{
+	return sizeof(struct brcmf_pno_scanresults_v3_le) +
+	       sizeof(struct brcmf_pno_net_info_v3_le);
+}
+
+static int brcmf_pno_validate_pfn_results_v3(void *data, u32 eventlen)
+{
+	struct brcmf_pno_scanresults_v3_le *scanresult =
+		(struct brcmf_pno_scanresults_v3_le *)data;
+	struct brcmf_pno_net_info_v3_le *netinfo_v3_start =
+		brcmf_get_netinfo_v3_array(scanresult);
+	u32 datalen;
+
+	if (!netinfo_v3_start) {
+		brcmf_err("did not get netinfo_v3 data\n");
+		return -EINVAL;
+	}
+	datalen = eventlen - ((void *)netinfo_v3_start - (void *)data);
+	if (datalen < le32_to_cpu(scanresult->count) * sizeof(struct brcmf_pno_net_info_v3_le)) {
+		brcmf_err("insufficient event data\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int brcmf_pno_validate_pfn_results(void *data, u32 eventlen)
+{
+	struct brcmf_pno_scanresults_le *scanresult =
+		(struct brcmf_pno_scanresults_le *)data;
+	struct brcmf_pno_net_info_le *netinfo_start =
+		brcmf_get_netinfo_array(scanresult);
+	u32 datalen;
+
+	if (!netinfo_start) {
+		brcmf_err("did not get netinfo data\n");
+		return -EINVAL;
+	}
+	datalen = eventlen - ((void *)netinfo_start - (void *)data);
+	if (datalen < le32_to_cpu(scanresult->count) * sizeof(struct brcmf_pno_net_info_le)) {
+		brcmf_err("insufficient event data\n");
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static int brcmf_pno_get_result_info(void *data, int result_idx,
+				     u8 (*ssid)[IEEE80211_MAX_SSID_LEN],
+				     u8 *ssid_len, u8 *channel,
+				     enum nl80211_band *band)
+{
+	struct brcmf_pno_scanresults_le *scanresult =
+		(struct brcmf_pno_scanresults_le *)data;
+	struct brcmf_pno_net_info_le *netinfo_start =
+		brcmf_get_netinfo_array(scanresult);
+	struct brcmf_pno_net_info_le *netinfo = &netinfo_start[result_idx];
+
+	*channel = netinfo->channel;
+	*band = netinfo->channel <= CH_MAX_2G_CHANNEL ? NL80211_BAND_2GHZ :
+							NL80211_BAND_5GHZ;
+	*ssid_len = netinfo->SSID_len;
+	if (netinfo->SSID_len > IEEE80211_MAX_SSID_LEN)
+		*ssid_len = IEEE80211_MAX_SSID_LEN;
+	memcpy(ssid, netinfo->SSID, *ssid_len);
+
+	return 0;
+}
+
+static int brcmf_pno_get_result_info_v3(void *data, int result_idx,
+					u8 (*ssid)[IEEE80211_MAX_SSID_LEN],
+					u8 *ssid_len, u8 *channel,
+					enum nl80211_band *band)
+{
+	struct brcmf_pno_scanresults_v3_le *scanresult =
+		(struct brcmf_pno_scanresults_v3_le *)data;
+	struct brcmf_pno_net_info_v3_le *netinfo_v3_start =
+		brcmf_get_netinfo_v3_array(scanresult);
+	struct brcmf_pno_net_info_v3_le *netinfo_v3 =
+		&netinfo_v3_start[result_idx];
+
+	*channel = CHSPEC_CHANNEL(netinfo_v3->chanspec);
+	*band = fwil_band_to_nl80211(CHSPEC_BAND(netinfo_v3->chanspec));
+	*ssid_len = netinfo_v3->SSID_len;
+	if (netinfo_v3->SSID_len > IEEE80211_MAX_SSID_LEN)
+		*ssid_len = IEEE80211_MAX_SSID_LEN;
+	memcpy(ssid, netinfo_v3->SSID, *ssid_len);
+
+	return 0;
+}
+
+/* The count and status fields are in the same place for v1/2/3 */
+static u32 brcmf_pno_get_result_count_v123(void *data)
+{
+	struct brcmf_pno_scanresults_le *results =
+		(struct brcmf_pno_scanresults_le *)data;
+	return le32_to_cpu(results->count);
+}
+static u32 brcmf_pno_get_result_status_v123(void *data)
+{
+	struct brcmf_pno_scanresults_le *results =
+		(struct brcmf_pno_scanresults_le *)data;
+	return le32_to_cpu(results->status);
+}
+
+int brcmf_pno_setup_for_version(struct brcmf_pub *drvr, u8 vers)
+{
+	/* The first supported version by this driver was version 2.
+	 * The v2 functions handle version one structures if handed to them,
+	 * but the config was always set to interface version 2.  */
+	switch (vers) {
+	case BRCMF_PNO_VERSION_2: {
+		drvr->pno_handler.version = BRCMF_PNO_VERSION_2;
+		drvr->pno_handler.pno_config = brcmf_pno_config_v2;
+		drvr->pno_handler.get_result_count = brcmf_pno_get_result_count_v123;
+		drvr->pno_handler.get_result_status = brcmf_pno_get_result_status_v123;
+		drvr->pno_handler.get_bucket_map = brcmf_pno_get_bucket_map;
+		drvr->pno_handler.get_min_data_len = brcmf_pno_min_data_len;
+		drvr->pno_handler.get_result_info = brcmf_pno_get_result_info;
+		drvr->pno_handler.validate_pfn_results =
+			brcmf_pno_validate_pfn_results;
+		break;
+	}
+	case BRCMF_PNO_VERSION_3: {
+		drvr->pno_handler.version = BRCMF_PNO_VERSION_3;
+		drvr->pno_handler.pno_config = brcmf_pno_config_v3;
+		drvr->pno_handler.get_result_count = brcmf_pno_get_result_count_v123;
+		drvr->pno_handler.get_result_status = brcmf_pno_get_result_status_v123;
+		drvr->pno_handler.get_bucket_map = brcmf_pno_get_bucket_map_v3;
+		drvr->pno_handler.get_min_data_len = brcmf_pno_min_data_len_v3;
+		drvr->pno_handler.get_result_info = brcmf_pno_get_result_info_v3;
+		drvr->pno_handler.validate_pfn_results =
+			brcmf_pno_validate_pfn_results_v3;
+		break;
+	}
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
index 25d406019ac340..0163c762f5385a 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pno.h
@@ -61,12 +61,12 @@ void brcmf_pno_detach(struct brcmf_cfg80211_info *cfg);
 u64 brcmf_pno_find_reqid_by_bucket(struct brcmf_pno_info *pi, u32 bucket);
 
 /**
- * brcmf_pno_get_bucket_map - determine bucket map for given netinfo.
+ * brcmf_pno_setup_for_version - setup our PNO handler for whatever version structures
+ * are supported by the chip
  *
- * @pi: pno instance used.
- * @netinfo: netinfo to compare with bucket configuration.
+ * @cfg: CFG to fill in.
+ * @vers: Version to use
  */
-u32 brcmf_pno_get_bucket_map(struct brcmf_pno_info *pi,
-			     struct brcmf_pno_net_info_le *netinfo);
+int brcmf_pno_setup_for_version(struct brcmf_pub *drvr, u8 vers);
 
 #endif /* _BRCMF_PNO_H */

From 1efbe181772f3a1da4a5da349112399822d6cff9 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 22 Oct 2023 12:40:57 -0400
Subject: [PATCH 0702/1027] [brcmfmac] Structurize scan parameter handling

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/Makefile      |   2 +
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 233 ++-------
 .../broadcom/brcm80211/brcmfmac/core.h        |   9 +
 .../broadcom/brcm80211/brcmfmac/feature.c     |  18 +-
 .../broadcom/brcm80211/brcmfmac/feature.h     |   4 -
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  | 190 +++++---
 .../broadcom/brcm80211/brcmfmac/scan_param.c  | 446 ++++++++++++++++++
 .../broadcom/brcm80211/brcmfmac/scan_param.h  |  22 +
 8 files changed, 643 insertions(+), 281 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
index e5ca0f51182271..f3f72f9524578c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
@@ -25,7 +25,9 @@ brcmfmac-objs += \
 		btcoex.o \
 		vendor.o \
 		pno.o \
+		scan_param.o \
 		xtlv.o
+
 brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \
 		bcdc.o \
 		fwsignal.o
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 470ed48f55fe9b..07d6b761cc0257 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -1099,170 +1099,13 @@ void brcmf_set_mpc(struct brcmf_if *ifp, int mpc)
 	}
 }
 
-static void brcmf_scan_params_v2_to_v1(struct brcmf_scan_params_v2_le *params_v2_le,
-				       struct brcmf_scan_params_le *params_le)
-{
-	size_t params_size;
-	u32 ch;
-	int n_channels, n_ssids;
-
-	memcpy(&params_le->ssid_le, &params_v2_le->ssid_le,
-	       sizeof(params_le->ssid_le));
-	memcpy(&params_le->bssid, &params_v2_le->bssid,
-	       sizeof(params_le->bssid));
-
-	params_le->bss_type = params_v2_le->bss_type;
-	params_le->scan_type = le32_to_cpu(params_v2_le->scan_type);
-	params_le->nprobes = params_v2_le->nprobes;
-	params_le->active_time = params_v2_le->active_time;
-	params_le->passive_time = params_v2_le->passive_time;
-	params_le->home_time = params_v2_le->home_time;
-	params_le->channel_num = params_v2_le->channel_num;
-
-	ch = le32_to_cpu(params_v2_le->channel_num);
-	n_channels = ch & BRCMF_SCAN_PARAMS_COUNT_MASK;
-	n_ssids = ch >> BRCMF_SCAN_PARAMS_NSSID_SHIFT;
-
-	params_size = sizeof(u16) * n_channels;
-	if (n_ssids > 0) {
-		params_size = roundup(params_size, sizeof(u32));
-		params_size += sizeof(struct brcmf_ssid_le) * n_ssids;
-	}
-
-	memcpy(&params_le->channel_list[0],
-	       &params_v2_le->channel_list[0], params_size);
-}
-
-static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags)
-{
-	u32 scan_flags = 0;
-	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) {
-		scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN;
-		brcmf_dbg(SCAN, "requested low span scan\n");
-	}
-	if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) {
-		scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY;
-		brcmf_dbg(SCAN, "requested high accuracy scan\n");
-	}
-	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) {
-		scan_flags |= BRCMF_SCANFLAGS_LOW_POWER;
-		brcmf_dbg(SCAN, "requested low power scan\n");
-	}
-	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) {
-		scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO;
-		brcmf_dbg(SCAN, "requested low priority scan\n");
-	}
-	return scan_flags;
-}
-
-static void brcmf_escan_prep(struct brcmf_cfg80211_info *cfg,
-			     struct brcmf_if *ifp,
-			     struct brcmf_scan_params_v2_le *params_le,
-			     struct cfg80211_scan_request *request)
-{
-	u32 n_ssids;
-	u32 n_channels;
-	s32 i;
-	s32 offset;
-	u16 chanspec;
-	char *ptr;
-	int length;
-	struct brcmf_ssid_le ssid_le;
-	u32 scan_type = BRCMF_SCANTYPE_ACTIVE;
-
-	eth_broadcast_addr(params_le->bssid);
-
-	length = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE;
-
-	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3))
-		params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3);
-	else
-		params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2);
-
-	params_le->bss_type = DOT11_BSSTYPE_ANY;
-	params_le->ssid_type = 0;
-	params_le->channel_num = 0;
-	params_le->nprobes = cpu_to_le32(-1);
-	params_le->active_time = cpu_to_le32(-1);
-	params_le->passive_time = cpu_to_le32(-1);
-	params_le->home_time = cpu_to_le32(-1);
-	memset(&params_le->ssid_le, 0, sizeof(params_le->ssid_le));
-
-	/* Scan abort */
-	if (!request) {
-		length += sizeof(u16);
-		params_le->channel_num = cpu_to_le32(1);
-		params_le->channel_list[0] = cpu_to_le16(-1);
-		params_le->length = cpu_to_le16(length);
-		return;
-	}
-
-	n_ssids = request->n_ssids;
-	n_channels = request->n_channels;
-
-	/* Copy channel array if applicable */
-	brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
-		  n_channels);
-	if (n_channels > 0) {
-		length += roundup(sizeof(u16) * n_channels, sizeof(u32));
-		for (i = 0; i < n_channels; i++) {
-			chanspec = channel_to_chanspec(&cfg->d11inf,
-						       request->channels[i]);
-			brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n",
-				  request->channels[i]->hw_value, chanspec);
-			params_le->channel_list[i] = cpu_to_le16(chanspec);
-		}
-	} else {
-		brcmf_dbg(SCAN, "Scanning all channels\n");
-	}
-
-	/* Copy ssid array if applicable */
-	brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
-	if (n_ssids > 0) {
-		offset = offsetof(struct brcmf_scan_params_v2_le, channel_list) +
-				n_channels * sizeof(u16);
-		offset = roundup(offset, sizeof(u32));
-		length += sizeof(ssid_le) * n_ssids,
-		ptr = (char *)params_le + offset;
-		for (i = 0; i < n_ssids; i++) {
-			memset(&ssid_le, 0, sizeof(ssid_le));
-			ssid_le.SSID_len =
-					cpu_to_le32(request->ssids[i].ssid_len);
-			memcpy(ssid_le.SSID, request->ssids[i].ssid,
-			       request->ssids[i].ssid_len);
-			if (!ssid_le.SSID_len)
-				brcmf_dbg(SCAN, "%d: Broadcast scan\n", i);
-			else
-				brcmf_dbg(SCAN, "%d: scan for  %.32s size=%d\n",
-					  i, ssid_le.SSID, ssid_le.SSID_len);
-			memcpy(ptr, &ssid_le, sizeof(ssid_le));
-			ptr += sizeof(ssid_le);
-		}
-	} else {
-		brcmf_dbg(SCAN, "Performing passive scan\n");
-		scan_type = BRCMF_SCANTYPE_PASSIVE;
-	}
-	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
-	params_le->scan_type = cpu_to_le32(scan_type);
-	params_le->length = cpu_to_le16(length);
 
-	/* Include RNR results if requested */
-	if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) {
-		params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR;
-	}
-
-	/* Adding mask to channel numbers */
-	params_le->channel_num =
-		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
-			(n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
-}
 
 s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
 				struct brcmf_if *ifp, bool aborted,
 				bool fw_abort)
 {
 	struct brcmf_pub *drvr = cfg->pub;
-	struct brcmf_scan_params_v2_le params_v2_le;
 	struct cfg80211_scan_request *scan_request;
 	u64 reqid;
 	u32 bucket;
@@ -1278,25 +1121,16 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
 	timer_delete_sync(&cfg->escan_timeout);
 
 	if (fw_abort) {
+		u32 len;
+		void *data = drvr->scan_param_handler.get_prepped_struct(cfg, &len, NULL);
+		if (!data){
+			bphy_err(drvr, "Scan abort failed to prepare abort struct\n");
+			return 0;
+		}
 		/* Do a scan abort to stop the driver's scan engine */
 		brcmf_dbg(SCAN, "ABORT scan in firmware\n");
-
-		brcmf_escan_prep(cfg, ifp, &params_v2_le, NULL);
-
-		/* E-Scan (or anyother type) can be aborted by SCAN */
-		if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) {
-			err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN,
-						     &params_v2_le,
-						     sizeof(params_v2_le));
-		} else {
-			struct brcmf_scan_params_le params_le;
-
-			brcmf_scan_params_v2_to_v1(&params_v2_le, &params_le);
-			err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN,
-						     &params_le,
-						     sizeof(params_le));
-		}
-
+		err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SCAN, data, len);
+		kfree(data);
 		if (err)
 			bphy_err(drvr, "Scan abort failed\n");
 	}
@@ -1520,19 +1354,24 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
 		struct cfg80211_scan_request *request)
 {
 	struct brcmf_pub *drvr = cfg->pub;
-	s32 params_size = BRCMF_SCAN_PARAMS_V2_FIXED_SIZE +
-			  offsetof(struct brcmf_escan_params_le, params_v2_le);
+	u32 struct_size = 0;
+	void *prepped_params = NULL;
+	u32 params_size = 0;
 	struct brcmf_escan_params_le *params;
 	s32 err = 0;
 
 	brcmf_dbg(SCAN, "E-SCAN START\n");
 
-	if (request != NULL) {
-		/* Allocate space for populating ssids in struct */
-		params_size += sizeof(u32) * ((request->n_channels + 1) / 2);
-
-		/* Allocate space for populating ssids in struct */
-		params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids;
+	prepped_params = drvr->scan_param_handler.get_prepped_struct(cfg, &struct_size, request);
+	if (!prepped_params) {
+		err = -EINVAL;
+		goto exit;
+	}
+	params_size = struct_size +
+		      offsetof(struct brcmf_escan_params_le, params_v4_le);
+	if (!params_size) {
+		err = -EINVAL;
+		goto exit;
 	}
 
 	params = kzalloc(params_size, GFP_KERNEL);
@@ -1540,29 +1379,14 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
 		err = -ENOMEM;
 		goto exit;
 	}
-	BUG_ON(params_size + sizeof("escan") >= BRCMF_DCMD_MEDLEN);
-	brcmf_escan_prep(cfg, ifp, &params->params_v2_le, request);
-
-	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V3)) {
-		params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V3);
-	} else if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SCAN_V2)) {
-		params->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION_V2);
-	} else {
-		struct brcmf_escan_params_le *params_v1;
-
-		params_size -= BRCMF_SCAN_PARAMS_V2_FIXED_SIZE;
-		params_size += BRCMF_SCAN_PARAMS_FIXED_SIZE;
-		params_v1 = kzalloc(params_size, GFP_KERNEL);
-		if (!params_v1) {
-			err = -ENOMEM;
-			goto exit_params;
-		}
-		params_v1->version = cpu_to_le32(BRCMF_ESCAN_REQ_VERSION);
-		brcmf_scan_params_v2_to_v1(&params->params_v2_le, &params_v1->params_le);
-		kfree(params);
-		params = params_v1;
-	}
+	/* Copy into the largest part */
+	unsafe_memcpy(
+		&params->params_v4_le, prepped_params, struct_size,
+		/* A composite flex-array that is at least as large as the memcpy due to the allocation above */);
 
+	/* We can now free the original prepped parameters */
+	kfree(prepped_params);
+	params->version = cpu_to_le32(drvr->scan_param_handler.version);
 	params->action = cpu_to_le16(WL_ESCAN_ACTION_START);
 	params->sync_id = cpu_to_le16(0x1234);
 
@@ -1574,7 +1398,6 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
 			bphy_err(drvr, "error (%d)\n", err);
 	}
 
-exit_params:
 	kfree(params);
 exit:
 	return err;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index 772778b2d30efb..cbe0d706736517 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -98,6 +98,7 @@ struct brcmf_rev_info {
 };
 
 struct brcmf_pno_info;
+enum nl80211_band;
 /**
  * struct pno_struct_handler
  */
@@ -114,6 +115,13 @@ struct pno_struct_handler {
 			       u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len,
 			       u8 *channel, enum nl80211_band *band);
 };
+struct cfg80211_scan_request;
+struct scan_param_struct_handler {
+	u8 version;
+	void *(*get_prepped_struct)(struct brcmf_cfg80211_info *cfg,
+				    u32 *struct_size,
+				    struct cfg80211_scan_request *request);
+};
 
 /* Common structure for module and instance linkage */
 struct brcmf_pub {
@@ -165,6 +173,7 @@ struct brcmf_pub {
 	void *vdata;
 	u16 cnt_ver;
 	struct pno_struct_handler pno_handler;
+	struct scan_param_struct_handler scan_param_handler;
 };
 
 /* forward declarations */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 36a7f1408fa85d..59fcb489cb25b4 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -17,6 +17,7 @@
 #include "feature.h"
 #include "common.h"
 #include "pno.h"
+#include "scan_param.h"
 
 #define BRCMF_FW_UNSUPPORTED	23
 
@@ -290,7 +291,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
 void brcmf_feat_attach(struct brcmf_pub *drvr)
 {
 	struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
-	struct brcmf_wl_scan_version_le scan_ver;
+	struct brcmf_scan_version_le scan_ver;
 	struct brcmf_pno_param_v3_le pno_params;
 	struct brcmf_pno_macaddr_le pfn_mac;
 	struct brcmf_gscan_config gscan_cfg;
@@ -346,16 +347,11 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 
 	err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver));
 	if (!err) {
-		int ver = le16_to_cpu(scan_ver.scan_ver_major);
-
-		if (ver == 2) {
-			ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2);
-		} else if (ver == 3) {
-			/* We consider SCAN_V3 a subtype of SCAN_V2 since the
-			 * structure is essentially the same.
-			 */
-			ifp->drvr->feat_flags |= BIT(BRCMF_FEAT_SCAN_V2) | BIT(BRCMF_FEAT_SCAN_V3);
-		}
+		u16 ver = le16_to_cpu(scan_ver.scan_ver_major);
+		brcmf_scan_param_setup_for_version(drvr, ver);
+	} else {
+		/* Default tp version 1. */
+		brcmf_scan_param_setup_for_version(drvr, 1);
 	}
 
 	/* See what version of PFN scan is supported*/
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 99867f76df376f..7cec120e8a038d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -30,8 +30,6 @@
  * SAE: simultaneous authentication of equals
  * FWAUTH: Firmware authenticator
  * DUMP_OBSS: Firmware has capable to dump obss info to support ACS
- * SCAN_V2: Version 2 scan params
- * SCAN_v3: Version 3 scan params
  * PMKID_V2: Version 2 PMKID
  * PMKID_V3: Version 3 PMKID
  * EVENT_MSGS_EXT: Event messages extension
@@ -61,8 +59,6 @@
 	BRCMF_FEAT_DEF(SAE) \
 	BRCMF_FEAT_DEF(FWAUTH) \
 	BRCMF_FEAT_DEF(DUMP_OBSS) \
-	BRCMF_FEAT_DEF(SCAN_V2) \
-	BRCMF_FEAT_DEF(SCAN_V3) \
 	BRCMF_FEAT_DEF(PMKID_V2) \
 	BRCMF_FEAT_DEF(PMKID_V3) \
 	BRCMF_FEAT_DEF(EVENT_MSGS_EXT) \
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 151cef2c2e3196..e4b3b13a8ff92c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -47,13 +47,10 @@
 #define BRCMF_STA_DWDS_CAP		0x01000000	/* DWDS CAP */
 #define BRCMF_STA_DWDS			0x02000000	/* DWDS active */
 
-/* size of brcmf_scan_params not including variable length array */
-#define BRCMF_SCAN_PARAMS_FIXED_SIZE	64
-#define BRCMF_SCAN_PARAMS_V2_FIXED_SIZE	72
-
 /* version of brcmf_scan_params structure */
 #define BRCMF_SCAN_PARAMS_VERSION_V2	2
 #define BRCMF_SCAN_PARAMS_VERSION_V3	3
+#define BRCMF_SCAN_PARAMS_VERSION_V4	4
 
 /* masks for channel and ssid count */
 #define BRCMF_SCAN_PARAMS_COUNT_MASK	0x0000ffff
@@ -406,23 +403,23 @@ struct brcmf_ssid8_le {
 };
 
 struct brcmf_scan_params_le {
-	struct brcmf_ssid_le ssid_le;	/* default: {0, ""} */
-	u8 bssid[ETH_ALEN];	/* default: bcast */
-	s8 bss_type;		/* default: any,
+	struct brcmf_ssid_le ssid_le; /* default: {0, ""} */
+	u8 bssid[ETH_ALEN]; 	/* default: bcast */
+	s8 bss_type; 		/* default: any,
 				 * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
 				 */
-	u8 scan_type;	/* flags, 0 use default */
-	__le32 nprobes;	  /* -1 use default, number of probes per channel */
-	__le32 active_time;	/* -1 use default, dwell time per channel for
+	u8 scan_type; 		/* flags, 0 use default */
+	__le32 nprobes; 	/* -1 use default, number of probes per channel */
+	__le32 active_time; 	/* -1 use default, dwell time per channel for
 				 * active scanning
 				 */
-	__le32 passive_time;	/* -1 use default, dwell time per channel
+	__le32 passive_time; 	/* -1 use default, dwell time per channel
 				 * for passive scanning
 				 */
 	__le32 home_time;	/* -1 use default, dwell time for the
 				 * home channel between channel scans
 				 */
-	__le32 channel_num;	/* count of channels and ssids that follow
+	__le32 channel_num; 	/* count of channels and ssids that follow
 				 *
 				 * low half is count of channels in
 				 * channel_list, 0 means default (use all
@@ -438,56 +435,125 @@ struct brcmf_scan_params_le {
 				 * fixed parameter portion is assumed, otherwise
 				 * ssid in the fixed portion is ignored
 				 */
-	union {
-		__le16 padding;	/* Reserve space for at least 1 entry for abort
-				 * which uses an on stack brcmf_scan_params_le
-				 */
-		DECLARE_FLEX_ARRAY(__le16, channel_list);	/* chanspecs */
-	};
+	__le16 channel_list[]; /* chanspecs */
 };
 
 struct brcmf_scan_params_v2_le {
-	__le16 version;		/* structure version */
-	__le16 length;		/* structure length */
-	struct brcmf_ssid_le ssid_le;	/* default: {0, ""} */
-	u8 bssid[ETH_ALEN];	/* default: bcast */
-	s8 bss_type;		/* default: any,
-				 * DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
-				 */
-	u8 ssid_type;		/* v3 only */
-	__le32 scan_type;	/* flags, 0 use default */
-	__le32 nprobes;		/* -1 use default, number of probes per channel */
-	__le32 active_time;	/* -1 use default, dwell time per channel for
-				 * active scanning
-				 */
-	__le32 passive_time;	/* -1 use default, dwell time per channel
-				 * for passive scanning
-				 */
-	__le32 home_time;	/* -1 use default, dwell time for the
-				 * home channel between channel scans
-				 */
-	__le32 channel_num;	/* count of channels and ssids that follow
-				 *
-				 * low half is count of channels in
-				 * channel_list, 0 means default (use all
-				 * available channels)
-				 *
-				 * high half is entries in struct brcmf_ssid
-				 * array that follows channel_list, aligned for
-				 * s32 (4 bytes) meaning an odd channel count
-				 * implies a 2-byte pad between end of
-				 * channel_list and first ssid
-				 *
-				 * if ssid count is zero, single ssid in the
-				 * fixed parameter portion is assumed, otherwise
-				 * ssid in the fixed portion is ignored
-				 */
-	union {
-		__le16 padding;	/* Reserve space for at least 1 entry for abort
-				 * which uses an on stack brcmf_scan_params_v2_le
-				 */
-		DECLARE_FLEX_ARRAY(__le16, channel_list);	/* chanspecs */
-	};
+	__le16 version; /* structure version */
+	__le16 length; /* structure length */
+	struct brcmf_ssid_le ssid_le;  /* default: {0, ""} */
+	u8 bssid[ETH_ALEN];	       /* default: bcast */
+	s8 bss_type; 		       /* default: any,
+					* DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
+					*/
+	u8 PAD;
+	__le32 scan_type; 	       /* flags, 0 use default */
+	__le32 nprobes; 	       /* -1 use default, number of probes per channel */
+	__le32 active_time; 	       /* -1 use default, dwell time per channel for
+					* active scanning
+					*/
+	__le32 passive_time;	       /* -1 use default, dwell time per channel
+					* for passive scanning
+					*/
+	__le32 home_time;	       /* -1 use default, dwell time for the
+					* home channel between channel scans
+					*/
+	__le32 channel_num;	       /* count of channels and ssids that follow
+					*
+					* low half is count of channels in
+					* channel_list, 0 means default (use all
+					* available channels)
+					*
+					* high half is entries in struct brcmf_ssid
+					* array that follows channel_list, aligned for
+					* s32 (4 bytes) meaning an odd channel count
+					* implies a 2-byte pad between end of
+					* channel_list and first ssid
+					*
+					* if ssid count is zero, single ssid in the
+					* fixed parameter portion is assumed, otherwise
+					* ssid in the fixed portion is ignored
+					*/
+	__le16 channel_list[]; 		/* chanspecs */
+};
+
+struct brcmf_scan_params_v3_le {
+	__le16 version; 	       /* structure version */
+	__le16 length; 		       /* structure length */
+	struct brcmf_ssid_le ssid_le;  /* default: {0, ""} */
+	u8 bssid[ETH_ALEN]; 	       /* default: bcast */
+	s8 bss_type; 		       /* default: any,
+					* DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
+					*/
+	u8 ssid_type;		       /* short vs regular SSID */
+	__le32 scan_type; 	       /* flags, 0 use default */
+	__le32 nprobes;		       /* -1 use default, number of probes per channel */
+	__le32 active_time; 	       /* -1 use default, dwell time per channel for
+					* active scanning
+					*/
+	__le32 passive_time;	       /* -1 use default, dwell time per channel
+					* for passive scanning
+					*/
+	__le32 home_time; 	       /* -1 use default, dwell time for the
+					* home channel between channel scans
+					*/
+	__le32 channel_num; 	       /* count of channels and ssids that follow
+					*
+					* low half is count of channels in
+					* channel_list, 0 means default (use all
+					* available channels)
+					*
+					* high half is entries in struct brcmf_ssid
+					* array that follows channel_list, aligned for
+					* s32 (4 bytes) meaning an odd channel count
+					* implies a 2-byte pad between end of
+					* channel_list and first ssid
+					*
+					* if ssid count is zero, single ssid in the
+					* fixed parameter portion is assumed, otherwise
+					* ssid in the fixed portion is ignored
+					*/
+	__le16 channel_list[]; 		/* chanspecs */
+};
+
+struct brcmf_scan_params_v4_le {
+	__le16 version; 	       /* structure version */
+	__le16 length; 		       /* structure length */
+	struct brcmf_ssid_le ssid_le;  /* default: {0, ""} */
+	u8 bssid[ETH_ALEN]; 	       /* default: bcast */
+	s8 bss_type;		       /* default: any,
+					* DOT11_BSSTYPE_ANY/INFRASTRUCTURE/INDEPENDENT
+					*/
+	u8 ssid_type; 		       /* short vs regular SSID */
+	__le32 scan_type;	       /* flags, 0 use default */
+	__le32 scan_type_ext;	       /* ext flags, 0 use default */
+	__le32 nprobes; 	       /* -1 use default, number of probes per channel */
+	__le32 active_time;	       /* -1 use default, dwell time per channel for
+					* active scanning
+					*/
+	__le32 passive_time; 	       /* -1 use default, dwell time per channel
+					* for passive scanning
+					*/
+	__le32 home_time; 	       /* -1 use default, dwell time for the
+					* home channel between channel scans
+					*/
+	__le32 channel_num;	       /* count of channels and ssids that follow
+					*
+					* low half is count of channels in
+					* channel_list, 0 means default (use all
+					* available channels)
+					*
+					* high half is entries in struct brcmf_ssid
+					* array that follows channel_list, aligned for
+					* s32 (4 bytes) meaning an odd channel count
+					* implies a 2-byte pad between end of
+					* channel_list and first ssid
+					*
+					* if ssid count is zero, single ssid in the
+					* fixed parameter portion is assumed, otherwise
+					* ssid in the fixed portion is ignored
+					*/
+	__le16 channel_list[]; 	       /* chanspecs */
 };
 
 struct brcmf_scan_results {
@@ -504,6 +570,8 @@ struct brcmf_escan_params_le {
 	union {
 		struct brcmf_scan_params_le params_le;
 		struct brcmf_scan_params_v2_le params_v2_le;
+		struct brcmf_scan_params_v3_le params_v3_le;
+		struct brcmf_scan_params_v4_le params_v4_le;
 	};
 };
 
@@ -880,13 +948,13 @@ struct brcmf_wlc_version_le {
 /**
  * struct brcmf_wl_scan_version_le - scan interface version
  */
-struct brcmf_wl_scan_version_le {
+struct brcmf_scan_version_le {
         __le16  version;
         __le16  length;
         __le16  scan_ver_major;
 };
 
-#define BRCMF_WL_SCAN_VERSION_VERSION 1
+#define BRCMF_SCAN_VERSION_VERSION 1
 
 /**
  * struct brcmf_assoclist_le - request assoc list.
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
new file mode 100644
index 00000000000000..6bd5f6d1616c04
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
@@ -0,0 +1,446 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+#include <linux/gcd.h>
+#include <net/cfg80211.h>
+
+#include "core.h"
+#include "debug.h"
+#include "fwil_types.h"
+#include "cfg80211.h"
+#include "scan_param.h"
+
+static void brcmf_scan_param_set_defaults(u8 (*bssid)[ETH_ALEN], s8 *bss_type, __le32 *channel_num,
+					  __le32 *nprobes, __le32 *active_time,
+					  __le32 *passive_time,
+					  __le32 *home_time)
+{
+	eth_broadcast_addr(*bssid);
+	*bss_type = DOT11_BSSTYPE_ANY;
+	*channel_num = 0;
+	*nprobes = cpu_to_le32(-1);
+	*active_time = cpu_to_le32(-1);
+	*passive_time = cpu_to_le32(-1);
+	*home_time = cpu_to_le32(-1);
+}
+
+static void brcmf_scan_param_copy_chanspecs(
+	struct brcmf_cfg80211_info *cfg, __le16 (*dest_channels)[],
+	struct ieee80211_channel **in_channels, u32 n_channels)
+{
+	int i;
+	for (i = 0; i < n_channels; i++) {
+		u32 chanspec =
+			channel_to_chanspec(&cfg->d11inf, in_channels[i]);
+		brcmf_dbg(SCAN, "Chan : %d, Channel spec: %x\n",
+			  in_channels[i]->hw_value, chanspec);
+		(*dest_channels)[i] = cpu_to_le16(chanspec);
+	}
+}
+
+static void brcmf_scan_param_copy_ssids(char *dest_ssids,
+					struct cfg80211_ssid *in_ssids,
+					u32 n_ssids)
+{
+	int i;
+	for (i = 0; i < n_ssids; i++) {
+		struct brcmf_ssid_le ssid_le;
+		memset(&ssid_le, 0, sizeof(ssid_le));
+		ssid_le.SSID_len = cpu_to_le32(in_ssids[i].ssid_len);
+		memcpy(ssid_le.SSID, in_ssids[i].ssid, in_ssids[i].ssid_len);
+		if (!ssid_le.SSID_len)
+			brcmf_dbg(SCAN, "%d: Broadcast scan\n", i);
+		else
+			brcmf_dbg(SCAN, "%d: scan for  %.32s size=%d\n", i,
+				  ssid_le.SSID, ssid_le.SSID_len);
+		memcpy(dest_ssids, &ssid_le, sizeof(ssid_le));
+		dest_ssids += sizeof(ssid_le);
+	}
+}
+
+/* The scan parameter structures have an array of SSID's that appears at the end in some cases.
+ * In these cases, the chan list is really the lower half of a pair, the upper half is a ssid number,
+ * and then after all of that there is an array of SSIDs */
+static u32
+brcmf_scan_param_tail_size(const struct cfg80211_scan_request *request,
+			   u32 params_size)
+{
+	if (request != NULL) {
+		/* Allocate space for populating ssid upper half in struct */
+		params_size += sizeof(u32) * ((request->n_channels + 1) / 2);
+		/* Allocate space for populating ssids in struct */
+		params_size += sizeof(struct brcmf_ssid_le) * request->n_ssids;
+	} else {
+		params_size += sizeof(u16);
+	}
+	return params_size;
+}
+
+static u32 brcmf_nl80211_scan_flags_to_scan_flags(u32 nl80211_flags)
+{
+	u32 scan_flags = 0;
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_SPAN) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_SPAN;
+		brcmf_dbg(SCAN, "requested low span scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_HIGH_ACCURACY) {
+		scan_flags |= BRCMF_SCANFLAGS_HIGH_ACCURACY;
+		brcmf_dbg(SCAN, "requested high accuracy scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_POWER) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_POWER;
+		brcmf_dbg(SCAN, "requested low power scan\n");
+	}
+	if (nl80211_flags & NL80211_SCAN_FLAG_LOW_PRIORITY) {
+		scan_flags |= BRCMF_SCANFLAGS_LOW_PRIO;
+		brcmf_dbg(SCAN, "requested low priority scan\n");
+	}
+	return scan_flags;
+}
+
+static void *
+brcmf_scan_param_get_prepped_struct_v1(struct brcmf_cfg80211_info *cfg,
+				       u32 *struct_size,
+				       struct cfg80211_scan_request *request)
+{
+	u32 n_ssids;
+	u32 n_channels;
+	u32 params_size = sizeof(struct brcmf_scan_params_le);
+	u32 length;
+	struct brcmf_scan_params_le *params_le = NULL;
+	u8 scan_type = BRCMF_SCANTYPE_ACTIVE;
+
+	length = offsetof(struct brcmf_scan_params_le, channel_list);
+	params_size = brcmf_scan_param_tail_size(request, params_size);
+	params_le = kzalloc(params_size, GFP_KERNEL);
+	if (!params_le) {
+		bphy_err(cfg, "Could not allocate scan params\n");
+		return NULL;
+	}
+	brcmf_scan_param_set_defaults(&params_le->bssid,
+		&params_le->bss_type, &params_le->channel_num,
+		&params_le->nprobes, &params_le->active_time,
+		&params_le->passive_time, &params_le->home_time);
+
+	/* Scan abort */
+	if (!request) {
+		params_le->channel_num = cpu_to_le32(1);
+		params_le->channel_list[0] = cpu_to_le16(-1);
+		goto done;
+	}
+
+	n_ssids = request->n_ssids;
+	n_channels = request->n_channels;
+
+	/* Copy channel array if applicable */
+	brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
+		  n_channels);
+	if (n_channels > 0) {
+		length += roundup(sizeof(u16) * n_channels, sizeof(u32));
+		brcmf_scan_param_copy_chanspecs(cfg, &params_le->channel_list,
+						request->channels, n_channels);
+	} else {
+		brcmf_dbg(SCAN, "Scanning all channels\n");
+	}
+
+	/* Copy ssid array if applicable */
+	brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
+	if (n_ssids > 0) {
+		s32 offset;
+		char *ptr;
+
+		offset =
+			offsetof(struct brcmf_scan_params_le, channel_list) +
+			n_channels * sizeof(u16);
+		offset = roundup(offset, sizeof(u32));
+		length += sizeof(struct brcmf_ssid_le) * n_ssids;
+		ptr = (char *)params_le + offset;
+		brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids);
+	} else {
+		brcmf_dbg(SCAN, "Performing passive scan\n");
+		scan_type = BRCMF_SCANTYPE_PASSIVE;
+	}
+	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
+	params_le->scan_type =scan_type;
+	/* Adding mask to channel numbers */
+	params_le->channel_num =
+		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
+			    (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
+done:
+	*struct_size = length;
+	return params_le;
+}
+
+static void *
+brcmf_scan_param_get_prepped_struct_v2(struct brcmf_cfg80211_info *cfg,
+				       u32 *struct_size,
+				       struct cfg80211_scan_request *request)
+{
+	u32 n_ssids;
+	u32 n_channels;
+	u32 params_size = sizeof(struct brcmf_scan_params_v2_le);
+	u32 length;
+	struct brcmf_scan_params_v2_le *params_le = NULL;
+	u32 scan_type = BRCMF_SCANTYPE_ACTIVE;
+
+	length = offsetof(struct brcmf_scan_params_v2_le, channel_list);
+	params_size = brcmf_scan_param_tail_size(request, params_size);
+	params_le = kzalloc(params_size, GFP_KERNEL);
+	if (!params_le) {
+		bphy_err(cfg, "Could not allocate scan params\n");
+		return NULL;
+	}
+	params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V2);
+	brcmf_scan_param_set_defaults(&params_le->bssid,
+		&params_le->bss_type, &params_le->channel_num,
+		&params_le->nprobes, &params_le->active_time,
+		&params_le->passive_time, &params_le->home_time);
+
+	/* Scan abort */
+	if (!request) {
+		length += sizeof(u16);
+		params_le->channel_num = cpu_to_le32(1);
+		params_le->channel_list[0] = cpu_to_le16(-1);
+		params_le->length = cpu_to_le16(length);
+		goto done;
+	}
+
+	n_ssids = request->n_ssids;
+	n_channels = request->n_channels;
+
+	/* Copy channel array if applicable */
+	brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
+		  n_channels);
+	if (n_channels > 0) {
+		length += roundup(sizeof(u16) * n_channels, sizeof(u32));
+		brcmf_scan_param_copy_chanspecs(cfg, &params_le->channel_list,
+						request->channels, n_channels);
+	} else {
+		brcmf_dbg(SCAN, "Scanning all channels\n");
+	}
+
+	/* Copy ssid array if applicable */
+	brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
+	if (n_ssids > 0) {
+		s32 offset;
+		char *ptr;
+
+		offset =
+			offsetof(struct brcmf_scan_params_v2_le, channel_list) +
+			n_channels * sizeof(u16);
+		offset = roundup(offset, sizeof(u32));
+		length += sizeof(struct brcmf_ssid_le) * n_ssids;
+		ptr = (char *)params_le + offset;
+		brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids);
+
+	} else {
+		brcmf_dbg(SCAN, "Performing passive scan\n");
+		scan_type = BRCMF_SCANTYPE_PASSIVE;
+	}
+	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
+	params_le->scan_type = cpu_to_le32(scan_type);
+	params_le->length = cpu_to_le16(length);
+	/* Adding mask to channel numbers */
+	params_le->channel_num =
+		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
+			    (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
+done:
+	*struct_size = length;
+	return params_le;
+}
+
+static void *
+brcmf_scan_param_get_prepped_struct_v3(struct brcmf_cfg80211_info *cfg,
+				       u32 *struct_size,
+				       struct cfg80211_scan_request *request)
+{
+	u32 n_ssids;
+	u32 n_channels;
+	u32 params_size = sizeof(struct brcmf_scan_params_v3_le);
+	u32 length;
+	struct brcmf_scan_params_v3_le *params_le = NULL;
+	u32 scan_type = BRCMF_SCANTYPE_ACTIVE;
+
+	length = offsetof(struct brcmf_scan_params_v3_le, channel_list);
+	params_size = brcmf_scan_param_tail_size(request, params_size);
+	params_le = kzalloc(params_size, GFP_KERNEL);
+	if (!params_le) {
+		bphy_err(cfg, "Could not allocate scan params\n");
+		return NULL;
+	}
+
+	params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V3);
+	params_le->ssid_type = 0;
+	brcmf_scan_param_set_defaults(&params_le->bssid,
+		&params_le->bss_type, &params_le->channel_num,
+		&params_le->nprobes, &params_le->active_time,
+		&params_le->passive_time, &params_le->home_time);
+
+	/* Scan abort */
+	if (!request) {
+		length += sizeof(u16);
+		params_le->channel_num = cpu_to_le32(1);
+		params_le->channel_list[0] = cpu_to_le16(-1);
+		params_le->length = cpu_to_le16(length);
+		goto done;
+	}
+
+	n_ssids = request->n_ssids;
+	n_channels = request->n_channels;
+
+	/* Copy channel array if applicable */
+	brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
+		  n_channels);
+	if (n_channels > 0) {
+		length += roundup(sizeof(u16) * n_channels, sizeof(u32));
+		brcmf_scan_param_copy_chanspecs(cfg, &params_le->channel_list,
+						request->channels, n_channels);
+
+	} else {
+		brcmf_dbg(SCAN, "Scanning all channels\n");
+	}
+
+	/* Copy ssid array if applicable */
+	brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
+	if (n_ssids > 0) {
+		s32 offset;
+		char *ptr;
+
+		offset =
+			offsetof(struct brcmf_scan_params_v3_le, channel_list) +
+			n_channels * sizeof(u16);
+		offset = roundup(offset, sizeof(u32));
+		length += sizeof(struct brcmf_ssid_le) * n_ssids;
+		ptr = (char *)params_le + offset;
+		brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids);
+
+	} else {
+		brcmf_dbg(SCAN, "Performing passive scan\n");
+		scan_type = BRCMF_SCANTYPE_PASSIVE;
+	}
+	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
+	params_le->scan_type = cpu_to_le32(scan_type);
+	params_le->length = cpu_to_le16(length);
+	params_le->channel_num =
+		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
+			    (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
+
+	/* Include RNR results if requested */
+	if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) {
+		params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR;
+	}
+	/* Adding mask to channel numbers */
+done:
+	*struct_size = length;
+	return params_le;
+}
+
+static void *
+brcmf_scan_param_get_prepped_struct_v4(struct brcmf_cfg80211_info *cfg,
+				       u32 *struct_size,
+				       struct cfg80211_scan_request *request)
+{
+	u32 n_ssids;
+	u32 n_channels;
+	u32 params_size = sizeof(struct brcmf_scan_params_v4_le);
+	u32 length;
+	struct brcmf_scan_params_v4_le *params_le = NULL;
+	u32 scan_type = BRCMF_SCANTYPE_ACTIVE;
+
+	length = offsetof(struct brcmf_scan_params_v4_le, channel_list);
+	params_size = brcmf_scan_param_tail_size(request, params_size);
+	params_le = kzalloc(params_size, GFP_KERNEL);
+	if (!params_le) {
+		bphy_err(cfg, "Could not allocate scan params\n");
+		return NULL;
+	}
+	params_le->version = cpu_to_le16(BRCMF_SCAN_PARAMS_VERSION_V4);
+	params_le->ssid_type = 0;
+	brcmf_scan_param_set_defaults(&params_le->bssid,
+		&params_le->bss_type, &params_le->channel_num,
+		&params_le->nprobes, &params_le->active_time,
+		&params_le->passive_time, &params_le->home_time);
+
+	/* Scan abort */
+	if (!request) {
+		length += sizeof(u16);
+		params_le->channel_num = cpu_to_le32(1);
+		params_le->channel_list[0] = cpu_to_le16(-1);
+		params_le->length = cpu_to_le16(length);
+		goto done;
+	}
+
+	n_ssids = request->n_ssids;
+	n_channels = request->n_channels;
+
+	/* Copy channel array if applicable */
+	brcmf_dbg(SCAN, "### List of channelspecs to scan ### %d\n",
+		  n_channels);
+	if (n_channels > 0) {
+		length += roundup(sizeof(u16) * n_channels, sizeof(u32));
+		brcmf_scan_param_copy_chanspecs(cfg, &params_le->channel_list,
+						request->channels, n_channels);
+	} else {
+		brcmf_dbg(SCAN, "Scanning all channels\n");
+	}
+
+	/* Copy ssid array if applicable */
+	brcmf_dbg(SCAN, "### List of SSIDs to scan ### %d\n", n_ssids);
+	if (n_ssids > 0) {
+		s32 offset;
+		char *ptr;
+
+		offset =
+			offsetof(struct brcmf_scan_params_v4_le, channel_list) +
+			n_channels * sizeof(u16);
+		offset = roundup(offset, sizeof(u32));
+		length += sizeof(struct brcmf_ssid_le) * n_ssids;
+		ptr = (char *)params_le + offset;
+		brcmf_scan_param_copy_ssids(ptr, request->ssids, n_ssids);
+	} else {
+		brcmf_dbg(SCAN, "Performing passive scan\n");
+		scan_type = BRCMF_SCANTYPE_PASSIVE;
+	}
+	scan_type |= brcmf_nl80211_scan_flags_to_scan_flags(request->flags);
+	params_le->scan_type = cpu_to_le32(scan_type);
+	params_le->length = cpu_to_le16(length);
+	/* Adding mask to channel numbers */
+	params_le->channel_num =
+		cpu_to_le32((n_ssids << BRCMF_SCAN_PARAMS_NSSID_SHIFT) |
+			    (n_channels & BRCMF_SCAN_PARAMS_COUNT_MASK));
+	/* Include RNR results if requested */
+	if (request->flags & NL80211_SCAN_FLAG_COLOCATED_6GHZ) {
+		params_le->ssid_type |= BRCMF_SCANSSID_INC_RNR;
+	}
+done:
+	*struct_size = length;
+	return params_le;
+}
+
+int brcmf_scan_param_setup_for_version(struct brcmf_pub *drvr, u8 version)
+{
+	drvr->scan_param_handler.version = version;
+	switch (version) {
+	case 1: {
+		drvr->scan_param_handler.get_prepped_struct =
+			brcmf_scan_param_get_prepped_struct_v1;
+	} break;
+	case 2: {
+		drvr->scan_param_handler.get_prepped_struct =
+			brcmf_scan_param_get_prepped_struct_v2;
+	} break;
+	case 3: {
+		drvr->scan_param_handler.get_prepped_struct =
+			brcmf_scan_param_get_prepped_struct_v3;
+	} break;
+	case 4: {
+		drvr->scan_param_handler.get_prepped_struct =
+			brcmf_scan_param_get_prepped_struct_v4;
+
+	} break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h
new file mode 100644
index 00000000000000..577de083c6e3cd
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+
+#ifndef _BRCMF_SCAN_PARAM_H
+#define _BRCMF_SCAN_PARAM_H
+
+struct brcmf_pub;
+
+/**
+ * brcmf_scan_param_setup_for_version() - Setup the driver to handle join structures
+ *
+ * There are a number of different structures and interface versions for scanning info
+ * This sets up the driver to handle a particular interface version.
+ *
+ * @drvr Driver structure to setup
+ * @ver Interface version
+ * Return: %0 if okay, error code otherwise
+ */
+int brcmf_scan_param_setup_for_version(struct brcmf_pub *, u8 ver);
+#endif /* _BRCMF_SCAN_PARAM_H */

From e2e3a80767382f2a8b1f96fdb8987506b775eeb3 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 5 Oct 2023 01:23:32 +0900
Subject: [PATCH 0703/1027] [brcmfmac] Support new join parameter structure
 versions

To support new join parameter versions, we move to using a function
pointer structure that knows how to deal with the different versions
of structures

Drive-by fix: Always count the assoc_params length even if no bssid is
provided. It doesn't make sense to truncate it off, since we need to set
the bssid to the broadcast addr anyway in that case.

Signed-off-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/Makefile      |   1 +
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 309 +++++++-----------
 .../broadcom/brcm80211/brcmfmac/cfg80211.h    |   2 +
 .../broadcom/brcm80211/brcmfmac/core.h        |  43 ++-
 .../broadcom/brcm80211/brcmfmac/feature.c     |  42 ++-
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  | 118 ++++++-
 .../broadcom/brcm80211/brcmfmac/join_param.c  | 288 ++++++++++++++++
 .../broadcom/brcm80211/brcmfmac/join_param.h  |  22 ++
 .../broadcom/brcm80211/brcmfmac/scan_param.c  |   8 +-
 9 files changed, 629 insertions(+), 204 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
index f3f72f9524578c..694b50a0664f24 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
@@ -25,6 +25,7 @@ brcmfmac-objs += \
 		btcoex.o \
 		vendor.o \
 		pno.o \
+		join_param.o \
 		scan_param.o \
 		xtlv.o
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 07d6b761cc0257..520ee892c2727b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -79,10 +79,6 @@
 #define	DOT11_MGMT_HDR_LEN		24	/* d11 management header len */
 #define	DOT11_BCN_PRB_FIXED_LEN		12	/* beacon/probe fixed length */
 
-#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS	320
-#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS	400
-#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS	20
-
 #define BRCMF_SCAN_CHANNEL_TIME		40
 #define BRCMF_SCAN_UNASSOC_TIME		40
 #define BRCMF_SCAN_PASSIVE_TIME		120
@@ -101,9 +97,6 @@
 #define PKT_TOKEN_IDX			15
 #define IDLE_TOKEN_IDX			12
 
-#define BRCMF_ASSOC_PARAMS_FIXED_SIZE \
-	(sizeof(struct brcmf_assoc_params_le) - sizeof(u16))
-
 #define BRCMF_MAX_CHANSPEC_LIST \
 	(BRCMF_DCMD_MEDLEN / sizeof(__le32) - 1)
 
@@ -392,8 +385,8 @@ static int nl80211_band_to_chanspec_band(enum nl80211_band band)
 	}
 }
 
-static u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
-			       struct cfg80211_chan_def *ch)
+u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
+			struct cfg80211_chan_def *ch)
 {
 	struct brcmu_chan ch_inf;
 	s32 primary_offset;
@@ -1122,7 +1115,7 @@ s32 brcmf_notify_escan_complete(struct brcmf_cfg80211_info *cfg,
 
 	if (fw_abort) {
 		u32 len;
-		void *data = drvr->scan_param_handler.get_prepped_struct(cfg, &len, NULL);
+		void *data = drvr->scan_param_handler.get_struct_for_request(cfg, &len, NULL);
 		if (!data){
 			bphy_err(drvr, "Scan abort failed to prepare abort struct\n");
 			return 0;
@@ -1362,7 +1355,7 @@ brcmf_run_escan(struct brcmf_cfg80211_info *cfg, struct brcmf_if *ifp,
 
 	brcmf_dbg(SCAN, "E-SCAN START\n");
 
-	prepped_params = drvr->scan_param_handler.get_prepped_struct(cfg, &struct_size, request);
+	prepped_params = drvr->scan_param_handler.get_struct_for_request(cfg, &struct_size, request);
 	if (!prepped_params) {
 		err = -EINVAL;
 		goto exit;
@@ -1680,21 +1673,19 @@ static void brcmf_link_down(struct brcmf_cfg80211_vif *vif, u16 reason,
 	brcmf_dbg(TRACE, "Exit\n");
 }
 
-static s32
-brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev,
-		      struct cfg80211_ibss_params *params)
+static s32 brcmf_cfg80211_join_ibss(struct wiphy *wiphy,
+				    struct net_device *ndev,
+				    struct cfg80211_ibss_params *params)
 {
 	struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
 	struct brcmf_if *ifp = netdev_priv(ndev);
 	struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
 	struct brcmf_pub *drvr = cfg->pub;
-	struct brcmf_join_params join_params;
-	size_t join_params_size = 0;
-	s32 err = 0;
+	void *join_params;
+	u32 join_params_size = 0;
 	s32 wsec = 0;
 	s32 bcnprd;
-	u16 chanspec;
-	u32 ssid_len;
+	s32 err = 0;
 
 	brcmf_dbg(TRACE, "Enter\n");
 	if (!check_vif_up(ifp->vif))
@@ -1768,58 +1759,40 @@ brcmf_cfg80211_join_ibss(struct wiphy *wiphy, struct net_device *ndev,
 		goto done;
 	}
 
-	/* Configure required join parameter */
-	memset(&join_params, 0, sizeof(struct brcmf_join_params));
-
-	/* SSID */
-	ssid_len = min_t(u32, params->ssid_len, IEEE80211_MAX_SSID_LEN);
-	memcpy(join_params.ssid_le.SSID, params->ssid, ssid_len);
-	join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len);
-	join_params_size = sizeof(join_params.ssid_le);
-
-	/* BSSID */
 	if (params->bssid) {
-		memcpy(join_params.params_le.bssid, params->bssid, ETH_ALEN);
-		join_params_size += BRCMF_ASSOC_PARAMS_FIXED_SIZE;
 		memcpy(profile->bssid, params->bssid, ETH_ALEN);
 	} else {
-		eth_broadcast_addr(join_params.params_le.bssid);
 		eth_zero_addr(profile->bssid);
 	}
 
-	/* Channel */
+	cfg->ibss_starter = false;
+	cfg->channel = 0;
 	if (params->chandef.chan) {
-		u32 target_channel;
+		u16 chanspec;
+		cfg->channel = ieee80211_frequency_to_channel(
+			params->chandef.chan->center_freq);
+		/* adding chanspec */
+		chanspec = chandef_to_chanspec(&cfg->d11inf, &params->chandef);
+
+		/* set chanspec */
+		err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec);
 
-		cfg->channel =
-			ieee80211_frequency_to_channel(
-				params->chandef.chan->center_freq);
-		if (params->channel_fixed) {
-			/* adding chanspec */
-			chanspec = chandef_to_chanspec(&cfg->d11inf,
-						       &params->chandef);
-			join_params.params_le.chanspec_list[0] =
-				cpu_to_le16(chanspec);
-			join_params.params_le.chanspec_num = cpu_to_le32(1);
-			join_params_size += sizeof(join_params.params_le);
-		}
-
-		/* set channel for starter */
-		target_channel = cfg->channel;
-		err = brcmf_fil_cmd_int_set(ifp, BRCMF_C_SET_CHANNEL,
-					    target_channel);
 		if (err) {
 			bphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err);
 			goto done;
 		}
-	} else
-		cfg->channel = 0;
-
-	cfg->ibss_starter = false;
-
+	}
 
-	err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID,
-				     &join_params, join_params_size);
+	join_params = drvr->join_param_handler.get_struct_for_ibss(
+		cfg, &join_params_size, params);
+	if (!join_params) {
+		bphy_err(drvr, "Converting join params failed\n");
+		goto done;
+	}
+	err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID, join_params,
+				     join_params_size);
+	/* Free params no matter what */
+	kfree(join_params);
 	if (err) {
 		bphy_err(drvr, "WLC_SET_SSID failed (%d)\n", err);
 		goto done;
@@ -2346,52 +2319,51 @@ static void brcmf_set_join_pref(struct brcmf_if *ifp,
 
 static s32
 brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
-		       struct cfg80211_connect_params *sme)
+		       struct cfg80211_connect_params *params)
 {
 	struct brcmf_cfg80211_info *cfg = wiphy_to_cfg(wiphy);
 	struct brcmf_if *ifp = netdev_priv(ndev);
 	struct brcmf_cfg80211_profile *profile = &ifp->vif->profile;
-	struct ieee80211_channel *chan = sme->channel;
+	struct ieee80211_channel *chan = params->channel;
 	struct brcmf_pub *drvr = ifp->drvr;
-	struct brcmf_join_params join_params;
-	size_t join_params_size;
+	void *join_params;
+	u32 join_params_size;
+	void *fallback_join_params;
+	u32 fallback_join_params_size;
 	const struct brcmf_tlv *rsn_ie;
 	const struct brcmf_vs_tlv *wpa_ie;
 	const void *ie;
 	u32 ie_len;
-	struct brcmf_ext_join_params_le *ext_join_params;
-	u16 chanspec;
 	s32 err = 0;
-	u32 ssid_len;
 
 	brcmf_dbg(TRACE, "Enter\n");
 	if (!check_vif_up(ifp->vif))
 		return -EIO;
 
-	if (!sme->ssid) {
+	if (!params->ssid) {
 		bphy_err(drvr, "Invalid ssid\n");
 		return -EOPNOTSUPP;
 	}
 
-	if (sme->channel_hint)
-		chan = sme->channel_hint;
+	if (params->channel_hint)
+		chan = params->channel_hint;
 
-	if (sme->bssid_hint)
-		sme->bssid = sme->bssid_hint;
+	if (params->bssid_hint)
+		params->bssid = params->bssid_hint;
 
 	if (ifp->vif == cfg->p2p.bss_idx[P2PAPI_BSSCFG_PRIMARY].vif) {
 		/* A normal (non P2P) connection request setup. */
 		ie = NULL;
 		ie_len = 0;
 		/* find the WPA_IE */
-		wpa_ie = brcmf_find_wpaie((u8 *)sme->ie, sme->ie_len);
+		wpa_ie = brcmf_find_wpaie((u8 *)params->ie, params->ie_len);
 		if (wpa_ie) {
 			ie = wpa_ie;
 			ie_len = wpa_ie->len + TLV_HDR_LEN;
 		} else {
 			/* find the RSN_IE */
-			rsn_ie = brcmf_parse_tlvs((const u8 *)sme->ie,
-						  sme->ie_len,
+			rsn_ie = brcmf_parse_tlvs((const u8 *)params->ie,
+						  params->ie_len,
 						  WLAN_EID_RSN);
 			if (rsn_ie) {
 				ie = rsn_ie;
@@ -2402,7 +2374,7 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
 	}
 
 	err = brcmf_vif_set_mgmt_ie(ifp->vif, BRCMF_VNDR_IE_ASSOCREQ_FLAG,
-				    sme->ie, sme->ie_len);
+				    params->ie, params->ie_len);
 	if (err)
 		bphy_err(drvr, "Set Assoc REQ IE Failed\n");
 	else
@@ -2413,166 +2385,117 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
 	if (chan) {
 		cfg->channel =
 			ieee80211_frequency_to_channel(chan->center_freq);
-		chanspec = channel_to_chanspec(&cfg->d11inf, chan);
-		brcmf_dbg(CONN, "channel=%d, center_req=%d, chanspec=0x%04x\n",
-			  cfg->channel, chan->center_freq, chanspec);
+		brcmf_dbg(CONN, "channel=%d, center_req=%d\n",
+			  cfg->channel, chan->center_freq);
 	} else {
 		cfg->channel = 0;
-		chanspec = 0;
 	}
 
-	brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", sme->ie, sme->ie_len);
+	brcmf_dbg(INFO, "ie (%p), ie_len (%zd)\n", params->ie, params->ie_len);
 
-	err = brcmf_set_wpa_version(ndev, sme);
+	err = brcmf_set_wpa_version(ndev, params);
 	if (err) {
 		bphy_err(drvr, "wl_set_wpa_version failed (%d)\n", err);
 		goto done;
 	}
 
-	sme->auth_type = brcmf_war_auth_type(ifp, sme->auth_type);
-	err = brcmf_set_auth_type(ndev, sme);
+	params->auth_type = brcmf_war_auth_type(ifp, params->auth_type);
+	err = brcmf_set_auth_type(ndev, params);
 	if (err) {
 		bphy_err(drvr, "wl_set_auth_type failed (%d)\n", err);
 		goto done;
 	}
 
-	err = brcmf_set_wsec_mode(ndev, sme);
+	err = brcmf_set_wsec_mode(ndev, params);
 	if (err) {
 		bphy_err(drvr, "wl_set_set_cipher failed (%d)\n", err);
 		goto done;
 	}
 
-	err = brcmf_set_key_mgmt(ndev, sme);
+	err = brcmf_set_key_mgmt(ndev, params);
 	if (err) {
 		bphy_err(drvr, "wl_set_key_mgmt failed (%d)\n", err);
 		goto done;
 	}
 
-	err = brcmf_set_sharedkey(ndev, sme);
+	err = brcmf_set_sharedkey(ndev, params);
 	if (err) {
 		bphy_err(drvr, "brcmf_set_sharedkey failed (%d)\n", err);
 		goto done;
 	}
-
-	if (sme->crypto.psk &&
-	    profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) {
-		if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) {
-			err = -EINVAL;
-			goto done;
+	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) {
+		if (params->crypto.psk) {
+			if ((profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) &&
+			    (profile->use_fwsup != BRCMF_PROFILE_FWSUP_PSK)) {
+				if (WARN_ON(profile->use_fwsup !=
+					    BRCMF_PROFILE_FWSUP_NONE)) {
+					err = -EINVAL;
+					goto done;
+				}
+				brcmf_dbg(INFO, "using PSK offload\n");
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
+			}
 		}
-		brcmf_dbg(INFO, "using PSK offload\n");
-		profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
-	}
 
-	if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
-		/* enable firmware supplicant for this interface */
-		err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
-		if (err < 0) {
-			bphy_err(drvr, "failed to enable fw supplicant\n");
-			goto done;
+		if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) &&
+		    params->crypto.psk)
+			err = brcmf_set_pmk(ifp, params->crypto.psk,
+					    BRCMF_WSEC_MAX_PSK_LEN);
+		else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) {
+			/* clean up user-space RSNE */
+			if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) {
+				bphy_err(
+					drvr,
+					"failed to clean up user-space RSNE\n");
+				goto done;
+			}
+			err = brcmf_fwvid_set_sae_password(ifp, &params->crypto);
+			if (!err && params->crypto.psk)
+				err = brcmf_set_pmk(ifp, params->crypto.psk,
+						    BRCMF_WSEC_MAX_PSK_LEN);
 		}
-	}
-
-	if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK)
-		err = brcmf_set_pmk(ifp, sme->crypto.psk,
-				    BRCMF_WSEC_MAX_PSK_LEN);
-	else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) {
-		/* clean up user-space RSNE */
-		err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0);
-		if (err) {
-			bphy_err(drvr, "failed to clean up user-space RSNE\n");
+		if (err)
 			goto done;
-		}
-		err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto);
-		if (!err && sme->crypto.psk)
-			err = brcmf_set_pmk(ifp, sme->crypto.psk,
-					    BRCMF_WSEC_MAX_PSK_LEN);
 	}
-	if (err)
-		goto done;
-
-	/* Join with specific BSSID and cached SSID
-	 * If SSID is zero join based on BSSID only
-	 */
-	join_params_size = offsetof(struct brcmf_ext_join_params_le, assoc_le) +
-		offsetof(struct brcmf_assoc_params_le, chanspec_list);
-	if (cfg->channel)
-		join_params_size += sizeof(u16);
-	ext_join_params = kzalloc(sizeof(*ext_join_params), GFP_KERNEL);
-	if (ext_join_params == NULL) {
-		err = -ENOMEM;
-		goto done;
-	}
-	ssid_len = min_t(u32, sme->ssid_len, IEEE80211_MAX_SSID_LEN);
-	ext_join_params->ssid_le.SSID_len = cpu_to_le32(ssid_len);
-	memcpy(&ext_join_params->ssid_le.SSID, sme->ssid, ssid_len);
-	if (ssid_len < IEEE80211_MAX_SSID_LEN)
-		brcmf_dbg(CONN, "SSID \"%s\", len (%d)\n",
-			  ext_join_params->ssid_le.SSID, ssid_len);
-
-	/* Set up join scan parameters */
-	ext_join_params->scan_le.scan_type = -1;
-	ext_join_params->scan_le.home_time = cpu_to_le32(-1);
-
-	if (sme->bssid)
-		memcpy(&ext_join_params->assoc_le.bssid, sme->bssid, ETH_ALEN);
-	else
-		eth_broadcast_addr(ext_join_params->assoc_le.bssid);
+	brcmf_set_join_pref(ifp, &params->bss_select);
+	if (params->ssid_len < IEEE80211_MAX_SSID_LEN)
+		brcmf_dbg(CONN, "SSID \"%s\", len (%zu)\n", params->ssid,
+			  params->ssid_len);
+	join_params = drvr->join_param_handler.get_struct_for_connect(
+		cfg, &join_params_size, params);
 
-	if (cfg->channel) {
-		ext_join_params->assoc_le.chanspec_num = cpu_to_le32(1);
+	if (join_params) {
+		err = brcmf_fil_bsscfg_data_set(ifp, "join", join_params,
+						join_params_size);
 
-		ext_join_params->assoc_le.chanspec_list[0] =
-			cpu_to_le16(chanspec);
-		/* Increase dwell time to receive probe response or detect
-		 * beacon from target AP at a noisy air only during connect
-		 * command.
-		 */
-		ext_join_params->scan_le.active_time =
-			cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS);
-		ext_join_params->scan_le.passive_time =
-			cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS);
-		/* To sync with presence period of VSDB GO send probe request
-		 * more frequently. Probe request will be stopped when it gets
-		 * probe response from target AP/GO.
-		 */
-		ext_join_params->scan_le.nprobes =
-			cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS /
-				    BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS);
-	} else {
-		ext_join_params->scan_le.active_time = cpu_to_le32(-1);
-		ext_join_params->scan_le.passive_time = cpu_to_le32(-1);
-		ext_join_params->scan_le.nprobes = cpu_to_le32(-1);
+		/* We only free the join parameters if we were successful.
+		 * Otherwise they are used to extract the fallback, below */
+		if (!err) {
+			kfree(join_params);
+			/* This is it. join command worked, we are done */
+			goto done;
+		}
+		/* For versions >= 1, this should have worked, so report the error */
+		if (drvr->join_param_handler.version >= 1) {
+			bphy_err(drvr, "Failed to use join iovar to join: %d\n",
+				 err);
+		}
 	}
 
-	brcmf_set_join_pref(ifp, &sme->bss_select);
-
-	err  = brcmf_fil_bsscfg_data_set(ifp, "join", ext_join_params,
-					 join_params_size);
-	kfree(ext_join_params);
-	if (!err)
-		/* This is it. join command worked, we are done */
+	/* Fallback to using WLC_SET_SSID approach, which just uses join_params parts of the structure */
+	fallback_join_params = drvr->join_param_handler.get_join_from_ext_join(
+		join_params, &fallback_join_params_size);
+	if (!fallback_join_params) {
+		bphy_err(drvr, "Unable to generate fallback join params\n");
+		kfree(join_params);
 		goto done;
-
-	/* join command failed, fallback to set ssid */
-	memset(&join_params, 0, sizeof(join_params));
-	join_params_size = sizeof(join_params.ssid_le);
-
-	memcpy(&join_params.ssid_le.SSID, sme->ssid, ssid_len);
-	join_params.ssid_le.SSID_len = cpu_to_le32(ssid_len);
-
-	if (sme->bssid)
-		memcpy(join_params.params_le.bssid, sme->bssid, ETH_ALEN);
-	else
-		eth_broadcast_addr(join_params.params_le.bssid);
-
-	if (cfg->channel) {
-		join_params.params_le.chanspec_list[0] = cpu_to_le16(chanspec);
-		join_params.params_le.chanspec_num = cpu_to_le32(1);
-		join_params_size += sizeof(join_params.params_le);
 	}
 	err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_SSID,
-				     &join_params, join_params_size);
+				     fallback_join_params,
+				     fallback_join_params_size);
+
+	kfree(join_params);
+	kfree(fallback_join_params);
 	if (err)
 		bphy_err(drvr, "BRCMF_C_SET_SSID failed (%d)\n", err);
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index ff7db073731da3..61ae0d384cc675 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -473,6 +473,8 @@ s32 brcmf_vif_set_mgmt_ie(struct brcmf_cfg80211_vif *vif, s32 pktflag,
 s32 brcmf_vif_clear_mgmt_ies(struct brcmf_cfg80211_vif *vif);
 u16 channel_to_chanspec(struct brcmu_d11inf *d11inf,
 			struct ieee80211_channel *ch);
+u16 chandef_to_chanspec(struct brcmu_d11inf *d11inf,
+			struct cfg80211_chan_def *ch);
 bool brcmf_get_vif_state_any(struct brcmf_cfg80211_info *cfg,
 			     unsigned long state);
 void brcmf_cfg80211_arm_vif_event(struct brcmf_cfg80211_info *cfg,
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
index cbe0d706736517..2499a1d175417e 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.h
@@ -115,12 +115,48 @@ struct pno_struct_handler {
 			       u8 (*ssid)[IEEE80211_MAX_SSID_LEN], u8 *ssid_len,
 			       u8 *channel, enum nl80211_band *band);
 };
+
 struct cfg80211_scan_request;
 struct scan_param_struct_handler {
 	u8 version;
-	void *(*get_prepped_struct)(struct brcmf_cfg80211_info *cfg,
-				    u32 *struct_size,
-				    struct cfg80211_scan_request *request);
+	void *(*get_struct_for_request)(struct brcmf_cfg80211_info *cfg,
+					u32 *struct_size,
+					struct cfg80211_scan_request *request);
+};
+
+struct cfg80211_ibss_params;
+struct cfg80211_connect_params;
+
+/**
+ * struct join_param_struct_handler - Handler for different join parameter versions
+ *
+ * There are a number of different, incompatible structures and interface versions for join/extended join parameters
+ * We abstract away the actual structures used, so that code does not have to worry about filling in structs properly.
+ *
+ * This interface deliberately takes and returns opaque structures.
+ *
+ * @version - Interface version the firmware supports/uses
+ * @get_struct_for_ibss - Return a join parameter structure for a set of IBSS parameters.
+ * This structure can be used to join the passed BSS.
+ * @get_struct_for_connect - Return an extended join parameter structure for a set of connect
+ * parameters.  This structure can be used to join the SSID specified in the parameters.
+ * @get_join_from_ext_join - When an extended join does not work, we fall back to a regular join.
+ * This function produces a join parameter struture from an extended join one.
+ */
+struct join_param_struct_handler {
+	u8 version;
+	/* This returns a join_param type struct */
+	void *(*get_struct_for_ibss)(struct brcmf_cfg80211_info *cfg,
+				     u32 *struct_size,
+				     struct cfg80211_ibss_params *params);
+	/* This returns an ext_join_param type struct */
+	void *(*get_struct_for_connect)(struct brcmf_cfg80211_info *cfg,
+					u32 *struct_size,
+					struct cfg80211_connect_params *params);
+	/* This returns the join param portion of an ext_join_param type struct.
+	 * The memory returned is separately allocated from the passed-in struct.
+	 */
+	void *(*get_join_from_ext_join)(void *ext_join_param, u32 *struct_size);
 };
 
 /* Common structure for module and instance linkage */
@@ -174,6 +210,7 @@ struct brcmf_pub {
 	u16 cnt_ver;
 	struct pno_struct_handler pno_handler;
 	struct scan_param_struct_handler scan_param_handler;
+	struct join_param_struct_handler join_param_handler;
 };
 
 /* forward declarations */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 59fcb489cb25b4..7aea92ee52e131 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -18,9 +18,22 @@
 #include "common.h"
 #include "pno.h"
 #include "scan_param.h"
+#include "join_param.h"
 
 #define BRCMF_FW_UNSUPPORTED	23
 
+/* MIN branch version supporting join iovar versioning */
+#define MIN_JOINEXT_V1_FW_MAJOR 17u
+/* Branch/es supporting join iovar versioning prior to
+ * MIN_JOINEXT_V1_FW_MAJOR
+ */
+#define MIN_JOINEXT_V1_BR2_FW_MAJOR      16
+#define MIN_JOINEXT_V1_BR2_FW_MINOR      1
+
+#define MIN_JOINEXT_V1_BR1_FW_MAJOR      14
+#define MIN_JOINEXT_V1_BR1_FW_MINOR_2    2
+#define MIN_JOINEXT_V1_BR1_FW_MINOR_4    4
+
 /*
  * expand feature list to array of feature strings.
  */
@@ -138,7 +151,7 @@ struct brcmf_feat_wlcfeat {
 
 static const struct brcmf_feat_wlcfeat brcmf_feat_wlcfeat_map[] = {
 	{ 12, 0, BIT(BRCMF_FEAT_PMKID_V2) },
-	{ 13, 0, BIT(BRCMF_FEAT_PMKID_V3) },
+	{ 13, 0, BIT(BRCMF_FEAT_PMKID_V3) }
 };
 
 static void brcmf_feat_wlc_version_overrides(struct brcmf_pub *drv)
@@ -291,6 +304,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
 void brcmf_feat_attach(struct brcmf_pub *drvr)
 {
 	struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
+	struct brcmf_join_version_le join_ver;
 	struct brcmf_scan_version_le scan_ver;
 	struct brcmf_pno_param_v3_le pno_params;
 	struct brcmf_pno_macaddr_le pfn_mac;
@@ -345,12 +359,36 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 
 	brcmf_feat_iovar_int_get(ifp, BRCMF_FEAT_FWSUP, "sup_wpa");
 
+	err = brcmf_fil_iovar_data_get(ifp, "join_ver", &join_ver, sizeof(join_ver));
+	if (!err) {
+		u16 ver = le16_to_cpu(join_ver.join_ver_major);
+		brcmf_join_param_setup_for_version(drvr, ver);
+	} else {
+		/* Default to version 0, unless it is one of the firmware branches
+		 * that doesn't have a join_ver iovar but are still version 1 */
+		u8 version = 0;
+		struct brcmf_wlc_version_le ver;
+		err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver));
+		if (!err) {
+			u16 major = le16_to_cpu(ver.wlc_ver_major);
+			u16 minor = le16_to_cpu(ver.wlc_ver_minor);
+			if (((major == MIN_JOINEXT_V1_BR1_FW_MAJOR) &&
+			     ((minor == MIN_JOINEXT_V1_BR1_FW_MINOR_2) ||
+			      (minor == MIN_JOINEXT_V1_BR1_FW_MINOR_4))) ||
+			    ((major == MIN_JOINEXT_V1_BR2_FW_MAJOR) &&
+			     (minor >= MIN_JOINEXT_V1_BR2_FW_MINOR)) ||
+			    (major >= MIN_JOINEXT_V1_FW_MAJOR)) {
+				version = 1;
+			}
+		}
+		brcmf_join_param_setup_for_version(drvr, version);
+	}
 	err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver));
 	if (!err) {
 		u16 ver = le16_to_cpu(scan_ver.scan_ver_major);
 		brcmf_scan_param_setup_for_version(drvr, ver);
 	} else {
-		/* Default tp version 1. */
+		/* Default to version 1. */
 		brcmf_scan_param_setup_for_version(drvr, 1);
 	}
 
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index e4b3b13a8ff92c..14d91e7749e82d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -590,11 +590,67 @@ struct brcmf_escan_result_le {
 struct brcmf_assoc_params_le {
 	/* 00:00:00:00:00:00: broadcast scan */
 	u8 bssid[ETH_ALEN];
+	/* 0: use chanspec_num, and the single bssid,
+	 * otherwise count of chanspecs in chanspec_list
+	 * AND paired bssids following chanspec_list
+	 * also, chanspec_num has to be set to zero
+	 * for bssid list to be used
+	 */
+	__le16 bssid_cnt;
+	/* 0: all available channels, otherwise count of chanspecs in
+	 * chanspec_list */
+	__le32 chanspec_num;
+	/* list of chanspecs */
+	__le16 chanspec_list[];
+};
+
+struct brcmf_assoc_params_v1_le {
+	__le16 version;
+	__le16 flags;
+	/* 00:00:00:00:00:00: broadcast scan */
+	u8 bssid[ETH_ALEN];
+	/* 0: use chanspec_num, and the single bssid,
+	 * otherwise count of chanspecs in chanspec_list
+	 * AND paired bssids following chanspec_list
+	 * also, chanspec_num has to be set to zero
+	 * for bssid list to be used
+	 */
+	__le16 bssid_cnt;
+	/* 0: all available channels, otherwise count of chanspecs in
+	 * chanspec_list */
+	__le32 chanspec_num;
+	/* list of chanspecs */
+	__le16 chanspec_list[];
+};
+
+/* ML assoc and scan params */
+struct brcmf_ml_assoc_scan_params_v1_le {
+	/* whether to follow strictly ordered assoc ? */
+	u8 ml_assoc_mode;
+	/* to identify whether ml scan needs to be triggered */
+	u8 ml_scan_mode;
+	u8 pad[2];
+};
+
+struct brcmf_assoc_params_v2_le {
+	__le16 version;
+	__le16 flags;
+	/* 00:00:00:00:00:00: broadcast scan */
+	u8 bssid[ETH_ALEN];
+	/* 0: use chanspec_num, and the single bssid,
+	 * otherwise count of chanspecs in chanspec_list
+	 * AND paired bssids following chanspec_list
+	 * also, chanspec_num has to be set to zero
+	 * for bssid list to be used
+	 */
+	__le16 bssid_cnt;
+	/* Multilink association and scan params */
+	struct brcmf_ml_assoc_scan_params_v1_le ml_assoc_scan_params;
 	/* 0: all available channels, otherwise count of chanspecs in
 	 * chanspec_list */
 	__le32 chanspec_num;
 	/* list of chanspecs */
-	__le16 chanspec_list[1];
+	__le16 chanspec_list[];
 };
 
 /**
@@ -619,9 +675,19 @@ struct brcmf_join_params {
 	struct brcmf_assoc_params_le params_le;
 };
 
+struct brcmf_join_params_v1 {
+	struct brcmf_ssid_le ssid_le;
+	struct brcmf_assoc_params_v1_le params_le;
+};
+struct brcmf_join_params_v2 {
+	struct brcmf_ssid_le ssid_le;
+	struct brcmf_assoc_params_v2_le params_le;
+};
+
 /* scan params for extended join */
 struct brcmf_join_scan_params_le {
 	u8 scan_type;		/* 0 use default, active or passive scan */
+	u8 PAD[3];
 	__le32 nprobes;		/* -1 use default, nr of probes per channel */
 	__le32 active_time;	/* -1 use default, dwell time per channel for
 				 * active scanning
@@ -634,6 +700,23 @@ struct brcmf_join_scan_params_le {
 				 */
 };
 
+/* scan params for extended join */
+struct brcmf_join_scan_params_v1_le {
+	u8 scan_type; /* 0 use default, active or passive scan */
+	u8 ml_scan_mode; /* 0 scan ML channels in RNR, 1 scan only provided channels */
+	u8 PAD[2];
+	__le32 nprobes; /* -1 use default, nr of probes per channel */
+	__le32 active_time; /* -1 use default, dwell time per channel for
+				 * active scanning
+				 */
+	__le32 passive_time; /* -1 use default, dwell time per channel
+				 * for passive scanning
+				 */
+	__le32 home_time; /* -1 use default, dwell time for the home
+				 * channel between channel scans
+				 */
+};
+
 /* extended join params */
 struct brcmf_ext_join_params_le {
 	struct brcmf_ssid_le ssid_le;	/* {0, ""}: wildcard scan */
@@ -641,6 +724,24 @@ struct brcmf_ext_join_params_le {
 	struct brcmf_assoc_params_le assoc_le;
 };
 
+/* extended join params */
+struct brcmf_ext_join_params_v1_le {
+	__le16 version;
+	u16 pad;
+	struct brcmf_ssid_le ssid_le;	/* {0, ""}: wildcard scan */
+	struct brcmf_join_scan_params_le scan_le;
+	struct brcmf_assoc_params_v1_le assoc_le;
+};
+
+/* extended join params v2 */
+struct brcmf_ext_join_params_v2_le {
+	__le16 version;
+	u16 pad;
+	struct brcmf_ssid_le ssid_le;	/* {0, ""}: wildcard scan */
+	struct brcmf_join_scan_params_v1_le scan_le;
+	struct brcmf_assoc_params_v2_le assoc_le;
+};
+
 struct brcmf_wsec_key {
 	u32 index;		/* key index */
 	u32 len;		/* key length */
@@ -946,7 +1047,20 @@ struct brcmf_wlc_version_le {
 };
 
 /**
- * struct brcmf_wl_scan_version_le - scan interface version
+ * struct brcmf_join_version_le - join interface version
+ */
+struct brcmf_join_version_le {
+	__le16	version;		/**< version of the structure */
+	__le16	length;			/**< length of the entire structure */
+
+	/* join interface version numbers */
+	__le16	join_ver_major;		/**< join interface major version number */
+	u8	pad[2];
+};
+#define BRCMF_JOIN_VERSION_VERSION 1
+
+/**
+ * struct brcmf_scan_version_le - scan interface version
  */
 struct brcmf_scan_version_le {
         __le16  version;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c
new file mode 100644
index 00000000000000..4f026571c7e7eb
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.c
@@ -0,0 +1,288 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+#include <linux/gcd.h>
+#include <net/cfg80211.h>
+
+#include "core.h"
+#include "debug.h"
+#include "fwil_types.h"
+#include "cfg80211.h"
+#include "join_param.h"
+
+/* These defaults are the same as found in the DHD drivers, and represent
+ * reasonable defaults for various scan dwell and probe times.   */
+#define BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS 320
+#define BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS 400
+#define BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS 20
+
+/* Most of the actual structure fields we fill in are the same for various versions
+ * However, due to various incompatible changes and variants, the fields are not always
+ * in the same place.
+ * This makes for code duplication, so we try to commonize setting fields where it makes sense.
+ */
+
+static void brcmf_joinscan_set_ssid(struct brcmf_ssid_le *ssid_le,
+				    const u8 *ssid, u32 ssid_len)
+{
+	ssid_len = min_t(u32, ssid_len, IEEE80211_MAX_SSID_LEN);
+	ssid_le->SSID_len = cpu_to_le32(ssid_len);
+	memcpy(ssid_le->SSID, ssid, ssid_len);
+}
+
+static void brcmf_joinscan_set_bssid(u8 out_bssid[6], const u8 *in_bssid)
+{
+	if (in_bssid) {
+		memcpy(out_bssid, in_bssid, ETH_ALEN);
+	} else {
+		eth_broadcast_addr(out_bssid);
+	}
+}
+
+/* Create a single channel chanspec list from a wireless stack channel */
+static void brcmf_joinscan_set_single_chanspec_from_channel(
+	struct brcmf_cfg80211_info *cfg, struct ieee80211_channel *chan,
+	__le32 *chanspec_count, __le16 (*chanspec_list)[])
+{
+	u16 chanspec = channel_to_chanspec(&cfg->d11inf, chan);
+	*chanspec_count = cpu_to_le32(1);
+	(*chanspec_list)[0] = cpu_to_le16(chanspec);
+}
+
+/* Create a single channel chanspec list from a wireless stack chandef */
+static void brcmf_joinscan_set_single_chanspec_from_chandef(
+	struct brcmf_cfg80211_info *cfg, struct cfg80211_chan_def *chandef,
+	__le32 *chanspec_count, __le16 (*chanspec_list)[])
+{
+	u16 chanspec = chandef_to_chanspec(&cfg->d11inf, chandef);
+	*chanspec_count = cpu_to_le32(1);
+	(*chanspec_list)[0] = cpu_to_le16(chanspec);
+}
+
+static void *brcmf_get_struct_for_ibss_v0(struct brcmf_cfg80211_info *cfg,
+					  u32 *struct_size,
+					  struct cfg80211_ibss_params *params)
+{
+	struct brcmf_join_params *join_params;
+
+	u32 join_params_size = struct_size(join_params, params_le.chanspec_list,
+					   params->chandef.chan != NULL);
+
+	*struct_size = join_params_size;
+	join_params = kzalloc(join_params_size, GFP_KERNEL);
+	if (!join_params) {
+		bphy_err(cfg, "Unable to allocate memory for join params\n");
+		return NULL;
+	}
+	brcmf_joinscan_set_ssid(&join_params->ssid_le, params->ssid,
+				params->ssid_len);
+	brcmf_joinscan_set_bssid(join_params->params_le.bssid, params->bssid);
+	/* Channel */
+	if (cfg->channel) {
+		brcmf_joinscan_set_single_chanspec_from_chandef(
+			cfg, &params->chandef,
+			&join_params->params_le.chanspec_num,
+			&join_params->params_le.chanspec_list);
+	}
+	return join_params;
+}
+
+static void *
+brcmf_get_prepped_struct_for_ibss_v1(struct brcmf_cfg80211_info *cfg,
+				     u32 *struct_size,
+				     struct cfg80211_ibss_params *params)
+{
+	struct brcmf_join_params_v1 *join_params;
+	u32 join_params_size = struct_size(join_params, params_le.chanspec_list,
+					   params->chandef.chan != NULL);
+
+	*struct_size = join_params_size;
+	join_params = kzalloc(join_params_size, GFP_KERNEL);
+	if (!join_params) {
+		bphy_err(cfg, "Unable to allocate memory for join params\n");
+		return NULL;
+	}
+	join_params->params_le.version = cpu_to_le16(1);
+	brcmf_joinscan_set_ssid(&join_params->ssid_le, params->ssid,
+				params->ssid_len);
+	brcmf_joinscan_set_bssid(join_params->params_le.bssid, params->bssid);
+	/* Channel */
+	if (cfg->channel) {
+		brcmf_joinscan_set_single_chanspec_from_chandef(
+			cfg, &params->chandef,
+			&join_params->params_le.chanspec_num,
+			&join_params->params_le.chanspec_list);
+	}
+	return join_params;
+}
+
+static void
+brcmf_joinscan_set_common_v0v1_params(struct brcmf_join_scan_params_le *scan_le,
+				      bool have_channel)
+{
+	/* Set up join scan parameters */
+	scan_le->scan_type = 0;
+	scan_le->home_time = cpu_to_le32(-1);
+
+	if (have_channel) {
+		/* Increase dwell time to receive probe response or detect
+		 * beacon from target AP at a noisy air only during connect
+		 * command.
+		 */
+		scan_le->active_time =
+			cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS);
+		scan_le->passive_time =
+			cpu_to_le32(BRCMF_SCAN_JOIN_PASSIVE_DWELL_TIME_MS);
+		/* To sync with presence period of VSDB GO send probe request
+		 * more frequently. Probe request will be stopped when it gets
+		 * probe response from target AP/GO.
+		 */
+		scan_le->nprobes =
+			cpu_to_le32(BRCMF_SCAN_JOIN_ACTIVE_DWELL_TIME_MS /
+				    BRCMF_SCAN_JOIN_PROBE_INTERVAL_MS);
+	} else {
+		scan_le->active_time = cpu_to_le32(-1);
+		scan_le->passive_time = cpu_to_le32(-1);
+		scan_le->nprobes = cpu_to_le32(-1);
+	}
+}
+static void *
+brcmf_get_struct_for_connect_v0(struct brcmf_cfg80211_info *cfg,
+				u32 *struct_size,
+				struct cfg80211_connect_params *params)
+{
+	struct brcmf_ext_join_params_le *ext_v0;
+	u32 join_params_size =
+		struct_size(ext_v0, assoc_le.chanspec_list, cfg->channel != 0);
+
+	*struct_size = join_params_size;
+	ext_v0 = kzalloc(join_params_size, GFP_KERNEL);
+	if (!ext_v0) {
+		bphy_err(
+			cfg,
+			"Could not allocate memory for extended join parameters\n");
+		return NULL;
+	}
+	brcmf_joinscan_set_ssid(&ext_v0->ssid_le, params->ssid,
+				params->ssid_len);
+	brcmf_joinscan_set_common_v0v1_params(&ext_v0->scan_le,
+					      cfg->channel != 0);
+	brcmf_joinscan_set_bssid(ext_v0->assoc_le.bssid, params->bssid);
+	if (cfg->channel) {
+		struct ieee80211_channel *chan = params->channel_hint ?
+							 params->channel_hint :
+							 params->channel;
+		brcmf_joinscan_set_single_chanspec_from_channel(
+			cfg, chan, &ext_v0->assoc_le.chanspec_num,
+			&ext_v0->assoc_le.chanspec_list);
+	}
+	return ext_v0;
+}
+
+static void *
+brcmf_get_struct_for_connect_v1(struct brcmf_cfg80211_info *cfg,
+				u32 *struct_size,
+				struct cfg80211_connect_params *params)
+{
+	struct brcmf_ext_join_params_v1_le *ext_v1;
+	u32 join_params_size =
+		struct_size(ext_v1, assoc_le.chanspec_list, cfg->channel != 0);
+
+	*struct_size = join_params_size;
+	ext_v1 = kzalloc(join_params_size, GFP_KERNEL);
+	if (!ext_v1) {
+		bphy_err(
+			cfg,
+			"Could not allocate memory for extended join parameters\n");
+		return NULL;
+	}
+	ext_v1->version = cpu_to_le16(1);
+	ext_v1->assoc_le.version = cpu_to_le16(1);
+	brcmf_joinscan_set_ssid(&ext_v1->ssid_le, params->ssid,
+				params->ssid_len);
+	brcmf_joinscan_set_common_v0v1_params(&ext_v1->scan_le,
+					      cfg->channel != 0);
+	brcmf_joinscan_set_bssid(ext_v1->assoc_le.bssid, params->bssid);
+	if (cfg->channel) {
+		struct ieee80211_channel *chan = params->channel_hint ?
+							 params->channel_hint :
+							 params->channel;
+		brcmf_joinscan_set_single_chanspec_from_channel(
+			cfg, chan, &ext_v1->assoc_le.chanspec_num,
+			&ext_v1->assoc_le.chanspec_list);
+	}
+	return ext_v1;
+}
+
+static void *brcmf_get_join_from_ext_join_v0(void *ext_join, u32 *struct_size)
+{
+	struct brcmf_ext_join_params_le *ext_join_v0 =
+		(struct brcmf_ext_join_params_le *)ext_join;
+	u32 chanspec_num = le32_to_cpu(ext_join_v0->assoc_le.chanspec_num);
+	struct brcmf_join_params *join_params;
+	u32 join_params_size =
+		struct_size(join_params, params_le.chanspec_list, chanspec_num);
+	u32 assoc_size = struct_size_t(struct brcmf_assoc_params_le,
+				       chanspec_list, chanspec_num);
+
+	*struct_size = join_params_size;
+	join_params = kzalloc(join_params_size, GFP_KERNEL);
+	if (!join_params) {
+		return NULL;
+	}
+	memcpy(&join_params->ssid_le, &ext_join_v0->ssid_le,
+	       sizeof(ext_join_v0->ssid_le));
+	memcpy(&join_params->params_le, &ext_join_v0->assoc_le, assoc_size);
+
+	return join_params;
+}
+
+static void *brcmf_get_join_from_ext_join_v1(void *ext_join, u32 *struct_size)
+{
+	struct brcmf_ext_join_params_v1_le *ext_join_v1 =
+		(struct brcmf_ext_join_params_v1_le *)ext_join;
+	u32 chanspec_num = le32_to_cpu(ext_join_v1->assoc_le.chanspec_num);
+	struct brcmf_join_params_v1 *join_params;
+	u32 join_params_size =
+		struct_size(join_params, params_le.chanspec_list, chanspec_num);
+	u32 assoc_size = struct_size_t(struct brcmf_assoc_params_le,
+				       chanspec_list, chanspec_num);
+
+	*struct_size = join_params_size;
+	join_params = kzalloc(join_params_size, GFP_KERNEL);
+	if (!join_params) {
+		return NULL;
+	}
+	memcpy(&join_params->ssid_le, &ext_join_v1->ssid_le,
+	       sizeof(ext_join_v1->ssid_le));
+	memcpy(&join_params->params_le, &ext_join_v1->assoc_le, assoc_size);
+
+	return join_params;
+}
+
+int brcmf_join_param_setup_for_version(struct brcmf_pub *drvr, u8 version)
+{
+	drvr->join_param_handler.version = version;
+	switch (version) {
+	case 0:
+		drvr->join_param_handler.get_struct_for_ibss =
+			brcmf_get_struct_for_ibss_v0;
+		drvr->join_param_handler.get_struct_for_connect =
+			brcmf_get_struct_for_connect_v0;
+		drvr->join_param_handler.get_join_from_ext_join =
+			brcmf_get_join_from_ext_join_v0;
+		break;
+	case 1:
+		drvr->join_param_handler.get_struct_for_ibss =
+			brcmf_get_prepped_struct_for_ibss_v1;
+		drvr->join_param_handler.get_struct_for_connect =
+			brcmf_get_struct_for_connect_v1;
+		drvr->join_param_handler.get_join_from_ext_join =
+			brcmf_get_join_from_ext_join_v1;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h
new file mode 100644
index 00000000000000..f549fe2a740823
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/join_param.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+
+#ifndef _BRCMF_JOIN_PARAM_H
+#define _BRCMF_JOIN_PARAM_H
+
+struct brcmf_pub;
+
+/**
+ * brcmf_join_param_setup_for_version() - Setup the driver to handle join structures
+ *
+ * There are a number of different structures and interface versions for join/extended join parameters
+ * This sets up the driver to handle a particular interface version.
+ *
+ * @drvr Driver structure to setup
+ * @ver Interface version
+ * Return: %0 if okay, error code otherwise
+ */
+int brcmf_join_param_setup_for_version(struct brcmf_pub *drvr, u8 ver);
+#endif /* _BRCMF_JOIN_PARAM_H */
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
index 6bd5f6d1616c04..4f634509d25256 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/scan_param.c
@@ -423,19 +423,19 @@ int brcmf_scan_param_setup_for_version(struct brcmf_pub *drvr, u8 version)
 	drvr->scan_param_handler.version = version;
 	switch (version) {
 	case 1: {
-		drvr->scan_param_handler.get_prepped_struct =
+		drvr->scan_param_handler.get_struct_for_request =
 			brcmf_scan_param_get_prepped_struct_v1;
 	} break;
 	case 2: {
-		drvr->scan_param_handler.get_prepped_struct =
+		drvr->scan_param_handler.get_struct_for_request =
 			brcmf_scan_param_get_prepped_struct_v2;
 	} break;
 	case 3: {
-		drvr->scan_param_handler.get_prepped_struct =
+		drvr->scan_param_handler.get_struct_for_request =
 			brcmf_scan_param_get_prepped_struct_v3;
 	} break;
 	case 4: {
-		drvr->scan_param_handler.get_prepped_struct =
+		drvr->scan_param_handler.get_struct_for_request =
 			brcmf_scan_param_get_prepped_struct_v4;
 
 	} break;

From a358ebe37a2f926bc95a244bbbb02302ad76700e Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Mon, 30 Oct 2023 21:17:22 -0400
Subject: [PATCH 0704/1027] [brcmfmac] Let feature attachment fail, and fail if
 we can't handle the interface versions we find.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/core.c        |  4 +-
 .../broadcom/brcm80211/brcmfmac/feature.c     | 39 +++++++++++++------
 .../broadcom/brcm80211/brcmfmac/feature.h     |  4 +-
 3 files changed, 34 insertions(+), 13 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
index 1e0196c0e521fa..463a0d2eb05255 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/core.c
@@ -1220,7 +1220,9 @@ static int brcmf_bus_started(struct brcmf_pub *drvr, struct cfg80211_ops *ops)
 	if (ret < 0)
 		goto fail;
 
-	brcmf_feat_attach(drvr);
+	ret = brcmf_feat_attach(drvr);
+	if (ret)
+		goto fail;
 
 	/* Setup event_msgs, enable E_IF */
 	ret = brcmf_fweh_init_events(ifp);
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index 7aea92ee52e131..b1a5543b450f45 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -301,7 +301,7 @@ static int brcmf_feat_fwcap_debugfs_read(struct seq_file *seq, void *data)
 	return 0;
 }
 
-void brcmf_feat_attach(struct brcmf_pub *drvr)
+int brcmf_feat_attach(struct brcmf_pub *drvr)
 {
 	struct brcmf_if *ifp = brcmf_get_ifp(drvr, 0);
 	struct brcmf_join_version_le join_ver;
@@ -362,13 +362,14 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 	err = brcmf_fil_iovar_data_get(ifp, "join_ver", &join_ver, sizeof(join_ver));
 	if (!err) {
 		u16 ver = le16_to_cpu(join_ver.join_ver_major);
-		brcmf_join_param_setup_for_version(drvr, ver);
+		err = brcmf_join_param_setup_for_version(drvr, ver);
 	} else {
 		/* Default to version 0, unless it is one of the firmware branches
 		 * that doesn't have a join_ver iovar but are still version 1 */
 		u8 version = 0;
 		struct brcmf_wlc_version_le ver;
-		err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver, sizeof(ver));
+		err = brcmf_fil_iovar_data_get(ifp, "wlc_ver", &ver,
+					       sizeof(ver));
 		if (!err) {
 			u16 major = le16_to_cpu(ver.wlc_ver_major);
 			u16 minor = le16_to_cpu(ver.wlc_ver_minor);
@@ -381,32 +382,47 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 				version = 1;
 			}
 		}
-		brcmf_join_param_setup_for_version(drvr, version);
+		err = brcmf_join_param_setup_for_version(drvr, version);
 	}
-	err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver, sizeof(scan_ver));
+	if (err) {
+		bphy_err(drvr, "Error setting up join structure handler: %d\n",
+			 err);
+		return err;
+	}
+	err = brcmf_fil_iovar_data_get(ifp, "scan_ver", &scan_ver,
+				       sizeof(scan_ver));
 	if (!err) {
 		u16 ver = le16_to_cpu(scan_ver.scan_ver_major);
-		brcmf_scan_param_setup_for_version(drvr, ver);
+		err = brcmf_scan_param_setup_for_version(drvr, ver);
 	} else {
 		/* Default to version 1. */
-		brcmf_scan_param_setup_for_version(drvr, 1);
+		err = brcmf_scan_param_setup_for_version(drvr, 1);
+	}
+	if (err) {
+		bphy_err(drvr, "Error setting up scan structure handler: %d\n",
+			 err);
+		return err;
 	}
-
 	/* See what version of PFN scan is supported*/
 	err = brcmf_fil_iovar_data_get(ifp, "pno_set", &pno_params,
 				       sizeof(pno_params));
 	if (!err) {
-		brcmf_pno_setup_for_version(drvr, le16_to_cpu(pno_params.version));
+		err = brcmf_pno_setup_for_version(
+			drvr, le16_to_cpu(pno_params.version));
 	} else {
 		/* Default to version 2, supported by all chips we support. */
-		brcmf_pno_setup_for_version(drvr, 2);
+		err = brcmf_pno_setup_for_version(drvr, 2);
+	}
+	if (err) {
+		bphy_err(drvr, "Error setting up escan structure handler: %d\n",
+			 err);
+		return err;
 	}
 
 	brcmf_feat_wlc_version_overrides(drvr);
 	brcmf_feat_firmware_overrides(drvr);
 
 	brcmf_fwvid_feat_attach(ifp);
-
 	if (drvr->settings->feature_disable) {
 		brcmf_dbg(INFO, "Features: 0x%02x, disable: 0x%02x\n",
 			  ifp->drvr->feat_flags,
@@ -426,6 +442,7 @@ void brcmf_feat_attach(struct brcmf_pub *drvr)
 		/* no quirks */
 		break;
 	}
+	return 0;
 }
 
 void brcmf_feat_debugfs_create(struct brcmf_pub *drvr)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
index 7cec120e8a038d..66e533e993e22f 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.h
@@ -100,8 +100,10 @@ enum brcmf_feat_quirk {
  * brcmf_feat_attach() - determine features and quirks.
  *
  * @drvr: driver instance.
+ *
+ * Return: 0 in case of success, error code otherwise.
  */
-void brcmf_feat_attach(struct brcmf_pub *drvr);
+int brcmf_feat_attach(struct brcmf_pub *drvr);
 
 /**
  * brcmf_feat_debugfs_create() - create debugfs entries.

From eb29f7c099d1d76a967e36d4e90c9046351100f9 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Tue, 17 Oct 2023 20:36:07 -0400
Subject: [PATCH 0705/1027] [brcmfmac] Add support for more auth suites in
 roaming offload

This adds support for more authentication types during roaming offload,
enabling the firmware to handle roaming for ~all authentication types.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 207 ++++++++++++++++--
 .../broadcom/brcm80211/brcmfmac/cfg80211.h    |   4 +-
 .../broadcom/brcm80211/brcmfmac/feature.c     |   2 +-
 .../broadcom/brcm80211/include/brcmu_wifi.h   |   7 +
 4 files changed, 194 insertions(+), 26 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 520ee892c2727b..8e47fc13854480 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -66,6 +66,8 @@
 #define RSN_CAP_MFPR_MASK		BIT(6)
 #define RSN_CAP_MFPC_MASK		BIT(7)
 #define RSN_PMKID_COUNT_LEN		2
+#define DPP_AKM_SUITE_TYPE		2
+#define WLAN_AKM_SUITE_DPP		SUITE(WLAN_OUI_WFA, DPP_AKM_SUITE_TYPE)
 
 #define VNDR_IE_CMD_LEN			4	/* length of the set command
 						 * string :"add", "del" (+ NUL)
@@ -1836,15 +1838,20 @@ static s32 brcmf_set_wpa_version(struct net_device *ndev,
 	struct brcmf_cfg80211_security *sec;
 	s32 val = 0;
 	s32 err = 0;
-
-	if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1)
+	if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_1) {
 		val = WPA_AUTH_PSK | WPA_AUTH_UNSPECIFIED;
-	else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2)
-		val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
-	else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3)
+	} else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_2) {
+		if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_SAE)
+			val = WPA3_AUTH_SAE_PSK;
+		else if (sme->crypto.akm_suites[0] == WLAN_AKM_SUITE_OWE)
+			val = WPA3_AUTH_OWE;
+		else
+			val = WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED;
+	} else if (sme->crypto.wpa_versions & NL80211_WPA_VERSION_3) {
 		val = WPA3_AUTH_SAE_PSK;
-	else
+	} else {
 		val = WPA_AUTH_DISABLED;
+	}
 	brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
 	err = brcmf_fil_bsscfg_int_set(ifp, "wpa_auth", val);
 	if (err) {
@@ -2059,9 +2066,13 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 	u16 rsn_cap;
 	u32 mfp;
 	u16 count;
+	s32 okc_enable;
+	u16 pmkid_count;
+	const u8 *group_mgmt_cs = NULL;
 
 	profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE;
 	profile->is_ft = false;
+	profile->is_okc = false;
 
 	if (!sme->crypto.n_akm_suites)
 		return 0;
@@ -2078,13 +2089,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 			val = WPA_AUTH_UNSPECIFIED;
 			if (sme->want_1x)
 				profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
 			break;
 		case WLAN_AKM_SUITE_PSK:
 			val = WPA_AUTH_PSK;
 			break;
 		default:
-			bphy_err(drvr, "invalid akm suite (%d)\n",
-				 sme->crypto.akm_suites[0]);
+			bphy_err(drvr, "invalid cipher group (%d)\n",
+				 sme->crypto.cipher_group);
 			return -EINVAL;
 		}
 	} else if (val & (WPA2_AUTH_PSK | WPA2_AUTH_UNSPECIFIED)) {
@@ -2093,11 +2106,15 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 			val = WPA2_AUTH_UNSPECIFIED;
 			if (sme->want_1x)
 				profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
 			break;
 		case WLAN_AKM_SUITE_8021X_SHA256:
 			val = WPA2_AUTH_1X_SHA256;
 			if (sme->want_1x)
 				profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
 			break;
 		case WLAN_AKM_SUITE_PSK_SHA256:
 			val = WPA2_AUTH_PSK_SHA256;
@@ -2110,14 +2127,35 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 			profile->is_ft = true;
 			if (sme->want_1x)
 				profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
 			break;
 		case WLAN_AKM_SUITE_FT_PSK:
 			val = WPA2_AUTH_PSK | WPA2_AUTH_FT;
 			profile->is_ft = true;
+				if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP))
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
+			break;
+		case WLAN_AKM_SUITE_DPP:
+			val = WFA_AUTH_DPP;
+			profile->use_fwsup = BRCMF_PROFILE_FWSUP_NONE;
+			break;
+		case WLAN_AKM_SUITE_OWE:
+			val = WPA3_AUTH_OWE;
+			profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
+			break;
+		case WLAN_AKM_SUITE_8021X_SUITE_B_192:
+			val = WPA3_AUTH_1X_SUITE_B_SHA384;
+			if (sme->want_1x)
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_1X;
+			else
+				profile->use_fwsup = BRCMF_PROFILE_FWSUP_ROAM;
 			break;
 		default:
-			bphy_err(drvr, "invalid akm suite (%d)\n",
-				 sme->crypto.akm_suites[0]);
+			bphy_err(drvr, "invalid cipher group (%d)\n",
+				 sme->crypto.cipher_group);
 			return -EINVAL;
 		}
 	} else if (val & WPA3_AUTH_SAE_PSK) {
@@ -2138,15 +2176,34 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 			}
 			break;
 		default:
-			bphy_err(drvr, "invalid akm suite (%d)\n",
-				 sme->crypto.akm_suites[0]);
+			bphy_err(drvr, "invalid cipher group (%d)\n",
+				 sme->crypto.cipher_group);
 			return -EINVAL;
 		}
 	}
-
-	if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X)
+	if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X) ||
+	    (profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM)) {
 		brcmf_dbg(INFO, "using 1X offload\n");
-
+		err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable",
+					       &okc_enable);
+		if (err) {
+			bphy_err(drvr, "get okc_enable failed (%d)\n", err);
+		} else {
+			brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable);
+			profile->is_okc = okc_enable;
+		}
+	} else if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE &&
+		   (val == WPA3_AUTH_SAE_PSK)) {
+		brcmf_dbg(INFO, "not using SAE offload\n");
+		err = brcmf_fil_bsscfg_int_get(netdev_priv(ndev), "okc_enable",
+					       &okc_enable);
+		if (err) {
+			bphy_err(drvr, "get okc_enable failed (%d)\n", err);
+		} else {
+			brcmf_dbg(INFO, "get okc_enable (%d)\n", okc_enable);
+			profile->is_okc = okc_enable;
+		}
+	}
 	if (!brcmf_feat_is_enabled(ifp, BRCMF_FEAT_MFP))
 		goto skip_mfp_config;
 	/* The MFP mode (1 or 2) needs to be determined, parse IEs. The
@@ -2179,14 +2236,47 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
 		mfp = BRCMF_MFP_REQUIRED;
 	else if (rsn_cap & RSN_CAP_MFPC_MASK)
 		mfp = BRCMF_MFP_CAPABLE;
+	/* In case of dpp, very low tput is observed if MFPC is set in
+	 * firmmare. Firmware needs to ensure that MFPC is not set when
+	 * MFPR was requested from fmac. However since this change being
+	 * specific to DPP, fmac needs to set wpa_auth prior to mfp, so
+	 * that firmware can use this info to prevent MFPC being set in
+	 * case of dpp.
+	 */
+	if (val == WFA_AUTH_DPP) {
+		brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
+		err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth",
+					       val);
+		if (err) {
+			bphy_err(drvr, "could not set wpa_auth (%d)\n", err);
+			return err;
+		}
+	}
+
 	brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "mfp", mfp);
+	offset += RSN_CAP_LEN;
+	if (mfp && (ie_len - offset >= RSN_PMKID_COUNT_LEN)) {
+		pmkid_count = ie[offset] + (ie[offset + 1] << 8);
+		offset += RSN_PMKID_COUNT_LEN + (pmkid_count * WLAN_PMKID_LEN);
+		if (ie_len - offset >= WPA_IE_MIN_OUI_LEN) {
+			group_mgmt_cs = &ie[offset];
+			if (memcmp(group_mgmt_cs, RSN_OUI, TLV_OUI_LEN) == 0) {
+				brcmf_fil_bsscfg_data_set(ifp, "bip",
+							  (void *)group_mgmt_cs,
+							  WPA_IE_MIN_OUI_LEN);
+			}
+		}
+	}
 
 skip_mfp_config:
-	brcmf_dbg(CONN, "setting wpa_auth to %d\n", val);
-	err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth", val);
-	if (err) {
-		bphy_err(drvr, "could not set wpa_auth (%d)\n", err);
-		return err;
+	if (val != WFA_AUTH_DPP) {
+		brcmf_dbg(CONN, "setting wpa_auth to 0x%0x\n", val);
+		err = brcmf_fil_bsscfg_int_set(netdev_priv(ndev), "wpa_auth",
+					       val);
+		if (err) {
+			bphy_err(drvr, "could not set wpa_auth (%d)\n", err);
+			return err;
+		}
 	}
 
 	return err;
@@ -2437,6 +2527,18 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
 			}
 		}
 
+		if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
+			/* enable firmware supplicant for this interface */
+			err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
+			if (err < 0) {
+				bphy_err(drvr,
+					 "failed to enable fw supplicant\n");
+				goto done;
+			}
+		} else {
+			err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0);
+		}
+
 		if ((profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK) &&
 		    params->crypto.psk)
 			err = brcmf_set_pmk(ifp, params->crypto.psk,
@@ -5878,17 +5980,29 @@ static int brcmf_cfg80211_set_pmk(struct wiphy *wiphy, struct net_device *dev,
 				  const struct cfg80211_pmk_conf *conf)
 {
 	struct brcmf_if *ifp;
-
+	struct brcmf_pub *drvr;
+	int ret;
 	brcmf_dbg(TRACE, "enter\n");
 
 	/* expect using firmware supplicant for 1X */
 	ifp = netdev_priv(dev);
-	if (WARN_ON(ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X))
+	drvr = ifp->drvr;
+	if (WARN_ON((ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_1X) &&
+		    (ifp->vif->profile.use_fwsup != BRCMF_PROFILE_FWSUP_ROAM) &&
+		    (ifp->vif->profile.is_ft != true) &&
+		    (ifp->vif->profile.is_okc != true)))
 		return -EINVAL;
 
 	if (conf->pmk_len > BRCMF_WSEC_MAX_PSK_LEN)
 		return -ERANGE;
 
+	if (ifp->vif->profile.is_okc) {
+		ret = brcmf_fil_iovar_data_set(ifp, "okc_info_pmk", conf->pmk,
+					       conf->pmk_len);
+		if (ret < 0)
+			bphy_err(drvr, "okc_info_pmk iovar failed: ret=%d\n",
+				 ret);
+	}
 	return brcmf_set_pmk(ifp, conf->pmk, conf->pmk_len);
 }
 
@@ -6325,6 +6439,46 @@ static s32 brcmf_get_assoc_ies(struct brcmf_cfg80211_info *cfg,
 	return err;
 }
 
+static bool brcmf_has_pmkid(const u8 *parse, u32 len)
+{
+	const struct brcmf_tlv *rsn_ie;
+	const u8 *ie;
+	u32 ie_len;
+	u32 offset;
+	u16 count;
+
+	rsn_ie = brcmf_parse_tlvs(parse, len, WLAN_EID_RSN);
+	if (!rsn_ie)
+		goto done;
+	ie = (const u8 *)rsn_ie;
+	ie_len = rsn_ie->len + TLV_HDR_LEN;
+	/* Skip group data cipher suite */
+	offset = TLV_HDR_LEN + WPA_IE_VERSION_LEN + WPA_IE_MIN_OUI_LEN;
+	if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len)
+		goto done;
+	/* Skip pairwise cipher suite(s) */
+	count = ie[offset] + (ie[offset + 1] << 8);
+	offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN);
+	if (offset + WPA_IE_SUITE_COUNT_LEN >= ie_len)
+		goto done;
+	/* Skip auth key management suite(s) */
+	count = ie[offset] + (ie[offset + 1] << 8);
+	offset += WPA_IE_SUITE_COUNT_LEN + (count * WPA_IE_MIN_OUI_LEN);
+	if (offset + RSN_CAP_LEN >= ie_len)
+		goto done;
+	/* Skip rsn capabilities */
+	offset += RSN_CAP_LEN;
+	if (offset + RSN_PMKID_COUNT_LEN > ie_len)
+		goto done;
+	/* Extract PMKID count */
+	count = ie[offset] + (ie[offset + 1] << 8);
+	if (count)
+		return true;
+
+done:
+	return false;
+}
+
 static s32
 brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg,
 		       struct net_device *ndev,
@@ -6395,11 +6549,16 @@ brcmf_bss_roaming_done(struct brcmf_cfg80211_info *cfg,
 	cfg80211_roamed(ndev, &roam_info, GFP_KERNEL);
 	brcmf_dbg(CONN, "Report roaming result\n");
 
-	if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X && profile->is_ft) {
-		cfg80211_port_authorized(ndev, profile->bssid, NULL, 0, GFP_KERNEL);
+	if (((profile->use_fwsup == BRCMF_PROFILE_FWSUP_1X ||
+	    profile->use_fwsup == BRCMF_PROFILE_FWSUP_ROAM) &&
+	    (brcmf_has_pmkid(roam_info.req_ie, roam_info.req_ie_len) ||
+	     profile->is_ft || profile->is_okc))) {
+		cfg80211_port_authorized(ndev, profile->bssid, NULL, 0,
+					 GFP_KERNEL);
 		brcmf_dbg(CONN, "Report port authorized\n");
 	}
 
+	clear_bit(BRCMF_VIF_STATUS_CONNECTING, &ifp->vif->sme_state);
 	set_bit(BRCMF_VIF_STATUS_CONNECTED, &ifp->vif->sme_state);
 	brcmf_dbg(TRACE, "Exit\n");
 	return err;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
index 61ae0d384cc675..c00b00ca3dcd6b 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.h
@@ -126,7 +126,8 @@ enum brcmf_profile_fwsup {
 	BRCMF_PROFILE_FWSUP_NONE,
 	BRCMF_PROFILE_FWSUP_PSK,
 	BRCMF_PROFILE_FWSUP_1X,
-	BRCMF_PROFILE_FWSUP_SAE
+	BRCMF_PROFILE_FWSUP_SAE,
+	BRCMF_PROFILE_FWSUP_ROAM
 };
 
 /**
@@ -156,6 +157,7 @@ struct brcmf_cfg80211_profile {
 	enum brcmf_profile_fwsup use_fwsup;
 	u16 use_fwauth;
 	bool is_ft;
+	bool is_okc;
 };
 
 /**
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
index b1a5543b450f45..4575c250202052 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/feature.c
@@ -59,7 +59,7 @@ static const struct brcmf_feat_fwcap brcmf_fwcap_map[] = {
 	{ BRCMF_FEAT_DOT11H, "802.11h" },
 	{ BRCMF_FEAT_SAE, "sae" },
 	{ BRCMF_FEAT_FWAUTH, "idauth" },
-	{ BRCMF_FEAT_GCMP, "gcmp"}
+	{ BRCMF_FEAT_GCMP, "gcmp" }
 };
 
 #ifdef DEBUG
diff --git a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
index 0ab1b95318e581..ef042beeb586f9 100644
--- a/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
+++ b/drivers/net/wireless/broadcom/brcm80211/include/brcmu_wifi.h
@@ -254,6 +254,13 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec)
 #define WPA2_AUTH_PSK_SHA256	0x8000	/* PSK with SHA256 key derivation */
 
 #define WPA3_AUTH_SAE_PSK	0x40000	/* SAE with 4-way handshake */
+#define WPA3_AUTH_OWE		0x100000 /* OWE */
+#define WFA_AUTH_DPP		0x200000 /* WFA DPP AUTH */
+#define WPA3_AUTH_1X_SUITE_B_SHA384	0x400000 /* Suite B-192 SHA384 */
+
+
+#define WFA_OUI			"\x50\x6F\x9A"	/* WFA OUI */
+#define DPP_VER			0x1A	/* WFA DPP v1.0 */
 
 #define DOT11_DEFAULT_RTS_LEN		2347
 #define DOT11_DEFAULT_FRAG_LEN		2346

From 30507dd2863c9934523f6991c344be7412f56977 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sun, 29 Oct 2023 16:22:24 -0400
Subject: [PATCH 0706/1027] [brcmfmac] Set chanspec during join.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 8e47fc13854480..5e069dd82fb51c 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -1778,9 +1778,8 @@ static s32 brcmf_cfg80211_join_ibss(struct wiphy *wiphy,
 
 		/* set chanspec */
 		err = brcmf_fil_iovar_int_set(ifp, "chanspec", chanspec);
-
 		if (err) {
-			bphy_err(drvr, "WLC_SET_CHANNEL failed (%d)\n", err);
+			bphy_err(drvr, "Setting chanspec failed (%d)\n", err);
 			goto done;
 		}
 	}

From 25405923004f6434b490e51b04140a8bcde23618 Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Tue, 31 Oct 2023 00:06:22 -0400
Subject: [PATCH 0707/1027] [brcmfmac] Add support for more rate info in
 station dumps

We try to retrieve a newer sta_info structure that has
both rx and tx ratespecs, but if we don't get the
structure we are expecting we fall back to tx rate info only.

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    |  93 ++++++-
 .../broadcom/brcm80211/brcmfmac/fwil_types.h  |  12 +
 .../broadcom/brcm80211/brcmfmac/ratespec.h    | 252 ++++++++++++++++++
 3 files changed, 355 insertions(+), 2 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 5e069dd82fb51c..a114c2e932d0ad 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -35,6 +35,7 @@
 #include "feature.h"
 #include "fwvid.h"
 #include "xtlv.h"
+#include "ratespec.h"
 
 #define BRCMF_SCAN_IE_LEN_MAX		2048
 
@@ -3162,6 +3163,70 @@ brcmf_cfg80211_get_station_ibss(struct brcmf_if *ifp,
 	return 0;
 }
 
+static void brcmf_convert_ratespec_to_rateinfo(u32 ratespec,
+					       struct rate_info *rateinfo)
+{
+	/* First extract the bandwidth info */
+	switch (ratespec & BRCMF_RSPEC_BW_MASK) {
+	case BRCMF_RSPEC_BW_20MHZ:
+		rateinfo->bw = RATE_INFO_BW_20;
+		break;
+	case BRCMF_RSPEC_BW_40MHZ:
+		rateinfo->bw = RATE_INFO_BW_40;
+		break;
+	case BRCMF_RSPEC_BW_80MHZ:
+		rateinfo->bw = RATE_INFO_BW_80;
+		break;
+	case BRCMF_RSPEC_BW_160MHZ:
+		rateinfo->bw = RATE_INFO_BW_160;
+		break;
+	case BRCMF_RSPEC_BW_320MHZ:
+		rateinfo->bw = RATE_INFO_BW_320;
+		break;
+	default:
+		/* Fill in nothing */
+		break;
+	}
+	if (BRCMF_RSPEC_ISHT(ratespec)) {
+		rateinfo->flags |= RATE_INFO_FLAGS_MCS;
+		rateinfo->mcs = ratespec & BRCMF_RSPEC_HT_MCS_MASK;
+	} else if (BRCMF_RSPEC_ISVHT(ratespec)) {
+		rateinfo->flags |= RATE_INFO_FLAGS_VHT_MCS;
+		rateinfo->mcs = ratespec & BRCMF_RSPEC_VHT_MCS_MASK;
+		rateinfo->nss = (ratespec & BRCMF_RSPEC_VHT_NSS_MASK) >>
+				BRCMF_RSPEC_VHT_NSS_SHIFT;
+	} else if (BRCMF_RSPEC_ISHE(ratespec)) {
+		u32 ltf_gi = BRCMF_RSPEC_HE_LTF_GI(ratespec);
+
+		rateinfo->flags |= RATE_INFO_FLAGS_HE_MCS;
+		rateinfo->mcs = ratespec & BRCMF_RSPEC_HE_MCS_MASK;
+		rateinfo->nss = (ratespec & BRCMF_RSPEC_HE_NSS_MASK) >>
+				BRCMF_RSPEC_HE_NSS_SHIFT;
+		rateinfo->he_dcm = BRCMF_RSPEC_HE_DCM(ratespec);
+		if (HE_IS_GI_0_8us(ltf_gi)) {
+			rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_0_8;
+		} else if (HE_IS_GI_1_6us(ltf_gi)) {
+			rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_1_6;
+		} else if (HE_IS_GI_3_2us(ltf_gi)) {
+			rateinfo->he_gi = NL80211_RATE_INFO_HE_GI_3_2;
+		}
+	} else if (BRCMF_RSPEC_ISEHT(ratespec)) {
+		u32 ltf_gi = BRCMF_RSPEC_EHT_LTF_GI(ratespec);
+
+		rateinfo->flags |= RATE_INFO_FLAGS_EHT_MCS;
+		rateinfo->mcs = ratespec & BRCMF_RSPEC_EHT_MCS_MASK;
+		rateinfo->nss = (ratespec & BRCMF_RSPEC_EHT_NSS_MASK) >>
+				BRCMF_RSPEC_EHT_NSS_SHIFT;
+		if (EHT_IS_GI_0_8us(ltf_gi)) {
+			rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_0_8;
+		} else if (EHT_IS_GI_1_6us(ltf_gi)) {
+			rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_1_6;
+		} else if (EHT_IS_GI_3_2us(ltf_gi)) {
+			rateinfo->eht_gi = NL80211_RATE_INFO_EHT_GI_3_2;
+		}
+	}
+}
+
 static s32
 brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
 			   const u8 *mac, struct station_info *sinfo)
@@ -3179,6 +3244,8 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
 	s32 count_rssi = 0;
 	int rssi;
 	u32 i;
+	u16 struct_ver;
+	u16 info_len;
 
 	brcmf_dbg(TRACE, "Enter, MAC %pM\n", mac);
 	if (!check_vif_up(ifp->vif))
@@ -3202,7 +3269,9 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
 			goto done;
 		}
 	}
-	brcmf_dbg(TRACE, "version %d\n", le16_to_cpu(sta_info_le.ver));
+	info_len = le16_to_cpu(sta_info_le.len);
+	struct_ver = le16_to_cpu(sta_info_le.ver);
+	brcmf_dbg(TRACE, "version %d\n", struct_ver);
 	sinfo->filled = BIT_ULL(NL80211_STA_INFO_INACTIVE_TIME);
 	sinfo->inactive_time = le32_to_cpu(sta_info_le.idle) * 1000;
 	sta_flags = le32_to_cpu(sta_info_le.flags);
@@ -3236,12 +3305,13 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
 			sinfo->rxrate.legacy =
 				le32_to_cpu(sta_info_le.rx_rate) / 100;
 		}
-		if (le16_to_cpu(sta_info_le.ver) >= 4) {
+		if (struct_ver >= 4) {
 			sinfo->filled |= BIT_ULL(NL80211_STA_INFO_TX_BYTES);
 			sinfo->tx_bytes = le64_to_cpu(sta_info_le.tx_tot_bytes);
 			sinfo->filled |= BIT_ULL(NL80211_STA_INFO_RX_BYTES);
 			sinfo->rx_bytes = le64_to_cpu(sta_info_le.rx_tot_bytes);
 		}
+
 		for (i = 0; i < BRCMF_ANT_MAX; i++) {
 			if (sta_info_le.rssi[i] == 0 ||
 			    sta_info_le.rx_lastpkt_rssi[i] == 0)
@@ -3280,6 +3350,25 @@ brcmf_cfg80211_get_station(struct wiphy *wiphy, struct net_device *ndev,
 			}
 		}
 	}
+	/* Some version 7 structs have ratespecs from the last packet. */
+	if (struct_ver >= 7) {
+		if (info_len >= sizeof(sta_info_le)) {
+			brcmf_convert_ratespec_to_rateinfo(
+				le32_to_cpu(sta_info_le.v7.tx_rspec),
+				&sinfo->txrate);
+			brcmf_convert_ratespec_to_rateinfo(
+				le32_to_cpu(sta_info_le.v7.rx_rspec),
+				&sinfo->rxrate);
+		} else {
+			/* We didn't get the fields we were expecting, fallback to nrate */
+			u32 nrate = 0;
+			err = brcmf_fil_iovar_int_get(ifp, "nrate", &nrate);
+			if (!err) {
+				brcmf_convert_ratespec_to_rateinfo(
+					nrate, &sinfo->txrate);
+			}
+		}
+	}
 done:
 	brcmf_dbg(TRACE, "Exit\n");
 	return err;
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
index 14d91e7749e82d..7b8f809cdc412d 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/fwil_types.h
@@ -824,13 +824,17 @@ struct brcmf_channel_info_le {
 	__le32 scan_channel;
 };
 
+#define BRCMF_MAX_ASSOC_OUI_NUM 6
+#define BRCMF_ASSOC_OUI_LEN 3
 struct brcmf_sta_info_le {
 	__le16 ver;		/* version of this struct */
 	__le16 len;		/* length in bytes of this structure */
 	__le16 cap;		/* sta's advertised capabilities */
+	u16 PAD;
 	__le32 flags;		/* flags defined below */
 	__le32 idle;		/* time since data pkt rx'd from sta */
 	u8 ea[ETH_ALEN];		/* Station address */
+	u16 PAD2;
 	__le32 count;			/* # rates in this set */
 	u8 rates[BRCMF_MAXRATES_IN_SET];	/* rates in 500kbps units */
 						/* w/hi bit set if basic */
@@ -862,6 +866,7 @@ struct brcmf_sta_info_le {
 	__le16 aid;                    /* association ID */
 	__le16 ht_capabilities;        /* advertised ht caps */
 	__le16 vht_flags;              /* converted vht flags */
+	u16 PAD3;
 	__le32 tx_pkts_retry_cnt;      /* # of frames where a retry was
 					 * exhausted.
 					 */
@@ -914,6 +919,13 @@ struct brcmf_sta_info_le {
 			__le32 tx_rspec;	/* Rate of last successful tx frame */
 			__le32 rx_rspec;	/* Rate of last successful rx frame */
 			__le32 wnm_cap;		/* wnm capabilities */
+			__le16 he_flags;	/* converted he flags */
+			u16 PAD;
+			struct {
+				u8 count;
+				u8 oui[BRCMF_MAX_ASSOC_OUI_NUM][BRCMF_ASSOC_OUI_LEN];
+			} vendor_oui;
+			u8 link_bw;
 		} v7;
 	};
 };
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h
new file mode 100644
index 00000000000000..37e722daab14d4
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/ratespec.h
@@ -0,0 +1,252 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+
+#ifndef BRCMFMAC_RATESPEC_H
+#define BRCMFMAC_RATESPEC_H
+/* Rate spec. definitions */
+/* for BRCMF_RSPEC_ENCODING field >= BRCMF_RSPEC_ENCODING_HE, backward compatible */
+
+/**< Legacy rate or MCS or MCS + NSS */
+#define BRCMF_RSPEC_RATE_MASK 0x000000FFu
+/**< Tx chain expansion beyond Nsts */
+#define BRCMF_RSPEC_TXEXP_MASK 0x00000300u
+#define BRCMF_RSPEC_TXEXP_SHIFT 8u
+/* EHT GI indices */
+#define BRCMF_RSPEC_EHT_GI_MASK 0x00000C00u
+#define BRCMF_RSPEC_EHT_GI_SHIFT 10u
+/* HE GI indices */
+#define BRCMF_RSPEC_HE_GI_MASK 0x00000C00u
+#define BRCMF_RSPEC_HE_GI_SHIFT 10u
+/**< Range extension mask */
+#define BRCMF_RSPEC_ER_MASK 0x0000C000u
+#define BRCMF_RSPEC_ER_SHIFT 14u
+/**< Range extension tone config */
+#define BRCMF_RSPEC_ER_TONE_MASK 0x00004000u
+#define BRCMF_RSPEC_ER_TONE_SHIFT 14u
+/**< Range extension enable */
+#define BRCMF_RSPEC_ER_ENAB_MASK 0x00008000u
+#define BRCMF_RSPEC_ER_ENAB_SHIFT 15u
+/**< Bandwidth */
+#define BRCMF_RSPEC_BW_MASK 0x00070000u
+#define BRCMF_RSPEC_BW_SHIFT 16u
+/**< Dual Carrier Modulation */
+#define BRCMF_RSPEC_DCM 0x00080000u
+#define BRCMF_RSPEC_DCM_SHIFT 19u
+/**< STBC expansion, Nsts = 2 * Nss */
+#define BRCMF_RSPEC_STBC 0x00100000u
+#define BRCMF_RSPEC_TXBF 0x00200000u
+#define BRCMF_RSPEC_LDPC 0x00400000u
+/* HT/VHT SGI indication */
+#define BRCMF_RSPEC_SGI 0x00800000u
+/**< DSSS short preable - Encoding 0 */
+#define BRCMF_RSPEC_SHORT_PREAMBLE 0x00800000u
+/**< Encoding of RSPEC_RATE field */
+#define BRCMF_RSPEC_ENCODING_MASK 0x07000000u
+#define BRCMF_RSPEC_ENCODING_SHIFT 24u
+#define BRCMF_RSPEC_OVERRIDE_RATE 0x40000000u /**< override rate only */
+#define BRCMF_RSPEC_OVERRIDE_MODE 0x80000000u /**< override both rate & mode */
+
+/* ======== RSPEC_EHT_GI|RSPEC_SGI fields for EHT ======== */
+/* 11be Draft 0.4 Table 36-35:Common field for non-OFDMA transmission.
+ * Table 36-32 Common field for OFDMA transmission
+ */
+#define BRCMF_RSPEC_EHT_LTF_GI(rspec) \
+	(((rspec) & BRCMF_RSPEC_EHT_GI_MASK) >> BRCMF_RSPEC_EHT_GI_SHIFT)
+#define BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us (0x0u)
+#define BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us (0x1u)
+#define BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us (0x2u)
+#define BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us (0x3u)
+#define WL_EHT_GI_TO_RSPEC(gi)                             \
+	((u32)(((gi) << BRCMF_RSPEC_EHT_GI_SHIFT) & \
+		      BRCMF_RSPEC_EHT_GI_MASK))
+#define WL_EHT_GI_TO_RSPEC_SET(rspec, gi) \
+	((rspec & (~BRCMF_RSPEC_EHT_GI_MASK)) | WL_EHT_GI_TO_RSPEC(gi))
+
+/* Macros for EHT LTF and GI */
+#define EHT_IS_2X_LTF(gi)                             \
+	(((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us) || \
+	 ((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us))
+#define EHT_IS_4X_LTF(gi)                             \
+	(((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us) || \
+	 ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us))
+
+#define EHT_IS_GI_0_8us(gi)                           \
+	(((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_0_8us) || \
+	 ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_0_8us))
+#define EHT_IS_GI_1_6us(gi) ((gi) == BRCMF_RSPEC_EHT_2x_LTF_GI_1_6us)
+#define EHT_IS_GI_3_2us(gi) ((gi) == BRCMF_RSPEC_EHT_4x_LTF_GI_3_2us)
+
+/* ======== RSPEC_HE_GI|RSPEC_SGI fields for HE ======== */
+
+/* GI for HE */
+#define BRCMF_RSPEC_HE_LTF_GI(rspec) \
+	(((rspec) & BRCMF_RSPEC_HE_GI_MASK) >> BRCMF_RSPEC_HE_GI_SHIFT)
+#define BRCMF_RSPEC_HE_1x_LTF_GI_0_8us (0x0u)
+#define BRCMF_RSPEC_HE_2x_LTF_GI_0_8us (0x1u)
+#define BRCMF_RSPEC_HE_2x_LTF_GI_1_6us (0x2u)
+#define BRCMF_RSPEC_HE_4x_LTF_GI_3_2us (0x3u)
+#define BRCMF_RSPEC_ISHEGI(rspec) \
+	(RSPEC_HE_LTF_GI(rspec) > BRCMF_RSPEC_HE_1x_LTF_GI_0_8us)
+#define HE_GI_TO_RSPEC(gi) \
+	(((u32)(gi) << BRCMF_RSPEC_HE_GI_SHIFT) & BRCMF_RSPEC_HE_GI_MASK)
+#define HE_GI_TO_RSPEC_SET(rspec, gi) \
+	((rspec & (~BRCMF_RSPEC_HE_GI_MASK)) | HE_GI_TO_RSPEC(gi))
+
+/* Macros for HE LTF and GI */
+#define HE_IS_1X_LTF(gi) ((gi) == BRCMF_RSPEC_HE_1x_LTF_GI_0_8us)
+#define HE_IS_2X_LTF(gi)                             \
+	(((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_0_8us) || \
+	 ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_1_6us))
+#define HE_IS_4X_LTF(gi) ((gi) == BRCMF_RSPEC_HE_4x_LTF_GI_3_2us)
+
+#define HE_IS_GI_0_8us(gi)                           \
+	(((gi) == BRCMF_RSPEC_HE_1x_LTF_GI_0_8us) || \
+	 ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_0_8us))
+#define HE_IS_GI_1_6us(gi) ((gi) == BRCMF_RSPEC_HE_2x_LTF_GI_1_6us)
+#define HE_IS_GI_3_2us(gi) ((gi) == BRCMF_RSPEC_HE_4x_LTF_GI_3_2us)
+
+/* RSPEC Macros for extracting and using HE-ER and DCM */
+#define BRCMF_RSPEC_HE_DCM(rspec) \
+	(((rspec) & BRCMF_RSPEC_DCM) >> BRCMF_RSPEC_DCM_SHIFT)
+#define BRCMF_RSPEC_HE_ER(rspec) \
+	(((rspec) & BRCMF_RSPEC_ER_MASK) >> BRCMF_RSPEC_ER_SHIFT)
+#define BRCMF_RSPEC_HE_ER_ENAB(rspec) \
+	(((rspec) & BRCMF_RSPEC_ER_ENAB_MASK) >> BRCMF_RSPEC_ER_ENAB_SHIFT)
+#define BRCMF_RSPEC_HE_ER_TONE(rspec) \
+	(((rspec) & BRCMF_RSPEC_ER_TONE_MASK) >> BRCMF_RSPEC_ER_TONE_SHIFT)
+/* ======== RSPEC_RATE field ======== */
+
+/* Encoding 0 - legacy rate */
+/* DSSS, CCK, and OFDM rates in [500kbps] units */
+#define BRCMF_RSPEC_LEGACY_RATE_MASK 0x0000007F
+#define WLC_RATE_1M 2
+#define WLC_RATE_2M 4
+#define WLC_RATE_5M5 11
+#define WLC_RATE_11M 22
+#define WLC_RATE_6M 12
+#define WLC_RATE_9M 18
+#define WLC_RATE_12M 24
+#define WLC_RATE_18M 36
+#define WLC_RATE_24M 48
+#define WLC_RATE_36M 72
+#define WLC_RATE_48M 96
+#define WLC_RATE_54M 108
+
+/* Encoding 1 - HT MCS */
+/**< HT MCS value mask in rspec */
+#define BRCMF_RSPEC_HT_MCS_MASK 0x0000007F
+
+/* Encoding >= 2 */
+/* NSS & MCS values mask in rspec */
+#define BRCMF_RSPEC_NSS_MCS_MASK 0x000000FF
+/* mimo MCS value mask in rspec */
+#define BRCMF_RSPEC_MCS_MASK 0x0000000F
+/* mimo NSS value mask in rspec */
+#define BRCMF_RSPEC_NSS_MASK 0x000000F0
+/* mimo NSS value shift in rspec */
+#define BRCMF_RSPEC_NSS_SHIFT 4
+
+/* Encoding 2 - VHT MCS + NSS */
+/**< VHT MCS value mask in rspec */
+#define BRCMF_RSPEC_VHT_MCS_MASK BRCMF_RSPEC_MCS_MASK
+/**< VHT Nss value mask in rspec */
+#define BRCMF_RSPEC_VHT_NSS_MASK BRCMF_RSPEC_NSS_MASK
+/**< VHT Nss value shift in rspec */
+#define BRCMF_RSPEC_VHT_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT
+
+/* Encoding 3 - HE MCS + NSS */
+/**< HE MCS value mask in rspec */
+#define BRCMF_RSPEC_HE_MCS_MASK BRCMF_RSPEC_MCS_MASK
+/**< HE Nss value mask in rspec */
+#define BRCMF_RSPEC_HE_NSS_MASK BRCMF_RSPEC_NSS_MASK
+/**< HE Nss value shift in rpsec */
+#define BRCMF_RSPEC_HE_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT
+
+#define BRCMF_RSPEC_HE_NSS_UNSPECIFIED 0xf
+
+/* Encoding 4 - EHT MCS + NSS */
+/**< EHT MCS value mask in rspec */
+#define BRCMF_RSPEC_EHT_MCS_MASK BRCMF_RSPEC_MCS_MASK
+/**< EHT Nss value mask in rspec */
+#define BRCMF_RSPEC_EHT_NSS_MASK BRCMF_RSPEC_NSS_MASK
+/**< EHT Nss value shift in rpsec */
+#define BRCMF_RSPEC_EHT_NSS_SHIFT BRCMF_RSPEC_NSS_SHIFT
+
+/* ======== RSPEC_BW field ======== */
+
+#define BRCMF_RSPEC_BW_UNSPECIFIED 0u
+#define BRCMF_RSPEC_BW_20MHZ 0x00010000u
+#define BRCMF_RSPEC_BW_40MHZ 0x00020000u
+#define BRCMF_RSPEC_BW_80MHZ 0x00030000u
+#define BRCMF_RSPEC_BW_160MHZ 0x00040000u
+#define BRCMF_RSPEC_BW_320MHZ 0x00060000u
+
+/* ======== RSPEC_ENCODING field ======== */
+
+/* NOTE: Assuming the rate field is always NSS+MCS starting from VHT encoding!
+ *       Modify/fix RSPEC_ISNSSMCS() macro if above condition changes any time.
+ */
+/**< Legacy rate is stored in RSPEC_RATE */
+#define BRCMF_RSPEC_ENCODE_RATE 0x00000000u
+/**< HT MCS is stored in RSPEC_RATE */
+#define BRCMF_RSPEC_ENCODE_HT 0x01000000u
+/**< VHT MCS and NSS are stored in RSPEC_RATE */
+#define BRCMF_RSPEC_ENCODE_VHT 0x02000000u
+/**< HE MCS and NSS are stored in RSPEC_RATE */
+#define BRCMF_RSPEC_ENCODE_HE 0x03000000u
+/**< EHT MCS and NSS are stored in RSPEC_RATE */
+#define BRCMF_RSPEC_ENCODE_EHT 0x04000000u
+
+/**
+ * ===============================
+ * Handy macros to parse rate spec
+ * ===============================
+ */
+#define BRCMF_RSPEC_BW(rspec) ((rspec) & BRCMF_RSPEC_BW_MASK)
+#define BRCMF_RSPEC_IS20MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_20MHZ)
+#define BRCMF_RSPEC_IS40MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_40MHZ)
+#define BRCMF_RSPEC_IS80MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_80MHZ)
+#define BRCMF_RSPEC_IS160MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_160MHZ)
+#if defined(WL_BW320MHZ)
+#define BRCMF_RSPEC_IS320MHZ(rspec) (RSPEC_BW(rspec) == BRCMF_RSPEC_BW_320MHZ)
+#else
+#define BRCMF_RSPEC_IS320MHZ(rspec) (FALSE)
+#endif /* WL_BW320MHZ */
+
+#define BRCMF_RSPEC_BW_GE(rspec, rspec_bw) (RSPEC_BW(rspec) >= rspec_bw)
+#define BRCMF_RSPEC_BW_LE(rspec, rspec_bw) (RSPEC_BW(rspec) <= rspec_bw)
+#define BRCMF_RSPEC_BW_GT(rspec, rspec_bw) (!RSPEC_BW_LE(rspec, rspec_bw))
+#define BRCMF_RSPEC_BW_LT(rspec, rspec_bw) (!RSPEC_BW_GE(rspec, rspec_bw))
+
+#define BRCMF_RSPEC_ISSGI(rspec) (((rspec) & BRCMF_RSPEC_SGI) != 0)
+#define BRCMF_RSPEC_ISLDPC(rspec) (((rspec) & BRCMF_RSPEC_LDPC) != 0)
+#define BRCMF_RSPEC_ISSTBC(rspec) (((rspec) & BRCMF_RSPEC_STBC) != 0)
+#define BRCMF_RSPEC_ISTXBF(rspec) (((rspec) & BRCMF_RSPEC_TXBF) != 0)
+
+#define BRCMF_RSPEC_TXEXP(rspec) \
+	(((rspec) & BRCMF_RSPEC_TXEXP_MASK) >> BRCMF_RSPEC_TXEXP_SHIFT)
+
+#define BRCMF_RSPEC_ENCODE(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) >> BRCMF_RSPEC_ENCODING_SHIFT)
+#define BRCMF_RSPEC_ISLEGACY(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_RATE)
+
+#define BRCMF_RSPEC_ISHT(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_HT)
+#define BRCMF_RSPEC_ISVHT(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_VHT)
+#define BRCMF_RSPEC_ISHE(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_HE)
+#define BRCMF_RSPEC_ISEHT(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) == BRCMF_RSPEC_ENCODE_EHT)
+
+/* fast check if rate field is NSS+MCS format (starting from VHT ratespec) */
+#define BRCMF_RSPEC_ISVHTEXT(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) >= BRCMF_RSPEC_ENCODE_VHT)
+/* fast check if rate field is NSS+MCS format (starting from HE ratespec) */
+#define BRCMF_RSPEC_ISHEEXT(rspec) \
+	(((rspec) & BRCMF_RSPEC_ENCODING_MASK) >= BRCMF_RSPEC_ENCODE_HE)
+
+#endif /* BRCMFMAC_RATESPEC_H */

From 85a9d75351754508bf65a6d25c932162b8904a5e Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Thu, 19 Oct 2023 23:55:07 -0400
Subject: [PATCH 0708/1027] [brcmfmac] Support bandwidth caps for all bands

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 21 +++++++++++++------
 1 file changed, 15 insertions(+), 6 deletions(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index a114c2e932d0ad..6cc638aa110c42 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7264,6 +7264,7 @@ static int brcmf_construct_chaninfo(struct brcmf_cfg80211_info *cfg,
 				break;
 			}
 		}
+
 		if (!channel) {
 			/* It seems firmware supports some channel we never
 			 * considered. Something new in IEEE standard?
@@ -7336,17 +7337,25 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg)
 	struct brcmu_chan ch;
 	u32 num_chan;
 	int i, j;
+	s32 updown;
 
 	/* verify support for bw_cap command */
-	val = WLC_BAND_5G;
+	val = WLC_BAND_2G;
 	err = brcmf_fil_iovar_int_query(ifp, "bw_cap", &val);
-
+	brcmf_dbg(INFO, "Check bw_cap support:%d\n", err);
 	if (!err) {
+		/* Setting the bw_cap is DOWN restricted. */
+		updown = 0;
+		brcmf_fil_cmd_data_set(ifp, BRCMF_C_DOWN, &updown, sizeof(s32));
 		/* only set 2G bandwidth using bw_cap command */
 		band_bwcap.band = cpu_to_le32(WLC_BAND_2G);
 		band_bwcap.bw_cap = cpu_to_le32(WLC_BW_CAP_40MHZ);
 		err = brcmf_fil_iovar_data_set(ifp, "bw_cap", &band_bwcap,
 					       sizeof(band_bwcap));
+		brcmf_dbg(INFO, "set bw_cap support:%d\n", err);
+		brcmf_c_set_joinpref_default(ifp);
+		updown = 1;
+		brcmf_fil_cmd_data_set(ifp, BRCMF_C_UP, &updown, sizeof(s32));
 	} else {
 		brcmf_dbg(INFO, "fallback to mimo_bw_cap\n");
 		val = WLC_N_BW_40ALL;
@@ -7408,7 +7417,7 @@ static int brcmf_enable_bw40_2g(struct brcmf_cfg80211_info *cfg)
 	return err;
 }
 
-static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g)
+static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[4], bool has_6g)
 {
 	struct brcmf_pub *drvr = ifp->drvr;
 	u32 band, mimo_bwcap;
@@ -7462,7 +7471,7 @@ static void brcmf_get_bwcap(struct brcmf_if *ifp, u32 bw_cap[], bool has_6g)
 }
 
 static void brcmf_update_ht_cap(struct ieee80211_supported_band *band,
-				u32 bw_cap[2], u32 nrxchain)
+				u32 bw_cap[4], u32 nrxchain)
 {
 	/* Not supported in 6G band */
 	if (band->band == NL80211_BAND_6GHZ)
@@ -7493,7 +7502,7 @@ static __le16 brcmf_get_mcs_map(u32 nstreams,
 }
 
 static void brcmf_update_vht_cap(struct ieee80211_supported_band *band,
-				 u32 bw_cap[2], u32 txstreams, u32 rxstreams,
+				 u32 bw_cap[4], u32 txstreams, u32 rxstreams,
 				 u32 txbf_bfe_cap, u32 txbf_bfr_cap,
 				 u32 ldpc_cap, u32 stbc_rx, u32 stbc_tx)
 {
@@ -7678,7 +7687,7 @@ static int brcmf_setup_wiphybands(struct brcmf_cfg80211_info *cfg)
 	u32 nmode;
 	u32 vhtmode = 0;
 	/* 2GHZ, 5GHZ, 60GHZ, 6GHZ */
-	u32 bw_cap[4] = { WLC_BW_20MHZ_BIT, WLC_BW_20MHZ_BIT, 0, 0 };
+	u32 bw_cap[4] = { 0, 0, 0, 0 };
 	u32 rxchain;
 	u32 txchain;
 	u32 nrxchain;

From 0d45b525eb70ad2fe25d5f8027ac9dbd23ec7afa Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Sat, 11 Nov 2023 21:28:39 -0500
Subject: [PATCH 0709/1027] [brcmfmac] Clean up and common interface creation
 handling

This makes firmware-side interface creation structures private
to interface creation, and commons out how creation is handled

Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 .../broadcom/brcm80211/brcmfmac/Makefile      |   3 +-
 .../broadcom/brcm80211/brcmfmac/cfg80211.c    | 270 +-----------------
 .../brcm80211/brcmfmac/interface_create.c     | 270 ++++++++++++++++++
 .../brcm80211/brcmfmac/interface_create.h     |  13 +
 4 files changed, 286 insertions(+), 270 deletions(-)
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c
 create mode 100644 drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
index 694b50a0664f24..6fd805023500be 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/Makefile
@@ -27,7 +27,8 @@ brcmfmac-objs += \
 		pno.o \
 		join_param.o \
 		scan_param.o \
-		xtlv.o
+		xtlv.o \
+		interface_create.o
 
 brcmfmac-$(CONFIG_BRCMFMAC_PROTO_BCDC) += \
 		bcdc.o \
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 6cc638aa110c42..3ed406700ff0c6 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -36,6 +36,7 @@
 #include "fwvid.h"
 #include "xtlv.h"
 #include "ratespec.h"
+#include "interface_create.h"
 
 #define BRCMF_SCAN_IE_LEN_MAX		2048
 
@@ -313,48 +314,6 @@ struct parsed_vndr_ies {
 	struct parsed_vndr_ie_info ie_info[VNDR_IE_PARSE_LIMIT];
 };
 
-#define WL_INTERFACE_CREATE_VER_1		1
-#define WL_INTERFACE_CREATE_VER_2		2
-#define WL_INTERFACE_CREATE_VER_3		3
-#define WL_INTERFACE_CREATE_VER_MAX		WL_INTERFACE_CREATE_VER_3
-
-#define WL_INTERFACE_MAC_DONT_USE	0x0
-#define WL_INTERFACE_MAC_USE		0x2
-
-#define WL_INTERFACE_CREATE_STA		0x0
-#define WL_INTERFACE_CREATE_AP		0x1
-
-struct wl_interface_create_v1 {
-	u16	ver;			/* structure version */
-	u32	flags;			/* flags for operation */
-	u8	mac_addr[ETH_ALEN];	/* MAC address */
-	u32	wlc_index;		/* optional for wlc index */
-};
-
-struct wl_interface_create_v2 {
-	u16	ver;			/* structure version */
-	u8	pad1[2];
-	u32	flags;			/* flags for operation */
-	u8	mac_addr[ETH_ALEN];	/* MAC address */
-	u8	iftype;			/* type of interface created */
-	u8	pad2;
-	u32	wlc_index;		/* optional for wlc index */
-};
-
-struct wl_interface_create_v3 {
-	u16 ver;			/* structure version */
-	u16 len;			/* length of structure + data */
-	u16 fixed_len;			/* length of structure */
-	u8 iftype;			/* type of interface created */
-	u8 wlc_index;			/* optional for wlc index */
-	u32 flags;			/* flags for operation */
-	u8 mac_addr[ETH_ALEN];		/* MAC address */
-	u8 bssid[ETH_ALEN];		/* optional for BSSID */
-	u8 if_index;			/* interface index request */
-	u8 pad[3];
-	u8 data[];			/* Optional for specific data */
-};
-
 static u8 nl80211_band_to_fwil(enum nl80211_band band)
 {
 	switch (band) {
@@ -637,231 +596,6 @@ brcmf_cfg80211_update_proto_addr_mode(struct wireless_dev *wdev)
 						ADDR_INDIRECT);
 }
 
-static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr)
-{
-	int bsscfgidx;
-
-	for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) {
-		/* bsscfgidx 1 is reserved for legacy P2P */
-		if (bsscfgidx == 1)
-			continue;
-		if (!drvr->iflist[bsscfgidx])
-			return bsscfgidx;
-	}
-
-	return -ENOMEM;
-}
-
-static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr)
-{
-	u8 mac_idx = ifp->drvr->sta_mac_idx;
-
-	/* set difference MAC address with locally administered bit */
-	memcpy(mac_addr, ifp->mac_addr, ETH_ALEN);
-	mac_addr[0] |= 0x02;
-	mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0;
-	mac_idx++;
-	mac_idx = mac_idx % 2;
-	ifp->drvr->sta_mac_idx = mac_idx;
-}
-
-static int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr)
-{
-	struct wl_interface_create_v1 iface_v1;
-	struct wl_interface_create_v2 iface_v2;
-	struct wl_interface_create_v3 iface_v3;
-	u32 iface_create_ver;
-	int err;
-
-	/* interface_create version 1 */
-	memset(&iface_v1, 0, sizeof(iface_v1));
-	iface_v1.ver = WL_INTERFACE_CREATE_VER_1;
-	iface_v1.flags = WL_INTERFACE_CREATE_STA |
-			 WL_INTERFACE_MAC_USE;
-	if (!is_zero_ether_addr(macaddr))
-		memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN);
-	else
-		brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr);
-
-	err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-				       &iface_v1,
-				       sizeof(iface_v1));
-	if (err) {
-		brcmf_info("failed to create interface(v1), err=%d\n",
-			   err);
-	} else {
-		brcmf_dbg(INFO, "interface created(v1)\n");
-		return 0;
-	}
-
-	/* interface_create version 2 */
-	memset(&iface_v2, 0, sizeof(iface_v2));
-	iface_v2.ver = WL_INTERFACE_CREATE_VER_2;
-	iface_v2.flags = WL_INTERFACE_MAC_USE;
-	iface_v2.iftype = WL_INTERFACE_CREATE_STA;
-	if (!is_zero_ether_addr(macaddr))
-		memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN);
-	else
-		brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr);
-
-	err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-				       &iface_v2,
-				       sizeof(iface_v2));
-	if (err) {
-		brcmf_info("failed to create interface(v2), err=%d\n",
-			   err);
-	} else {
-		brcmf_dbg(INFO, "interface created(v2)\n");
-		return 0;
-	}
-
-	/* interface_create version 3+ */
-	/* get supported version from firmware side */
-	iface_create_ver = 0;
-	err = brcmf_fil_bsscfg_int_query(ifp, "interface_create",
-					 &iface_create_ver);
-	if (err) {
-		brcmf_err("fail to get supported version, err=%d\n", err);
-		return -EOPNOTSUPP;
-	}
-
-	switch (iface_create_ver) {
-	case WL_INTERFACE_CREATE_VER_3:
-		memset(&iface_v3, 0, sizeof(iface_v3));
-		iface_v3.ver = WL_INTERFACE_CREATE_VER_3;
-		iface_v3.flags = WL_INTERFACE_MAC_USE;
-		iface_v3.iftype = WL_INTERFACE_CREATE_STA;
-		if (!is_zero_ether_addr(macaddr))
-			memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN);
-		else
-			brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr);
-
-		err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-					       &iface_v3,
-					       sizeof(iface_v3));
-
-		if (!err)
-			brcmf_dbg(INFO, "interface created(v3)\n");
-		break;
-	default:
-		brcmf_err("not support interface create(v%d)\n",
-			  iface_create_ver);
-		err = -EOPNOTSUPP;
-		break;
-	}
-
-	if (err) {
-		brcmf_info("station interface creation failed (%d)\n",
-			   err);
-		return -EIO;
-	}
-
-	return 0;
-}
-
-static int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp)
-{
-	struct wl_interface_create_v1 iface_v1;
-	struct wl_interface_create_v2 iface_v2;
-	struct wl_interface_create_v3 iface_v3;
-	u32 iface_create_ver;
-	struct brcmf_pub *drvr = ifp->drvr;
-	struct brcmf_mbss_ssid_le mbss_ssid_le;
-	int bsscfgidx;
-	int err;
-
-	/* interface_create version 1 */
-	memset(&iface_v1, 0, sizeof(iface_v1));
-	iface_v1.ver = WL_INTERFACE_CREATE_VER_1;
-	iface_v1.flags = WL_INTERFACE_CREATE_AP |
-			 WL_INTERFACE_MAC_USE;
-
-	brcmf_set_vif_sta_macaddr(ifp, iface_v1.mac_addr);
-
-	err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-				       &iface_v1,
-				       sizeof(iface_v1));
-	if (err) {
-		brcmf_info("failed to create interface(v1), err=%d\n",
-			   err);
-	} else {
-		brcmf_dbg(INFO, "interface created(v1)\n");
-		return 0;
-	}
-
-	/* interface_create version 2 */
-	memset(&iface_v2, 0, sizeof(iface_v2));
-	iface_v2.ver = WL_INTERFACE_CREATE_VER_2;
-	iface_v2.flags = WL_INTERFACE_MAC_USE;
-	iface_v2.iftype = WL_INTERFACE_CREATE_AP;
-
-	brcmf_set_vif_sta_macaddr(ifp, iface_v2.mac_addr);
-
-	err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-				       &iface_v2,
-				       sizeof(iface_v2));
-	if (err) {
-		brcmf_info("failed to create interface(v2), err=%d\n",
-			   err);
-	} else {
-		brcmf_dbg(INFO, "interface created(v2)\n");
-		return 0;
-	}
-
-	/* interface_create version 3+ */
-	/* get supported version from firmware side */
-	iface_create_ver = 0;
-	err = brcmf_fil_bsscfg_int_query(ifp, "interface_create",
-					 &iface_create_ver);
-	if (err) {
-		brcmf_err("fail to get supported version, err=%d\n", err);
-		return -EOPNOTSUPP;
-	}
-
-	switch (iface_create_ver) {
-	case WL_INTERFACE_CREATE_VER_3:
-		memset(&iface_v3, 0, sizeof(iface_v3));
-		iface_v3.ver = WL_INTERFACE_CREATE_VER_3;
-		iface_v3.flags = WL_INTERFACE_MAC_USE;
-		iface_v3.iftype = WL_INTERFACE_CREATE_AP;
-		brcmf_set_vif_sta_macaddr(ifp, iface_v3.mac_addr);
-
-		err = brcmf_fil_iovar_data_get(ifp, "interface_create",
-					       &iface_v3,
-					       sizeof(iface_v3));
-
-		if (!err)
-			brcmf_dbg(INFO, "interface created(v3)\n");
-		break;
-	default:
-		brcmf_err("not support interface create(v%d)\n",
-			  iface_create_ver);
-		err = -EOPNOTSUPP;
-		break;
-	}
-
-	if (err) {
-		brcmf_info("Does not support interface_create (%d)\n",
-			   err);
-		memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le));
-		bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr);
-		if (bsscfgidx < 0)
-			return bsscfgidx;
-
-		mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx);
-		mbss_ssid_le.SSID_len = cpu_to_le32(5);
-		sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx);
-
-		err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid", &mbss_ssid_le,
-						sizeof(mbss_ssid_le));
-
-		if (err < 0)
-			bphy_err(drvr, "setting ssid failed %d\n", err);
-	}
-
-	return err;
-}
-
 /**
  * brcmf_apsta_add_vif() - create a new AP or STA virtual interface
  *
@@ -7103,8 +6837,6 @@ static s32 brcmf_dongle_roam(struct brcmf_if *ifp)
 	if (err)
 		bphy_err(drvr, "WLC_SET_ROAM_TRIGGER error (%d)\n", err);
 
-	roam_delta[0] = cpu_to_le32(WL_ROAM_DELTA);
-	roam_delta[1] = cpu_to_le32(BRCM_BAND_ALL);
 	err = brcmf_fil_cmd_data_set(ifp, BRCMF_C_SET_ROAM_DELTA,
 				     (void *)roam_delta, sizeof(roam_delta));
 	if (err)
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c
new file mode 100644
index 00000000000000..1f40ff8d632c25
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.c
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+
+/* This file handles firmware-side interface creation */
+
+#include <linux/kernel.h>
+#include <linux/etherdevice.h>
+#include <linux/module.h>
+#include <linux/vmalloc.h>
+#include <net/cfg80211.h>
+#include <net/netlink.h>
+
+#include <brcmu_utils.h>
+#include <defs.h>
+#include "cfg80211.h"
+#include "debug.h"
+#include "fwil.h"
+#include "proto.h"
+#include "bus.h"
+#include "common.h"
+#include "interface_create.h"
+
+#define BRCMF_INTERFACE_CREATE_VER_1 1
+#define BRCMF_INTERFACE_CREATE_VER_2 2
+#define BRCMF_INTERFACE_CREATE_VER_3 3
+#define BRCMF_INTERFACE_CREATE_VER_MAX BRCMF_INTERFACE_CREATE_VER_3
+
+/* These sets of flags specify whether to use various fields in the interface create structures */
+
+/* This is only used with version 0 or 1 */
+#define BRCMF_INTERFACE_CREATE_STA (0 << 0)
+#define BRCMF_INTERFACE_CREATE_AP (1 << 0)
+
+#define BRCMF_INTERFACE_MAC_DONT_USE (0 << 1)
+#define BRCMF_INTERFACE_MAC_USE (1 << 1)
+
+#define BRCMF_INTERFACE_WLC_INDEX_DONT_USE (0 << 2)
+#define BRCMF_INTERFACE_WLC_INDEX_USE (1 << 2)
+
+#define BRCMF_INTERFACE_IF_INDEX_DONT_USE (0 << 3)
+#define BRCMF_INTERFACE_IF_INDEX_USE (1 << 3)
+
+#define BRCMF_INTERFACE_BSSID_DONT_USE (0 << 4)
+#define BRCMF_INTERFACE_BSSID_USE (1 << 4)
+
+/*
+ * From revision >= 2 Bit 0 of flags field will not be used  for STA or AP interface creation.
+ * "iftype" field shall be used for identifying the interface type.
+ */
+enum brcmf_interface_type {
+	BRCMF_INTERFACE_TYPE_STA = 0,
+	BRCMF_INTERFACE_TYPE_AP = 1,
+	/* The missing number here is deliberate */
+	BRCMF_INTERFACE_TYPE_NAN = 3,
+	BRCMF_INTERFACE_TYPE_P2P_GO = 4,
+	BRCMF_INTERFACE_TYPE_P2P_GC = 5,
+	BRCMF_INTERFACE_TYPE_P2P_DISC = 6,
+	BRCMF_INTERFACE_TYPE_IBSS = 7,
+	BRCMF_INTERFACE_TYPE_MESH = 8
+};
+
+
+/* All sources treat these structures as being host endian.
+ * However, firmware treats it as little endian, so we do as well */
+
+struct brcmf_interface_create_v1 {
+	__le16 ver; /* structure version */
+	u8 pad1[2];
+	__le32 flags; /* flags for operation */
+	u8 mac_addr[ETH_ALEN]; /* MAC address */
+	u8 pad2[2];
+	__le32 wlc_index; /* optional for wlc index */
+};
+
+struct brcmf_interface_create_v2 {
+	__le16 ver; /* structure version */
+	u8 pad1[2];
+	__le32 flags; /* flags for operation */
+	u8 mac_addr[ETH_ALEN]; /* MAC address */
+	u8 iftype; /* type of interface created */
+	u8 pad2;
+	u32 wlc_index; /* optional for wlc index */
+};
+
+struct brcmf_interface_create_v3 {
+	__le16 ver; /* structure version */
+	__le16 len; /* length of structure + data */
+	__le16 fixed_len; /* length of structure */
+	u8 iftype; /* type of interface created */
+	u8 wlc_index; /* optional for wlc index */
+	__le32 flags; /* flags for operation */
+	u8 mac_addr[ETH_ALEN]; /* MAC address */
+	u8 bssid[ETH_ALEN]; /* optional for BSSID */
+	u8 if_index; /* interface index request */
+	u8 pad[3];
+	u8 data[]; /* Optional for specific data */
+};
+
+static int brcmf_get_first_free_bsscfgidx(struct brcmf_pub *drvr)
+{
+	int bsscfgidx;
+
+	for (bsscfgidx = 0; bsscfgidx < BRCMF_MAX_IFS; bsscfgidx++) {
+		/* bsscfgidx 1 is reserved for legacy P2P */
+		if (bsscfgidx == 1)
+			continue;
+		if (!drvr->iflist[bsscfgidx])
+			return bsscfgidx;
+	}
+
+	return -ENOMEM;
+}
+
+static void brcmf_set_vif_sta_macaddr(struct brcmf_if *ifp, u8 *mac_addr)
+{
+	u8 mac_idx = ifp->drvr->sta_mac_idx;
+
+	/* set difference MAC address with locally administered bit */
+	memcpy(mac_addr, ifp->mac_addr, ETH_ALEN);
+	mac_addr[0] |= 0x02;
+	mac_addr[3] ^= mac_idx ? 0xC0 : 0xA0;
+	mac_idx++;
+	mac_idx = mac_idx % 2;
+	ifp->drvr->sta_mac_idx = mac_idx;
+}
+
+static int brcmf_cfg80211_request_if_internal(struct brcmf_if *ifp, u32 version,
+					      enum brcmf_interface_type if_type,
+					      u8 *macaddr)
+{
+	switch (version) {
+	case BRCMF_INTERFACE_CREATE_VER_1: {
+		struct brcmf_interface_create_v1 iface_v1 = {};
+		u32 flags = if_type;
+
+		iface_v1.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_1);
+		if (macaddr) {
+			flags |= BRCMF_INTERFACE_MAC_USE;
+			if (!is_zero_ether_addr(macaddr))
+				memcpy(iface_v1.mac_addr, macaddr, ETH_ALEN);
+			else
+				brcmf_set_vif_sta_macaddr(ifp,
+							  iface_v1.mac_addr);
+		}
+		iface_v1.flags = cpu_to_le32(flags);
+		return brcmf_fil_iovar_data_get(ifp, "interface_create",
+						&iface_v1, sizeof(iface_v1));
+	}
+	case BRCMF_INTERFACE_CREATE_VER_2: {
+		struct brcmf_interface_create_v2 iface_v2 = {};
+		u32 flags = 0;
+
+		iface_v2.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_2);
+		iface_v2.iftype = if_type;
+		if (macaddr) {
+			flags = BRCMF_INTERFACE_MAC_USE;
+			if (!is_zero_ether_addr(macaddr))
+				memcpy(iface_v2.mac_addr, macaddr, ETH_ALEN);
+			else
+				brcmf_set_vif_sta_macaddr(ifp,
+							  iface_v2.mac_addr);
+		}
+		iface_v2.flags = cpu_to_le32(flags);
+		return brcmf_fil_iovar_data_get(ifp, "interface_create",
+						&iface_v2, sizeof(iface_v2));
+	}
+	case BRCMF_INTERFACE_CREATE_VER_3: {
+		struct brcmf_interface_create_v3 iface_v3 = {};
+		u32 flags = 0;
+
+		iface_v3.ver = cpu_to_le16(BRCMF_INTERFACE_CREATE_VER_3);
+		iface_v3.iftype = if_type;
+		iface_v3.len = cpu_to_le16(sizeof(iface_v3));
+		iface_v3.fixed_len = cpu_to_le16(sizeof(iface_v3));
+		if (macaddr) {
+			flags = BRCMF_INTERFACE_MAC_USE;
+			if (!is_zero_ether_addr(macaddr))
+				memcpy(iface_v3.mac_addr, macaddr, ETH_ALEN);
+			else
+				brcmf_set_vif_sta_macaddr(ifp,
+							  iface_v3.mac_addr);
+		}
+		iface_v3.flags = cpu_to_le32(flags);
+		return brcmf_fil_iovar_data_get(ifp, "interface_create",
+						&iface_v3, sizeof(iface_v3));
+	}
+	default:
+		bphy_err(ifp->drvr, "Unknown interface create version:%d\n",
+			 version);
+		return -EINVAL;
+	}
+}
+static int brcmf_cfg80211_request_if(struct brcmf_if *ifp,
+				     enum brcmf_interface_type if_type,
+				     u8 *macaddr)
+{
+	s32 err;
+	u32 iface_create_ver;
+
+	/* Query the creation version, see if the firmware knows */
+	iface_create_ver = 0;
+	err = brcmf_fil_bsscfg_int_query(ifp, "interface_create",
+					 &iface_create_ver);
+	if (!err) {
+		err = brcmf_cfg80211_request_if_internal(ifp, iface_create_ver,
+							 if_type, macaddr);
+		if (!err) {
+			brcmf_info("interface created (version %d)\n",
+				   iface_create_ver);
+		} else {
+			bphy_err(ifp->drvr,
+				 "failed to create interface (version %d):%d\n",
+				 iface_create_ver, err);
+		}
+		return err;
+	}
+	/* Either version one or version two */
+	err = brcmf_cfg80211_request_if_internal(
+		ifp, if_type, BRCMF_INTERFACE_CREATE_VER_2, macaddr);
+	if (!err) {
+		brcmf_info("interface created (version 2)\n");
+		return 0;
+	}
+	err = brcmf_cfg80211_request_if_internal(
+		ifp, if_type, BRCMF_INTERFACE_CREATE_VER_1, macaddr);
+	if (!err) {
+		brcmf_info("interface created (version 1)\n");
+		return 0;
+	}
+	bphy_err(ifp->drvr,
+		 "interface creation failed, tried query, v2, v1: %d\n", err);
+	return -EINVAL;
+}
+
+int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr)
+{
+	return brcmf_cfg80211_request_if(ifp, BRCMF_INTERFACE_TYPE_STA,
+					 macaddr);
+}
+
+int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp)
+{
+	int err;
+
+	err = brcmf_cfg80211_request_if(ifp, BRCMF_INTERFACE_TYPE_AP, NULL);
+	if (err) {
+		struct brcmf_mbss_ssid_le mbss_ssid_le;
+		int bsscfgidx;
+
+		brcmf_info("Does not support interface_create (%d)\n", err);
+		memset(&mbss_ssid_le, 0, sizeof(mbss_ssid_le));
+		bsscfgidx = brcmf_get_first_free_bsscfgidx(ifp->drvr);
+		if (bsscfgidx < 0)
+			return bsscfgidx;
+
+		mbss_ssid_le.bsscfgidx = cpu_to_le32(bsscfgidx);
+		mbss_ssid_le.SSID_len = cpu_to_le32(5);
+		sprintf(mbss_ssid_le.SSID, "ssid%d", bsscfgidx);
+
+		err = brcmf_fil_bsscfg_data_set(ifp, "bsscfg:ssid",
+						&mbss_ssid_le,
+						sizeof(mbss_ssid_le));
+
+		if (err < 0)
+			bphy_err(ifp->drvr, "setting ssid failed %d\n", err);
+	}
+	return err;
+}
diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h
new file mode 100644
index 00000000000000..669fa1508b67f6
--- /dev/null
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/interface_create.h
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: ISC
+/*
+ * Copyright (c) 2023 Daniel Berlin
+ */
+
+#ifndef _BRCMF_INTERFACE_CREATE_H_
+#define _BRCMF_INTERFACE_CREATE_H_
+#include "core.h"
+
+int brcmf_cfg80211_request_sta_if(struct brcmf_if *ifp, u8 *macaddr);
+int brcmf_cfg80211_request_ap_if(struct brcmf_if *ifp);
+
+#endif /* _BRCMF_INTERFACE_CREATE_H_ */

From 415060a987cb034a133d08c9b0a16312cd77baec Mon Sep 17 00:00:00 2001
From: Daniel Berlin <dberlin@dberlin.org>
Date: Wed, 29 Nov 2023 19:57:34 -0500
Subject: [PATCH 0710/1027] [brcmfmac] Disable partial SAE offload

Right now, partial SAE offload support is not working with supplicants.
This patch currently disables it until we figure that out.

We still do full SAE offload.
Signed-off-by: Daniel Berlin <dberlin@dberlin.org>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
index 3ed406700ff0c6..7d2cc8f638ec13 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/cfg80211.c
@@ -7839,7 +7839,9 @@ static int brcmf_setup_wiphy(struct wiphy *wiphy, struct brcmf_if *ifp)
 			wiphy_ext_feature_set(wiphy,
 					      NL80211_EXT_FEATURE_SAE_OFFLOAD_AP);
 	}
-	if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) {
+
+	/* FIXME: Currently our partial SAE offload is breaking with some AP's */
+	if (0 && brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) {
 		wiphy->features |= NL80211_FEATURE_SAE;
 	}
 

From e4d55da111e31edfd853df12983c0215082d8853 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 2 Jun 2024 19:04:35 +0200
Subject: [PATCH 0711/1027] fixup! wifi: brcmfmac: Add support for firmware
 signatures

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
index 2a8b5b83e03300..d884a3288fc992 100644
--- a/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
+++ b/drivers/net/wireless/broadcom/brcm80211/brcmfmac/pcie.c
@@ -1958,7 +1958,7 @@ brcmf_pcie_add_random_seed(struct brcmf_pciedev_info *devinfo, u32 *address)
 	brcmf_dbg(PCIE, "Download random seed\n");
 
 	get_random_bytes(randbuf, BRCMF_RANDOM_SEED_LENGTH);
-	memcpy_toio(devinfo->tcm + address, randbuf, BRCMF_RANDOM_SEED_LENGTH);
+	memcpy_toio(devinfo->tcm + *address, randbuf, BRCMF_RANDOM_SEED_LENGTH);
 
 	return 0;
 }

From 8f1cad1676f3b877b4d17b86c2f9827d4a181466 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 30 Aug 2022 02:09:14 +0900
Subject: [PATCH 0712/1027] usb: renesas-xhci: Build loader into the main
 xhci-pci module

This doesn't make any sense as a module, since it becomes a symbol
dependency of the main driver if enabled, and therefore would always be
loaded regardless of the user's hardware. Just compile it in, if
enabled.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/usb/host/Kconfig                         | 4 ++--
 drivers/usb/host/Makefile                        | 3 ++-
 drivers/usb/host/{xhci-pci.c => xhci-pci-core.c} | 0
 drivers/usb/host/xhci-pci-renesas.c              | 5 +----
 drivers/usb/host/xhci-pci.h                      | 2 +-
 5 files changed, 6 insertions(+), 8 deletions(-)
 rename drivers/usb/host/{xhci-pci.c => xhci-pci-core.c} (100%)

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 4448d0ab06f0d4..17b66c10716aef 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -40,11 +40,11 @@ config USB_XHCI_DBGCAP
 config USB_XHCI_PCI
 	tristate
 	depends on USB_PCI
-	depends on USB_XHCI_PCI_RENESAS || !USB_XHCI_PCI_RENESAS
 	default y
 
 config USB_XHCI_PCI_RENESAS
-	tristate "Support for additional Renesas xHCI controller with firmware"
+	bool "Support for Renesas xHCI controllers with firmware"
+	depends on USB_XHCI_PCI
 	help
 	  Say 'Y' to enable the support for the Renesas xHCI controller with
 	  firmware. Make sure you have the firmware for the device and
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index be4e5245c52fe9..7776f997a55703 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -68,7 +68,8 @@ obj-$(CONFIG_USB_UHCI_HCD)	+= uhci-hcd.o
 obj-$(CONFIG_USB_FHCI_HCD)	+= fhci.o
 obj-$(CONFIG_USB_XHCI_HCD)	+= xhci-hcd.o
 obj-$(CONFIG_USB_XHCI_PCI)	+= xhci-pci.o
-obj-$(CONFIG_USB_XHCI_PCI_RENESAS)	+= xhci-pci-renesas.o
+xhci-pci-y			+= xhci-pci-core.o
+xhci-pci-$(CONFIG_USB_XHCI_PCI_RENESAS)	+= xhci-pci-renesas.o
 obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
 obj-$(CONFIG_USB_XHCI_HISTB)	+= xhci-histb.o
 obj-$(CONFIG_USB_XHCI_RCAR)	+= xhci-rcar-hcd.o
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci-core.c
similarity index 100%
rename from drivers/usb/host/xhci-pci.c
rename to drivers/usb/host/xhci-pci-core.c
diff --git a/drivers/usb/host/xhci-pci-renesas.c b/drivers/usb/host/xhci-pci-renesas.c
index 247cc7c2ce7009..dddd35e50a2c54 100644
--- a/drivers/usb/host/xhci-pci-renesas.c
+++ b/drivers/usb/host/xhci-pci-renesas.c
@@ -3,7 +3,6 @@
 
 #include <linux/acpi.h>
 #include <linux/firmware.h>
-#include <linux/module.h>
 #include <linux/pci.h>
 #include <linux/slab.h>
 #include <asm/unaligned.h>
@@ -625,7 +624,5 @@ int renesas_xhci_check_request_fw(struct pci_dev *pdev,
 	release_firmware(fw);
 	return err;
 }
-EXPORT_SYMBOL_GPL(renesas_xhci_check_request_fw);
 
-MODULE_DESCRIPTION("Support for Renesas xHCI controller with firmware");
-MODULE_LICENSE("GPL v2");
+MODULE_FIRMWARE("renesas_usb_fw.mem");
diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h
index cb9a8f331a4463..dff1b99564cc69 100644
--- a/drivers/usb/host/xhci-pci.h
+++ b/drivers/usb/host/xhci-pci.h
@@ -9,7 +9,7 @@ int renesas_xhci_check_request_fw(struct pci_dev *dev,
 				  const struct pci_device_id *id);
 
 #else
-static int renesas_xhci_check_request_fw(struct pci_dev *dev,
+static inline int renesas_xhci_check_request_fw(struct pci_dev *dev,
 					 const struct pci_device_id *id)
 {
 	return 0;

From 9bc2455dc0bef7182bb42ea6e8138f3d4d07a29e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 30 Aug 2022 02:11:48 +0900
Subject: [PATCH 0713/1027] xhci-pci: asmedia: Add a firmware loader for
 ASM2214a chips

Apple ships ASM2214a ICs in some Apple Silicon hardware (notably, the
2021 iMac and the 2022 Mac Studio) without a flash ROM, and relies on
the OS to load firmware on startup. Add support for this to the generic
xhci-pci driver.

The loader code first checks the firmware version, and only attempts to
load firmware if the version isn't the known ROM version.

Since this arrangement only exists on Apple machines so far, and Apple
are the only source of the (non-redistributable) firmware intended for
use on these machines, the firmware is named asmedia/asm2214a-apple.bin.
If this style of firmware loading ever becomes necessary on non-Apple
machines, we should add a generic firmware name at the time (if it can
be part of linux-firmware) or another vendor-specific firmware name.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/usb/host/Kconfig            |   9 +
 drivers/usb/host/Makefile           |   1 +
 drivers/usb/host/xhci-pci-asmedia.c | 394 ++++++++++++++++++++++++++++
 drivers/usb/host/xhci-pci-core.c    |  24 ++
 drivers/usb/host/xhci-pci.h         |  13 +
 drivers/usb/host/xhci.h             |   1 +
 6 files changed, 442 insertions(+)
 create mode 100644 drivers/usb/host/xhci-pci-asmedia.c

diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig
index 17b66c10716aef..fdcbc71153182c 100644
--- a/drivers/usb/host/Kconfig
+++ b/drivers/usb/host/Kconfig
@@ -51,6 +51,15 @@ config USB_XHCI_PCI_RENESAS
 	  installed on your system for this device to work.
 	  If unsure, say 'N'.
 
+config USB_XHCI_PCI_ASMEDIA
+	bool "Support for ASMedia xHCI controller with firmware"
+	default ARCH_APPLE
+	depends on USB_XHCI_PCI
+	help
+	  Say 'Y' to enable support for ASMedia xHCI controllers with
+	  host-supplied firmware. These are usually present on Apple devices.
+	  If unsure, say 'N'.
+
 config USB_XHCI_PLATFORM
 	tristate "Generic xHCI driver for a platform device"
 	help
diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile
index 7776f997a55703..b64cb3398b7606 100644
--- a/drivers/usb/host/Makefile
+++ b/drivers/usb/host/Makefile
@@ -70,6 +70,7 @@ obj-$(CONFIG_USB_XHCI_HCD)	+= xhci-hcd.o
 obj-$(CONFIG_USB_XHCI_PCI)	+= xhci-pci.o
 xhci-pci-y			+= xhci-pci-core.o
 xhci-pci-$(CONFIG_USB_XHCI_PCI_RENESAS)	+= xhci-pci-renesas.o
+xhci-pci-$(CONFIG_USB_XHCI_PCI_ASMEDIA)	+= xhci-pci-asmedia.o
 obj-$(CONFIG_USB_XHCI_PLATFORM) += xhci-plat-hcd.o
 obj-$(CONFIG_USB_XHCI_HISTB)	+= xhci-histb.o
 obj-$(CONFIG_USB_XHCI_RCAR)	+= xhci-rcar-hcd.o
diff --git a/drivers/usb/host/xhci-pci-asmedia.c b/drivers/usb/host/xhci-pci-asmedia.c
new file mode 100644
index 00000000000000..80f1b3eabd6deb
--- /dev/null
+++ b/drivers/usb/host/xhci-pci-asmedia.c
@@ -0,0 +1,394 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+/*
+ * ASMedia xHCI firmware loader
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/acpi.h>
+#include <linux/firmware.h>
+#include <linux/pci.h>
+#include <linux/iopoll.h>
+#include <linux/slab.h>
+#include <asm/unaligned.h>
+
+#include "xhci.h"
+#include "xhci-trace.h"
+#include "xhci-pci.h"
+
+/* Configuration space registers */
+#define ASMT_CFG_CONTROL		0xe0
+#define ASMT_CFG_CONTROL_WRITE		BIT(1)
+#define ASMT_CFG_CONTROL_READ		BIT(0)
+
+#define ASMT_CFG_SRAM_ADDR		0xe2
+
+#define ASMT_CFG_SRAM_ACCESS		0xef
+#define ASMT_CFG_SRAM_ACCESS_READ	BIT(6)
+#define ASMT_CFG_SRAM_ACCESS_ENABLE	BIT(7)
+
+#define ASMT_CFG_DATA_READ0		0xf0
+#define ASMT_CFG_DATA_READ1		0xf4
+
+#define ASMT_CFG_DATA_WRITE0		0xf8
+#define ASMT_CFG_DATA_WRITE1		0xfc
+
+#define ASMT_CMD_GET_FWVER		0x8000060840
+#define ASMT_FWVER_ROM			0x010250090816
+
+/* BAR0 registers */
+#define ASMT_REG_ADDR			0x3000
+
+#define ASMT_REG_WDATA			0x3004
+#define ASMT_REG_RDATA			0x3008
+
+#define ASMT_REG_STATUS			0x3009
+#define ASMT_REG_STATUS_BUSY		BIT(7)
+
+#define ASMT_REG_CODE_WDATA		0x3010
+#define ASMT_REG_CODE_RDATA		0x3018
+
+#define ASMT_MMIO_CPU_MISC		0x500e
+#define ASMT_MMIO_CPU_MISC_CODE_RAM_WR	BIT(0)
+
+#define ASMT_MMIO_CPU_MODE_NEXT		0x5040
+#define ASMT_MMIO_CPU_MODE_CUR		0x5041
+
+#define ASMT_MMIO_CPU_MODE_RAM		BIT(0)
+#define ASMT_MMIO_CPU_MODE_HALFSPEED	BIT(1)
+
+#define ASMT_MMIO_CPU_EXEC_CTRL		0x5042
+#define ASMT_MMIO_CPU_EXEC_CTRL_RESET	BIT(0)
+#define ASMT_MMIO_CPU_EXEC_CTRL_HALT	BIT(1)
+
+#define TIMEOUT_USEC			10000
+#define RESET_TIMEOUT_USEC		500000
+
+static int asmedia_mbox_tx(struct pci_dev *pdev, u64 data)
+{
+	u8 op;
+	int i;
+
+	for (i = 0; i < TIMEOUT_USEC; i++) {
+		pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
+		if (!(op & ASMT_CFG_CONTROL_WRITE))
+			break;
+		udelay(1);
+	}
+
+	if (op & ASMT_CFG_CONTROL_WRITE) {
+		dev_err(&pdev->dev,
+			"Timed out on mailbox tx: 0x%llx\n",
+			data);
+		return -ETIMEDOUT;
+	}
+
+	pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE0, data);
+	pci_write_config_dword(pdev, ASMT_CFG_DATA_WRITE1, data >> 32);
+	pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
+			      ASMT_CFG_CONTROL_WRITE);
+
+	return 0;
+}
+
+static int asmedia_mbox_rx(struct pci_dev *pdev, u64 *data)
+{
+	u8 op;
+	u32 low, high;
+	int i;
+
+	for (i = 0; i < TIMEOUT_USEC; i++) {
+		pci_read_config_byte(pdev, ASMT_CFG_CONTROL, &op);
+		if (op & ASMT_CFG_CONTROL_READ)
+			break;
+		udelay(1);
+	}
+
+	if (!(op & ASMT_CFG_CONTROL_READ)) {
+		dev_err(&pdev->dev, "Timed out on mailbox rx\n");
+		return -ETIMEDOUT;
+	}
+
+	pci_read_config_dword(pdev, ASMT_CFG_DATA_READ0, &low);
+	pci_read_config_dword(pdev, ASMT_CFG_DATA_READ1, &high);
+	pci_write_config_byte(pdev, ASMT_CFG_CONTROL,
+			      ASMT_CFG_CONTROL_READ);
+
+	*data = ((u64)high << 32) | low;
+	return 0;
+}
+
+static int asmedia_get_fw_version(struct pci_dev *pdev, u64 *version)
+{
+	int err = 0;
+	u64 cmd;
+
+	err = asmedia_mbox_tx(pdev, ASMT_CMD_GET_FWVER);
+	if (err)
+		return err;
+	err = asmedia_mbox_tx(pdev, 0);
+	if (err)
+		return err;
+
+	err = asmedia_mbox_rx(pdev, &cmd);
+	if (err)
+		return err;
+	err = asmedia_mbox_rx(pdev, version);
+	if (err)
+		return err;
+
+	if (cmd != ASMT_CMD_GET_FWVER) {
+		dev_err(&pdev->dev, "Unexpected reply command 0x%llx\n", cmd);
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static bool asmedia_check_firmware(struct pci_dev *pdev)
+{
+	u64 fwver;
+	int ret;
+
+	ret = asmedia_get_fw_version(pdev, &fwver);
+	if (ret)
+		return ret;
+
+	dev_info(&pdev->dev, "Firmware version: 0x%llx\n", fwver);
+
+	return fwver != ASMT_FWVER_ROM;
+}
+
+static int asmedia_wait_reset(struct pci_dev *pdev)
+{
+	struct usb_hcd *hcd = dev_get_drvdata(&pdev->dev);
+	struct xhci_cap_regs __iomem *cap = hcd->regs;
+	struct xhci_op_regs __iomem *op;
+	u32 val;
+	int ret;
+
+	op = hcd->regs + HC_LENGTH(readl(&cap->hc_capbase));
+
+	ret = readl_poll_timeout(&op->command,
+				 val, !(val & CMD_RESET),
+				 1000, RESET_TIMEOUT_USEC);
+
+	if (!ret)
+		return 0;
+
+	dev_err(hcd->self.controller, "Reset timed out, trying to kick it\n");
+
+	pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
+			      ASMT_CFG_SRAM_ACCESS_ENABLE);
+
+	pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
+
+	ret = readl_poll_timeout(&op->command,
+				 val, !(val & CMD_RESET),
+				 1000, RESET_TIMEOUT_USEC);
+
+	if (ret)
+		dev_err(hcd->self.controller, "Reset timed out, giving up\n");
+
+	return ret;
+}
+
+static u8 asmedia_read_reg(struct usb_hcd *hcd, u16 addr) {
+	void __iomem *regs = hcd->regs;
+	u8 status;
+	int ret;
+
+	ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+				 status, !(status & ASMT_REG_STATUS_BUSY),
+				 1000, TIMEOUT_USEC);
+
+	if (ret) {
+		dev_err(hcd->self.controller,
+			"Read reg wait timed out ([%04x])\n", addr);
+		return ~0;
+	}
+
+	writew_relaxed(addr, regs + ASMT_REG_ADDR);
+
+	ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+				 status, !(status & ASMT_REG_STATUS_BUSY),
+				 1000, TIMEOUT_USEC);
+
+	if (ret) {
+		dev_err(hcd->self.controller,
+			"Read reg addr timed out ([%04x])\n", addr);
+		return ~0;
+	}
+
+	return readb_relaxed(regs + ASMT_REG_RDATA);
+}
+
+static void asmedia_write_reg(struct usb_hcd *hcd, u16 addr, u8 data, bool wait) {
+	void __iomem *regs = hcd->regs;
+	u8 status;
+	int ret, i;
+
+	writew_relaxed(addr, regs + ASMT_REG_ADDR);
+
+	ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+				 status, !(status & ASMT_REG_STATUS_BUSY),
+				 1000, TIMEOUT_USEC);
+
+	if (ret)
+		dev_err(hcd->self.controller,
+			"Write reg addr timed out ([%04x] = %02x)\n",
+			addr, data);
+
+	writeb_relaxed(data, regs + ASMT_REG_WDATA);
+
+	ret = readb_poll_timeout(regs + ASMT_REG_STATUS,
+				 status, !(status & ASMT_REG_STATUS_BUSY),
+				 1000, TIMEOUT_USEC);
+
+	if (ret)
+		dev_err(hcd->self.controller,
+			"Write reg data timed out ([%04x] = %02x)\n",
+			addr, data);
+
+	if (!wait)
+		return;
+
+	for (i = 0; i < TIMEOUT_USEC; i++) {
+		if (asmedia_read_reg(hcd, addr) == data)
+			break;
+	}
+
+	if (i >= TIMEOUT_USEC) {
+		dev_err(hcd->self.controller,
+			"Verify register timed out ([%04x] = %02x)\n",
+			addr, data);
+	}
+}
+
+static int asmedia_load_fw(struct pci_dev *pdev, const struct firmware *fw)
+{
+	struct usb_hcd *hcd;
+	void __iomem *regs;
+	const u16 *fw_data = (const u16 *)fw->data;
+	u16 raddr;
+	u32 data;
+	size_t index = 0, addr = 0;
+	size_t words = fw->size >> 1;
+	int ret, i;
+
+	hcd = dev_get_drvdata(&pdev->dev);
+	regs = hcd->regs;
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT,
+			  ASMT_MMIO_CPU_MODE_HALFSPEED, false);
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL,
+			  ASMT_MMIO_CPU_EXEC_CTRL_RESET, false);
+
+	ret = asmedia_wait_reset(pdev);
+	if (ret) {
+		dev_err(hcd->self.controller, "Failed pre-upload reset\n");
+		return ret;
+	}
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL,
+			  ASMT_MMIO_CPU_EXEC_CTRL_HALT, false);
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC,
+			  ASMT_MMIO_CPU_MISC_CODE_RAM_WR, true);
+
+	pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS,
+			      ASMT_CFG_SRAM_ACCESS_ENABLE);
+
+	/* The firmware upload is interleaved in 0x4000 word blocks */
+	addr = index = 0;
+	while (index < words) {
+		data = fw_data[index];
+		if ((index | 0x4000) < words)
+			data |= fw_data[index | 0x4000] << 16;
+
+		pci_write_config_word(pdev, ASMT_CFG_SRAM_ADDR,
+				      addr);
+
+		writel_relaxed(data, regs + ASMT_REG_CODE_WDATA);
+
+		for (i = 0; i < TIMEOUT_USEC; i++) {
+			pci_read_config_word(pdev, ASMT_CFG_SRAM_ADDR, &raddr);
+			if (raddr != addr)
+				break;
+			udelay(1);
+		}
+
+		if (raddr == addr) {
+			dev_err(hcd->self.controller, "Word write timed out\n");
+			return -ETIMEDOUT;
+		}
+
+		if (++index & 0x4000)
+			index += 0x4000;
+		addr += 2;
+	}
+
+	pci_write_config_byte(pdev, ASMT_CFG_SRAM_ACCESS, 0);
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_MISC, 0, true);
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_MODE_NEXT,
+			  ASMT_MMIO_CPU_MODE_RAM |
+			  ASMT_MMIO_CPU_MODE_HALFSPEED, false);
+
+	asmedia_write_reg(hcd, ASMT_MMIO_CPU_EXEC_CTRL, 0, false);
+
+	ret = asmedia_wait_reset(pdev);
+	if (ret) {
+		dev_err(hcd->self.controller, "Failed post-upload reset\n");
+		return ret;
+	}
+
+	return 0;
+}
+
+int asmedia_xhci_check_request_fw(struct pci_dev *pdev,
+				  const struct pci_device_id *id)
+{
+	struct xhci_driver_data *driver_data =
+			(struct xhci_driver_data *)id->driver_data;
+	const char *fw_name = driver_data->firmware;
+	const struct firmware *fw;
+	int ret;
+
+	/* Check if device has firmware, if so skip everything */
+	ret = asmedia_check_firmware(pdev);
+	if (ret < 0)
+		return ret;
+	else if (ret == 1)
+		return 0;
+
+	pci_dev_get(pdev);
+	ret = request_firmware(&fw, fw_name, &pdev->dev);
+	pci_dev_put(pdev);
+	if (ret) {
+		dev_err(&pdev->dev, "Could not load firmware %s: %d\n",
+			fw_name, ret);
+		return ret;
+	}
+
+	ret = asmedia_load_fw(pdev, fw);
+	if (ret) {
+		dev_err(&pdev->dev, "Firmware upload failed: %d\n", ret);
+		goto err;
+	}
+
+	ret = asmedia_check_firmware(pdev);
+	if (ret < 0) {
+		goto err;
+	} else if (ret != 1) {
+		dev_err(&pdev->dev, "Firmware version is too old after upload\n");
+		ret = -EIO;
+	} else {
+		ret = 0;
+	}
+
+err:
+	release_firmware(fw);
+	return ret;
+}
diff --git a/drivers/usb/host/xhci-pci-core.c b/drivers/usb/host/xhci-pci-core.c
index 994fd8b38bd015..b4ec78f62e4178 100644
--- a/drivers/usb/host/xhci-pci-core.c
+++ b/drivers/usb/host/xhci-pci-core.c
@@ -548,6 +548,18 @@ static int xhci_pci_setup(struct usb_hcd *hcd)
 	struct xhci_hcd		*xhci;
 	struct pci_dev		*pdev = to_pci_dev(hcd->self.controller);
 	int			retval;
+	struct xhci_driver_data	*driver_data;
+	const struct pci_device_id *id;
+
+	id = pci_match_id(to_pci_driver(pdev->dev.driver)->id_table, pdev);
+	if (id && id->driver_data && usb_hcd_is_primary_hcd(hcd)) {
+		driver_data = (struct xhci_driver_data *)id->driver_data;
+		if (driver_data->quirks & XHCI_ASMEDIA_FW_QUIRK) {
+			retval = asmedia_xhci_check_request_fw(pdev, id);
+			if (retval < 0)
+				return retval;
+		}
+	}
 
 	xhci = hcd_to_xhci(hcd);
 	if (!xhci->sbrn)
@@ -903,6 +915,11 @@ static const struct xhci_driver_data reneses_data = {
 	.firmware = "renesas_usb_fw.mem",
 };
 
+static const struct xhci_driver_data asmedia_data = {
+	.quirks  = XHCI_ASMEDIA_FW_QUIRK,
+	.firmware = "asmedia/asm2214a-apple.bin",
+};
+
 /* PCI driver selection metadata; PCI hotplugging uses this */
 static const struct pci_device_id pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0014),
@@ -911,6 +928,9 @@ static const struct pci_device_id pci_ids[] = {
 	{ PCI_DEVICE(PCI_VENDOR_ID_RENESAS, 0x0015),
 		.driver_data =  (unsigned long)&reneses_data,
 	},
+	{ PCI_DEVICE(0x1b21, 0x2142),
+		.driver_data =  (unsigned long)&asmedia_data,
+	},
 	/* handle any USB 3.0 xHCI controller */
 	{ PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0),
 	},
@@ -926,6 +946,10 @@ MODULE_DEVICE_TABLE(pci, pci_ids);
 MODULE_FIRMWARE("renesas_usb_fw.mem");
 #endif
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA)
+MODULE_FIRMWARE("asmedia/asm2214a-apple.bin");
+#endif
+
 /* pci driver glue; this is a "new style" PCI driver module */
 static struct pci_driver xhci_pci_driver = {
 	.name =		hcd_name,
diff --git a/drivers/usb/host/xhci-pci.h b/drivers/usb/host/xhci-pci.h
index dff1b99564cc69..279c95acc43f6d 100644
--- a/drivers/usb/host/xhci-pci.h
+++ b/drivers/usb/host/xhci-pci.h
@@ -17,6 +17,19 @@ static inline int renesas_xhci_check_request_fw(struct pci_dev *dev,
 
 #endif
 
+#if IS_ENABLED(CONFIG_USB_XHCI_PCI_ASMEDIA)
+int asmedia_xhci_check_request_fw(struct pci_dev *dev,
+				  const struct pci_device_id *id);
+
+#else
+static inline int asmedia_xhci_check_request_fw(struct pci_dev *dev,
+					 const struct pci_device_id *id)
+{
+	return 0;
+}
+
+#endif
+
 struct xhci_driver_data {
 	u64 quirks;
 	const char *firmware;
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 0ea95ad4cb9022..bcfbce53e74453 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -1629,6 +1629,7 @@ struct xhci_hcd {
 #define XHCI_ZHAOXIN_HOST	BIT_ULL(46)
 #define XHCI_WRITE_64_HI_LO	BIT_ULL(47)
 #define XHCI_CDNS_SCTX_QUIRK	BIT_ULL(48)
+#define XHCI_ASMEDIA_FW_QUIRK	BIT_ULL(49)
 
 	unsigned int		num_active_eps;
 	unsigned int		limit_active_eps;

From 4a7b793b35446911a6af7daee0271f9e0b33535b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 15 Apr 2023 16:43:39 +0200
Subject: [PATCH 0714/1027] drm/apple: Remove simpledrm framebuffer before DRM
 device alloc

Should result in drm apple to be registered as first DRM device
replacing simpledrm. Should resolve problems with userspace assuming
that card0 is the main displays device.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 8220b7d7aa062e..20e6edc1fd9981 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -468,6 +468,14 @@ static int apple_drm_init(struct device *dev)
 	if (ret)
 		return ret;
 
+	fb_size = fb_r.end - fb_r.start + 1;
+	ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
+						&apple_drm_driver);
+	if (ret) {
+		dev_err(dev, "Failed remove fb: %d\n", ret);
+		goto err_unbind;
+	}
+
 	apple = devm_drm_dev_alloc(dev, &apple_drm_driver,
 				   struct apple_drm_private, drm);
 	if (IS_ERR(apple))
@@ -479,14 +487,6 @@ static int apple_drm_init(struct device *dev)
 	if (ret)
 		return ret;
 
-	fb_size = fb_r.end - fb_r.start + 1;
-	ret = drm_aperture_remove_conflicting_framebuffers(fb_r.start, fb_size,
-						false, &apple_drm_driver);
-	if (ret) {
-		dev_err(dev, "Failed remove fb: %d\n", ret);
-		goto err_unbind;
-	}
-
 	ret = drmm_mode_config_init(&apple->drm);
 	if (ret)
 		goto err_unbind;

From d192782afce4254129c7bef88d0ed3252856ea8f Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Tue, 25 Apr 2023 01:49:14 +0900
Subject: [PATCH 0715/1027] drm/apple: Mark DCP as being in the wakeup path

This prevents the PD from being shut down on suspend, which we need
until we support runtime PM properly again.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/dcp.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9522690dfde89b..053b483e8075b1 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -584,6 +584,26 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 	component_del(&pdev->dev, &dcp_comp_ops);
 }
 
+static __maybe_unused int dcp_platform_suspend(struct device *dev)
+{
+	/*
+	 * Set the device as a wakeup device, which forces its power
+	 * domains to stay on. We need this as we do not support full
+	 * shutdown properly yet.
+	 */
+	device_set_wakeup_path(dev);
+
+	return 0;
+}
+
+static __maybe_unused int dcp_platform_resume(struct device *dev)
+{
+	return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops,
+			 dcp_platform_suspend, dcp_platform_resume);
+
 static const struct of_device_id of_match[] = {
 	{ .compatible = "apple,dcp" },
 	{}
@@ -597,6 +617,7 @@ static struct platform_driver apple_platform_driver = {
 	.driver	= {
 		.name = "apple-dcp",
 		.of_match_table	= of_match,
+		.pm = pm_sleep_ptr(&dcp_platform_pm_ops),
 	},
 };
 

From ddf5e08119612da90bd4a387b2f148a41ccf6888 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 16:36:33 +0200
Subject: [PATCH 0716/1027] drm: apple: iomfb: Increase modeset timeout to 2.5
 seconds

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 67cf1499a86707..e22688d7d9bab9 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1276,12 +1276,12 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 
 		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
 		ret = wait_for_completion_timeout(&cookie->done,
-						  msecs_to_jiffies(500));
+						  msecs_to_jiffies(2500));
 
 		kref_put(&cookie->refcount, release_wait_cookie);
 
 		if (ret == 0) {
-			dev_dbg(dcp->dev, "set_digital_out_mode 200 ms");
+			dev_info(dcp->dev, "set_digital_out_mode timed out");
 			schedule_work(&dcp->vblank_wq);
 			return;
 		} else if (ret > 0) {

From 2e0709fca8dd7d0d7015b267d5b70b41641cddbc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 16:01:01 +0200
Subject: [PATCH 0717/1027] drm: apple: Only match backlight service on DCP
 with panel

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   |  1 +
 drivers/gpu/drm/apple/dcp.c            |  5 +++++
 drivers/gpu/drm/apple/iomfb_template.c | 24 +++++++++++++++++++-----
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  2 +-
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  2 +-
 5 files changed, 27 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index fee15867b5c0cf..e4857e16616b8c 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -195,5 +195,6 @@ struct apple_dcp {
 };
 
 int dcp_backlight_register(struct apple_dcp *dcp);
+bool dcp_has_panel(struct apple_dcp *dcp);
 
 #endif /* __APPLE_DCP_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 053b483e8075b1..3f5e8218dbf9ec 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -85,6 +85,11 @@ void dcp_set_dimensions(struct apple_dcp *dcp)
 	}
 }
 
+bool dcp_has_panel(struct apple_dcp *dcp)
+{
+	return dcp->panel.width_mm > 0;
+}
+
 /*
  * Helper to send a DRM vblank event. We do not know how call swap_submit_dcp
  * without surfaces. To avoid timeouts in drm_atomic_helper_wait_for_vblanks
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index e22688d7d9bab9..e522da82a01f52 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -183,6 +183,12 @@ static bool iomfbep_cb_match_backlight_service(struct apple_dcp *dcp, int tag, v
 {
 	trace_iomfb_callback(dcp, tag, __func__);
 
+	if (!dcp_has_panel(dcp)) {
+		u8 *succ = out;
+		*succ = true;
+		return true;
+	}
+
 	iomfb_a132_backlight_service_matched(dcp, false, complete_backlight_service_matched, out);
 
 	// return false for deferred ACK
@@ -194,11 +200,13 @@ static void iomfb_cb_pr_publish(struct apple_dcp *dcp, struct iomfb_property *pr
 	switch (prop->id) {
 	case IOMFB_PROPERTY_NITS:
 	{
-		dcp->brightness.nits = prop->value / dcp->brightness.scale;
-		/* notify backlight device of the initial brightness */
-		if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
-			schedule_work(&dcp->bl_register_wq);
-		trace_iomfb_brightness(dcp, prop->value);
+		if (dcp_has_panel(dcp)) {
+			dcp->brightness.nits = prop->value / dcp->brightness.scale;
+			/* notify backlight device of the initial brightness */
+			if (!dcp->brightness.bl_dev && dcp->brightness.maximum > 0)
+				schedule_work(&dcp->bl_register_wq);
+			trace_iomfb_brightness(dcp, prop->value);
+		}
 		break;
 	}
 	default:
@@ -1003,6 +1011,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
 	};
 }
 
+static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp)
+{
+	return dcp_has_panel(dcp);
+}
+
 TRAMPOLINE_VOID(trampoline_nop, dcpep_cb_nop);
 TRAMPOLINE_OUT(trampoline_true, dcpep_cb_true, u8);
 TRAMPOLINE_OUT(trampoline_false, dcpep_cb_false, u8);
@@ -1053,6 +1066,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
 	      struct iomfb_property);
 TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
 		 struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
+TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8);
 
 /*
  * Callback for swap requests. If a swap failed, we'll never get a swap
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index 8188321004a63f..5bc8bc2f8bd290 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -49,7 +49,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[108] = trampoline_true, /* create_product_service */
 	[109] = trampoline_true, /* create_pmu_service */
 	[110] = trampoline_true, /* create_iomfb_service */
-	[111] = trampoline_true, /* create_backlight_service */
+	[111] = trampoline_create_backlight_service,
 	[116] = dcpep_cb_boot_1,
 	[117] = trampoline_false, /* is_dark_boot */
 	[118] = trampoline_false, /* is_dark_boot / is_waking_from_hibernate*/
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 18020c6cd39493..b82ed1f32e0e8e 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -51,7 +51,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[109] = trampoline_true, /* create_product_service */
 	[110] = trampoline_true, /* create_pmu_service */
 	[111] = trampoline_true, /* create_iomfb_service */
-	[112] = trampoline_true, /* create_backlight_service */
+	[112] = trampoline_create_backlight_service,
 	[113] = trampoline_true, /* create_nvram_servce? */
 	[114] = trampoline_get_tiling_state,
 	[115] = trampoline_false, /* set_tiling_state */

From a41a68a407ef58dbcbb67668942bf2d235d2fcf1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 16:34:14 +0200
Subject: [PATCH 0718/1027] drm: apple: iomfb: limit backlight updates to
 integrated panels

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index e522da82a01f52..ec4d13f62822fa 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -876,9 +876,11 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 	 * subsequent update on poweron an actual change and restore the
 	 * brightness.
 	 */
-	swap->swap.bl_unk = 1;
-	swap->swap.bl_value = 0;
-	swap->swap.bl_power = 0;
+	if (dcp_has_panel(dcp)) {
+		swap->swap.bl_unk = 1;
+		swap->swap.bl_value = 0;
+		swap->swap.bl_power = 0;
+	}
 
 	for (int l = 0; l < SWAP_SURFACES; l++)
 		swap->surf_null[l] = true;
@@ -1324,7 +1326,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 	req->swap.swap_completed = req->swap.swap_enabled;
 
 	/* update brightness if changed */
-	if (dcp->brightness.update) {
+	if (dcp_has_panel(dcp) && dcp->brightness.update) {
 		req->swap.bl_unk = 1;
 		req->swap.bl_value = dcp->brightness.dac;
 		req->swap.bl_power = 0x40;

From 04dcfb76bf46b7513cb1ad0acf5f8619ea633a5a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 16 Jul 2023 17:51:10 +0200
Subject: [PATCH 0719/1027] drm: apple: backlight: avoid updating the
 brightness with a commit

An atomic_commit for brightness changes will consume a DCP swap without
frame buffer updates and will result in a lost frame. After updating
the next brightness values wait for 1 frame duration (at 23.976 fps).
Check if the brightness update still needs to be send to DVCP or if a
swap did that in the meintime.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp_backlight.c | 28 ++++++++++++++++++---------
 1 file changed, 19 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index d063ecd7ad2068..0eeb3d6d92c5a2 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -136,18 +136,24 @@ static u32 calculate_dac(struct apple_dcp *dcp, int val)
 	return 16 * dac;
 }
 
-static int drm_crtc_set_brightness(struct drm_crtc *crtc,
-				   struct drm_modeset_acquire_ctx *ctx)
+static int drm_crtc_set_brightness(struct apple_dcp *dcp)
 {
 	struct drm_atomic_state *state;
 	struct drm_crtc_state *crtc_state;
+	struct drm_modeset_acquire_ctx ctx;
+	struct drm_crtc *crtc = &dcp->crtc->base;
 	int ret = 0;
 
+	DRM_MODESET_LOCK_ALL_BEGIN(crtc->dev, ctx, 0, ret);
+
+	if (!dcp->brightness.update)
+		goto done;
+
 	state = drm_atomic_state_alloc(crtc->dev);
 	if (!state)
 		return -ENOMEM;
 
-	state->acquire_ctx = ctx;
+	state->acquire_ctx = &ctx;
 	crtc_state = drm_atomic_get_crtc_state(state, crtc);
 	if (IS_ERR(crtc_state)) {
 		ret = PTR_ERR(crtc_state);
@@ -160,6 +166,9 @@ static int drm_crtc_set_brightness(struct drm_crtc *crtc,
 
 fail:
 	drm_atomic_state_put(state);
+done:
+	DRM_MODESET_LOCK_ALL_END(crtc->dev, ctx, ret);
+
 	return ret;
 }
 
@@ -175,6 +184,8 @@ static int dcp_set_brightness(struct backlight_device *bd)
 	dcp->brightness.dac = calculate_dac(dcp, brightness);
 	dcp->brightness.update = true;
 
+	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
+
 	/*
 	 * Do not actively try to change brightness if no mode is set.
 	 * TODO: should this be reflected the in backlight's power property?
@@ -182,14 +193,13 @@ static int dcp_set_brightness(struct backlight_device *bd)
 	 *       drm integrated backlight handling
 	 */
 	if (!dcp->valid_mode)
-		goto out;
-
-	ret = drm_crtc_set_brightness(&dcp->crtc->base, &ctx);
+		return 0;
 
-out:
-	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
+	/* Wait 1 vblank cycle in the hope an atomic swap has already updated
+	 * the brightness */
+	msleep((1001 + 23) / 24); // 42ms for 23.976 fps
 
-	return ret;
+	return drm_crtc_set_brightness(dcp);
 }
 
 static const struct backlight_ops dcp_backlight_ops = {

From 77dfba358a42c105ccb7905793619cc508799fe4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 15 Jul 2023 18:59:10 +0200
Subject: [PATCH 0720/1027] drm/apple: Get rid of the piodma dummy driver

It's only needed to configure the display contoller's iommu to share
buffers between the DCP co-processor and the display controller.
Possible concern is runtime PM for it and its iommu. If we don't set it
up the power domain might never go to lower power states even if it
could.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile       |  2 -
 drivers/gpu/drm/apple/apple_drv.c    | 23 ----------
 drivers/gpu/drm/apple/dcp.c          | 64 ++++++++++++++++++--------
 drivers/gpu/drm/apple/dummy-piodma.c | 68 ----------------------------
 4 files changed, 44 insertions(+), 113 deletions(-)
 delete mode 100644 drivers/gpu/drm/apple/dummy-piodma.c

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 67e0bae6caf004..910095e5839c35 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -9,11 +9,9 @@ apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
-apple_piodma-y := dummy-piodma.o
 
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
 obj-$(CONFIG_DRM_APPLE) += apple_dcp.o
-obj-$(CONFIG_DRM_APPLE) += apple_piodma.o
 
 # header test
 
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 20e6edc1fd9981..a5ac277d76684a 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -557,26 +557,6 @@ const struct component_master_ops apple_drm_ops = {
 	.unbind	= apple_drm_unbind,
 };
 
-static const struct of_device_id apple_component_id_tbl[] = {
-	{ .compatible = "apple,dcp-piodma" },
-	{},
-};
-
-static int add_display_components(struct device *dev,
-				  struct component_match **matchptr)
-{
-	struct device_node *np;
-
-	for_each_matching_node(np, apple_component_id_tbl) {
-		if (of_device_is_available(np))
-			drm_of_component_match_add(dev, matchptr,
-						   component_compare_of, np);
-		of_node_put(np);
-	}
-
-	return 0;
-}
-
 static int add_dcp_components(struct device *dev,
 			      struct component_match **matchptr)
 {
@@ -601,9 +581,6 @@ static int apple_platform_probe(struct platform_device *pdev)
 	struct component_match *match = NULL;
 	int num_dcp;
 
-	/* add PIODMA mapper components */
-	add_display_components(mdev, &match);
-
 	/* add DCP components, handle less than 1 as probe error */
 	num_dcp = add_dcp_components(mdev, &match);
 	if (num_dcp < 1)
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 3f5e8218dbf9ec..cb8514cfd3b67c 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -13,6 +13,7 @@
 #include <linux/module.h>
 #include <linux/moduleparam.h>
 #include <linux/of_device.h>
+#include <linux/of_platform.h>
 #include <linux/slab.h>
 #include <linux/soc/apple/rtkit.h>
 #include <linux/string.h>
@@ -316,17 +317,39 @@ static void dcp_work_register_backlight(struct work_struct *work)
 	mutex_unlock(&dcp->bl_register_mutex);
 }
 
-static struct platform_device *dcp_get_dev(struct device *dev, const char *name)
+static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp)
 {
-	struct platform_device *pdev;
-	struct device_node *node = of_parse_phandle(dev->of_node, name, 0);
+	int ret;
+	struct device_node *node = of_get_child_by_name(dcp->dev->of_node, "piodma");
 
 	if (!node)
-		return NULL;
+		return dev_err_probe(dcp->dev, -ENODEV,
+				     "Failed to get piodma child DT node\n");
+
+	dcp->piodma = of_platform_device_create(node, NULL, dcp->dev);
+	if (!dcp->piodma) {
+		of_node_put(node);
+		return dev_err_probe(dcp->dev, -ENODEV, "Failed to gcreate piodma pdev for %pOF\n", node);
+	}
+
+	ret = dma_set_mask_and_coherent(&dcp->piodma->dev, DMA_BIT_MASK(42));
+	if (ret)
+		goto err_destroy_pdev;
+
+	ret = of_dma_configure(&dcp->piodma->dev, node, true);
+	if (ret) {
+		ret = dev_err_probe(dcp->dev, ret,
+			"Failed to configure IOMMU child DMA\n");
+		goto err_destroy_pdev;
+	}
+	of_node_put(node);
 
-	pdev = of_find_device_by_node(node);
+	return 0;
+
+err_destroy_pdev:
 	of_node_put(node);
-	return pdev;
+	of_platform_device_destroy(&dcp->piodma->dev, NULL);
+	return ret;
 }
 
 static int dcp_get_disp_regs(struct apple_dcp *dcp)
@@ -432,8 +455,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (IS_ERR(dcp->coproc_reg))
 		return PTR_ERR(dcp->coproc_reg);
 
-	of_platform_default_populate(dev->of_node, NULL, dev);
-
 	if (!show_notch)
 		ret = of_property_read_u32(dev->of_node, "apple,notch-height",
 					   &dcp->notch_height);
@@ -479,16 +500,10 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	else
 		dcp->connector_type = DRM_MODE_CONNECTOR_Unknown;
 
-	/*
-	 * Components do not ensure the bind order of sub components but
-	 * the piodma device is only used for its iommu. The iommu is fully
-	 * initialized by the time dcp_piodma_probe() calls component_add().
-	 */
-	dcp->piodma = dcp_get_dev(dev, "apple,piodma-mapper");
-	if (!dcp->piodma) {
-		dev_err(dev, "failed to find piodma\n");
-		return -ENODEV;
-	}
+	ret = dcp_create_piodma_iommu_dev(dcp);
+	if (ret)
+		return dev_err_probe(dev, ret,
+				"Failed to created PIODMA iommu child device");
 
 	ret = dcp_get_disp_regs(dcp);
 	if (ret) {
@@ -545,8 +560,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 	if (dcp && dcp->shmem)
 		iomfb_shutdown(dcp);
 
-	platform_device_put(dcp->piodma);
-	dcp->piodma = NULL;
+	if (dcp->piodma) {
+		of_platform_device_destroy(&dcp->piodma->dev, NULL);
+		dcp->piodma = NULL;
+	}
 
 	devm_clk_put(dev, dcp->clk);
 	dcp->clk = NULL;
@@ -562,6 +579,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	enum dcp_firmware_version fw_compat;
 	struct device *dev = &pdev->dev;
 	struct apple_dcp *dcp;
+	int ret;
 
 	fw_compat = dcp_check_firmware_version(dev);
 	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
@@ -576,6 +594,12 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, dcp);
 
+	ret = devm_of_platform_populate(dev);
+	if (ret) {
+		dev_err(dev, "failed to populate child devices: %d\n", ret);
+		return ret;
+	}
+
 	return component_add(&pdev->dev, &dcp_comp_ops);
 }
 
diff --git a/drivers/gpu/drm/apple/dummy-piodma.c b/drivers/gpu/drm/apple/dummy-piodma.c
deleted file mode 100644
index eaa0476854a603..00000000000000
--- a/drivers/gpu/drm/apple/dummy-piodma.c
+++ /dev/null
@@ -1,68 +0,0 @@
-// SPDX-License-Identifier: GPL-2.0-only OR MIT
-/* Copyright 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
-
-#include <drm/drm_module.h>
-
-#include <linux/component.h>
-#include <linux/dma-mapping.h>
-#include <linux/module.h>
-#include <linux/of_device.h>
-
-static int dcp_piodma_comp_bind(struct device *dev, struct device *main,
-				void *data)
-{
-	return 0;
-}
-
-static void dcp_piodma_comp_unbind(struct device *dev, struct device *main,
-				   void *data)
-{
-	/* nothing to do */
-}
-
-static const struct component_ops dcp_piodma_comp_ops = {
-	.bind	= dcp_piodma_comp_bind,
-	.unbind	= dcp_piodma_comp_unbind,
-};
-static int dcp_piodma_probe(struct platform_device *pdev)
-{
-	int ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(42));
-	if (ret)
-		return ret;
-
-	return component_add(&pdev->dev, &dcp_piodma_comp_ops);
-}
-
-static int dcp_piodma_remove(struct platform_device *pdev)
-{
-	component_del(&pdev->dev, &dcp_piodma_comp_ops);
-
-	return 0;
-}
-
-static void dcp_piodma_shutdown(struct platform_device *pdev)
-{
-	component_del(&pdev->dev, &dcp_piodma_comp_ops);
-}
-
-static const struct of_device_id of_match[] = {
-	{ .compatible = "apple,dcp-piodma" },
-	{}
-};
-MODULE_DEVICE_TABLE(of, of_match);
-
-static struct platform_driver dcp_piodma_platform_driver = {
-	.probe		= dcp_piodma_probe,
-	.remove		= dcp_piodma_remove,
-	.shutdown	= dcp_piodma_shutdown,
-	.driver	= {
-		.name = "apple,dcp-piodma",
-		.of_match_table	= of_match,
-	},
-};
-
-drm_module_platform_driver(dcp_piodma_platform_driver);
-
-MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
-MODULE_DESCRIPTION("[HACK] Apple DCP PIODMA shim");
-MODULE_LICENSE("Dual MIT/GPL");

From e9c79203baaffa2c8ba024e2bb983085d253aa19 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 19 Jul 2023 09:22:24 +0200
Subject: [PATCH 0721/1027] drm/apple: Use iommu domain for piodma maps

The current use of of dma_get_sgtable/dma_map_sgtable is deemed unsafe.
Replace it with an unmanaged iommu domain for the piodma iommu to map
the buffers.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   |  1 +
 drivers/gpu/drm/apple/dcp.c            | 18 +++++++++++-
 drivers/gpu/drm/apple/iomfb_template.c | 40 +++++++++++++-------------
 3 files changed, 38 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index e4857e16616b8c..8baf529f1463c7 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -97,6 +97,7 @@ struct dcp_panel {
 struct apple_dcp {
 	struct device *dev;
 	struct platform_device *piodma;
+	struct iommu_domain *iommu_dom;
 	struct apple_rtkit *rtk;
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index cb8514cfd3b67c..476e8765003e8b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -344,8 +344,22 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp)
 	}
 	of_node_put(node);
 
-	return 0;
+	dcp->iommu_dom = iommu_domain_alloc(&platform_bus_type);
+	if (!dcp->iommu_dom) {
+		ret = -ENOMEM;
+		goto err_destroy_pdev;
+	}
+
+	ret = iommu_attach_device(dcp->iommu_dom, &dcp->piodma->dev);
+	if (ret) {
+		ret = dev_err_probe(dcp->dev, ret,
+					"Failed to attach IOMMU child domain\n");
+		goto err_free_domain;
+	}
 
+	return 0;
+err_free_domain:
+	iommu_domain_free(dcp->iommu_dom);
 err_destroy_pdev:
 	of_node_put(node);
 	of_platform_device_destroy(&dcp->piodma->dev, NULL);
@@ -561,6 +575,8 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 		iomfb_shutdown(dcp);
 
 	if (dcp->piodma) {
+		iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev);
+		iommu_domain_free(dcp->iommu_dom);
 		of_platform_device_destroy(&dcp->piodma->dev, NULL);
 		dcp->piodma = NULL;
 	}
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index ec4d13f62822fa..8a4d28d370bf67 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -258,32 +258,33 @@ static void iomfbep_cb_set_fx_prop(struct apple_dcp *dcp, struct iomfb_set_fx_pr
  * Callback to map a buffer allocated with allocate_buf for PIODMA usage.
  * PIODMA is separate from the main DCP and uses own IOVA space on a dedicated
  * stream of the display DART, rather than the expected DCP DART.
- *
- * XXX: This relies on dma_get_sgtable in concert with dma_map_sgtable, which
- * is a "fundamentally unsafe" operation according to the docs. And yet
- * everyone does it...
  */
 static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
 						   struct dcp_map_buf_req *req)
 {
+	struct dcp_mem_descriptor *memdesc;
 	struct sg_table *map;
-	int ret;
+	ssize_t ret;
 
 	if (req->buffer >= ARRAY_SIZE(dcp->memdesc))
 		goto reject;
 
-	map = &dcp->memdesc[req->buffer].map;
+	memdesc = &dcp->memdesc[req->buffer];
+	map = &memdesc->map;
 
 	if (!map->sgl)
 		goto reject;
 
-	/* Use PIODMA device instead of DCP to map against the right IOMMU. */
-	ret = dma_map_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+	/* use the piodma iommu domain to map against the right IOMMU */
+	ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map,
+				IOMMU_READ | IOMMU_WRITE);
 
-	if (ret)
+	if (ret != memdesc->size) {
+		dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size);
 		goto reject;
+	}
 
-	return (struct dcp_map_buf_resp){ .dva = sg_dma_address(map->sgl) };
+	return (struct dcp_map_buf_resp){ .dva = memdesc->dva };
 
 reject:
 	dev_err(dcp->dev, "denying map of invalid buffer %llx for piodma\n",
@@ -294,8 +295,7 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
 static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 				  struct dcp_unmap_buf_resp *resp)
 {
-	struct sg_table *map;
-	dma_addr_t dma_addr;
+	struct dcp_mem_descriptor *memdesc;
 
 	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
 		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
@@ -303,24 +303,24 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 		return;
 	}
 
-	map = &dcp->memdesc[resp->buffer].map;
+	memdesc = &dcp->memdesc[resp->buffer];
 
-	if (!map->sgl) {
+	if (!memdesc->buf) {
 		dev_warn(dcp->dev,
 			 "unmap for non-mapped buffer %llu iova:0x%08llx",
 			 resp->buffer, resp->dva);
 		return;
 	}
 
-	dma_addr = sg_dma_address(map->sgl);
-	if (dma_addr != resp->dva) {
-		dev_warn(dcp->dev, "unmap buffer %llu address mismatch dma_addr:%llx dva:%llx",
-			 resp->buffer, dma_addr, resp->dva);
+	if (memdesc->dva != resp->dva) {
+		dev_warn(dcp->dev, "unmap buffer %llu address mismatch "
+			 "memdesc.dva:%llx dva:%llx", resp->buffer,
+			 memdesc->dva, resp->dva);
 		return;
 	}
 
-	/* Use PIODMA device instead of DCP to unmap from the right IOMMU. */
-	dma_unmap_sgtable(&dcp->piodma->dev, map, DMA_BIDIRECTIONAL, 0);
+	/* use the piodma iommu domain to unmap from the right IOMMU */
+	iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size);
 }
 
 /*

From f0aea3cdd4ed3008c06066bf6e534608e7076a94 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 20 Jul 2023 00:36:51 +0200
Subject: [PATCH 0722/1027] drm: apple: Align PIODMA buffers to SZ_16K

The iommu scatter table/list mapping can only map full iommu page size
extents. Just align the actual the allocation to the iommu page size.
This could be handled differently using DARTs subpage protection but
there's no easy way to integrate that.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 8a4d28d370bf67..76ebb2f0d7426b 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -279,7 +279,10 @@ static struct dcp_map_buf_resp dcpep_cb_map_piodma(struct apple_dcp *dcp,
 	ret = iommu_map_sgtable(dcp->iommu_dom, memdesc->dva, map,
 				IOMMU_READ | IOMMU_WRITE);
 
-	if (ret != memdesc->size) {
+	/* HACK: expect size to be 16K aligned since the iommu API only maps
+	 *       full pages
+	 */
+	if (ret < 0 || ret != ALIGN(memdesc->size, SZ_16K)) {
 		dev_err(dcp->dev, "iommu_map_sgtable() returned %zd instead of expected buffer size of %zu\n", ret, memdesc->size);
 		goto reject;
 	}
@@ -334,6 +337,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
 {
 	struct dcp_allocate_buffer_resp resp = { 0 };
 	struct dcp_mem_descriptor *memdesc;
+	size_t size;
 	u32 id;
 
 	resp.dva_size = ALIGN(req->size, 4096);
@@ -352,11 +356,13 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
 	memdesc = &dcp->memdesc[id];
 
 	memdesc->size = resp.dva_size;
-	memdesc->buf = dma_alloc_coherent(dcp->dev, memdesc->size,
+	/* HACK: align size to 16K since the iommu API only maps full pages */
+	size = ALIGN(resp.dva_size, SZ_16K);
+	memdesc->buf = dma_alloc_coherent(dcp->dev, size,
 					  &memdesc->dva, GFP_KERNEL);
 
 	dma_get_sgtable(dcp->dev, &memdesc->map, memdesc->buf, memdesc->dva,
-			memdesc->size);
+			size);
 	resp.dva = memdesc->dva;
 
 	return resp;

From 86f2b256dbe88aff8b671a3172a956736934e2c6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 23 Aug 2023 20:50:35 +0200
Subject: [PATCH 0723/1027] drm: apple: Add D129 allocate_bandwidth iomfb
 callback

Used on M2 Ultra During startup. Units are unclear.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.h          | 15 +++++++++++++++
 drivers/gpu/drm/apple/iomfb_template.c | 12 ++++++++++++
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  1 +
 3 files changed, 28 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index e8168338d374ef..e8d7fef09f9f86 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -127,6 +127,21 @@ struct dcp_component_types {
 	u8 types[7];
 } __packed;
 
+struct dcp_allocate_bandwidth_req {
+	u64 unk1;
+	u64 unk2;
+	u64 unk3;
+	u8 unk1_null;
+	u8 unk2_null;
+	u8 padding[8];
+} __packed;
+
+struct dcp_allocate_bandwidth_resp {
+	u64 unk1;
+	u64 unk2;
+	u32 ret;
+} __packed;
+
 struct dcp_rt_bandwidth {
 	u64 unk1;
 	u64 reg_scratch;
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 76ebb2f0d7426b..9c9011981f84ba 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -652,6 +652,16 @@ static bool dcpep_cb_boot_1(struct apple_dcp *dcp, int tag, void *out, void *in)
 	return false;
 }
 
+static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct apple_dcp *dcp,
+						struct dcp_allocate_bandwidth_req *req)
+{
+	return (struct dcp_allocate_bandwidth_resp){
+		.unk1 = req->unk1,
+		.unk2 = req->unk2,
+		.ret = 1,
+	};
+}
+
 static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 {
 	if (dcp->disp_registers[5] && dcp->disp_registers[6]) {
@@ -1057,6 +1067,8 @@ TRAMPOLINE_INOUT(trampoline_prop_chunk, dcpep_cb_prop_chunk,
 		 struct dcp_set_dcpav_prop_chunk_req, u8);
 TRAMPOLINE_INOUT(trampoline_prop_end, dcpep_cb_prop_end,
 		 struct dcp_set_dcpav_prop_end_req, u8);
+TRAMPOLINE_INOUT(trampoline_allocate_bandwidth, dcpep_cb_allocate_bandwidth,
+	       struct dcp_allocate_bandwidth_req, struct dcp_allocate_bandwidth_resp);
 TRAMPOLINE_OUT(trampoline_rt_bandwidth, dcpep_cb_rt_bandwidth,
 	       struct dcp_rt_bandwidth);
 TRAMPOLINE_INOUT(trampoline_set_frame_sync_props, dcpep_cb_set_frame_sync_props,
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index b82ed1f32e0e8e..8e45fca918c320 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -62,6 +62,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[126] = trampoline_prop_start,
 	[127] = trampoline_prop_chunk,
 	[128] = trampoline_prop_end,
+	[129] = trampoline_allocate_bandwidth,
 	[201] = trampoline_map_piodma,
 	[202] = trampoline_unmap_piodma,
 	[206] = iomfbep_cb_match_pmu_service_2,

From 5d60de3174bf078273db690aa4380ba99bcce997 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 4 Sep 2023 23:07:45 +0200
Subject: [PATCH 0724/1027] drm: apple: Update supported firmware versions to
 12.3 and 13.5

Removes support for all firmware versions which report as compatible to
13.3 except 13.5. This will be removed after m1n1 reports firmware 13.5
as "apple,firmware-compat" for a while.
The files with "v13_3" will be renamed at a later point to avoid
conflicts with development trees.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h |  2 +-
 drivers/gpu/drm/apple/dcp.c          | 14 ++++++++++++--
 drivers/gpu/drm/apple/iomfb.c        | 12 ++++++------
 3 files changed, 19 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 8baf529f1463c7..7d54d28913f331 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -21,7 +21,7 @@ struct apple_dcp;
 enum dcp_firmware_version {
 	DCP_FIRMWARE_UNKNOWN,
 	DCP_FIRMWARE_V_12_3,
-	DCP_FIRMWARE_V_13_3,
+	DCP_FIRMWARE_V_13_5,
 };
 
 enum {
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 476e8765003e8b..8f77470af019ab 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -445,8 +445,18 @@ static enum dcp_firmware_version dcp_check_firmware_version(struct device *dev)
 
 	if (strncmp(compat_str, "12.3.0", sizeof(compat_str)) == 0)
 		return DCP_FIRMWARE_V_12_3;
-	if (strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0)
-		return DCP_FIRMWARE_V_13_3;
+	/*
+	 * m1n1 reports firmware version 13.5 as compatible with 13.3. This is
+	 * only true for the iomfb endpoint. The interface for the dptx-port
+	 * endpoint changed between 13.3 and 13.5. The driver will only support
+	 * firmware 13.5. Check the actual firmware version for compat version
+	 * 13.3 until m1n1 reports 13.5 as "firmware-compat".
+	 */
+	else if ((strncmp(compat_str, "13.3.0", sizeof(compat_str)) == 0) &&
+		 (strncmp(fw_str, "13.5.0", sizeof(compat_str)) == 0))
+		return DCP_FIRMWARE_V_13_5;
+	else if (strncmp(compat_str, "13.5.0", sizeof(compat_str)) == 0)
+		return DCP_FIRMWARE_V_13_5;
 
 	dev_err(dev, "DCP firmware-compat %s (FW: %s) is not supported\n",
 		compat_str, fw_str);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 335fa804ee24aa..6e68d4eda8f491 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -225,7 +225,7 @@ void dcp_sleep(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_sleep_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_sleep_v13_3(dcp);
 		break;
 	default:
@@ -242,7 +242,7 @@ void dcp_poweron(struct platform_device *pdev)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_poweron_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_poweron_v13_3(dcp);
 		break;
 	default:
@@ -260,7 +260,7 @@ void dcp_poweroff(struct platform_device *pdev)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_poweroff_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_poweroff_v13_3(dcp);
 		break;
 	default:
@@ -505,7 +505,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_flush_v12_3(dcp, crtc, state);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_flush_v13_3(dcp, crtc, state);
 		break;
 	default:
@@ -521,7 +521,7 @@ static void iomfb_start(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_start_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_start_v13_3(dcp);
 		break;
 	default:
@@ -574,7 +574,7 @@ void iomfb_shutdown(struct apple_dcp *dcp)
 	case DCP_FIRMWARE_V_12_3:
 		iomfb_shutdown_v12_3(dcp);
 		break;
-	case DCP_FIRMWARE_V_13_3:
+	case DCP_FIRMWARE_V_13_5:
 		iomfb_shutdown_v13_3(dcp);
 		break;
 	default:

From c07f62a277c764b1cc0bafb537f56801d60cd6fc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 7 Nov 2023 00:14:55 +0100
Subject: [PATCH 0725/1027] drm: apple: dcp: Port over to
 DEFINE_SIMPLE_DEV_PM_OPS

Avoids ugly "__maybe_unused".

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 8f77470af019ab..9420ab2a41b626 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -639,7 +639,7 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 	component_del(&pdev->dev, &dcp_comp_ops);
 }
 
-static __maybe_unused int dcp_platform_suspend(struct device *dev)
+static int dcp_platform_suspend(struct device *dev)
 {
 	/*
 	 * Set the device as a wakeup device, which forces its power
@@ -651,13 +651,13 @@ static __maybe_unused int dcp_platform_suspend(struct device *dev)
 	return 0;
 }
 
-static __maybe_unused int dcp_platform_resume(struct device *dev)
+static int dcp_platform_resume(struct device *dev)
 {
 	return 0;
 }
 
-static SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops,
-			 dcp_platform_suspend, dcp_platform_resume);
+static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops,
+				dcp_platform_suspend, dcp_platform_resume);
 
 static const struct of_device_id of_match[] = {
 	{ .compatible = "apple,dcp" },

From a5feec0fbab2f0d74fbc89dc7a54f8b38171981f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 7 Nov 2023 00:30:54 +0100
Subject: [PATCH 0726/1027] drm: apple: dcp: Remove cargo-culted
 devm_of_platform_populate

It does not do anything for dcp and its iommu only child node.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 7 -------
 1 file changed, 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9420ab2a41b626..e7e0cdf8e03703 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -605,7 +605,6 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	enum dcp_firmware_version fw_compat;
 	struct device *dev = &pdev->dev;
 	struct apple_dcp *dcp;
-	int ret;
 
 	fw_compat = dcp_check_firmware_version(dev);
 	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
@@ -620,12 +619,6 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	platform_set_drvdata(pdev, dcp);
 
-	ret = devm_of_platform_populate(dev);
-	if (ret) {
-		dev_err(dev, "failed to populate child devices: %d\n", ret);
-		return ret;
-	}
-
 	return component_add(&pdev->dev, &dcp_comp_ops);
 }
 

From ba219c9ea22dce105111da2082c2f641b394914e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 16:26:20 +0200
Subject: [PATCH 0727/1027] drm: apple: iomfb: implement abort_swaps_dcp

To match macOS behavior and in the hope to fix dcpext crashes on t8112.
Crashes still occur but let's keep this. Shouldn;t make a difference
since we're on the swaps to finish.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.h          | 20 ++++++++++++++++
 drivers/gpu/drm/apple/iomfb_template.c | 32 ++++++++++++++++++++++----
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  1 +
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  1 +
 4 files changed, 49 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index e8d7fef09f9f86..5d54a59c7ced45 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -189,6 +189,7 @@ enum dcpep_method {
 	iomfbep_a358_vi_set_temperature_hint,
 	iomfbep_get_color_remap_mode,
 	iomfbep_last_client_close,
+	iomfbep_abort_swaps_dcp,
 	iomfbep_set_matrix,
 	dcpep_num_methods
 };
@@ -380,6 +381,25 @@ struct iomfb_last_client_close_resp {
 	u32 unkint;
 } __packed;
 
+struct io_user_client {
+	u64 addr;
+	u32 unk;
+	u8 flag1;
+	u8 flag2;
+	u8 pad[2];
+} __packed;
+
+struct iomfb_abort_swaps_dcp_req {
+	struct io_user_client client;
+	u8 client_null;
+	u8 pad[3];
+} __packed;
+
+struct iomfb_abort_swaps_dcp_resp {
+	struct io_user_client client;
+	u32 ret;
+} __packed;
+
 struct iomfb_set_matrix_req {
 	u32 unk_u32; // maybe length?
 	u64 r[3];
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 9c9011981f84ba..3aedd2fd4a1140 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -59,6 +59,7 @@ DCP_THUNK_OUT(iomfb_a358_vi_set_temperature_hint, iomfbep_a358_vi_set_temperatur
 IOMFB_THUNK_INOUT(set_matrix);
 IOMFB_THUNK_INOUT(get_color_remap_mode);
 IOMFB_THUNK_INOUT(last_client_close);
+IOMFB_THUNK_INOUT(abort_swaps_dcp);
 
 DCP_THUNK_INOUT(dcp_swap_submit, dcpep_swap_submit,
 		struct DCP_FW_NAME(dcp_swap_submit_req),
@@ -859,10 +860,21 @@ static void last_client_closed_poff(struct apple_dcp *dcp, void *out, void *cook
 			    cookie);
 }
 
+static void aborted_swaps_dcp_poff(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct iomfb_last_client_close_req last_client_req = {};
+	iomfb_last_client_close(dcp, false, &last_client_req,
+				last_client_closed_poff, cookie);
+}
+
 void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 {
 	int ret, swap_id;
-	struct iomfb_last_client_close_req last_client_req = {};
+	struct iomfb_abort_swaps_dcp_req abort_req = {
+		.client = {
+			.flag2 = 1,
+		},
+	};
 	struct dcp_swap_cookie *cookie;
 	struct dcp_wait_cookie *poff_cookie;
 	struct dcp_swap_start_req swap_req = { 0 };
@@ -927,8 +939,8 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 	/* increase refcount to ensure the receiver has a reference */
 	kref_get(&poff_cookie->refcount);
 
-	iomfb_last_client_close(dcp, false, &last_client_req,
-				last_client_closed_poff, poff_cookie);
+	iomfb_abort_swaps_dcp(dcp, false, &abort_req,
+				aborted_swaps_dcp_poff, poff_cookie);
 	ret = wait_for_completion_timeout(&poff_cookie->done,
 					  msecs_to_jiffies(1000));
 
@@ -953,10 +965,20 @@ static void last_client_closed_sleep(struct apple_dcp *dcp, void *out, void *coo
 	dcp_set_power_state(dcp, false, &power_req, complete_set_powerstate, cookie);
 }
 
+static void aborted_swaps_dcp_sleep(struct apple_dcp *dcp, void *out, void *cookie)
+{
+	struct iomfb_last_client_close_req req = { 0 };
+	iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep, cookie);
+}
+
 void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
 {
 	int ret;
-	struct iomfb_last_client_close_req req = {};
+	struct iomfb_abort_swaps_dcp_req req = {
+		.client = {
+			.flag2 = 1,
+		},
+	};
 
 	struct dcp_wait_cookie *cookie;
 
@@ -968,7 +990,7 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
 	/* increase refcount to ensure the receiver has a reference */
 	kref_get(&cookie->refcount);
 
-	iomfb_last_client_close(dcp, false, &req, last_client_closed_sleep,
+	iomfb_abort_swaps_dcp(dcp, false, &req, aborted_swaps_dcp_sleep,
 				cookie);
 	ret = wait_for_completion_timeout(&cookie->done,
 					  msecs_to_jiffies(1000));
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index 5bc8bc2f8bd290..abcd1e4aab3ff8 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	IOMFB_METHOD("A455", iomfbep_last_client_close),
 	IOMFB_METHOD("A460", dcpep_set_display_refresh_properties),
 	IOMFB_METHOD("A463", dcpep_flush_supports_power),
+	IOMFB_METHOD("A464", iomfbep_abort_swaps_dcp),
 	IOMFB_METHOD("A468", dcpep_set_power_state),
 };
 
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 8e45fca918c320..9c692ba3c81b92 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -27,6 +27,7 @@ static const struct dcp_method_entry dcp_methods[dcpep_num_methods] = {
 	IOMFB_METHOD("A457", iomfbep_last_client_close),
 	IOMFB_METHOD("A463", dcpep_set_display_refresh_properties),
 	IOMFB_METHOD("A466", dcpep_flush_supports_power),
+	IOMFB_METHOD("A467", iomfbep_abort_swaps_dcp),
 	IOMFB_METHOD("A472", dcpep_set_power_state),
 };
 

From 553a5ddfdde2ced435e26a0dfb3ffda349173c28 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 23:05:41 +0100
Subject: [PATCH 0728/1027] drm: apple: iomfb: Increase modeset tiemout to 8.5
 seconds

DCP itself uses with the 13.5 firmware a timeout of 8 seconds for
modesets. Using a longer timeout prevents overlapping calls to dcp and
might improve reliabilty with slower displays.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 3aedd2fd4a1140..64fe703061fe7d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1330,9 +1330,14 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
 					 complete_set_digital_out_mode, cookie);
 
+		/*
+		 * The DCP firmware has an internal timeout of ~8 seconds for
+		 * modesets. Add an extra 500ms to safe side that the modeset
+		 * call has returned.
+		 */
 		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
 		ret = wait_for_completion_timeout(&cookie->done,
-						  msecs_to_jiffies(2500));
+						  msecs_to_jiffies(8500));
 
 		kref_put(&cookie->refcount, release_wait_cookie);
 

From 6043ba1633975ab365efc7d06b41122bb423090c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 21:13:29 +0100
Subject: [PATCH 0729/1027] drm: apple: Remove explicit asc-dram-mask handling

This is no longer necessary after introducing "apple,dma-range" for the
dart driver.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h |  3 ---
 drivers/gpu/drm/apple/dcp.c          | 14 ++------------
 drivers/gpu/drm/apple/iomfb.c        |  1 -
 3 files changed, 2 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 7d54d28913f331..76c0cf5fae31f4 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -108,9 +108,6 @@ struct apple_dcp {
 	/* Coprocessor control register */
 	void __iomem *coproc_reg;
 
-	/* mask for DCP IO virtual addresses shared over rtkit */
-	u64 asc_dram_mask;
-
 	/* DCP has crashed */
 	bool crashed;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index e7e0cdf8e03703..6297e858d4c9f9 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -145,8 +145,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 			return -ENOMEM;
 
 		// TODO: get map from device-tree
-		phy_addr = iommu_iova_to_phys(domain,
-					      bfr->iova & ~dcp->asc_dram_mask);
+		phy_addr = iommu_iova_to_phys(domain, bfr->iova);
 		if (!phy_addr)
 			return -ENOMEM;
 
@@ -166,8 +165,6 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 		if (!bfr->buffer)
 			return -ENOMEM;
 
-		bfr->iova |= dcp->asc_dram_mask;
-
 		dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx",
 			 (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer);
 	}
@@ -182,8 +179,7 @@ static void dcp_rtk_shmem_destroy(void *cookie, struct apple_rtkit_shmem *bfr)
 	if (bfr->is_mapped)
 		memunmap(bfr->buffer);
 	else
-		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer,
-				  bfr->iova & ~dcp->asc_dram_mask);
+		dma_free_coherent(dcp->dev, bfr->size, bfr->buffer, bfr->iova);
 }
 
 static struct apple_rtkit_ops rtkit_ops = {
@@ -540,12 +536,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 		return dev_err_probe(dev, PTR_ERR(dcp->clk),
 				     "Unable to find clock\n");
 
-	ret = of_property_read_u64(dev->of_node, "apple,asc-dram-mask",
-				   &dcp->asc_dram_mask);
-	if (ret)
-		dev_warn(dev, "failed read 'apple,asc-dram-mask': %d\n", ret);
-	dev_dbg(dev, "'apple,asc-dram-mask': 0x%011llx\n", dcp->asc_dram_mask);
-
 	bitmap_zero(dcp->memdesc_map, DCP_MAX_MAPPINGS);
 	// TDOD: mem_desc IDs start at 1, for simplicity just skip '0' entry
 	set_bit(0, dcp->memdesc_map);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 6e68d4eda8f491..70bfcdf4a96bc8 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -558,7 +558,6 @@ int iomfb_start_rtkit(struct apple_dcp *dcp)
 	dcp->shmem = dma_alloc_coherent(dcp->dev, DCP_SHMEM_SIZE, &shmem_iova,
 					GFP_KERNEL);
 
-	shmem_iova |= dcp->asc_dram_mask;
 	dcp_send_message(dcp, IOMFB_ENDPOINT, dcpep_set_shmem(shmem_iova));
 
 	return 0;

From 1cc042bd0d596ad6e2e47d085cee098bbf4ea00c Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Sat, 5 Nov 2022 13:15:33 +0100
Subject: [PATCH 0730/1027] mux: apple DP xbar: Add Apple silicon DisplayPort
 crossbar

This drivers adds support for the display crossbar used to route
display controller streams to the three different modes
(DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports.

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/mux/Kconfig                  |  13 ++
 drivers/mux/Makefile                 |   2 +
 drivers/mux/apple-display-crossbar.c | 305 +++++++++++++++++++++++++++
 3 files changed, 320 insertions(+)
 create mode 100644 drivers/mux/apple-display-crossbar.c

diff --git a/drivers/mux/Kconfig b/drivers/mux/Kconfig
index 80f015cf6e54f6..c0f62ae4c8047f 100644
--- a/drivers/mux/Kconfig
+++ b/drivers/mux/Kconfig
@@ -31,6 +31,19 @@ config MUX_ADGS1408
 	  To compile the driver as a module, choose M here: the module will
 	  be called mux-adgs1408.
 
+config MUX_APPLE_DPXBAR
+	tristate "Apple Silicon Display Crossbar"
+	depends on ARCH_APPLE
+	help
+	  Apple Silicon Display Crossbar multiplexer.
+
+	  This drivers adds support for the display crossbar used to route
+	  display controller streams to the three different modes
+	  (DP AltMode, USB4 Tunnel #0/#1) of the Type-C ports.
+
+	  To compile this driver as a module, chose M here: the module will be
+	  called mux-apple-display-crossbar.
+
 config MUX_GPIO
 	tristate "GPIO-controlled Multiplexer"
 	depends on GPIOLIB || COMPILE_TEST
diff --git a/drivers/mux/Makefile b/drivers/mux/Makefile
index 6e9fa47daf5663..7b5b3325068010 100644
--- a/drivers/mux/Makefile
+++ b/drivers/mux/Makefile
@@ -8,9 +8,11 @@ mux-adg792a-objs		:= adg792a.o
 mux-adgs1408-objs		:= adgs1408.o
 mux-gpio-objs			:= gpio.o
 mux-mmio-objs			:= mmio.o
+mux-apple-display-crossbar-objs	:= apple-display-crossbar.o
 
 obj-$(CONFIG_MULTIPLEXER)	+= mux-core.o
 obj-$(CONFIG_MUX_ADG792A)	+= mux-adg792a.o
 obj-$(CONFIG_MUX_ADGS1408)	+= mux-adgs1408.o
+obj-$(CONFIG_MUX_APPLE_DPXBAR)	+= mux-apple-display-crossbar.o
 obj-$(CONFIG_MUX_GPIO)		+= mux-gpio.o
 obj-$(CONFIG_MUX_MMIO)		+= mux-mmio.o
diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
new file mode 100644
index 00000000000000..a241cba718c842
--- /dev/null
+++ b/drivers/mux/apple-display-crossbar.c
@@ -0,0 +1,305 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * Apple Silicon Display Crossbar multiplexer driver
+ *
+ * Copyright (C) Asahi Linux Contributors
+ *
+ * Author: Sven Peter <sven@svenpeter.dev>
+ */
+
+#include <linux/bitmap.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/mux/driver.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+#define FIFO_WR_DPTX_CLK_EN 0x000
+#define FIFO_WR_N_CLK_EN 0x004
+#define FIFO_WR_UNK_EN 0x008
+#define FIFO_RD_PCLK1_EN 0x020
+#define FIFO_RD_PCLK2_EN 0x024
+#define FIFO_RD_N_CLK_EN 0x028
+#define FIFO_RD_UNK_EN 0x02c
+
+#define OUT_PCLK1_EN 0x040
+#define OUT_PCLK2_EN 0x044
+#define OUT_N_CLK_EN 0x048
+#define OUT_UNK_EN 0x04c
+
+#define CROSSBAR_DISPEXT_EN 0x050
+#define CROSSBAR_MUX_CTRL 0x060
+#define CROSSBAR_MUX_CTRL_DPPHY_SELECT0 GENMASK(23, 20)
+#define CROSSBAR_MUX_CTRL_DPIN1_SELECT0 GENMASK(19, 16)
+#define CROSSBAR_MUX_CTRL_DPIN0_SELECT0 GENMASK(15, 12)
+#define CROSSBAR_MUX_CTRL_DPPHY_SELECT1 GENMASK(11, 8)
+#define CROSSBAR_MUX_CTRL_DPIN1_SELECT1 GENMASK(7, 4)
+#define CROSSBAR_MUX_CTRL_DPIN0_SELECT1 GENMASK(3, 0)
+#define CROSSBAR_ATC_EN 0x070
+
+#define FIFO_WR_DPTX_CLK_EN_STAT 0x800
+#define FIFO_WR_N_CLK_EN_STAT 0x804
+#define FIFO_RD_PCLK1_EN_STAT 0x820
+#define FIFO_RD_PCLK2_EN_STAT 0x824
+#define FIFO_RD_N_CLK_EN_STAT 0x828
+
+#define OUT_PCLK1_EN_STAT 0x840
+#define OUT_PCLK2_EN_STAT 0x844
+#define OUT_N_CLK_EN_STAT 0x848
+
+#define UNK_TUNABLE 0xc00
+
+#define ATC_DPIN0 BIT(0)
+#define ATC_DPIN1 BIT(4)
+#define ATC_DPPHY BIT(8)
+
+enum { MUX_DPPHY = 0, MUX_DPIN0 = 1, MUX_DPIN1 = 2, MUX_MAX = 3 };
+static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" };
+
+struct apple_dpxbar_hw {
+	unsigned int n_ufp;
+	u32 tunable;
+};
+
+struct apple_dpxbar {
+	struct device *dev;
+	void __iomem *regs;
+	int selected_dispext[MUX_MAX];
+	spinlock_t lock;
+};
+
+static inline void dpxbar_mask32(struct apple_dpxbar *xbar, u32 reg, u32 mask,
+				 u32 set)
+{
+	u32 value = readl(xbar->regs + reg);
+	value &= ~mask;
+	value |= set;
+	writel(value, xbar->regs + reg);
+}
+
+static inline void dpxbar_set32(struct apple_dpxbar *xbar, u32 reg, u32 set)
+{
+	dpxbar_mask32(xbar, reg, 0, set);
+}
+
+static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear)
+{
+	dpxbar_mask32(xbar, reg, clear, 0);
+}
+
+static int apple_dpxbar_set(struct mux_control *mux, int state)
+{
+	struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip);
+	unsigned int index = mux_control_get_index(mux);
+	unsigned long flags;
+	unsigned int mux_state;
+	unsigned int dispext_bit;
+	unsigned int atc_bit;
+	bool enable;
+	int ret = 0;
+	u32 mux_mask, mux_set;
+
+	if (state == MUX_IDLE_DISCONNECT) {
+		/*
+		 * Technically this will select dispext0,0 in the mux control
+		 * register. Practically that doesn't matter since everything
+		 * else is disabled.
+		 */
+		mux_state = 0;
+		enable = false;
+	} else if (state >= 0 && state < 9) {
+		dispext_bit = 1 << state;
+		mux_state = state;
+		enable = true;
+	} else {
+		return -EINVAL;
+	}
+
+	switch (index) {
+	case MUX_DPPHY:
+		mux_mask = CROSSBAR_MUX_CTRL_DPPHY_SELECT0 |
+			   CROSSBAR_MUX_CTRL_DPPHY_SELECT1;
+		mux_set =
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT0, mux_state) |
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPPHY_SELECT1, mux_state);
+		atc_bit = ATC_DPPHY;
+		break;
+	case MUX_DPIN0:
+		mux_mask = CROSSBAR_MUX_CTRL_DPIN0_SELECT0 |
+			   CROSSBAR_MUX_CTRL_DPIN0_SELECT1;
+		mux_set =
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT0, mux_state) |
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN0_SELECT1, mux_state);
+		atc_bit = ATC_DPIN0;
+		break;
+	case MUX_DPIN1:
+		mux_mask = CROSSBAR_MUX_CTRL_DPIN1_SELECT0 |
+			   CROSSBAR_MUX_CTRL_DPIN1_SELECT1;
+		mux_set =
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT0, mux_state) |
+			FIELD_PREP(CROSSBAR_MUX_CTRL_DPIN1_SELECT1, mux_state);
+		atc_bit = ATC_DPIN1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&dpxbar->lock, flags);
+
+	/* ensure the selected dispext isn't already used in this crossbar */
+	if (enable) {
+		for (int i = 0; i < MUX_MAX; ++i) {
+			if (i == index)
+				continue;
+			if (dpxbar->selected_dispext[i] == state) {
+				spin_unlock_irqrestore(&dpxbar->lock, flags);
+				return -EBUSY;
+			}
+		}
+	}
+
+	dpxbar_set32(dpxbar, OUT_N_CLK_EN, atc_bit);
+	dpxbar_clear32(dpxbar, OUT_UNK_EN, atc_bit);
+	dpxbar_clear32(dpxbar, OUT_PCLK1_EN, atc_bit);
+	dpxbar_clear32(dpxbar, CROSSBAR_ATC_EN, atc_bit);
+
+	if (dpxbar->selected_dispext[index] >= 0) {
+		u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index];
+
+		dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit);
+		dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit);
+
+		dpxbar->selected_dispext[index] = -1;
+	}
+
+	dpxbar_mask32(dpxbar, CROSSBAR_MUX_CTRL, mux_mask, mux_set);
+
+	if (enable) {
+		dpxbar_clear32(dpxbar, FIFO_WR_N_CLK_EN, dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit);
+		dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit);
+		dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit);
+		dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
+		dpxbar_set32(dpxbar, OUT_PCLK1_EN, atc_bit);
+		dpxbar_set32(dpxbar, CROSSBAR_ATC_EN, atc_bit);
+		dpxbar_set32(dpxbar, CROSSBAR_DISPEXT_EN, dispext_bit);
+
+		/*
+		 * Work around some HW quirk:
+		 * Without toggling the RD_PCLK enable here the connection
+		 * doesn't come up. Testing has shown that a delay of about
+		 * 5 usec is required which is doubled here to be on the
+		 * safe side.
+		 */
+		dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
+		udelay(10);
+		dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);
+
+		dpxbar->selected_dispext[index] = state;
+	}
+
+	spin_unlock_irqrestore(&dpxbar->lock, flags);
+
+	if (enable)
+		dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n",
+			 apple_dpxbar_names[index], mux_state >> 1,
+			 mux_state & 1);
+	else
+		dev_info(dpxbar->dev, "Switched %s to disconnected state\n",
+			 apple_dpxbar_names[index]);
+
+	return ret;
+}
+
+static const struct mux_control_ops apple_dpxbar_ops = {
+	.set = apple_dpxbar_set,
+};
+
+static int apple_dpxbar_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct mux_chip *mux_chip;
+	struct apple_dpxbar *dpxbar;
+	const struct apple_dpxbar_hw *hw;
+	int ret;
+
+	hw = of_device_get_match_data(dev);
+	mux_chip = devm_mux_chip_alloc(dev, MUX_MAX, sizeof(*dpxbar));
+	if (IS_ERR(mux_chip))
+		return PTR_ERR(mux_chip);
+
+	dpxbar = mux_chip_priv(mux_chip);
+	mux_chip->ops = &apple_dpxbar_ops;
+	spin_lock_init(&dpxbar->lock);
+
+	dpxbar->dev = dev;
+	dpxbar->regs = devm_platform_ioremap_resource(pdev, 0);
+	if (IS_ERR(dpxbar->regs))
+		return PTR_ERR(dpxbar->regs);
+
+	writel(hw->tunable, dpxbar->regs + UNK_TUNABLE);
+
+	for (unsigned int i = 0; i < MUX_MAX; ++i) {
+		mux_chip->mux[i].states = hw->n_ufp;
+		mux_chip->mux[i].idle_state = MUX_IDLE_DISCONNECT;
+		dpxbar->selected_dispext[i] = -1;
+	}
+
+	ret = devm_mux_chip_register(dev, mux_chip);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
+	.n_ufp = 2,
+	.tunable = 0,
+};
+
+const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
+	.n_ufp = 9,
+	.tunable = 5,
+};
+
+static const struct of_device_id apple_dpxbar_ids[] = {
+	{
+		.compatible = "apple,t8103-display-crossbar",
+		.data = &apple_dpxbar_hw_t8103,
+	},
+	{
+		.compatible = "apple,t8112-display-crossbar",
+		.data = &apple_dpxbar_hw_t8103,
+	},
+	{
+		.compatible = "apple,t6000-display-crossbar",
+		.data = &apple_dpxbar_hw_t6000,
+	},
+	{}
+};
+MODULE_DEVICE_TABLE(of, apple_dpxbar_ids);
+
+static struct platform_driver apple_dpxbar_driver = {
+	.driver = {
+		.name = "apple-display-crossbar",
+		.of_match_table	= apple_dpxbar_ids,
+	},
+	.probe = apple_dpxbar_probe,
+};
+module_platform_driver(apple_dpxbar_driver);
+
+MODULE_DESCRIPTION("Apple Silicon display crossbar multiplexer driver");
+MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>");
+MODULE_LICENSE("GPL v2");

From 6da696874f944206913074aae33e6678f4f32e3f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 15:04:35 +0200
Subject: [PATCH 0731/1027] mux: apple dp crossbar: Support t8112 varient

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/mux/apple-display-crossbar.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index a241cba718c842..0801c12949e394 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -269,6 +269,11 @@ const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
 	.tunable = 0,
 };
 
+const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = {
+	.n_ufp = 4,
+	.tunable = 4278196325,
+};
+
 const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
 	.n_ufp = 9,
 	.tunable = 5,
@@ -281,7 +286,7 @@ static const struct of_device_id apple_dpxbar_ids[] = {
 	},
 	{
 		.compatible = "apple,t8112-display-crossbar",
-		.data = &apple_dpxbar_hw_t8103,
+		.data = &apple_dpxbar_hw_t8112,
 	},
 	{
 		.compatible = "apple,t6000-display-crossbar",

From e41582644a8aa13ffaa716e87a13e0b71cfe88ad Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 15:05:36 +0200
Subject: [PATCH 0732/1027] mux: apple dp crossbar: FIFO_RD_UNK_EN seems to use
 2 bits per dispext*

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/mux/apple-display-crossbar.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index 0801c12949e394..8901ad2b1b2d3b 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -98,6 +98,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state)
 	unsigned long flags;
 	unsigned int mux_state;
 	unsigned int dispext_bit;
+	unsigned int dispext_bit_en;
 	unsigned int atc_bit;
 	bool enable;
 	int ret = 0;
@@ -113,6 +114,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state)
 		enable = false;
 	} else if (state >= 0 && state < 9) {
 		dispext_bit = 1 << state;
+		dispext_bit_en = 1 << (2 * state);
 		mux_state = state;
 		enable = true;
 	} else {
@@ -169,11 +171,12 @@ static int apple_dpxbar_set(struct mux_control *mux, int state)
 
 	if (dpxbar->selected_dispext[index] >= 0) {
 		u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index];
+		u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]);
 
 		dpxbar_set32(dpxbar, FIFO_WR_N_CLK_EN, prev_dispext_bit);
 		dpxbar_set32(dpxbar, FIFO_RD_N_CLK_EN, prev_dispext_bit);
 		dpxbar_clear32(dpxbar, FIFO_WR_UNK_EN, prev_dispext_bit);
-		dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, FIFO_RD_UNK_EN, prev_dispext_bit_en);
 		dpxbar_clear32(dpxbar, FIFO_WR_DPTX_CLK_EN, prev_dispext_bit);
 		dpxbar_clear32(dpxbar, FIFO_RD_PCLK1_EN, prev_dispext_bit);
 		dpxbar_clear32(dpxbar, CROSSBAR_DISPEXT_EN, prev_dispext_bit);
@@ -188,7 +191,7 @@ static int apple_dpxbar_set(struct mux_control *mux, int state)
 		dpxbar_clear32(dpxbar, FIFO_RD_N_CLK_EN, dispext_bit);
 		dpxbar_clear32(dpxbar, OUT_N_CLK_EN, atc_bit);
 		dpxbar_set32(dpxbar, FIFO_WR_UNK_EN, dispext_bit);
-		dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, FIFO_RD_UNK_EN, dispext_bit_en);
 		dpxbar_set32(dpxbar, OUT_UNK_EN, atc_bit);
 		dpxbar_set32(dpxbar, FIFO_WR_DPTX_CLK_EN, dispext_bit);
 		dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, dispext_bit);

From 6c302d067546c496881b445d2d8a7fbcd8bc66d3 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 30 Apr 2023 15:26:44 +0200
Subject: [PATCH 0733/1027] mux: apple dp crossbar: Read UNK_TUNABLE before and
 after writing it

Makes traces easier to compare with macOS.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/mux/apple-display-crossbar.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index 8901ad2b1b2d3b..6acd5a87bd7dbd 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -252,7 +252,9 @@ static int apple_dpxbar_probe(struct platform_device *pdev)
 	if (IS_ERR(dpxbar->regs))
 		return PTR_ERR(dpxbar->regs);
 
+	readl(dpxbar->regs + UNK_TUNABLE);
 	writel(hw->tunable, dpxbar->regs + UNK_TUNABLE);
+	readl(dpxbar->regs + UNK_TUNABLE);
 
 	for (unsigned int i = 0; i < MUX_MAX; ++i) {
 		mux_chip->mux[i].states = hw->n_ufp;

From 67d162cd659da254345747b0153aca130e6b2b68 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 23:00:08 +0200
Subject: [PATCH 0734/1027] mux: apple dp crossbar: Support t602x DP cross bar
 variant

This is a simplified version and probably should live in a separate
file. Even the shared registers are quite different.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/mux/apple-display-crossbar.c | 156 ++++++++++++++++++++++++++-
 1 file changed, 152 insertions(+), 4 deletions(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index 6acd5a87bd7dbd..cb172cf7815bb9 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -18,6 +18,32 @@
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
 
+/*
+ * T602x register interface is cleary different so most of the names below are
+ * probably wrong.
+ */
+
+#define T602X_FIFO_WR_DPTX_CLK_EN 0x000
+#define T602X_FIFO_WR_N_CLK_EN 0x004
+#define T602X_FIFO_WR_UNK_EN 0x008
+#define T602X_REG_00C 0x00c
+#define T602X_REG_014 0x014
+#define T602X_REG_018 0x018
+#define T602X_REG_01C 0x01c
+#define T602X_FIFO_RD_PCLK2_EN 0x024
+#define T602X_FIFO_RD_N_CLK_EN 0x028
+#define T602X_FIFO_RD_UNK_EN 0x02c
+#define T602X_REG_030 0x030
+#define T602X_REG_034 0x034
+
+#define T602X_REG_804_STAT 0x804 // status of 0x004
+#define T602X_REG_810_STAT 0x810 // status of 0x014
+#define T602X_REG_81C_STAT 0x81c // status of 0x024
+
+/**
+ * T8013, T600x, T8112 dp crossbar registers.
+ */
+
 #define FIFO_WR_DPTX_CLK_EN 0x000
 #define FIFO_WR_N_CLK_EN 0x004
 #define FIFO_WR_UNK_EN 0x008
@@ -63,6 +89,7 @@ static const char *apple_dpxbar_names[MUX_MAX] = { "dpphy", "dpin0", "dpin1" };
 struct apple_dpxbar_hw {
 	unsigned int n_ufp;
 	u32 tunable;
+	const struct mux_control_ops *ops;
 };
 
 struct apple_dpxbar {
@@ -91,6 +118,109 @@ static inline void dpxbar_clear32(struct apple_dpxbar *xbar, u32 reg, u32 clear)
 	dpxbar_mask32(xbar, reg, clear, 0);
 }
 
+static int apple_dpxbar_set_t602x(struct mux_control *mux, int state)
+{
+	struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip);
+	unsigned int index = mux_control_get_index(mux);
+	unsigned long flags;
+	unsigned int mux_state;
+	unsigned int dispext_bit;
+	unsigned int dispext_bit_en;
+	bool enable;
+	int ret = 0;
+
+	if (state == MUX_IDLE_DISCONNECT) {
+		/*
+		 * Technically this will select dispext0,0 in the mux control
+		 * register. Practically that doesn't matter since everything
+		 * else is disabled.
+		 */
+		mux_state = 0;
+		enable = false;
+	} else if (state >= 0 && state < 9) {
+		dispext_bit = 1 << state;
+		dispext_bit_en = 1 << (2 * state);
+		mux_state = state;
+		enable = true;
+	} else {
+		return -EINVAL;
+	}
+
+	spin_lock_irqsave(&dpxbar->lock, flags);
+
+	/* ensure the selected dispext isn't already used in this crossbar */
+	if (enable) {
+		for (int i = 0; i < MUX_MAX; ++i) {
+			if (i == index)
+				continue;
+			if (dpxbar->selected_dispext[i] == state) {
+				spin_unlock_irqrestore(&dpxbar->lock, flags);
+				return -EBUSY;
+			}
+		}
+	}
+
+	if (dpxbar->selected_dispext[index] >= 0) {
+		u32 prev_dispext_bit = 1 << dpxbar->selected_dispext[index];
+		u32 prev_dispext_bit_en = 1 << (2 * dpxbar->selected_dispext[index]);
+
+		dpxbar_clear32(dpxbar, T602X_FIFO_RD_UNK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, T602X_REG_00C, prev_dispext_bit_en);
+
+		dpxbar_clear32(dpxbar, T602X_REG_01C, 0x100);
+
+		dpxbar_clear32(dpxbar, T602X_FIFO_WR_UNK_EN, prev_dispext_bit);
+		dpxbar_clear32(dpxbar, T602X_REG_018, prev_dispext_bit_en);
+
+		dpxbar_clear32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100);
+
+		dpxbar_set32(dpxbar, T602X_FIFO_WR_N_CLK_EN, prev_dispext_bit);
+		dpxbar_set32(dpxbar, T602X_REG_014, 0x4);
+
+		dpxbar_set32(dpxbar, FIFO_RD_PCLK1_EN, 0x100);
+
+		dpxbar->selected_dispext[index] = -1;
+	}
+
+	if (enable) {
+		dpxbar_set32(dpxbar, T602X_REG_030, state << 20);
+		dpxbar_set32(dpxbar, T602X_REG_030, state << 8);
+		udelay(10);
+
+		dpxbar_clear32(dpxbar, T602X_FIFO_WR_N_CLK_EN, dispext_bit);
+		dpxbar_clear32(dpxbar, T602X_REG_014, 0x4);
+
+		dpxbar_clear32(dpxbar, T602X_FIFO_RD_PCLK2_EN, 0x100);
+
+		dpxbar_set32(dpxbar, T602X_FIFO_WR_UNK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, T602X_REG_018, dispext_bit_en);
+
+		dpxbar_set32(dpxbar, T602X_FIFO_RD_N_CLK_EN, 0x100);
+		dpxbar_set32(dpxbar, T602X_FIFO_WR_DPTX_CLK_EN, dispext_bit);
+		dpxbar_set32(dpxbar, T602X_REG_00C, dispext_bit);
+
+		dpxbar_set32(dpxbar, T602X_REG_01C, 0x100);
+		dpxbar_set32(dpxbar, T602X_REG_034, 0x100);
+
+		dpxbar_set32(dpxbar, T602X_FIFO_RD_UNK_EN, dispext_bit);
+
+		dpxbar->selected_dispext[index] = state;
+	}
+
+	spin_unlock_irqrestore(&dpxbar->lock, flags);
+
+	if (enable)
+		dev_info(dpxbar->dev, "Switched %s to dispext%u,%u\n",
+			 apple_dpxbar_names[index], mux_state >> 1,
+			 mux_state & 1);
+	else
+		dev_info(dpxbar->dev, "Switched %s to disconnected state\n",
+			 apple_dpxbar_names[index]);
+
+	return ret;
+}
+
 static int apple_dpxbar_set(struct mux_control *mux, int state)
 {
 	struct apple_dpxbar *dpxbar = mux_chip_priv(mux->chip);
@@ -230,6 +360,10 @@ static const struct mux_control_ops apple_dpxbar_ops = {
 	.set = apple_dpxbar_set,
 };
 
+static const struct mux_control_ops apple_dpxbar_t602x_ops = {
+	.set = apple_dpxbar_set_t602x,
+};
+
 static int apple_dpxbar_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
@@ -244,7 +378,7 @@ static int apple_dpxbar_probe(struct platform_device *pdev)
 		return PTR_ERR(mux_chip);
 
 	dpxbar = mux_chip_priv(mux_chip);
-	mux_chip->ops = &apple_dpxbar_ops;
+	mux_chip->ops = hw->ops;
 	spin_lock_init(&dpxbar->lock);
 
 	dpxbar->dev = dev;
@@ -252,9 +386,11 @@ static int apple_dpxbar_probe(struct platform_device *pdev)
 	if (IS_ERR(dpxbar->regs))
 		return PTR_ERR(dpxbar->regs);
 
-	readl(dpxbar->regs + UNK_TUNABLE);
-	writel(hw->tunable, dpxbar->regs + UNK_TUNABLE);
-	readl(dpxbar->regs + UNK_TUNABLE);
+	if (!of_device_is_compatible(dev->of_node, "apple,t6020-display-crossbar")) {
+		readl(dpxbar->regs + UNK_TUNABLE);
+		writel(hw->tunable, dpxbar->regs + UNK_TUNABLE);
+		readl(dpxbar->regs + UNK_TUNABLE);
+	}
 
 	for (unsigned int i = 0; i < MUX_MAX; ++i) {
 		mux_chip->mux[i].states = hw->n_ufp;
@@ -272,16 +408,24 @@ static int apple_dpxbar_probe(struct platform_device *pdev)
 const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
 	.n_ufp = 2,
 	.tunable = 0,
+	.ops = &apple_dpxbar_ops,
 };
 
 const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = {
 	.n_ufp = 4,
 	.tunable = 4278196325,
+	.ops = &apple_dpxbar_ops,
 };
 
 const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
 	.n_ufp = 9,
 	.tunable = 5,
+	.ops = &apple_dpxbar_ops,
+};
+
+const static struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = {
+	.n_ufp = 9,
+	.ops = &apple_dpxbar_t602x_ops,
 };
 
 static const struct of_device_id apple_dpxbar_ids[] = {
@@ -297,6 +441,10 @@ static const struct of_device_id apple_dpxbar_ids[] = {
 		.compatible = "apple,t6000-display-crossbar",
 		.data = &apple_dpxbar_hw_t6000,
 	},
+	{
+		.compatible = "apple,t6020-display-crossbar",
+		.data = &apple_dpxbar_hw_t6020,
+	},
 	{}
 };
 MODULE_DEVICE_TABLE(of, apple_dpxbar_ids);

From 242ab5e35aad83735ef37657868a91ab5438675f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 15 Feb 2023 16:20:22 +0100
Subject: [PATCH 0735/1027] gpu: drm: apple: Add utility functions for matching
 on dict keys
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/gpu/drm/apple/parser.c | 33 +++++++++++++++++++++++++++++++++
 1 file changed, 33 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 5665c2ba19682f..84a76440c6e7e2 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -117,6 +117,39 @@ static int skip(struct dcp_parse_ctx *handle)
 	}
 }
 
+static int skip_pair(struct dcp_parse_ctx *handle)
+{
+	int ret;
+
+	ret = skip(handle);
+	if (ret)
+		return ret;
+
+	return skip(handle);
+}
+
+static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen)
+{
+	struct dcp_parse_tag *tag;
+	const char *key;
+	ctx->pos = round_up(ctx->pos, 4);
+
+	if (ctx->pos + sizeof(*tag) + strlen(specimen) - 1 > ctx->len)
+		return false;
+	tag = ctx->blob + ctx->pos;
+	key = ctx->blob + ctx->pos + sizeof(*tag);
+	if (tag->padding)
+		return false;
+
+	if (tag->type != DCP_TYPE_STRING ||
+	    tag->size != strlen(specimen) ||
+	    strncmp(key, specimen, tag->size))
+		return false;
+
+	skip(ctx);
+	return true;
+}
+
 /* Caller must free the result */
 static char *parse_string(struct dcp_parse_ctx *handle)
 {

From 8a6b6500c9bfeedd9fa0a230f60d9ed86e1254c1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 23 Feb 2023 13:07:49 +0100
Subject: [PATCH 0736/1027] gpu: drm: apple: Add 'parse_blob'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/gpu/drm/apple/parser.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 84a76440c6e7e2..4f16e5505c3367 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -199,6 +199,26 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b)
 	return 0;
 }
 
+static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob)
+{
+	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB);
+	u8 *out;
+
+	if (IS_ERR(tag))
+		return PTR_ERR(tag);
+
+	if (tag->size < size)
+		return -EINVAL;
+
+	out = parse_bytes(handle, tag->size);
+
+	if (IS_ERR(out))
+		return PTR_ERR(out);
+
+	*blob = out;
+	return 0;
+}
+
 struct iterator {
 	struct dcp_parse_ctx *handle;
 	u32 idx, len;

From a01e3eb3d9c6225fe342c617f27ebf7ab7dbb7fc Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Wed, 15 Feb 2023 16:22:17 +0100
Subject: [PATCH 0737/1027] gpu: drm: apple: Add sound mode parsing
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/gpu/drm/apple/dcp-internal.h |   2 +
 drivers/gpu/drm/apple/parser.c       | 306 +++++++++++++++++++++++++++
 drivers/gpu/drm/apple/parser.h       |  20 ++
 drivers/gpu/drm/apple/trace.h        |  23 ++
 4 files changed, 351 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 76c0cf5fae31f4..c04585c3374991 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -195,4 +195,6 @@ struct apple_dcp {
 int dcp_backlight_register(struct apple_dcp *dcp);
 bool dcp_has_panel(struct apple_dcp *dcp);
 
+#define DCP_AUDIO_MAX_CHANS 15
+
 #endif /* __APPLE_DCP_INTERNAL_H__ */
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 4f16e5505c3367..50a4fc31cd06ce 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -7,6 +7,8 @@
 #include <linux/string.h>
 #include <linux/slab.h>
 
+#include <sound/pcm.h> // for sound format masks
+
 #include "parser.h"
 #include "trace.h"
 
@@ -586,3 +588,307 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 
 	return 0;
 }
+
+int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
+{
+	s64 rate;
+	int ret = parse_int(handle, &rate);
+
+	if (ret)
+		return ret;
+
+	*ratebit = snd_pcm_rate_to_rate_bit(rate);
+	if (*ratebit == SNDRV_PCM_RATE_KNOT) {
+		/*
+		 * The rate wasn't recognized, and unless we supply
+		 * a supplementary constraint, the SNDRV_PCM_RATE_KNOT bit
+		 * will allow any rate. So clear it.
+		 */
+		*ratebit = 0;
+	}
+
+	return 0;
+}
+
+int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit)
+{
+	s64 sample_size;
+	int ret = parse_int(handle, &sample_size);
+
+	if (ret)
+		return ret;
+
+	switch (sample_size) {
+	case 16:
+		*fmtbit = SNDRV_PCM_FMTBIT_S16;
+		break;
+	case 20:
+		*fmtbit = SNDRV_PCM_FMTBIT_S20;
+		break;
+	case 24:
+		*fmtbit = SNDRV_PCM_FMTBIT_S24;
+		break;
+	case 32:
+		*fmtbit = SNDRV_PCM_FMTBIT_S32;
+		break;
+	default:
+		*fmtbit = 0;
+		break;
+	}
+
+	return 0;
+}
+
+static struct {
+	const char *label;
+	u8 type;
+} chan_position_names[] = {
+	{ "Front Left", SNDRV_CHMAP_FL },
+	{ "Front Right", SNDRV_CHMAP_FR },
+	{ "Rear Left", SNDRV_CHMAP_RL },
+	{ "Rear Right", SNDRV_CHMAP_RR },
+	{ "Front Center", SNDRV_CHMAP_FC },
+	{ "Low Frequency Effects", SNDRV_CHMAP_LFE },
+	{ "Rear Center", SNDRV_CHMAP_RC },
+	{ "Front Left Center", SNDRV_CHMAP_FLC },
+	{ "Front Right Center", SNDRV_CHMAP_FRC },
+	{ "Rear Left Center", SNDRV_CHMAP_RLC },
+	{ "Rear Right Center", SNDRV_CHMAP_RRC },
+	{ "Front Left Wide", SNDRV_CHMAP_FLW },
+	{ "Front Right Wide", SNDRV_CHMAP_FRW },
+	{ "Front Left High", SNDRV_CHMAP_FLH },
+	{ "Front Center High", SNDRV_CHMAP_FCH },
+	{ "Front Right High", SNDRV_CHMAP_FRH },
+	{ "Top Center", SNDRV_CHMAP_TC },
+};
+
+static void append_chmap(struct snd_pcm_chmap_elem *chmap, u8 type)
+{
+	if (!chmap || chmap->channels >= ARRAY_SIZE(chmap->map))
+		return;
+
+	chmap->map[chmap->channels] = type;
+	chmap->channels++;
+}
+
+static int parse_chmap(struct dcp_parse_ctx *handle, struct snd_pcm_chmap_elem *chmap)
+{
+	struct iterator it;
+	int i, ret;
+
+	if (!chmap) {
+		skip(handle);
+		return 0;
+	}
+
+	chmap->channels = 0;
+
+	dcp_parse_foreach_in_array(handle, it) {
+		for (i = 0; i < ARRAY_SIZE(chan_position_names); i++)
+			if (consume_string(it.handle, chan_position_names[i].label))
+				break;
+
+		if (i == ARRAY_SIZE(chan_position_names)) {
+			ret = skip(it.handle);
+			if (ret)
+				return ret;
+
+			append_chmap(chmap, SNDRV_CHMAP_UNKNOWN);
+			continue;
+		}
+
+		append_chmap(chmap, chan_position_names[i].type);
+	}
+
+	return 0;
+}
+
+static int parse_chan_layout_element(struct dcp_parse_ctx *handle,
+				     unsigned int *nchans_out,
+				     struct snd_pcm_chmap_elem *chmap)
+{
+	struct iterator it;
+	int ret;
+	s64 nchans = 0;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		if (consume_string(it.handle, "ActiveChannelCount"))
+			ret = parse_int(it.handle, &nchans);
+		else if (consume_string(it.handle, "ChannelLayout"))
+			ret = parse_chmap(it.handle, chmap);
+		else
+			ret = skip_pair(it.handle);
+
+		if (ret)
+			return ret;
+	}
+
+	if (nchans_out)
+		*nchans_out = nchans;
+
+	return 0;
+}
+
+static int parse_nchans_mask(struct dcp_parse_ctx *handle, unsigned int *mask)
+{
+	struct iterator it;
+	int ret;
+
+	*mask = 0;
+
+	dcp_parse_foreach_in_array(handle, it) {
+		int nchans;
+
+		ret = parse_chan_layout_element(it.handle, &nchans, NULL);
+		if (ret)
+			return ret;
+		*mask |= 1 << nchans;
+	}
+
+	return 0;
+}
+
+static int parse_avep_element(struct dcp_parse_ctx *handle,
+			      struct dcp_sound_format_mask *sieve,
+			      struct dcp_sound_format_mask *hits)
+{
+	struct dcp_sound_format_mask mask = {0, 0, 0};
+	struct iterator it;
+	int ret;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		if (consume_string(handle, "StreamSampleRate"))
+			ret = parse_sample_rate_bit(it.handle, &mask.rates);
+		else if (consume_string(handle, "SampleSize"))
+			ret = parse_sample_fmtbit(it.handle, &mask.formats);
+		else if (consume_string(handle, "AudioChannelLayoutElements"))
+			ret = parse_nchans_mask(it.handle, &mask.nchans);
+		else
+			ret = skip_pair(it.handle);
+
+		if (ret)
+			return ret;
+	}
+
+	trace_avep_sound_mode(handle->dcp, mask.rates, mask.formats, mask.nchans);
+
+	if (!(mask.rates & sieve->rates) || !(mask.formats & sieve->formats) ||
+		!(mask.nchans & sieve->nchans))
+	    return 0;
+
+	if (hits) {
+		hits->rates |= mask.rates;
+		hits->formats |= mask.formats;
+		hits->nchans |= mask.nchans;
+	}
+
+	return 1;
+}
+
+static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle,
+				      unsigned int selected_nchans,
+				      struct snd_pcm_chmap_elem *chmap,
+				      struct dcp_sound_cookie *cookie)
+{
+	struct iterator it;
+	struct dcp_parse_ctx save_handle;
+	int ret;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		if (consume_string(it.handle, "AudioChannelLayoutElements")) {
+			struct iterator inner_it;
+			int nchans;
+
+			dcp_parse_foreach_in_array(it.handle, inner_it) {
+				save_handle = *it.handle;
+				ret = parse_chan_layout_element(inner_it.handle,
+								&nchans, NULL);
+				if (ret)
+					return ret;
+
+				if (nchans != selected_nchans)
+					continue;
+
+				/*
+				 * Now that we know this layout matches the
+				 * selected channel number, reread the element
+				 * and fill in the channel map.
+				 */
+				*inner_it.handle = save_handle;
+				ret = parse_chan_layout_element(inner_it.handle,
+								NULL, chmap);
+				if (ret)
+					return ret;
+			}
+		} else if (consume_string(it.handle, "ElementData")) {
+			u8 *blob;
+
+			ret = parse_blob(it.handle, sizeof(*cookie), &blob);
+			if (ret)
+				return ret;
+
+			if (cookie)
+				memcpy(cookie, blob, sizeof(*cookie));
+		} else {
+			ret = skip_pair(it.handle);
+			if (ret)
+				return ret;
+		}
+	}
+
+	return 0;
+}
+
+int parse_sound_constraints(struct dcp_parse_ctx *handle,
+			    struct dcp_sound_format_mask *sieve,
+			    struct dcp_sound_format_mask *hits)
+{
+	int ret;
+	struct iterator it;
+
+	if (hits) {
+		hits->rates = 0;
+		hits->formats = 0;
+		hits->nchans = 0;
+	}
+
+	dcp_parse_foreach_in_array(handle, it) {
+		ret = parse_avep_element(it.handle, sieve, hits);
+
+		if (ret < 0)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(parse_sound_constraints);
+
+int parse_sound_mode(struct dcp_parse_ctx *handle,
+		     struct dcp_sound_format_mask *sieve,
+		     struct snd_pcm_chmap_elem *chmap,
+		     struct dcp_sound_cookie *cookie)
+{
+	struct dcp_parse_ctx save_handle;
+	struct iterator it;
+	int ret;
+
+	dcp_parse_foreach_in_array(handle, it) {
+		save_handle = *it.handle;
+		ret = parse_avep_element(it.handle, sieve, NULL);
+
+		if (!ret)
+			continue;
+
+		if (ret < 0)
+			return ret;
+
+		ret = parse_mode_in_avep_element(&save_handle, __ffs(sieve->nchans),
+						 chmap, cookie);
+		if (ret < 0)
+			return ret;
+		return 1;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(parse_sound_mode);
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index 92fe9473d56718..030e3a33b4877d 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -32,4 +32,24 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 			     int *height_mm);
 
+
+struct dcp_sound_format_mask {
+	u64 formats;			/* SNDRV_PCM_FMTBIT_* */
+	unsigned int rates;		/* SNDRV_PCM_RATE_* */
+	unsigned int nchans;
+};
+
+struct dcp_sound_cookie {
+	u8 data[24];
+};
+
+struct snd_pcm_chmap_elem;
+int parse_sound_constraints(struct dcp_parse_ctx *handle,
+			    struct dcp_sound_format_mask *sieve,
+			    struct dcp_sound_format_mask *hits);
+int parse_sound_mode(struct dcp_parse_ctx *handle,
+		     struct dcp_sound_format_mask *sieve,
+		     struct snd_pcm_chmap_elem *chmap,
+		     struct dcp_sound_cookie *cookie);
+
 #endif
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index 127bda420592a0..c482b66ffca132 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -291,6 +291,29 @@ TRACE_EVENT(iomfb_timing_mode,
 	    )
 );
 
+TRACE_EVENT(avep_sound_mode,
+	    TP_PROTO(struct apple_dcp *dcp, u32 rates, u64 formats, unsigned int nchans),
+	    TP_ARGS(dcp, rates, formats, nchans),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, rates)
+			     __field(u64, formats)
+			     __field(unsigned int, nchans)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->rates = rates;
+			   __entry->formats = formats;
+			   __entry->nchans = nchans;
+	    ),
+	    TP_printk("dcp=%llx, rates=%#x, formats=%#llx, nchans=%#x",
+		      __entry->dcp,
+		      __entry->rates,
+		      __entry->formats,
+		      __entry->nchans
+	    )
+);
+
 #endif /* _TRACE_DCP_H */
 
 /* This part must be outside protection */

From 2a0e2186455e4f31ea1f1987c12556386662ba8c Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Sun, 12 Feb 2023 15:51:58 +0100
Subject: [PATCH 0738/1027] drm: apple: DCP AFK/EPIC support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Sven Peter <sven@svenpeter.dev>
Co-developed-by: Martin Povišer <povik+lin@cutebit.org>
Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile       |   2 +-
 drivers/gpu/drm/apple/afk.c          | 950 +++++++++++++++++++++++++++
 drivers/gpu/drm/apple/afk.h          | 187 ++++++
 drivers/gpu/drm/apple/dcp-internal.h |   1 +
 drivers/gpu/drm/apple/dcp.c          |   1 +
 drivers/gpu/drm/apple/parser.c       |  62 ++
 drivers/gpu/drm/apple/parser.h       |   3 +-
 drivers/gpu/drm/apple/trace.h        | 110 ++++
 8 files changed, 1314 insertions(+), 2 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/afk.c
 create mode 100644 drivers/gpu/drm/apple/afk.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 910095e5839c35..2cc9cfa9fb9ef9 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -4,7 +4,7 @@ CFLAGS_trace.o = -I$(src)
 
 appledrm-y := apple_drv.o
 
-apple_dcp-y := dcp.o dcp_backlight.o iomfb.o parser.o
+apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o
 apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
new file mode 100644
index 00000000000000..9f2f0b646ac6e0
--- /dev/null
+++ b/drivers/gpu/drm/apple/afk.c
@@ -0,0 +1,950 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
+
+#include <linux/bitfield.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/soc/apple/rtkit.h>
+
+#include "afk.h"
+#include "trace.h"
+
+struct afk_receive_message_work {
+	struct apple_dcp_afkep *ep;
+	u64 message;
+	struct work_struct work;
+};
+
+#define RBEP_TYPE GENMASK(63, 48)
+
+enum rbep_msg_type {
+	RBEP_INIT = 0x80,
+	RBEP_INIT_ACK = 0xa0,
+	RBEP_GETBUF = 0x89,
+	RBEP_GETBUF_ACK = 0xa1,
+	RBEP_INIT_TX = 0x8a,
+	RBEP_INIT_RX = 0x8b,
+	RBEP_START = 0xa3,
+	RBEP_START_ACK = 0x86,
+	RBEP_SEND = 0xa2,
+	RBEP_RECV = 0x85,
+	RBEP_SHUTDOWN = 0xc0,
+	RBEP_SHUTDOWN_ACK = 0xc1,
+};
+
+#define BLOCK_SHIFT 6
+
+#define GETBUF_SIZE GENMASK(31, 16)
+#define GETBUF_TAG GENMASK(15, 0)
+#define GETBUF_ACK_DVA GENMASK(47, 0)
+
+#define INITRB_OFFSET GENMASK(47, 32)
+#define INITRB_SIZE GENMASK(31, 16)
+#define INITRB_TAG GENMASK(15, 0)
+
+#define SEND_WPTR GENMASK(31, 0)
+
+static void afk_send(struct apple_dcp_afkep *ep, u64 message)
+{
+	dcp_send_message(ep->dcp, ep->endpoint, message);
+}
+
+struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
+				 const struct apple_epic_service_ops *ops)
+{
+	struct apple_dcp_afkep *afkep;
+	int ret;
+
+	afkep = devm_kzalloc(dcp->dev, sizeof(*afkep), GFP_KERNEL);
+	if (!afkep)
+		return ERR_PTR(-ENOMEM);
+
+	afkep->ops = ops;
+	afkep->dcp = dcp;
+	afkep->endpoint = endpoint;
+	afkep->wq = alloc_ordered_workqueue("apple-dcp-afkep%02x",
+					    WQ_MEM_RECLAIM, endpoint);
+	if (!afkep->wq) {
+		ret = -ENOMEM;
+		goto out_free_afkep;
+	}
+
+	// TODO: devm_ for wq
+
+	init_completion(&afkep->started);
+	init_completion(&afkep->stopped);
+	spin_lock_init(&afkep->lock);
+
+	return afkep;
+
+out_free_afkep:
+	devm_kfree(dcp->dev, afkep);
+	return ERR_PTR(ret);
+}
+
+int afk_start(struct apple_dcp_afkep *ep)
+{
+	int ret;
+
+	reinit_completion(&ep->started);
+	apple_rtkit_start_ep(ep->dcp->rtk, ep->endpoint);
+	afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_INIT));
+
+	ret = wait_for_completion_timeout(&ep->started, msecs_to_jiffies(1000));
+	if (ret <= 0)
+		return -ETIMEDOUT;
+	else
+		return 0;
+}
+
+static void afk_getbuf(struct apple_dcp_afkep *ep, u64 message)
+{
+	u16 size = FIELD_GET(GETBUF_SIZE, message) << BLOCK_SHIFT;
+	u16 tag = FIELD_GET(GETBUF_TAG, message);
+	u64 reply;
+
+	trace_afk_getbuf(ep, size, tag);
+
+	if (ep->bfr) {
+		dev_err(ep->dcp->dev,
+			"Got GETBUF message but buffer already exists\n");
+		return;
+	}
+
+	ep->bfr = dmam_alloc_coherent(ep->dcp->dev, size, &ep->bfr_dma,
+				      GFP_KERNEL);
+	if (!ep->bfr) {
+		dev_err(ep->dcp->dev, "Failed to allocate %d bytes buffer\n",
+			size);
+		return;
+	}
+
+	ep->bfr_size = size;
+	ep->bfr_tag = tag;
+
+	reply = FIELD_PREP(RBEP_TYPE, RBEP_GETBUF_ACK);
+	reply |= FIELD_PREP(GETBUF_ACK_DVA, ep->bfr_dma);
+	afk_send(ep, reply);
+}
+
+static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
+			  struct afk_ringbuffer *bfr)
+{
+	u16 base = FIELD_GET(INITRB_OFFSET, message) << BLOCK_SHIFT;
+	u16 size = FIELD_GET(INITRB_SIZE, message) << BLOCK_SHIFT;
+	u16 tag = FIELD_GET(INITRB_TAG, message);
+	u32 bufsz, end;
+
+	if (tag != ep->bfr_tag) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x",
+			ep->endpoint, ep->bfr_tag, tag);
+		return;
+	}
+
+	if (bfr->ready) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: buffer is already initialized\n",
+			ep->endpoint);
+		return;
+	}
+
+	if (base >= ep->bfr_size) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx",
+			ep->endpoint, base, ep->bfr_size);
+		return;
+	}
+
+	end = base + size;
+	if (end > ep->bfr_size) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: requested end 0x%x > max size 0x%lx",
+			ep->endpoint, end, ep->bfr_size);
+		return;
+	}
+
+	bfr->hdr = ep->bfr + base;
+	bufsz = le32_to_cpu(bfr->hdr->bufsz);
+	if (bufsz + sizeof(*bfr->hdr) != size) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx",
+			ep->endpoint, bufsz, sizeof(*bfr->hdr));
+		return;
+	}
+
+	bfr->buf = bfr->hdr + 1;
+	bfr->bufsz = bufsz;
+	bfr->ready = true;
+
+	if (ep->rxbfr.ready && ep->txbfr.ready)
+		afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START));
+}
+
+static const struct apple_epic_service_ops *
+afk_match_service(struct apple_dcp_afkep *ep, const char *name)
+{
+	const struct apple_epic_service_ops *ops;
+
+	if (!name[0])
+		return NULL;
+	if (!ep->ops)
+		return NULL;
+
+	for (ops = ep->ops; ops->name[0]; ops++) {
+		if (strcmp(ops->name, name))
+			continue;
+
+		return ops;
+	}
+
+	return NULL;
+}
+
+static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
+				 u8 *payload, size_t payload_size)
+{
+	char name[32];
+	s64 epic_unit = -1;
+	const char *service_name = name;
+	const char *epic_name = NULL, *epic_class = NULL;
+	const struct apple_epic_service_ops *ops;
+	struct dcp_parse_ctx ctx;
+	u8 *props = payload + sizeof(name);
+	size_t props_size = payload_size - sizeof(name);
+
+	WARN_ON(ep->services[channel].enabled);
+
+	if (payload_size < sizeof(name)) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n",
+			ep->endpoint, payload_size);
+		return;
+	}
+
+	strlcpy(name, payload, sizeof(name));
+
+	/*
+	 * in DCP firmware 13.2 DCP reports interface-name as name which starts
+	 * with "dispext%d" using -1 s ID for "dcp". In the 12.3 firmware
+	 * EPICProviderClass was used. If the init call has props parse them and
+	 * use EPICProviderClass to match the service.
+	 */
+	if (props_size > 36) {
+		int ret = parse(props, props_size, &ctx);
+		if (ret) {
+			dev_err(ep->dcp->dev,
+				"AFK[ep:%02x]: Failed to parse service init props for %s\n",
+				ep->endpoint, name);
+			return;
+		}
+		ret = parse_epic_service_init(&ctx, &epic_name, &epic_class, &epic_unit);
+		if (ret) {
+			dev_err(ep->dcp->dev,
+				"AFK[ep:%02x]: failed to extract init props: %d\n",
+				ep->endpoint, ret);
+			return;
+		}
+		service_name = epic_class;
+	} else {
+            service_name = name;
+        }
+
+	ops = afk_match_service(ep, service_name);
+	if (!ops) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: unable to match service %s on channel %d\n",
+			ep->endpoint, service_name, channel);
+		goto free;
+	}
+
+	spin_lock_init(&ep->services[channel].lock);
+	ep->services[channel].enabled = true;
+	ep->services[channel].ops = ops;
+	ep->services[channel].ep = ep;
+	ep->services[channel].channel = channel;
+	ep->services[channel].cmd_tag = 0;
+	ops->init(&ep->services[channel], epic_name, epic_class, epic_unit);
+	dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n",
+		 ep->endpoint, service_name, channel);
+free:
+	kfree(epic_name);
+	kfree(epic_class);
+}
+
+static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel)
+{
+	struct apple_epic_service *service = &ep->services[channel];
+	const struct apple_epic_service_ops *ops;
+	unsigned long flags;
+
+	WARN_ON(!service->enabled);
+
+	// TODO: think through what locking is necessary
+	spin_lock_irqsave(&service->lock, flags);
+	service->enabled = false;
+	ops = service->ops;
+	spin_unlock_irqrestore(&service->lock, flags);
+
+	if (ops->teardown)
+		ops->teardown(service);
+}
+
+static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
+				  u16 tag, void *payload, size_t payload_size)
+{
+	struct epic_cmd *cmd = payload;
+	struct apple_epic_service *service = &ep->services[channel];
+	unsigned long flags;
+	u8 idx = tag & 0xff;
+	void *rxbuf, *txbuf;
+	dma_addr_t rxbuf_dma, txbuf_dma;
+	size_t rxlen, txlen;
+
+	if (payload_size < sizeof(*cmd)) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: command reply on channel %d too small: %ld\n",
+			ep->endpoint, channel, payload_size);
+		return;
+	}
+
+	if (idx >= MAX_PENDING_CMDS) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: command reply on channel %d out of range: %d\n",
+			ep->endpoint, channel, idx);
+		return;
+	}
+
+	spin_lock_irqsave(&service->lock, flags);
+	if (service->cmds[idx].done) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: command reply on channel %d already handled\n",
+			ep->endpoint, channel);
+		spin_unlock_irqrestore(&service->lock, flags);
+		return;
+	}
+
+	if (tag != service->cmds[idx].tag) {
+		dev_err(ep->dcp->dev,
+			"AFK[ep:%02x]: command reply on channel %d has invalid tag: expected 0x%04x != 0x%04x\n",
+			ep->endpoint, channel, tag, service->cmds[idx].tag);
+		spin_unlock_irqrestore(&service->lock, flags);
+		return;
+	}
+
+	service->cmds[idx].done = true;
+	service->cmds[idx].retcode = le32_to_cpu(cmd->retcode);
+	if (service->cmds[idx].free_on_ack) {
+		/* defer freeing until we're no longer in atomic context */
+		rxbuf = service->cmds[idx].rxbuf;
+		txbuf = service->cmds[idx].txbuf;
+		rxlen = service->cmds[idx].rxlen;
+		txlen = service->cmds[idx].txlen;
+		rxbuf_dma = service->cmds[idx].rxbuf_dma;
+		txbuf_dma = service->cmds[idx].txbuf_dma;
+		bitmap_release_region(service->cmd_map, idx, 0);
+	} else {
+		rxbuf = txbuf = NULL;
+		rxlen = txlen = 0;
+	}
+	if (service->cmds[idx].completion)
+		complete(service->cmds[idx].completion);
+
+	spin_unlock_irqrestore(&service->lock, flags);
+
+	if (rxbuf && rxlen)
+		dma_free_coherent(ep->dcp->dev, rxlen, rxbuf, rxbuf_dma);
+	if (txbuf && txlen)
+		dma_free_coherent(ep->dcp->dev, txlen, txbuf, txbuf_dma);
+}
+
+struct epic_std_service_ap_call {
+	__le32 unk0;
+	__le32 unk1;
+	__le32 type;
+	__le32 len;
+	__le32 magic;
+	u8 _unk[48];
+} __attribute__((packed));
+
+static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel,
+					u32 type, struct epic_hdr *ehdr,
+					struct epic_sub_hdr *eshdr,
+					void *payload, size_t payload_size)
+{
+	struct apple_epic_service *service = &ep->services[channel];
+
+	if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) {
+		struct epic_std_service_ap_call *call = payload;
+		size_t call_size;
+		void *reply;
+		int ret;
+
+		if (payload_size < sizeof(*call))
+			return;
+
+		call_size = le32_to_cpu(call->len);
+		if (payload_size < sizeof(*call) + call_size)
+			return;
+
+		if (!service->ops->call)
+			return;
+		reply = kzalloc(payload_size, GFP_KERNEL);
+		if (!reply)
+			return;
+
+		ret = service->ops->call(service, le32_to_cpu(call->type),
+					 payload + sizeof(*call), call_size,
+					 reply + sizeof(*call), call_size);
+		if (ret) {
+			kfree(reply);
+			return;
+		}
+
+		memcpy(reply, call, sizeof(*call));
+		afk_send_epic(ep, channel, le16_to_cpu(eshdr->tag),
+			      EPIC_TYPE_NOTIFY_ACK, EPIC_CAT_REPLY,
+			      EPIC_SUBTYPE_STD_SERVICE, reply, payload_size);
+		kfree(reply);
+
+		return;
+	}
+
+	if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) {
+		struct epic_std_service_ap_call *call = payload;
+		size_t call_size;
+
+		if (payload_size < sizeof(*call))
+			return;
+
+		call_size = le32_to_cpu(call->len);
+		if (payload_size < sizeof(*call) + call_size)
+			return;
+
+		if (!service->ops->report)
+			return;
+
+		service->ops->report(service, le32_to_cpu(call->type),
+				     payload + sizeof(*call), call_size);
+		return;
+	}
+
+	dev_err(ep->dcp->dev,
+		"AFK[ep:%02x]: channel %d received unhandled standard service message: %x / %x\n",
+		ep->endpoint, channel, type, eshdr->category);
+	print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload,
+				   payload_size, true);
+}
+
+static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
+			    u8 *data, size_t data_size)
+{
+	struct epic_hdr *ehdr = (struct epic_hdr *)data;
+	struct epic_sub_hdr *eshdr =
+		(struct epic_sub_hdr *)(data + sizeof(*ehdr));
+	u16 subtype = le16_to_cpu(eshdr->type);
+	u8 *payload = data + sizeof(*ehdr) + sizeof(*eshdr);
+	size_t payload_size;
+
+	if (data_size < sizeof(*ehdr) + sizeof(*eshdr)) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n",
+			ep->endpoint, data_size);
+		return;
+	}
+	payload_size = data_size - sizeof(*ehdr) - sizeof(*eshdr);
+
+	trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr);
+
+	if (channel >= AFK_MAX_CHANNEL) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n",
+			ep->endpoint, channel);
+		return;
+	}
+
+	if (!ep->services[channel].enabled) {
+		if (type != EPIC_TYPE_NOTIFY) {
+			dev_err(ep->dcp->dev,
+				"AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n",
+				ep->endpoint, type, channel);
+			return;
+		}
+		if (eshdr->category != EPIC_CAT_REPORT) {
+			dev_err(ep->dcp->dev,
+				"AFK[ep:%02x]: expected report but got 0x%x on channel %d\n",
+				ep->endpoint, eshdr->category, channel);
+			return;
+		}
+		if (subtype != EPIC_SUBTYPE_ANNOUNCE) {
+			dev_err(ep->dcp->dev,
+				"AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n",
+				ep->endpoint, subtype, channel);
+			return;
+		}
+
+		return afk_recv_handle_init(ep, channel, payload, payload_size);
+	}
+
+	if (!ep->services[channel].enabled) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n",
+			ep->endpoint, channel);
+		return;
+	}
+
+	if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT &&
+	    subtype == EPIC_SUBTYPE_TEARDOWN)
+		return afk_recv_handle_teardown(ep, channel);
+
+	if (type == EPIC_TYPE_REPLY && eshdr->category == EPIC_CAT_REPLY)
+		return afk_recv_handle_reply(ep, channel,
+					     le16_to_cpu(eshdr->tag), payload,
+					     payload_size);
+
+	if (subtype == EPIC_SUBTYPE_STD_SERVICE)
+		return afk_recv_handle_std_service(
+			ep, channel, type, ehdr, eshdr, payload, payload_size);
+
+	dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d received unhandled message "
+		"(type %x subtype %x)\n", ep->endpoint, channel, type, subtype);
+	print_hex_dump(KERN_INFO, "AFK: ", DUMP_PREFIX_NONE, 16, 1, payload,
+				   payload_size, true);
+}
+
+static bool afk_recv(struct apple_dcp_afkep *ep)
+{
+	struct afk_qe *hdr;
+	u32 rptr, wptr;
+	u32 magic, size, channel, type;
+
+	if (!ep->rxbfr.ready) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: got RECV but not ready\n",
+			ep->endpoint);
+		return false;
+	}
+
+	rptr = le32_to_cpu(ep->rxbfr.hdr->rptr);
+	wptr = le32_to_cpu(ep->rxbfr.hdr->wptr);
+	trace_afk_recv_rwptr_pre(ep, rptr, wptr);
+
+	if (rptr == wptr)
+		return false;
+
+	if (rptr > (ep->rxbfr.bufsz - sizeof(*hdr))) {
+		dev_warn(ep->dcp->dev,
+			 "AFK[ep:%02x]: rptr out of bounds: 0x%x > 0x%lx\n",
+			 ep->endpoint, rptr, ep->rxbfr.bufsz - sizeof(*hdr));
+		return false;
+	}
+
+	dma_rmb();
+
+	hdr = ep->rxbfr.buf + rptr;
+	magic = le32_to_cpu(hdr->magic);
+	size = le32_to_cpu(hdr->size);
+	trace_afk_recv_qe(ep, rptr, magic, size);
+
+	if (magic != QE_MAGIC) {
+		dev_warn(ep->dcp->dev, "AFK[ep:%02x]: invalid queue entry magic: 0x%x\n",
+			 ep->endpoint, magic);
+		return false;
+	}
+
+	/*
+	 * If there's not enough space for the payload the co-processor inserted
+	 * the current dummy queue entry and we have to advance to the next one
+	 * which will contain the real data.
+	*/
+	if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) {
+		rptr = 0;
+		hdr = ep->rxbfr.buf + rptr;
+		magic = le32_to_cpu(hdr->magic);
+		size = le32_to_cpu(hdr->size);
+		trace_afk_recv_qe(ep, rptr, magic, size);
+
+		if (magic != QE_MAGIC) {
+			dev_warn(ep->dcp->dev,
+				 "AFK[ep:%02x]: invalid next queue entry magic: 0x%x\n",
+				 ep->endpoint, magic);
+			return false;
+		}
+
+		ep->rxbfr.hdr->rptr = cpu_to_le32(rptr);
+	}
+
+	if (rptr + size + sizeof(*hdr) > ep->rxbfr.bufsz) {
+		dev_warn(ep->dcp->dev,
+			 "AFK[ep:%02x]: queue entry out of bounds: 0x%lx > 0x%lx\n",
+			 ep->endpoint, rptr + size + sizeof(*hdr), ep->rxbfr.bufsz);
+		return false;
+	}
+
+	channel = le32_to_cpu(hdr->channel);
+	type = le32_to_cpu(hdr->type);
+
+	afk_recv_handle(ep, channel, type, hdr->data, size);
+
+	rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT);
+	if (WARN_ON(rptr > ep->rxbfr.bufsz))
+		rptr = 0;
+	if (rptr == ep->rxbfr.bufsz)
+		rptr = 0;
+
+	dma_mb();
+
+	ep->rxbfr.hdr->rptr = cpu_to_le32(rptr);
+	trace_afk_recv_rwptr_post(ep, rptr, wptr);
+
+	return true;
+}
+
+static void afk_receive_message_worker(struct work_struct *work_)
+{
+	struct afk_receive_message_work *work;
+	u16 type;
+
+	work = container_of(work_, struct afk_receive_message_work, work);
+
+	type = FIELD_GET(RBEP_TYPE, work->message);
+	switch (type) {
+	case RBEP_INIT_ACK:
+		break;
+
+	case RBEP_START_ACK:
+		complete_all(&work->ep->started);
+		break;
+
+	case RBEP_SHUTDOWN_ACK:
+		complete_all(&work->ep->stopped);
+		break;
+
+	case RBEP_GETBUF:
+		afk_getbuf(work->ep, work->message);
+		break;
+
+	case RBEP_INIT_TX:
+		afk_init_rxtx(work->ep, work->message, &work->ep->txbfr);
+		break;
+
+	case RBEP_INIT_RX:
+		afk_init_rxtx(work->ep, work->message, &work->ep->rxbfr);
+		break;
+
+	case RBEP_RECV:
+		while (afk_recv(work->ep))
+			;
+		break;
+
+	default:
+		dev_err(work->ep->dcp->dev,
+			"Received unknown AFK message type: 0x%x\n", type);
+	}
+
+	kfree(work);
+}
+
+int afk_receive_message(struct apple_dcp_afkep *ep, u64 message)
+{
+	struct afk_receive_message_work *work;
+
+	// TODO: comment why decoupling from rtkit thread is required here
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return -ENOMEM;
+
+	work->ep = ep;
+	work->message = message;
+	INIT_WORK(&work->work, afk_receive_message_worker);
+	queue_work(ep->wq, &work->work);
+
+	return 0;
+}
+
+int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
+		  enum epic_type etype, enum epic_category ecat, u8 stype,
+		  const void *payload, size_t payload_len)
+{
+	u32 rptr, wptr;
+	struct afk_qe *hdr, *hdr2;
+	struct epic_hdr *ehdr;
+	struct epic_sub_hdr *eshdr;
+	unsigned long flags;
+	size_t total_epic_size, total_size;
+	int ret;
+
+	spin_lock_irqsave(&ep->lock, flags);
+
+	dma_rmb();
+	rptr = le32_to_cpu(ep->txbfr.hdr->rptr);
+	wptr = le32_to_cpu(ep->txbfr.hdr->wptr);
+	trace_afk_send_rwptr_pre(ep, rptr, wptr);
+	total_epic_size = sizeof(*ehdr) + sizeof(*eshdr) + payload_len;
+	total_size = sizeof(*hdr) + total_epic_size;
+
+	hdr = hdr2 = NULL;
+
+	/*
+	 * We need to figure out how to place the entire headers and payload
+	 * into the ring buffer:
+	 * - If the write pointer is in front of the read pointer we just need
+	 *   enough space inbetween to store everything.
+	 * - If the read pointer has already wrapper around the end of the
+	 *   buffer we can
+	 *    a) either store the entire payload at the writer pointer if
+	 *       there's enough space until the end,
+	 *    b) or just store the queue entry at the write pointer to indicate
+	 *       that we need to wrap to the start and then store the headers
+	 *       and the payload at the beginning of the buffer. The queue
+	 *       header has to be store twice in this case.
+	 * In either case we have to ensure that there's always enough space
+	 * so that we don't accidentally overwrite other buffers.
+	 */
+	if (wptr < rptr) {
+		/*
+		 * If wptr < rptr we can't wrap around and only have to make
+		 * sure that there's enough space for the entire payload.
+		 */
+		if (wptr + total_size > rptr) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		hdr = ep->txbfr.buf + wptr;
+		wptr += sizeof(*hdr);
+	} else {
+		/* We need enough space to place at least a queue entry */
+		if (wptr + sizeof(*hdr) > ep->txbfr.bufsz) {
+			ret = -ENOMEM;
+			goto out;
+		}
+
+		/*
+		 * If we can place a single queue entry but not the full payload
+		 * we need to place one queue entry at the end of the ring
+		 * buffer and then another one together with the entire
+		 * payload at the beginning.
+		 */
+		if (wptr + total_size > ep->txbfr.bufsz) {
+			/*
+			 * Ensure there's space for the  queue entry at the
+			 * beginning
+			 */
+			if (sizeof(*hdr) > rptr) {
+				ret = -ENOMEM;
+				goto out;
+			}
+
+			/*
+			 * Place two queue entries to indicate we want to wrap
+			 * around to the firmware.
+			 */
+			hdr = ep->txbfr.buf + wptr;
+			hdr2 = ep->txbfr.buf;
+			wptr = sizeof(*hdr);
+
+			/* Ensure there's enough space for the entire payload */
+			if (wptr + total_epic_size > rptr) {
+				ret = -ENOMEM;
+				goto out;
+			}
+		} else {
+			/* We have enough space to place the entire payload */
+			hdr = ep->txbfr.buf + wptr;
+			wptr += sizeof(*hdr);
+		}
+	}
+	/*
+	 * At this point we're guaranteed that hdr (and possibly hdr2) point
+	 * to a buffer large enough to fit the queue entry and that we have
+	 * enough space at wptr to store the payload.
+	 */
+
+	hdr->magic = cpu_to_le32(QE_MAGIC);
+	hdr->size = cpu_to_le32(total_epic_size);
+	hdr->channel = cpu_to_le32(channel);
+	hdr->type = cpu_to_le32(etype);
+	if (hdr2)
+		memcpy(hdr2, hdr, sizeof(*hdr));
+
+	ehdr = ep->txbfr.buf + wptr;
+	memset(ehdr, 0, sizeof(*ehdr));
+	ehdr->version = 2;
+	ehdr->seq = cpu_to_le16(ep->qe_seq++);
+	ehdr->timestamp = cpu_to_le64(0);
+	wptr += sizeof(*ehdr);
+
+	eshdr = ep->txbfr.buf + wptr;
+	memset(eshdr, 0, sizeof(*eshdr));
+	eshdr->length = cpu_to_le32(payload_len);
+	eshdr->version = 3;
+	eshdr->category = ecat;
+	eshdr->type = cpu_to_le16(stype);
+	eshdr->timestamp = cpu_to_le64(0);
+	eshdr->tag = cpu_to_le16(tag);
+	eshdr->inline_len = cpu_to_le16(0);
+	wptr += sizeof(*eshdr);
+
+	memcpy(ep->txbfr.buf + wptr, payload, payload_len);
+	wptr += payload_len;
+	wptr = ALIGN(wptr, 1 << BLOCK_SHIFT);
+	if (wptr == ep->txbfr.bufsz)
+		wptr = 0;
+	trace_afk_send_rwptr_post(ep, rptr, wptr);
+
+	ep->txbfr.hdr->wptr = cpu_to_le32(wptr);
+	afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_SEND) |
+			     FIELD_PREP(SEND_WPTR, wptr));
+	ret = 0;
+
+out:
+	spin_unlock_irqrestore(&ep->lock, flags);
+	return ret;
+}
+
+int afk_send_command(struct apple_epic_service *service, u8 type,
+		     const void *payload, size_t payload_len, void *output,
+		     size_t output_len, u32 *retcode)
+{
+	struct epic_cmd cmd;
+	void *rxbuf, *txbuf;
+	dma_addr_t rxbuf_dma, txbuf_dma;
+	unsigned long flags;
+	int ret, idx;
+	u16 tag;
+	struct apple_dcp_afkep *ep = service->ep;
+	DECLARE_COMPLETION_ONSTACK(completion);
+
+	rxbuf = dma_alloc_coherent(ep->dcp->dev, output_len, &rxbuf_dma,
+				   GFP_KERNEL);
+	if (!rxbuf)
+		return -ENOMEM;
+	txbuf = dma_alloc_coherent(ep->dcp->dev, payload_len, &txbuf_dma,
+				   GFP_KERNEL);
+	if (!txbuf) {
+		ret = -ENOMEM;
+		goto err_free_rxbuf;
+	}
+
+	memcpy(txbuf, payload, payload_len);
+
+	cmd.retcode = cpu_to_le32(0);
+	cmd.rxbuf = cpu_to_le64(rxbuf_dma);
+	cmd.rxlen = cpu_to_le32(output_len);
+	cmd.txbuf = cpu_to_le64(txbuf_dma);
+	cmd.txlen = cpu_to_le32(payload_len);
+
+	spin_lock_irqsave(&service->lock, flags);
+	idx = bitmap_find_free_region(service->cmd_map, MAX_PENDING_CMDS, 0);
+	if (idx < 0) {
+		ret = -ENOSPC;
+		goto err_unlock;
+	}
+
+	tag = (service->cmd_tag & 0xff) << 8;
+	tag |= idx & 0xff;
+	service->cmd_tag++;
+
+	service->cmds[idx].tag = tag;
+	service->cmds[idx].rxbuf = rxbuf;
+	service->cmds[idx].txbuf = txbuf;
+	service->cmds[idx].rxbuf_dma = rxbuf_dma;
+	service->cmds[idx].txbuf_dma = txbuf_dma;
+	service->cmds[idx].rxlen = output_len;
+	service->cmds[idx].txlen = payload_len;
+	service->cmds[idx].free_on_ack = false;
+	service->cmds[idx].done = false;
+	service->cmds[idx].completion = &completion;
+	init_completion(&completion);
+
+	spin_unlock_irqrestore(&service->lock, flags);
+
+	ret = afk_send_epic(service->ep, service->channel, tag,
+			    EPIC_TYPE_COMMAND, EPIC_CAT_COMMAND, type, &cmd,
+			    sizeof(cmd));
+	if (ret)
+		goto err_free_cmd;
+
+	ret = wait_for_completion_timeout(&completion,
+					  msecs_to_jiffies(MSEC_PER_SEC));
+
+	if (ret <= 0) {
+		spin_lock_irqsave(&service->lock, flags);
+		/*
+		 * Check again while we're inside the lock to make sure
+		 * the command wasn't completed just after
+		 * wait_for_completion_timeout returned.
+		 */
+		if (!service->cmds[idx].done) {
+			service->cmds[idx].completion = NULL;
+			service->cmds[idx].free_on_ack = true;
+			spin_unlock_irqrestore(&service->lock, flags);
+			return -ETIMEDOUT;
+		}
+		spin_unlock_irqrestore(&service->lock, flags);
+	}
+
+	ret = 0;
+	if (retcode)
+		*retcode = service->cmds[idx].retcode;
+	if (output && output_len)
+		memcpy(output, rxbuf, output_len);
+
+err_free_cmd:
+	spin_lock_irqsave(&service->lock, flags);
+	bitmap_release_region(service->cmd_map, idx, 0);
+err_unlock:
+	spin_unlock_irqrestore(&service->lock, flags);
+	dma_free_coherent(ep->dcp->dev, payload_len, txbuf, txbuf_dma);
+err_free_rxbuf:
+	dma_free_coherent(ep->dcp->dev, output_len, rxbuf, rxbuf_dma);
+	return ret;
+}
+
+int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
+		     const void *data, size_t data_len, size_t data_pad,
+		     void *output, size_t output_len, size_t output_pad)
+{
+	struct epic_service_call *call;
+	void *bfr;
+	size_t bfr_len = max(data_len + data_pad, output_len + output_pad) +
+			 sizeof(*call);
+	int ret;
+	u32 retcode;
+	u32 retlen;
+
+	bfr = kzalloc(bfr_len, GFP_KERNEL);
+	if (!bfr)
+		return -ENOMEM;
+
+	call = bfr;
+	call->group = cpu_to_le16(group);
+	call->command = cpu_to_le32(command);
+	call->data_len = cpu_to_le32(data_len + data_pad);
+	call->magic = cpu_to_le32(EPIC_SERVICE_CALL_MAGIC);
+
+	memcpy(bfr + sizeof(*call), data, data_len);
+
+	ret = afk_send_command(service, EPIC_SUBTYPE_STD_SERVICE, bfr, bfr_len,
+			       bfr, bfr_len, &retcode);
+	if (ret)
+		goto out;
+	if (retcode) {
+		ret = -EINVAL;
+		goto out;
+	}
+	if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC ||
+	    le16_to_cpu(call->group) != group ||
+	    le32_to_cpu(call->command) != command) {
+		ret = -EINVAL;
+		goto out;
+	}
+
+	retlen = le32_to_cpu(call->data_len);
+	if (output_len < retlen)
+		retlen = output_len;
+	if (output && output_len) {
+		memset(output, 0, output_len);
+		memcpy(output, bfr + sizeof(*call), retlen);
+	}
+
+out:
+	kfree(bfr);
+	return ret;
+}
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
new file mode 100644
index 00000000000000..b800840b4f4a3a
--- /dev/null
+++ b/drivers/gpu/drm/apple/afk.h
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * AFK (Apple Firmware Kit) EPIC (EndPoint Interface Client) support
+ */
+/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
+
+#ifndef _DRM_APPLE_DCP_AFK_H
+#define _DRM_APPLE_DCP_AFK_H
+
+#include <linux/completion.h>
+#include <linux/types.h>
+
+#include "dcp.h"
+
+#define AFK_MAX_CHANNEL 16
+#define MAX_PENDING_CMDS 16
+
+struct apple_epic_service_ops;
+struct apple_dcp_afkep;
+
+struct epic_cmd_info {
+	u16 tag;
+
+	void *rxbuf;
+	void *txbuf;
+	dma_addr_t rxbuf_dma;
+	dma_addr_t txbuf_dma;
+	size_t rxlen;
+	size_t txlen;
+
+	u32 retcode;
+	bool done;
+	bool free_on_ack;
+	struct completion *completion;
+};
+
+struct apple_epic_service {
+	const struct apple_epic_service_ops *ops;
+	struct apple_dcp_afkep *ep;
+
+	struct epic_cmd_info cmds[MAX_PENDING_CMDS];
+	DECLARE_BITMAP(cmd_map, MAX_PENDING_CMDS);
+	u8 cmd_tag;
+	spinlock_t lock;
+
+	u32 channel;
+	bool enabled;
+
+	void *cookie;
+};
+
+struct apple_epic_service_ops {
+	const char name[32];
+
+	void (*init)(struct apple_epic_service *service, const char *name,
+			      const char *class, s64 unit);
+	int (*call)(struct apple_epic_service *service, u32 idx,
+		    const void *data, size_t data_size, void *reply,
+		    size_t reply_size);
+	int (*report)(struct apple_epic_service *service, u32 idx,
+		      const void *data, size_t data_size);
+	void (*teardown)(struct apple_epic_service *service);
+};
+
+struct afk_ringbuffer_header {
+	__le32 bufsz;
+	u32 unk;
+	u32 _pad1[14];
+	__le32 rptr;
+	u32 _pad2[15];
+	__le32 wptr;
+	u32 _pad3[15];
+};
+
+struct afk_qe {
+#define QE_MAGIC 0x20504f49 // ' POI'
+	__le32 magic;
+	__le32 size;
+	__le32 channel;
+	__le32 type;
+	u8 data[];
+};
+
+struct epic_hdr {
+	u8 version;
+	__le16 seq;
+	u8 _pad;
+	__le32 unk;
+	__le64 timestamp;
+} __attribute__((packed));
+
+struct epic_sub_hdr {
+	__le32 length;
+	u8 version;
+	u8 category;
+	__le16 type;
+	__le64 timestamp;
+	__le16 tag;
+	__le16 unk;
+	__le32 inline_len;
+} __attribute__((packed));
+
+struct epic_cmd {
+	__le32 retcode;
+	__le64 rxbuf;
+	__le64 txbuf;
+	__le32 rxlen;
+	__le32 txlen;
+} __attribute__((packed));
+
+struct epic_service_call {
+	u8 _pad0[2];
+	__le16 group;
+	__le32 command;
+	__le32 data_len;
+#define EPIC_SERVICE_CALL_MAGIC 0x69706378
+	__le32 magic;
+	u8 _pad1[48];
+} __attribute__((packed));
+static_assert(sizeof(struct epic_service_call) == 64);
+
+enum epic_type {
+	EPIC_TYPE_NOTIFY = 0,
+	EPIC_TYPE_COMMAND = 3,
+	EPIC_TYPE_REPLY = 4,
+	EPIC_TYPE_NOTIFY_ACK = 8,
+};
+
+enum epic_category {
+	EPIC_CAT_REPORT = 0x00,
+	EPIC_CAT_NOTIFY = 0x10,
+	EPIC_CAT_REPLY = 0x20,
+	EPIC_CAT_COMMAND = 0x30,
+};
+
+enum epic_subtype {
+	EPIC_SUBTYPE_ANNOUNCE = 0x30,
+	EPIC_SUBTYPE_TEARDOWN = 0x32,
+	EPIC_SUBTYPE_STD_SERVICE = 0xc0,
+};
+
+struct afk_ringbuffer {
+	bool ready;
+	struct afk_ringbuffer_header *hdr;
+	u32 rptr;
+	void *buf;
+	size_t bufsz;
+};
+
+struct apple_dcp_afkep {
+	struct apple_dcp *dcp;
+
+	u32 endpoint;
+	struct workqueue_struct *wq;
+
+	struct completion started;
+	struct completion stopped;
+
+	void *bfr;
+	u16 bfr_tag;
+	size_t bfr_size;
+	dma_addr_t bfr_dma;
+
+	struct afk_ringbuffer txbfr;
+	struct afk_ringbuffer rxbfr;
+
+	spinlock_t lock;
+	u16 qe_seq;
+
+	const struct apple_epic_service_ops *ops;
+	struct apple_epic_service services[AFK_MAX_CHANNEL];
+};
+
+struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
+				 const struct apple_epic_service_ops *ops);
+int afk_start(struct apple_dcp_afkep *ep);
+int afk_receive_message(struct apple_dcp_afkep *ep, u64 message);
+int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
+		  enum epic_type etype, enum epic_category ecat, u8 stype,
+		  const void *payload, size_t payload_len);
+int afk_send_command(struct apple_epic_service *service, u8 type,
+		     const void *payload, size_t payload_len, void *output,
+		     size_t output_len, u32 *retcode);
+int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
+		     const void *data, size_t data_len, size_t data_pad,
+		     void *output, size_t output_len, size_t output_pad);
+#endif
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index c04585c3374991..aa28ac54651a8a 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -17,6 +17,7 @@
 #define DCP_MAX_PLANES 2
 
 struct apple_dcp;
+struct apple_dcp_afkep;
 
 enum dcp_firmware_version {
 	DCP_FIRMWARE_UNKNOWN,
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6297e858d4c9f9..eabd600eb5c73e 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -26,6 +26,7 @@
 #include <drm/drm_probe_helper.h>
 #include <drm/drm_vblank.h>
 
+#include "afk.h"
 #include "dcp.h"
 #include "dcp-internal.h"
 #include "iomfb.h"
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 50a4fc31cd06ce..dde87b5d4052d5 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -589,6 +589,68 @@ int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 	return 0;
 }
 
+int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name,
+			    const char **class, s64 *unit)
+{
+	int ret = 0;
+	struct iterator it;
+	bool parsed_unit = false;
+	bool parsed_name = false;
+	bool parsed_class = false;
+
+	*name = ERR_PTR(-ENOENT);
+	*class = ERR_PTR(-ENOENT);
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		char *key = parse_string(it.handle);
+
+		if (IS_ERR(key)) {
+			ret = PTR_ERR(key);
+			break;
+		}
+
+		if (!strcmp(key, "EPICName")) {
+			*name = parse_string(it.handle);
+			if (IS_ERR(*name))
+				ret = PTR_ERR(*name);
+			else
+				parsed_name = true;
+		} else if (!strcmp(key, "EPICProviderClass")) {
+			*class = parse_string(it.handle);
+			if (IS_ERR(*class))
+				ret = PTR_ERR(*class);
+			else
+				parsed_class = true;
+		} else if (!strcmp(key, "EPICUnit")) {
+			ret = parse_int(it.handle, unit);
+			if (!ret)
+				parsed_unit = true;
+		} else {
+			skip(it.handle);
+		}
+
+		kfree(key);
+		if (ret)
+			break;
+	}
+
+	if (!parsed_unit || !parsed_name || !parsed_class)
+		ret = -ENOENT;
+
+	if (ret) {
+		if (!IS_ERR(*name)) {
+			kfree(*name);
+			*name = ERR_PTR(ret);
+		}
+		if (!IS_ERR(*class)) {
+			kfree(*class);
+			*class = ERR_PTR(ret);
+		}
+	}
+
+	return ret;
+}
+
 int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
 {
 	s64 rate;
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index 030e3a33b4877d..6d8717873cdd6c 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -31,7 +31,8 @@ struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 					 int height_mm, unsigned notch_height);
 int parse_display_attributes(struct dcp_parse_ctx *handle, int *width_mm,
 			     int *height_mm);
-
+int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name,
+			    const char **class, s64 *unit);
 
 struct dcp_sound_format_mask {
 	u64 formats;			/* SNDRV_PCM_FMTBIT_* */
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index c482b66ffca132..6b3d9886a4164e 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -7,7 +7,9 @@
 #if !defined(_TRACE_DCP_H) || defined(TRACE_HEADER_MULTI_READ)
 #define _TRACE_DCP_H
 
+#include "afk.h"
 #include "dcp-internal.h"
+#include "parser.h"
 
 #include <linux/stringify.h>
 #include <linux/types.h>
@@ -22,6 +24,17 @@
 			 { HDCP_ENDPOINT, "hdcp" },                \
 			 { REMOTE_ALLOC_ENDPOINT, "remotealloc" }, \
 			 { IOMFB_ENDPOINT, "iomfb" })
+#define print_epic_type(etype)                                  \
+	__print_symbolic(etype, { EPIC_TYPE_NOTIFY, "notify" }, \
+			 { EPIC_TYPE_COMMAND, "command" },      \
+			 { EPIC_TYPE_REPLY, "reply" },          \
+			 { EPIC_TYPE_NOTIFY_ACK, "notify-ack" })
+
+#define print_epic_category(ecat)                             \
+	__print_symbolic(ecat, { EPIC_CAT_REPORT, "report" }, \
+			 { EPIC_CAT_NOTIFY, "notify" },       \
+			 { EPIC_CAT_REPLY, "reply" },         \
+			 { EPIC_CAT_COMMAND, "command" })
 
 TRACE_EVENT(dcp_recv_msg,
 	    TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
@@ -55,6 +68,103 @@ TRACE_EVENT(dcp_send_msg,
 		      __get_str(devname), __entry->endpoint,
 		      show_dcp_endpoint(__entry->endpoint), __entry->message));
 
+TRACE_EVENT(
+	afk_getbuf, TP_PROTO(struct apple_dcp_afkep *ep, u16 size, u16 tag),
+	TP_ARGS(ep, size, tag),
+
+	TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
+				 __field(u8, endpoint) __field(u16, size)
+					 __field(u16, tag)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->endpoint = ep->endpoint; __entry->size = size;
+		       __entry->tag = tag;),
+
+	TP_printk(
+		"%s: endpoint 0x%x (%s): get buffer with size 0x%x and tag 0x%x",
+		__get_str(devname), __entry->endpoint,
+		show_dcp_endpoint(__entry->endpoint), __entry->size,
+		__entry->tag));
+
+DECLARE_EVENT_CLASS(afk_rwptr_template,
+	    TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
+	    TP_ARGS(ep, rptr, wptr),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
+				     __field(u8, endpoint) __field(u32, rptr)
+					     __field(u32, wptr)),
+
+	    TP_fast_assign(__assign_str(devname);
+			   __entry->endpoint = ep->endpoint;
+			   __entry->rptr = rptr; __entry->wptr = wptr;),
+
+	    TP_printk("%s: endpoint 0x%x (%s): rptr 0x%x, wptr 0x%x",
+		      __get_str(devname), __entry->endpoint,
+		      show_dcp_endpoint(__entry->endpoint), __entry->rptr,
+		      __entry->wptr));
+
+DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_pre,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
+	TP_ARGS(ep, rptr, wptr));
+DEFINE_EVENT(afk_rwptr_template, afk_recv_rwptr_post,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
+	TP_ARGS(ep, rptr, wptr));
+DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_pre,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
+	TP_ARGS(ep, rptr, wptr));
+DEFINE_EVENT(afk_rwptr_template, afk_send_rwptr_post,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 wptr),
+	TP_ARGS(ep, rptr, wptr));
+
+TRACE_EVENT(
+	afk_recv_qe,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 rptr, u32 magic, u32 size),
+	TP_ARGS(ep, rptr, magic, size),
+
+	TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev))
+				 __field(u8, endpoint) __field(u32, rptr)
+					 __field(u32, magic)
+						 __field(u32, size)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->endpoint = ep->endpoint; __entry->rptr = rptr;
+		       __entry->magic = magic; __entry->size = size;),
+
+	TP_printk("%s: endpoint 0x%x (%s): QE rptr 0x%x, magic 0x%x, size 0x%x",
+		  __get_str(devname), __entry->endpoint,
+		  show_dcp_endpoint(__entry->endpoint), __entry->rptr,
+		  __entry->magic, __entry->size));
+
+TRACE_EVENT(
+	afk_recv_handle,
+	TP_PROTO(struct apple_dcp_afkep *ep, u32 channel, u32 type,
+		 u32 data_size, struct epic_hdr *ehdr,
+		 struct epic_sub_hdr *eshdr),
+	TP_ARGS(ep, channel, type, data_size, ehdr, eshdr),
+
+	TP_STRUCT__entry(__string(devname, dev_name(ep->dcp->dev)) __field(
+		u8, endpoint) __field(u32, channel) __field(u32, type)
+				 __field(u32, data_size) __field(u8, category)
+					 __field(u16, subtype)
+						 __field(u16, tag)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->endpoint = ep->endpoint;
+		       __entry->channel = channel; __entry->type = type;
+		       __entry->data_size = data_size;
+		       __entry->category = eshdr->category,
+		       __entry->subtype = le16_to_cpu(eshdr->type),
+		       __entry->tag = le16_to_cpu(eshdr->tag)),
+
+	TP_printk(
+		"%s: endpoint 0x%x (%s): channel 0x%x, type 0x%x (%s), data_size 0x%x, category: 0x%x (%s), subtype: 0x%x, seq: 0x%x",
+		__get_str(devname), __entry->endpoint,
+		show_dcp_endpoint(__entry->endpoint), __entry->channel,
+		__entry->type, print_epic_type(__entry->type),
+		__entry->data_size, __entry->category,
+		print_epic_category(__entry->category), __entry->subtype,
+		__entry->tag));
+
 TRACE_EVENT(iomfb_callback,
 	    TP_PROTO(struct apple_dcp *dcp, int tag, const char *name),
 	    TP_ARGS(dcp, tag, name),

From 2e424cd1d64a732922f6df355635a05b3daa457d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 Nov 2023 12:35:51 +0100
Subject: [PATCH 0739/1027] drm: apple: afk: Use linear array of services

"Channel numbers" as received by AFK/EPIC are constantly increasing over
restarts of the endpoint. Use a linear array of services and match based
on the channel number. The number of services per endpoint is too small
to make a difference.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 74 +++++++++++++++++++++++++++----------
 drivers/gpu/drm/apple/afk.h |  1 +
 2 files changed, 55 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index 9f2f0b646ac6e0..d577f4ec055b03 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -201,11 +201,22 @@ afk_match_service(struct apple_dcp_afkep *ep, const char *name)
 	return NULL;
 }
 
+static struct apple_epic_service *afk_epic_find_service(struct apple_dcp_afkep *ep,
+						 u32 channel)
+{
+    for (u32 i = 0; i < ep->num_channels; i++)
+        if (ep->services[i].enabled && ep->services[i].channel == channel)
+            return &ep->services[i];
+
+    return NULL;
+}
+
 static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 				 u8 *payload, size_t payload_size)
 {
 	char name[32];
 	s64 epic_unit = -1;
+	u32 ch_idx;
 	const char *service_name = name;
 	const char *epic_name = NULL, *epic_class = NULL;
 	const struct apple_epic_service_ops *ops;
@@ -213,7 +224,7 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 	u8 *props = payload + sizeof(name);
 	size_t props_size = payload_size - sizeof(name);
 
-	WARN_ON(ep->services[channel].enabled);
+	WARN_ON(afk_epic_find_service(ep, channel));
 
 	if (payload_size < sizeof(name)) {
 		dev_err(ep->dcp->dev, "AFK[ep:%02x]: payload too small: %lx\n",
@@ -221,7 +232,13 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 		return;
 	}
 
-	strlcpy(name, payload, sizeof(name));
+	if (ep->num_channels >= AFK_MAX_CHANNEL) {
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: too many enabled services!\n",
+			ep->endpoint);
+		return;
+	}
+
+	strscpy(name, payload, sizeof(name));
 
 	/*
 	 * in DCP firmware 13.2 DCP reports interface-name as name which starts
@@ -257,13 +274,14 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 		goto free;
 	}
 
-	spin_lock_init(&ep->services[channel].lock);
-	ep->services[channel].enabled = true;
-	ep->services[channel].ops = ops;
-	ep->services[channel].ep = ep;
-	ep->services[channel].channel = channel;
-	ep->services[channel].cmd_tag = 0;
-	ops->init(&ep->services[channel], epic_name, epic_class, epic_unit);
+	ch_idx = ep->num_channels++;
+	spin_lock_init(&ep->services[ch_idx].lock);
+	ep->services[ch_idx].enabled = true;
+	ep->services[ch_idx].ops = ops;
+	ep->services[ch_idx].ep = ep;
+	ep->services[ch_idx].channel = channel;
+	ep->services[ch_idx].cmd_tag = 0;
+	ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit);
 	dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n",
 		 ep->endpoint, service_name, channel);
 free:
@@ -273,11 +291,16 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 
 static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel)
 {
-	struct apple_epic_service *service = &ep->services[channel];
+	struct apple_epic_service *service;
 	const struct apple_epic_service_ops *ops;
 	unsigned long flags;
 
-	WARN_ON(!service->enabled);
+	service = afk_epic_find_service(ep, channel);
+	if (!service) {
+		dev_warn(ep->dcp->dev, "AFK[ep:%02x]: teardown for disabled channel %u\n",
+			 ep->endpoint, channel);
+		return;
+	}
 
 	// TODO: think through what locking is necessary
 	spin_lock_irqsave(&service->lock, flags);
@@ -293,13 +316,20 @@ static void afk_recv_handle_reply(struct apple_dcp_afkep *ep, u32 channel,
 				  u16 tag, void *payload, size_t payload_size)
 {
 	struct epic_cmd *cmd = payload;
-	struct apple_epic_service *service = &ep->services[channel];
+	struct apple_epic_service *service;
 	unsigned long flags;
 	u8 idx = tag & 0xff;
 	void *rxbuf, *txbuf;
 	dma_addr_t rxbuf_dma, txbuf_dma;
 	size_t rxlen, txlen;
 
+	service = afk_epic_find_service(ep, channel);
+	if (!service) {
+		dev_warn(ep->dcp->dev, "AFK[ep:%02x]: command reply on disabled channel %u\n",
+			 ep->endpoint, channel);
+		return;
+	}
+
 	if (payload_size < sizeof(*cmd)) {
 		dev_err(ep->dcp->dev,
 			"AFK[ep:%02x]: command reply on channel %d too small: %ld\n",
@@ -371,7 +401,14 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel,
 					struct epic_sub_hdr *eshdr,
 					void *payload, size_t payload_size)
 {
-	struct apple_epic_service *service = &ep->services[channel];
+	struct apple_epic_service *service = afk_epic_find_service(ep, channel);
+
+	if (!service) {
+		dev_warn(ep->dcp->dev,
+			 "AFK[ep:%02x]: std service notify on disabled channel %u\n",
+			 ep->endpoint, channel);
+		return;
+	}
 
 	if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_NOTIFY) {
 		struct epic_std_service_ap_call *call = payload;
@@ -438,6 +475,7 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel,
 static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
 			    u8 *data, size_t data_size)
 {
+	struct apple_epic_service *service;
 	struct epic_hdr *ehdr = (struct epic_hdr *)data;
 	struct epic_sub_hdr *eshdr =
 		(struct epic_sub_hdr *)(data + sizeof(*ehdr));
@@ -454,13 +492,9 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
 
 	trace_afk_recv_handle(ep, channel, type, data_size, ehdr, eshdr);
 
-	if (channel >= AFK_MAX_CHANNEL) {
-		dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d out of bounds\n",
-			ep->endpoint, channel);
-		return;
-	}
+	service = afk_epic_find_service(ep, channel);
 
-	if (!ep->services[channel].enabled) {
+	if (!service) {
 		if (type != EPIC_TYPE_NOTIFY) {
 			dev_err(ep->dcp->dev,
 				"AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n",
@@ -483,7 +517,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
 		return afk_recv_handle_init(ep, channel, payload, payload_size);
 	}
 
-	if (!ep->services[channel].enabled) {
+	if (!service) {
 		dev_err(ep->dcp->dev, "AFK[ep:%02x]: channel %d has no service\n",
 			ep->endpoint, channel);
 		return;
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index b800840b4f4a3a..fe4ed35159ace0 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -169,6 +169,7 @@ struct apple_dcp_afkep {
 
 	const struct apple_epic_service_ops *ops;
 	struct apple_epic_service services[AFK_MAX_CHANNEL];
+	u32 num_channels;
 };
 
 struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,

From 3e98abd40c714de428872c3a3a999eb3ee952684 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Sat, 5 Nov 2022 13:15:34 +0100
Subject: [PATCH 0740/1027] drm: apple: Add DPTX support

This is required for DP Altmode, DP Thunderbolt tunneling and HDMI
output on 14/16-inch Macbook Pros and M2* desktop devices.

M2* desktops and 14 and 16 inch Macbook Pros expose a DisplayPort to
HDMI converter which is driven by the DP output of one of the DCP/DCPext
display coprocessor/controller blocks.
Two gpio pins are used for power control. Another gpio pin acts as HDMI
hpd.
Do not use the hpd as direct drm_connector interrupt since that is
already wired to DCPs hotplug notification. Instead use it to trigger
link setup via the dptx endpoint.

Signed-off-by: Sven Peter <sven@svenpeter.dev>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Kconfig        |   1 +
 drivers/gpu/drm/apple/Makefile       |   3 +-
 drivers/gpu/drm/apple/apple_drv.c    |  11 +-
 drivers/gpu/drm/apple/dcp-internal.h |  34 +++
 drivers/gpu/drm/apple/dcp.c          | 225 ++++++++++++++-
 drivers/gpu/drm/apple/dcp.h          |   3 +
 drivers/gpu/drm/apple/dcp_trace.c    |   3 +
 drivers/gpu/drm/apple/dptxep.c       | 408 +++++++++++++++++++++++++++
 drivers/gpu/drm/apple/dptxep.h       |  66 +++++
 drivers/gpu/drm/apple/ibootep.c      |  29 ++
 drivers/gpu/drm/apple/parser.c       |  11 +-
 drivers/gpu/drm/apple/parser.h       |   5 +
 drivers/gpu/drm/apple/systemep.c     | 100 +++++++
 drivers/gpu/drm/apple/trace.h        | 140 +++++++++
 14 files changed, 1026 insertions(+), 13 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/dcp_trace.c
 create mode 100644 drivers/gpu/drm/apple/dptxep.c
 create mode 100644 drivers/gpu/drm/apple/dptxep.h
 create mode 100644 drivers/gpu/drm/apple/ibootep.c
 create mode 100644 drivers/gpu/drm/apple/systemep.c

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index 9b9bcb7b5433e0..f0504641b5f641 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -7,5 +7,6 @@ config DRM_APPLE
 	select DRM_KMS_DMA_HELPER
 	select DRM_GEM_DMA_HELPER
 	select VIDEOMODE_HELPERS
+	select MULTIPLEXER
 	help
 	  Say Y if you have an Apple Silicon chipset.
diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 2cc9cfa9fb9ef9..50b7682ee4ccdb 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -4,7 +4,8 @@ CFLAGS_trace.o = -I$(src)
 
 appledrm-y := apple_drv.o
 
-apple_dcp-y := afk.o dcp.o dcp_backlight.o iomfb.o parser.o
+apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o
+apple_dcp-y += ibootep.o
 apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index a5ac277d76684a..7d2c927628d6e8 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -314,7 +314,7 @@ static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
 static int apple_probe_per_dcp(struct device *dev,
 			       struct drm_device *drm,
 			       struct platform_device *dcp,
-			       int num)
+			       int num, bool dcp_ext)
 {
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
@@ -346,6 +346,10 @@ static int apple_probe_per_dcp(struct device *dev,
 	drm_connector_helper_add(&connector->base,
 				 &apple_connector_helper_funcs);
 
+	// HACK:
+	if (dcp_ext)
+		connector->base.fwnode = fwnode_handle_get(dcp->dev.fwnode);
+
 	ret = drm_connector_init(drm, &connector->base, &apple_connector_funcs,
 				 dcp_get_connector_type(dcp));
 	if (ret)
@@ -397,6 +401,7 @@ static int apple_get_fb_resource(struct device *dev, const char *name,
 
 static const struct of_device_id apple_dcp_id_tbl[] = {
 	{ .compatible = "apple,dcp" },
+	{ .compatible = "apple,dcpext" },
 	{},
 };
 
@@ -409,10 +414,12 @@ static int apple_drm_init_dcp(struct device *dev)
 	int i, ret, num_dcp = 0;
 
 	for_each_matching_node(np, apple_dcp_id_tbl) {
+		bool dcp_ext;
 		if (!of_device_is_available(np)) {
 			of_node_put(np);
 			continue;
 		}
+		dcp_ext = of_device_is_compatible(np, "apple,dcpext");
 
 		dcp[num_dcp] = of_find_device_by_node(np);
 		of_node_put(np);
@@ -420,7 +427,7 @@ static int apple_drm_init_dcp(struct device *dev)
 			continue;
 
 		ret = apple_probe_per_dcp(dev, &apple->drm, dcp[num_dcp],
-					  num_dcp);
+					  num_dcp, dcp_ext);
 		if (ret)
 			continue;
 
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index aa28ac54651a8a..849bd2937ebb9d 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -7,9 +7,12 @@
 #include <linux/backlight.h>
 #include <linux/device.h>
 #include <linux/mutex.h>
+#include <linux/mux/consumer.h>
+#include <linux/phy/phy.h>
 #include <linux/platform_device.h>
 #include <linux/scatterlist.h>
 
+#include "dptxep.h"
 #include "iomfb.h"
 #include "iomfb_v12_3.h"
 #include "iomfb_v13_3.h"
@@ -94,6 +97,10 @@ struct dcp_panel {
 	bool has_mini_led;
 };
 
+struct apple_dcp_hw_data {
+	u32 num_dptx_ports;
+};
+
 /* TODO: move IOMFB members to its own struct */
 struct apple_dcp {
 	struct device *dev;
@@ -103,6 +110,8 @@ struct apple_dcp {
 	struct apple_crtc *crtc;
 	struct apple_connector *connector;
 
+	struct apple_dcp_hw_data hw;
+
 	/* firmware version and compatible firmware version */
 	enum dcp_firmware_version fw_compat;
 
@@ -127,6 +136,8 @@ struct apple_dcp {
 	struct resource *disp_registers[MAX_DISP_REGISTERS];
 	unsigned int nr_disp_registers;
 
+	u32 index;
+
 	/* Bitmap of memory descriptors used for mappings made by the DCP */
 	DECLARE_BITMAP(memdesc_map, DCP_MAX_MAPPINGS);
 
@@ -191,6 +202,29 @@ struct apple_dcp {
 
 	/* integrated panel if present */
 	struct dcp_panel panel;
+
+	struct apple_dcp_afkep *systemep;
+	struct completion systemep_done;
+
+	struct apple_dcp_afkep *ibootep;
+
+	struct apple_dcp_afkep *dptxep;
+
+	struct dptx_port dptxport[2];
+
+	/* these fields are output port specific */
+	struct phy *phy;
+	struct mux_control *xbar;
+
+	struct gpio_desc *hdmi_hpd;
+	struct gpio_desc *hdmi_pwren;
+	struct gpio_desc *dp2hdmi_pwren;
+
+	struct mutex hpd_mutex;
+
+	u32 dptx_phy;
+	u32 dptx_die;
+	int hdmi_hpd_irq;
 };
 
 int dcp_backlight_register(struct apple_dcp *dcp);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index eabd600eb5c73e..735acebdb289a3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -8,6 +8,7 @@
 #include <linux/component.h>
 #include <linux/delay.h>
 #include <linux/dma-mapping.h>
+#include <linux/gpio/consumer.h>
 #include <linux/iommu.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
@@ -115,6 +116,15 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 	switch (endpoint) {
 	case IOMFB_ENDPOINT:
 		return iomfb_recv_msg(dcp, message);
+	case SYSTEM_ENDPOINT:
+		afk_receive_message(dcp->systemep, message);
+		return;
+	case DISP0_ENDPOINT:
+		afk_receive_message(dcp->ibootep, message);
+		return;
+	case DPTX_ENDPOINT:
+		afk_receive_message(dcp->dptxep, message);
+		return;
 	default:
 		WARN(endpoint, "unknown DCP endpoint %hhu", endpoint);
 	}
@@ -194,7 +204,7 @@ void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message)
 {
 	trace_dcp_send_msg(dcp, endpoint, message);
 	apple_rtkit_send_message(dcp->rtk, endpoint, message, NULL,
-				 false);
+				 true);
 }
 
 int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
@@ -243,6 +253,66 @@ int dcp_get_connector_type(struct platform_device *pdev)
 }
 EXPORT_SYMBOL_GPL(dcp_get_connector_type);
 
+static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
+{
+	if (!dcp->phy) {
+		dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n");
+		return -ENODEV;
+	}
+
+	mutex_lock(&dcp->hpd_mutex);
+	if (!dcp->dptxport[port].enabled) {
+		dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port);
+		mutex_unlock(&dcp->hpd_mutex);
+		return -ENODEV;
+	}
+
+	if (dcp->dptxport[port].connected)
+		return 0;
+
+	dcp->dptxport[port].atcphy = dcp->phy;
+	dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die);
+	dptxport_request_display(dcp->dptxport[port].service);
+	dcp->dptxport[port].connected = true;
+	mutex_unlock(&dcp->hpd_mutex);
+
+	return 0;
+}
+
+static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
+{
+	struct apple_connector *connector = dcp->connector;
+
+	mutex_lock(&dcp->hpd_mutex);
+	if (connector && connector->connected) {
+		dcp->valid_mode = false;
+		schedule_work(&connector->hotplug_wq);
+	}
+
+	if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) {
+		dptxport_release_display(dcp->dptxport[port].service);
+		dcp->dptxport[port].connected = false;
+	}
+	mutex_unlock(&dcp->hpd_mutex);
+
+	return 0;
+}
+
+static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data)
+{
+	struct apple_dcp *dcp = data;
+	bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+
+	dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected);
+
+	if (connected)
+		dcp_dptx_connect(dcp, 0);
+	else
+		dcp_dptx_disconnect(dcp, 0);
+
+	return IRQ_HANDLED;
+}
+
 void dcp_link(struct platform_device *pdev, struct apple_crtc *crtc,
 	      struct apple_connector *connector)
 {
@@ -261,6 +331,28 @@ int dcp_start(struct platform_device *pdev)
 	init_completion(&dcp->start_done);
 
 	/* start RTKit endpoints */
+	ret = systemep_init(dcp);
+	if (ret)
+		dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret);
+
+	if (dcp->phy) {
+		if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
+			ret = ibootep_init(dcp);
+			if (ret)
+				dev_warn(dcp->dev,
+					 "Failed to start IBOOT endpoint: %d",
+					 ret);
+
+			ret = dptxep_init(dcp);
+			if (ret)
+				dev_warn(dcp->dev,
+					 "Failed to start DPTX endpoint: %d",
+					 ret);
+		} else
+			dev_warn(dcp->dev,
+				 "OS firmware incompatible with dptxport EP\n");
+	}
+
 	ret = iomfb_start_rtkit(dcp);
 	if (ret)
 		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret);
@@ -269,6 +361,23 @@ int dcp_start(struct platform_device *pdev)
 }
 EXPORT_SYMBOL(dcp_start);
 
+static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp)
+{
+	if (dcp->hdmi_hpd) {
+		bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+		dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
+
+		// necessary on j473/j474 but not on j314c
+		if (connected)
+			dcp_dptx_connect(dcp, 0);
+
+		if (dcp->hdmi_hpd_irq)
+			enable_irq(dcp->hdmi_hpd_irq);
+	}
+
+	return 0;
+}
+
 int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
 {
 	struct apple_dcp *dcp = platform_get_drvdata(pdev);
@@ -277,7 +386,7 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
 	if (dcp->crashed)
 		return -ENODEV;
 	if (dcp->active)
-		return 0;
+		return dcp_enable_dp2hdmi_hpd(dcp);;
 	if (timeout <= 0)
 		return -ETIMEDOUT;
 
@@ -288,6 +397,9 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
 	if (dcp->crashed)
 		return -ENODEV;
 
+	if (dcp->active)
+		dcp_enable_dp2hdmi_hpd(dcp);
+
 	return dcp->active ? 0 : -ETIMEDOUT;
 }
 EXPORT_SYMBOL(dcp_wait_ready);
@@ -476,6 +588,17 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (IS_ERR(dcp->coproc_reg))
 		return PTR_ERR(dcp->coproc_reg);
 
+	of_property_read_u32(dev->of_node, "apple,dcp-index",
+					   &dcp->index);
+	of_property_read_u32(dev->of_node, "apple,dptx-phy",
+					   &dcp->dptx_phy);
+	of_property_read_u32(dev->of_node, "apple,dptx-die",
+					   &dcp->dptx_die);
+	if (dcp->index || dcp->dptx_phy || dcp->dptx_die)
+		dev_info(dev, "DCP index:%u dptx target phy: %u dptx die: %u\n",
+			 dcp->index, dcp->dptx_phy, dcp->dptx_die);
+	mutex_init(&dcp->hpd_mutex);
+
 	if (!show_notch)
 		ret = of_property_read_u32(dev->of_node, "apple,notch-height",
 					   &dcp->notch_height);
@@ -560,7 +683,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (ret)
 		return dev_err_probe(dev, ret,
 				     "Failed to boot RTKit: %d", ret);
-
 	return ret;
 }
 
@@ -572,6 +694,9 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 {
 	struct apple_dcp *dcp = dev_get_drvdata(dev);
 
+	if (dcp->hdmi_hpd_irq)
+		disable_irq(dcp->hdmi_hpd_irq);
+
 	if (dcp && dcp->shmem)
 		iomfb_shutdown(dcp);
 
@@ -596,6 +721,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	enum dcp_firmware_version fw_compat;
 	struct device *dev = &pdev->dev;
 	struct apple_dcp *dcp;
+	u32 mux_index;
 
 	fw_compat = dcp_check_firmware_version(dev);
 	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
@@ -607,9 +733,71 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	dcp->fw_compat = fw_compat;
 	dcp->dev = dev;
+	dcp->hw = *(struct apple_dcp_hw_data *)of_device_get_match_data(dev);
 
 	platform_set_drvdata(pdev, dcp);
 
+	dcp->phy = devm_phy_optional_get(dev, "dp-phy");
+	if (IS_ERR(dcp->phy)) {
+		dev_err(dev, "Failed to get dp-phy: %ld", PTR_ERR(dcp->phy));
+		return PTR_ERR(dcp->phy);
+	}
+	if (dcp->phy) {
+		int ret;
+		/*
+		 * Request DP2HDMI related GPIOs as optional for DP-altmode
+		 * compatibility. J180D misses a dp2hdmi-pwren GPIO in the
+		 * template ADT. TODO: check device ADT
+		 */
+		dcp->hdmi_hpd = devm_gpiod_get_optional(dev, "hdmi-hpd", GPIOD_IN);
+		if (IS_ERR(dcp->hdmi_hpd))
+			return PTR_ERR(dcp->hdmi_hpd);
+		if (dcp->hdmi_hpd) {
+			int irq = gpiod_to_irq(dcp->hdmi_hpd);
+			if (irq < 0) {
+				dev_err(dev, "failed to translate HDMI hpd GPIO to IRQ\n");
+				return irq;
+			}
+			dcp->hdmi_hpd_irq = irq;
+
+			ret = devm_request_threaded_irq(dev, dcp->hdmi_hpd_irq,
+						NULL, dcp_dp2hdmi_hpd,
+						IRQF_ONESHOT | IRQF_NO_AUTOEN |
+						IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+						"dp2hdmi-hpd-irq", dcp);
+			if (ret < 0) {
+				dev_err(dev, "failed to request HDMI hpd irq %d: %d",
+					irq, ret);
+				return ret;
+			}
+		}
+
+		/*
+		 * Power DP2HDMI on as it is required for the HPD irq.
+		 * TODO: check if one is sufficient for the hpd to save power
+		 *       on battery powered Macbooks.
+		 */
+		dcp->hdmi_pwren = devm_gpiod_get_optional(dev, "hdmi-pwren", GPIOD_OUT_HIGH);
+		if (IS_ERR(dcp->hdmi_pwren))
+			return PTR_ERR(dcp->hdmi_pwren);
+
+		dcp->dp2hdmi_pwren = devm_gpiod_get_optional(dev, "dp2hdmi-pwren", GPIOD_OUT_HIGH);
+		if (IS_ERR(dcp->dp2hdmi_pwren))
+			return PTR_ERR(dcp->dp2hdmi_pwren);
+
+		ret = of_property_read_u32(dev->of_node, "mux-index", &mux_index);
+		if (!ret) {
+			dcp->xbar = devm_mux_control_get(dev, "dp-xbar");
+			if (IS_ERR(dcp->xbar)) {
+				dev_err(dev, "Failed to get dp-xbar: %ld", PTR_ERR(dcp->xbar));
+				return PTR_ERR(dcp->xbar);
+			}
+			ret = mux_control_select(dcp->xbar, mux_index);
+			if (ret)
+				dev_warn(dev, "mux_control_select failed: %d\n", ret);
+		}
+	}
+
 	return component_add(&pdev->dev, &dcp_comp_ops);
 }
 
@@ -625,6 +813,10 @@ static void dcp_platform_shutdown(struct platform_device *pdev)
 
 static int dcp_platform_suspend(struct device *dev)
 {
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+
+	if (dcp->hdmi_hpd_irq)
+		disable_irq(dcp->hdmi_hpd_irq);
 	/*
 	 * Set the device as a wakeup device, which forces its power
 	 * domains to stay on. We need this as we do not support full
@@ -637,14 +829,39 @@ static int dcp_platform_suspend(struct device *dev)
 
 static int dcp_platform_resume(struct device *dev)
 {
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+
+	if (dcp->hdmi_hpd_irq)
+		enable_irq(dcp->hdmi_hpd_irq);
+
 	return 0;
 }
 
 static DEFINE_SIMPLE_DEV_PM_OPS(dcp_platform_pm_ops,
 				dcp_platform_suspend, dcp_platform_resume);
 
+
+static const struct apple_dcp_hw_data apple_dcp_hw_t6020 = {
+	.num_dptx_ports = 1,
+};
+
+static const struct apple_dcp_hw_data apple_dcp_hw_t8112 = {
+	.num_dptx_ports = 2,
+};
+
+static const struct apple_dcp_hw_data apple_dcp_hw_dcp = {
+	.num_dptx_ports = 0,
+};
+
+static const struct apple_dcp_hw_data apple_dcp_hw_dcpext = {
+	.num_dptx_ports = 2,
+};
+
 static const struct of_device_id of_match[] = {
-	{ .compatible = "apple,dcp" },
+	{ .compatible = "apple,t6020-dcp", .data = &apple_dcp_hw_t6020,  },
+	{ .compatible = "apple,t8112-dcp", .data = &apple_dcp_hw_t8112,  },
+	{ .compatible = "apple,dcp",       .data = &apple_dcp_hw_dcp,    },
+	{ .compatible = "apple,dcpext",    .data = &apple_dcp_hw_dcpext, },
 	{}
 };
 MODULE_DEVICE_TABLE(of, of_match);
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 2011d27e809d53..e0dc96109b9d2b 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -68,4 +68,7 @@ void iomfb_shutdown(struct apple_dcp *dcp);
 /* rtkit message handler for IOMFB messages */
 void iomfb_recv_msg(struct apple_dcp *dcp, u64 message);
 
+int systemep_init(struct apple_dcp *dcp);
+int dptxep_init(struct apple_dcp *dcp);
+int ibootep_init(struct apple_dcp *dcp);
 #endif
diff --git a/drivers/gpu/drm/apple/dcp_trace.c b/drivers/gpu/drm/apple/dcp_trace.c
new file mode 100644
index 00000000000000..d18e71af73a74d
--- /dev/null
+++ b/drivers/gpu/drm/apple/dcp_trace.c
@@ -0,0 +1,3 @@
+// SPDX-License-Identifier: GPL-2.0
+#define CREATE_TRACE_POINTS
+#include "dcp_trace.h"
\ No newline at end of file
diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
new file mode 100644
index 00000000000000..2002f540d0e729
--- /dev/null
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -0,0 +1,408 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
+
+#include <linux/bitfield.h>
+#include <linux/completion.h>
+#include <linux/phy/phy.h>
+#include <linux/delay.h>
+
+#include "afk.h"
+#include "dcp.h"
+#include "dptxep.h"
+#include "parser.h"
+#include "trace.h"
+
+struct dcpdptx_connection_cmd {
+	__le32 unk;
+	__le32 target;
+} __attribute__((packed));
+
+struct dcpdptx_hotplug_cmd {
+	u8 _pad0[16];
+	__le32 unk;
+} __attribute__((packed));
+
+struct dptxport_apcall_link_rate {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le32 link_rate;
+	u8 _unk1[12];
+} __attribute__((packed));
+
+struct dptxport_apcall_get_support {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le32 supported;
+	u8 _unk1[12];
+} __attribute__((packed));
+
+struct dptxport_apcall_max_drive_settings {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le32 max_drive_settings[2];
+	u8 _unk1[8];
+};
+
+int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
+				 u8 atc, u8 die)
+{
+	struct dptx_port *dptx = service->cookie;
+	struct dcpdptx_connection_cmd cmd, resp;
+	int ret;
+	u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) |
+		     FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) |
+		     FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) |
+		     DCPDPTX_REMOTE_PORT_CONNECTED;
+
+	trace_dptxport_validate_connection(dptx, core, atc, die);
+
+	cmd.target = cpu_to_le32(target);
+	cmd.unk = cpu_to_le32(0x100);
+	ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp,
+			       sizeof(resp), 40);
+	if (ret)
+		return ret;
+
+	if (le32_to_cpu(resp.target) != target)
+		return -EINVAL;
+	if (le32_to_cpu(resp.unk) != 0x100)
+		return -EINVAL;
+
+	return 0;
+}
+
+int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
+		     u8 die)
+{
+	struct dptx_port *dptx = service->cookie;
+	struct dcpdptx_connection_cmd cmd, resp;
+	int ret;
+	u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) |
+		     FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) |
+		     FIELD_PREP(DCPDPTX_REMOTE_PORT_DIE, die) |
+		     DCPDPTX_REMOTE_PORT_CONNECTED;
+
+	trace_dptxport_connect(dptx, core, atc, die);
+
+	cmd.target = cpu_to_le32(target);
+	cmd.unk = cpu_to_le32(0x100);
+	ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp,
+			       sizeof(resp), 24);
+	if (ret)
+		return ret;
+
+	if (le32_to_cpu(resp.target) != target)
+		return -EINVAL;
+	if (le32_to_cpu(resp.unk) != 0x100)
+		return -EINVAL;
+
+	return 0;
+}
+
+int dptxport_request_display(struct apple_epic_service *service)
+{
+	return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16);
+}
+
+int dptxport_release_display(struct apple_epic_service *service)
+{
+	return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16);
+}
+
+int dptxport_set_hpd(struct apple_epic_service *service, bool hpd)
+{
+	struct dcpdptx_hotplug_cmd cmd, resp;
+	int ret;
+
+	memset(&cmd, 0, sizeof(cmd));
+
+	if (hpd)
+		cmd.unk = cpu_to_le32(1);
+
+	ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp,
+			       sizeof(resp), 12);
+	if (ret)
+		return ret;
+	if (le32_to_cpu(resp.unk) != 1)
+		return -EINVAL;
+	return 0;
+}
+
+static int
+dptxport_call_get_max_drive_settings(struct apple_epic_service *service,
+				     void *reply_, size_t reply_size)
+{
+	struct dptxport_apcall_max_drive_settings *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->max_drive_settings[0] = cpu_to_le32(0x3);
+	reply->max_drive_settings[1] = cpu_to_le32(0x3);
+
+	return 0;
+}
+
+static int dptxport_call_get_max_link_rate(struct apple_epic_service *service,
+					   void *reply_, size_t reply_size)
+{
+	struct dptxport_apcall_link_rate *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->link_rate = cpu_to_le32(LINK_RATE_HBR3);
+
+	return 0;
+}
+
+static int dptxport_call_get_link_rate(struct apple_epic_service *service,
+				       void *reply_, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	struct dptxport_apcall_link_rate *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->link_rate = cpu_to_le32(dptx->link_rate);
+
+	return 0;
+}
+
+static int
+dptxport_call_will_change_link_config(struct apple_epic_service *service)
+{
+	struct dptx_port *dptx = service->cookie;
+
+	dptx->phy_ops.dp.set_lanes = 0;
+	dptx->phy_ops.dp.set_rate = 0;
+	dptx->phy_ops.dp.set_voltages = 0;
+
+	return 0;
+}
+
+static int
+dptxport_call_did_change_link_config(struct apple_epic_service *service)
+{
+	/* assume the link config did change and wait a little bit */
+	mdelay(10);
+	return 0;
+}
+
+static int dptxport_call_set_link_rate(struct apple_epic_service *service,
+				       const void *data, size_t data_size,
+				       void *reply_, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	const struct dptxport_apcall_link_rate *request = data;
+	struct dptxport_apcall_link_rate *reply = reply_;
+	u32 link_rate, phy_link_rate;
+	bool phy_set_rate = false;
+	int ret;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+	if (data_size < sizeof(*request))
+		return -EINVAL;
+
+	link_rate = le32_to_cpu(request->link_rate);
+	trace_dptxport_call_set_link_rate(dptx, link_rate);
+
+	switch (link_rate) {
+	case LINK_RATE_RBR:
+		phy_link_rate = 1620;
+		phy_set_rate = true;
+		break;
+	case LINK_RATE_HBR:
+		phy_link_rate = 2700;
+		phy_set_rate = true;
+		break;
+	case LINK_RATE_HBR2:
+		phy_link_rate = 5400;
+		phy_set_rate = true;
+		break;
+	case LINK_RATE_HBR3:
+		phy_link_rate = 8100;
+		phy_set_rate = true;
+		break;
+	case 0:
+		phy_link_rate = 0;
+		phy_set_rate = true;
+		break;
+	default:
+		dev_err(service->ep->dcp->dev,
+			"DPTXPort: Unsupported link rate 0x%x requested\n",
+			link_rate);
+		link_rate = 0;
+		phy_set_rate = false;
+		break;
+	}
+
+	if (phy_set_rate) {
+		dptx->phy_ops.dp.link_rate = phy_link_rate;
+		dptx->phy_ops.dp.set_rate = 1;
+
+		if (dptx->atcphy) {
+			ret = phy_configure(dptx->atcphy, &dptx->phy_ops);
+			if (ret)
+				return ret;
+		}
+
+		//if (dptx->phy_ops.dp.set_rate)
+		dptx->link_rate = dptx->pending_link_rate = link_rate;
+
+	}
+
+	//dptx->pending_link_rate = link_rate;
+	reply->retcode = cpu_to_le32(0);
+	reply->link_rate = cpu_to_le32(link_rate);
+
+	return 0;
+}
+
+static int dptxport_call_get_supports_hpd(struct apple_epic_service *service,
+					  void *reply_, size_t reply_size)
+{
+	struct dptxport_apcall_get_support *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->supported = cpu_to_le32(0);
+	return 0;
+}
+
+static int
+dptxport_call_get_supports_downspread(struct apple_epic_service *service,
+				      void *reply_, size_t reply_size)
+{
+	struct dptxport_apcall_get_support *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->supported = cpu_to_le32(0);
+	return 0;
+}
+
+static int dptxport_call(struct apple_epic_service *service, u32 idx,
+			 const void *data, size_t data_size, void *reply,
+			 size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	trace_dptxport_apcall(dptx, idx, data_size);
+
+	switch (idx) {
+	case DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG:
+		return dptxport_call_will_change_link_config(service);
+	case DPTX_APCALL_DID_CHANGE_LINK_CONFIG:
+		return dptxport_call_did_change_link_config(service);
+	case DPTX_APCALL_GET_MAX_LINK_RATE:
+		return dptxport_call_get_max_link_rate(service, reply,
+						       reply_size);
+	case DPTX_APCALL_GET_LINK_RATE:
+		return dptxport_call_get_link_rate(service, reply, reply_size);
+	case DPTX_APCALL_SET_LINK_RATE:
+		return dptxport_call_set_link_rate(service, data, data_size,
+						   reply, reply_size);
+	case DPTX_APCALL_GET_SUPPORTS_HPD:
+		return dptxport_call_get_supports_hpd(service, reply,
+						      reply_size);
+	case DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD:
+		return dptxport_call_get_supports_downspread(service, reply,
+							     reply_size);
+	case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS:
+		return dptxport_call_get_max_drive_settings(service, reply,
+							    reply_size);
+	default:
+		/* just try to ACK and hope for the best... */
+		dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n",
+			idx);
+		fallthrough;
+	/* we can silently ignore and just ACK these calls */
+	case DPTX_APCALL_ACTIVATE:
+	case DPTX_APCALL_DEACTIVATE:
+	case DPTX_APCALL_SET_DRIVE_SETTINGS:
+	case DPTX_APCALL_GET_DRIVE_SETTINGS:
+		memcpy(reply, data, min(reply_size, data_size));
+		if (reply_size > 4)
+			memset(reply, 0, 4);
+		return 0;
+	}
+}
+
+static void dptxport_init(struct apple_epic_service *service, const char *name,
+			  const char *class, s64 unit)
+{
+
+	if (strcmp(name, "dcpdptx-port-epic"))
+		return;
+	if (strcmp(class, "AppleDCPDPTXRemotePort"))
+		return;
+
+	trace_dptxport_init(service->ep->dcp, unit);
+
+	switch (unit) {
+	case 0:
+	case 1:
+		if (service->ep->dcp->dptxport[unit].enabled) {
+			dev_err(service->ep->dcp->dev,
+				"DPTXPort: unit %lld already exists\n", unit);
+			return;
+		}
+		service->ep->dcp->dptxport[unit].unit = unit;
+		service->ep->dcp->dptxport[unit].service = service;
+		service->ep->dcp->dptxport[unit].enabled = true;
+		service->cookie = (void *)&service->ep->dcp->dptxport[unit];
+		complete(&service->ep->dcp->dptxport[unit].enable_completion);
+		break;
+	default:
+		dev_err(service->ep->dcp->dev, "DPTXPort: invalid unit %lld\n",
+			unit);
+	}
+}
+
+static const struct apple_epic_service_ops dptxep_ops[] = {
+	{
+		.name = "AppleDCPDPTXRemotePort",
+		.init = dptxport_init,
+		.call = dptxport_call,
+	},
+	{}
+};
+
+int dptxep_init(struct apple_dcp *dcp)
+{
+	int ret;
+	u32 port;
+	unsigned long timeout = msecs_to_jiffies(1000);
+
+	init_completion(&dcp->dptxport[0].enable_completion);
+	init_completion(&dcp->dptxport[1].enable_completion);
+
+	dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops);
+	if (IS_ERR(dcp->dptxep))
+		return PTR_ERR(dcp->dptxep);
+
+	ret = afk_start(dcp->dptxep);
+	if (ret)
+		return ret;
+
+	for (port = 0; port < dcp->hw.num_dptx_ports; port++) {
+		ret = wait_for_completion_timeout(&dcp->dptxport[port].enable_completion,
+						timeout);
+		if (!ret)
+			return -ETIMEDOUT;
+		else if (ret < 0)
+			return ret;
+		timeout = ret;
+	}
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h
new file mode 100644
index 00000000000000..efd1d5005f56da
--- /dev/null
+++ b/drivers/gpu/drm/apple/dptxep.h
@@ -0,0 +1,66 @@
+#ifndef __APPLE_DCP_DPTXEP_H__
+#define __APPLE_DCP_DPTXEP_H__
+
+#include <linux/phy/phy.h>
+#include <linux/mux/consumer.h>
+
+enum dptx_apcall {
+	DPTX_APCALL_ACTIVATE = 0,
+	DPTX_APCALL_DEACTIVATE = 1,
+	DPTX_APCALL_GET_MAX_DRIVE_SETTINGS = 2,
+	DPTX_APCALL_SET_DRIVE_SETTINGS = 3,
+	DPTX_APCALL_GET_DRIVE_SETTINGS = 4,
+	DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG = 5,
+	DPTX_APCALL_DID_CHANGE_LINK_CONFIG = 6,
+	DPTX_APCALL_GET_MAX_LINK_RATE = 7,
+	DPTX_APCALL_GET_LINK_RATE = 8,
+	DPTX_APCALL_SET_LINK_RATE = 9,
+	DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10,
+	DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11,
+	DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12,
+	DPTX_APCALL_GET_DOWN_SPREAD = 13,
+	DPTX_APCALL_SET_DOWN_SPREAD = 14,
+	DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15,
+	DPTX_APCALL_SET_LANE_MAP = 16,
+	DPTX_APCALL_GET_SUPPORTS_HPD = 17,
+	DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18,
+	DPTX_APCALL_INACTIVE_SINK_DETECTED = 19,
+	DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20,
+	DPTX_APCALL_DEVICE_NOT_RESPONDING = 21,
+	DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22,
+	DPTX_APCALL_DEVICE_NOT_STARTED = 23,
+};
+
+#define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0)
+#define DCPDPTX_REMOTE_PORT_ATC GENMASK(7, 4)
+#define DCPDPTX_REMOTE_PORT_DIE GENMASK(11, 8)
+#define DCPDPTX_REMOTE_PORT_CONNECTED BIT(15)
+
+enum dptx_link_rate {
+	LINK_RATE_RBR = 0x06,
+	LINK_RATE_HBR = 0x0a,
+	LINK_RATE_HBR2 = 0x14,
+	LINK_RATE_HBR3 = 0x1e,
+};
+
+struct apple_epic_service;
+
+struct dptx_port {
+	bool enabled, connected;
+	struct completion enable_completion;
+	u32 unit;
+	struct apple_epic_service *service;
+	union phy_configure_opts phy_ops;
+	struct phy *atcphy;
+	struct mux_control *mux;
+	u32 link_rate, pending_link_rate;
+};
+
+int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
+				 u8 atc, u8 die);
+int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
+		     u8 die);
+int dptxport_request_display(struct apple_epic_service *service);
+int dptxport_release_display(struct apple_epic_service *service);
+int dptxport_set_hpd(struct apple_epic_service *service, bool hpd);
+#endif
diff --git a/drivers/gpu/drm/apple/ibootep.c b/drivers/gpu/drm/apple/ibootep.c
new file mode 100644
index 00000000000000..ae4bc8a69f2a8d
--- /dev/null
+++ b/drivers/gpu/drm/apple/ibootep.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2023 */
+
+#include <linux/completion.h>
+
+#include "afk.h"
+#include "dcp.h"
+
+static void disp_service_init(struct apple_epic_service *service, const char *name,
+			const char *class, s64 unit)
+{
+}
+
+
+static const struct apple_epic_service_ops ibootep_ops[] = {
+	{
+		.name = "disp0-service",
+		.init = disp_service_init,
+	},
+	{}
+};
+
+int ibootep_init(struct apple_dcp *dcp)
+{
+	dcp->ibootep = afk_init(dcp, DISP0_ENDPOINT, ibootep_ops);
+	afk_start(dcp->ibootep);
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index dde87b5d4052d5..a837af1bd0a5e8 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -270,11 +270,6 @@ int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx)
 	return 0;
 }
 
-struct dimension {
-	s64 total, front_porch, sync_width, active;
-	s64 precise_sync_rate;
-};
-
 static int parse_dimension(struct dcp_parse_ctx *handle, struct dimension *dim)
 {
 	struct iterator it;
@@ -445,10 +440,14 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 		if (!IS_ERR_OR_NULL(key))
 			kfree(key);
 
-		if (ret)
+		if (ret) {
+			trace_iomfb_parse_mode_fail(id, &horiz, &vert, best_color_mode, is_virtual, *score);
 			return ret;
+		}
 	}
 
+	trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score);
+
 	/*
 	 * Reject modes without valid color mode.
 	 */
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index 6d8717873cdd6c..f9cc3718fa84f7 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -25,6 +25,11 @@ struct dcp_display_mode {
 	u32 timing_mode_id;
 };
 
+struct dimension {
+	s64 total, front_porch, sync_width, active;
+	s64 precise_sync_rate;
+};
+
 int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx);
 struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 					 unsigned int *count, int width_mm,
diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c
new file mode 100644
index 00000000000000..5383a83f1e6c28
--- /dev/null
+++ b/drivers/gpu/drm/apple/systemep.c
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
+
+#include <linux/completion.h>
+
+#include "afk.h"
+#include "dcp.h"
+
+static bool enable_verbose_logging;
+module_param(enable_verbose_logging, bool, 0644);
+MODULE_PARM_DESC(enable_verbose_logging, "Enable DCP firmware verbose logging");
+
+/*
+ * Serialized setProperty("gAFKConfigLogMask", 0xffff) IPC call which
+ * will set the DCP firmware log level to the most verbose setting
+ */
+#define SYSTEM_SET_PROPERTY 0x43
+static const u8 setprop_gAFKConfigLogMask_ffff[] = {
+	0x14, 0x00, 0x00, 0x00, 0x67, 0x41, 0x46, 0x4b, 0x43, 0x6f,
+	0x6e, 0x66, 0x69, 0x67, 0x4c, 0x6f, 0x67, 0x4d, 0x61, 0x73,
+	0x6b, 0x00, 0x00, 0x00, 0xd3, 0x00, 0x00, 0x00, 0x40, 0x00,
+	0x00, 0x84, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+};
+
+struct systemep_work {
+	struct apple_epic_service *service;
+	struct work_struct work;
+};
+
+static void system_log_work(struct work_struct *work_)
+{
+	struct systemep_work *work =
+		container_of(work_, struct systemep_work, work);
+
+	afk_send_command(work->service, SYSTEM_SET_PROPERTY,
+			 setprop_gAFKConfigLogMask_ffff,
+			 sizeof(setprop_gAFKConfigLogMask_ffff), NULL,
+			 sizeof(setprop_gAFKConfigLogMask_ffff), NULL);
+	complete(&work->service->ep->dcp->systemep_done);
+	kfree(work);
+}
+
+static void system_init(struct apple_epic_service *service, const char *name,
+			const char *class, s64 unit)
+{
+	struct systemep_work *work;
+
+	if (!enable_verbose_logging)
+		return;
+
+	/*
+	 * We're called from the service message handler thread and can't
+	 * dispatch blocking message from there.
+	 */
+	work = kzalloc(sizeof(*work), GFP_KERNEL);
+	if (!work)
+		return;
+
+	work->service = service;
+	INIT_WORK(&work->work, system_log_work);
+	schedule_work(&work->work);
+}
+
+static void powerlog_init(struct apple_epic_service *service, const char *name,
+			  const char *class, s64 unit)
+{
+}
+
+static const struct apple_epic_service_ops systemep_ops[] = {
+	{
+		.name = "system",
+		.init = system_init,
+	},
+	{
+		.name = "powerlog-service",
+		.init = powerlog_init,
+	},
+	{}
+};
+
+int systemep_init(struct apple_dcp *dcp)
+{
+	init_completion(&dcp->systemep_done);
+
+	dcp->systemep = afk_init(dcp, SYSTEM_ENDPOINT, systemep_ops);
+	afk_start(dcp->systemep);
+
+	if (!enable_verbose_logging)
+		return 0;
+
+	/*
+	 * Timeouts aren't really fatal here: in the worst case we just weren't
+	 * able to enable additional debug prints inside DCP
+	 */
+	if (!wait_for_completion_timeout(&dcp->systemep_done,
+					 msecs_to_jiffies(MSEC_PER_SEC)))
+		dev_err(dcp->dev, "systemep: couldn't enable verbose logs\n");
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index 6b3d9886a4164e..6edc9f1d5db919 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -8,6 +8,7 @@
 #define _TRACE_DCP_H
 
 #include "afk.h"
+#include "dptxep.h"
 #include "dcp-internal.h"
 #include "parser.h"
 
@@ -36,6 +37,43 @@
 			 { EPIC_CAT_REPLY, "reply" },         \
 			 { EPIC_CAT_COMMAND, "command" })
 
+#define show_dptxport_apcall(idx)                                              \
+	__print_symbolic(                                                     \
+		idx, { DPTX_APCALL_ACTIVATE, "activate" },                    \
+		{ DPTX_APCALL_DEACTIVATE, "deactivate" },                     \
+		{ DPTX_APCALL_GET_MAX_DRIVE_SETTINGS,                         \
+		  "get_max_drive_settings" },                                 \
+		{ DPTX_APCALL_SET_DRIVE_SETTINGS, "set_drive_settings" },     \
+		{ DPTX_APCALL_GET_DRIVE_SETTINGS, "get_drive_settings" },     \
+		{ DPTX_APCALL_WILL_CHANGE_LINKG_CONFIG,                       \
+		  "will_change_link_config" },                                \
+		{ DPTX_APCALL_DID_CHANGE_LINK_CONFIG,                         \
+		  "did_change_link_config" },                                 \
+		{ DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" },       \
+		{ DPTX_APCALL_GET_LINK_RATE, "get_link_rate" },               \
+		{ DPTX_APCALL_SET_LINK_RATE, "set_link_rate" },               \
+		{ DPTX_APCALL_GET_ACTIVE_LANE_COUNT,                          \
+		  "get_active_lane_count" },                                  \
+		{ DPTX_APCALL_SET_ACTIVE_LANE_COUNT,                          \
+		  "set_active_lane_count" },                                  \
+		{ DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD,                       \
+		  "get_supports_downspread" },                                \
+		{ DPTX_APCALL_GET_DOWN_SPREAD, "get_downspread" },            \
+		{ DPTX_APCALL_SET_DOWN_SPREAD, "set_downspread" },            \
+		{ DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING,                      \
+		  "get_supports_lane_mapping" },                              \
+		{ DPTX_APCALL_SET_LANE_MAP, "set_lane_map" },                 \
+		{ DPTX_APCALL_GET_SUPPORTS_HPD, "get_supports_hpd" },         \
+		{ DPTX_APCALL_FORCE_HOTPLUG_DETECT, "force_hotplug_detect" }, \
+		{ DPTX_APCALL_INACTIVE_SINK_DETECTED,                         \
+		  "inactive_sink_detected" },                                 \
+		{ DPTX_APCALL_SET_TILED_DISPLAY_HINTS,                        \
+		  "set_tiled_display_hints" },                                \
+		{ DPTX_APCALL_DEVICE_NOT_RESPONDING,                          \
+		  "device_not_responding" },                                  \
+		{ DPTX_APCALL_DEVICE_BUSY_TIMEOUT, "device_busy_timeout" },   \
+		{ DPTX_APCALL_DEVICE_NOT_STARTED, "device_not_started" })
+
 TRACE_EVENT(dcp_recv_msg,
 	    TP_PROTO(struct apple_dcp *dcp, u8 endpoint, u64 message),
 	    TP_ARGS(dcp, endpoint, message),
@@ -263,6 +301,108 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated,
 	    )
 );
 
+DECLARE_EVENT_CLASS(iomfb_parse_mode_template,
+	    TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
+	    TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score),
+
+	    TP_STRUCT__entry(__field(s64, id)
+			     __field_struct(struct dimension, horiz)
+			     __field_struct(struct dimension, vert)
+			     __field(s64, best_color_mode)
+			     __field(bool, is_virtual)
+			     __field(s64, score)),
+
+	    TP_fast_assign(__entry->id = id;
+			   __entry->horiz = *horiz;
+			   __entry->vert = *vert;
+			   __entry->best_color_mode = best_color_mode;
+			   __entry->is_virtual = is_virtual;
+			   __entry->score = score;),
+
+	    TP_printk("id: %lld, best_color_mode: %lld, resolution:%lldx%lld virtual: %d, score: %lld",
+		      __entry->id, __entry->best_color_mode,
+		      __entry->horiz.active, __entry->vert.active,
+		      __entry->is_virtual, __entry->score));
+
+DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_success,
+	    TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
+	    TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score));
+
+DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail,
+	    TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
+	    TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score));
+
+TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit),
+	    TP_ARGS(dcp, unit),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
+				     __field(u64, unit)),
+
+	    TP_fast_assign(__assign_str(devname);
+			   __entry->unit = unit;),
+
+	    TP_printk("%s: dptxport unit %lld initialized", __get_str(devname),
+		      __entry->unit));
+
+TRACE_EVENT(
+	dptxport_apcall,
+	TP_PROTO(struct dptx_port *dptx, int idx, size_t len),
+	TP_ARGS(dptx, idx, len),
+
+	TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
+			__field(u32, unit) __field(int, idx) __field(size_t, len)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->unit = dptx->unit; __entry->idx = idx; __entry->len = len;),
+
+	TP_printk("%s: dptx%d: AP Call %d (%s) with len %lu", __get_str(devname),
+		  __entry->unit,
+		  __entry->idx, show_dptxport_apcall(__entry->idx), __entry->len));
+
+TRACE_EVENT(
+	dptxport_validate_connection,
+	TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die),
+	TP_ARGS(dptx, core, atc, die),
+
+	TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
+			 __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;),
+
+	TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname),
+		  __entry->unit, __entry->core, __entry->atc, __entry->die));
+
+TRACE_EVENT(
+	dptxport_connect,
+	TP_PROTO(struct dptx_port *dptx, u8 core, u8 atc, u8 die),
+	TP_ARGS(dptx, core, atc, die),
+
+	TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
+			 __field(u32, unit) __field(u8, core) __field(u8, atc) __field(u8, die)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->unit = dptx->unit; __entry->core = core; __entry->atc = atc; __entry->die = die;),
+
+	TP_printk("%s: dptx%d: core %d, atc %d, die %d", __get_str(devname),
+		  __entry->unit, __entry->core, __entry->atc, __entry->die));
+
+TRACE_EVENT(
+	dptxport_call_set_link_rate,
+	TP_PROTO(struct dptx_port *dptx, u32 link_rate),
+	TP_ARGS(dptx, link_rate),
+
+	TP_STRUCT__entry(__string(devname, dev_name(dptx->service->ep->dcp->dev))
+			 __field(u32, unit)
+			 __field(u32, link_rate)),
+
+	TP_fast_assign(__assign_str(devname);
+		       __entry->unit = dptx->unit;
+		       __entry->link_rate = link_rate;),
+
+	TP_printk("%s: dptx%d: link rate 0x%x", __get_str(devname), __entry->unit,
+		  __entry->link_rate));
+
 TRACE_EVENT(iomfb_brightness,
 	    TP_PROTO(struct apple_dcp *dcp, u32 nits),
 	    TP_ARGS(dcp, nits),

From 4d45ef798e2a3701de64765b3a1e87697de42099 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 Nov 2023 10:06:45 +0100
Subject: [PATCH 0741/1027] drm: apple: Move offsets for rt_bandwidth callback
 to DT

The offsets differ for every DCP instance. Instead of hardcoding offsets
for each SoC family offsets and calculate the instance offset move
everything to the device tree. This helps multi die SoCs since there is
and unexpected offset between both dies.
On multi die SoCs device tree changes were necessary to avoid
translating the PMGR reg via the seconds die "ranges" property.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   |   8 ++
 drivers/gpu/drm/apple/dcp.c            | 122 ++++++++++++++++++++++++-
 drivers/gpu/drm/apple/iomfb_template.c |  51 +++++------
 3 files changed, 151 insertions(+), 30 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 849bd2937ebb9d..7fe2c3f98aa377 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -6,6 +6,7 @@
 
 #include <linux/backlight.h>
 #include <linux/device.h>
+#include <linux/ioport.h>
 #include <linux/mutex.h>
 #include <linux/mux/consumer.h>
 #include <linux/phy/phy.h>
@@ -136,6 +137,13 @@ struct apple_dcp {
 	struct resource *disp_registers[MAX_DISP_REGISTERS];
 	unsigned int nr_disp_registers;
 
+	struct resource disp_bw_scratch_res;
+	struct resource disp_bw_doorbell_res;
+	u32 disp_bw_scratch_index;
+	u32 disp_bw_scratch_offset;
+	u32 disp_bw_doorbell_index;
+	u32 disp_bw_doorbell_offset;
+
 	u32 index;
 
 	/* Bitmap of memory descriptors used for mappings made by the DCP */
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 735acebdb289a3..c00dc16125318a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -13,6 +13,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
+#include <linux/of_address.h>
 #include <linux/of_device.h>
 #include <linux/of_platform.h>
 #include <linux/slab.h>
@@ -475,11 +476,108 @@ static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp)
 	return ret;
 }
 
+static int dcp_get_bw_scratch_reg(struct apple_dcp *dcp, u32 expected)
+{
+	struct of_phandle_args ph_args;
+	u32 addr_idx, disp_idx, offset;
+	int ret;
+
+	ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-scratch",
+				   "#apple,bw-scratch-cells", 0, &ph_args);
+	if (ret < 0) {
+		dev_err(dcp->dev, "Failed to read 'apple,bw-scratch': %d\n", ret);
+		return ret;
+	}
+
+	if (ph_args.args_count != 3) {
+		dev_err(dcp->dev, "Unexpected 'apple,bw-scratch' arg count %d\n",
+			ph_args.args_count);
+		ret = -EINVAL;
+		goto err_of_node_put;
+	}
+
+	addr_idx = ph_args.args[0];
+	disp_idx = ph_args.args[1];
+	offset = ph_args.args[2];
+
+	if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) {
+		dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-scratch': %d\n",
+			disp_idx);
+		ret = -EINVAL;
+		goto err_of_node_put;
+	}
+
+	ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_scratch_res);
+	if (ret < 0) {
+		dev_err(dcp->dev, "Failed to get 'apple,bw-scratch' resource %d from %pOF\n",
+			addr_idx, ph_args.np);
+		goto err_of_node_put;
+	}
+	if (offset > resource_size(&dcp->disp_bw_scratch_res) - 4) {
+		ret = -EINVAL;
+		goto err_of_node_put;
+	}
+
+	dcp->disp_registers[disp_idx] = &dcp->disp_bw_scratch_res;
+	dcp->disp_bw_scratch_index = disp_idx;
+	dcp->disp_bw_scratch_offset = offset;
+	ret = 0;
+
+err_of_node_put:
+	of_node_put(ph_args.np);
+	return ret;
+}
+
+static int dcp_get_bw_doorbell_reg(struct apple_dcp *dcp, u32 expected)
+{
+	struct of_phandle_args ph_args;
+	u32 addr_idx, disp_idx;
+	int ret;
+
+	ret = of_parse_phandle_with_args(dcp->dev->of_node, "apple,bw-doorbell",
+				   "#apple,bw-doorbell-cells", 0, &ph_args);
+	if (ret < 0) {
+		dev_err(dcp->dev, "Failed to read 'apple,bw-doorbell': %d\n", ret);
+		return ret;
+	}
+
+	if (ph_args.args_count != 2) {
+		dev_err(dcp->dev, "Unexpected 'apple,bw-doorbell' arg count %d\n",
+			ph_args.args_count);
+		ret = -EINVAL;
+		goto err_of_node_put;
+	}
+
+	addr_idx = ph_args.args[0];
+	disp_idx = ph_args.args[1];
+
+	if (disp_idx != expected || disp_idx >= MAX_DISP_REGISTERS) {
+		dev_err(dcp->dev, "Unexpected disp_reg value in 'apple,bw-doorbell': %d\n",
+			disp_idx);
+		ret = -EINVAL;
+		goto err_of_node_put;
+	}
+
+	ret = of_address_to_resource(ph_args.np, addr_idx, &dcp->disp_bw_doorbell_res);
+	if (ret < 0) {
+		dev_err(dcp->dev, "Failed to get 'apple,bw-doorbell' resource %d from %pOF\n",
+			addr_idx, ph_args.np);
+		goto err_of_node_put;
+	}
+	dcp->disp_bw_doorbell_index = disp_idx;
+	dcp->disp_registers[disp_idx] = &dcp->disp_bw_doorbell_res;
+	ret = 0;
+
+err_of_node_put:
+	of_node_put(ph_args.np);
+	return ret;
+}
+
 static int dcp_get_disp_regs(struct apple_dcp *dcp)
 {
 	struct platform_device *pdev = to_platform_device(dcp->dev);
 	int count = pdev->num_resources - 1;
-	int i;
+	int i, ret;
 
 	if (count <= 0 || count > MAX_DISP_REGISTERS)
 		return -EINVAL;
@@ -489,6 +587,20 @@ static int dcp_get_disp_regs(struct apple_dcp *dcp)
 			platform_get_resource(pdev, IORESOURCE_MEM, 1 + i);
 	}
 
+	/* load pmgr bandwidth scratch resource and offset */
+	ret = dcp_get_bw_scratch_reg(dcp, count);
+	if (ret < 0)
+		return ret;
+	count += 1;
+
+	/* load pmgr bandwidth doorbell resource if present (only on t8103) */
+	if (of_property_present(dcp->dev->of_node, "apple,bw-doorbell")) {
+		ret = dcp_get_bw_doorbell_reg(dcp, count);
+		if (ret < 0)
+			return ret;
+		count += 1;
+	}
+
 	dcp->nr_disp_registers = count;
 	return 0;
 }
@@ -727,6 +839,14 @@ static int dcp_platform_probe(struct platform_device *pdev)
 	if (fw_compat == DCP_FIRMWARE_UNKNOWN)
 		return -ENODEV;
 
+	/* Check for "apple,bw-scratch" to avoid probing appledrm with outdated
+	 * device trees. This prevents replacing simpledrm and ending up without
+	 * display.
+	 */
+	if (!of_property_present(dev->of_node, "apple,bw-scratch"))
+		return dev_err_probe(dev, -ENODEV, "Incompatible devicetree! "
+			"Use devicetree matching this kernel.\n");
+
 	dcp = devm_kzalloc(dev, sizeof(*dcp), GFP_KERNEL);
 	if (!dcp)
 		return -ENOMEM;
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 64fe703061fe7d..3590bcffbdaacb 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -33,11 +33,7 @@
 #include "version_utils.h"
 
 /* Register defines used in bandwidth setup structure */
-#define REG_SCRATCH (0x14)
-#define REG_SCRATCH_T600X (0x988)
-#define REG_SCRATCH_T602X (0x1208)
-#define REG_DOORBELL (0x0)
-#define REG_DOORBELL_BIT (2)
+#define REG_DOORBELL_BIT(idx) (2 + (idx))
 
 struct dcp_wait_cookie {
 	struct kref refcount;
@@ -665,34 +661,31 @@ static struct dcp_allocate_bandwidth_resp dcpep_cb_allocate_bandwidth(struct app
 
 static struct dcp_rt_bandwidth dcpep_cb_rt_bandwidth(struct apple_dcp *dcp)
 {
-	if (dcp->disp_registers[5] && dcp->disp_registers[6]) {
-		return (struct dcp_rt_bandwidth){
-			.reg_scratch =
-				dcp->disp_registers[5]->start + REG_SCRATCH,
-			.reg_doorbell =
-				dcp->disp_registers[6]->start + REG_DOORBELL,
-			.doorbell_bit = REG_DOORBELL_BIT,
-
-			.padding[3] = 0x4, // XXX: required by 11.x firmware
-		};
-	} else if (dcp->disp_registers[4]) {
-		u32 offset = REG_SCRATCH_T600X;
-		if (of_device_is_compatible(dcp->dev->of_node, "apple,t6020-dcp"))
-			offset = REG_SCRATCH_T602X;
-
-		return (struct dcp_rt_bandwidth){
-			.reg_scratch = dcp->disp_registers[4]->start +
-				       offset,
-			.reg_doorbell = 0,
-			.doorbell_bit = 0,
-		};
-	} else {
-		return (struct dcp_rt_bandwidth){
+	struct dcp_rt_bandwidth rt_bw = (struct dcp_rt_bandwidth){
 			.reg_scratch = 0,
 			.reg_doorbell = 0,
 			.doorbell_bit = 0,
-		};
+	};
+
+	if (dcp->disp_bw_scratch_index) {
+		u32 offset = dcp->disp_bw_scratch_offset;
+		u32 index = dcp->disp_bw_scratch_index;
+		rt_bw.reg_scratch = dcp->disp_registers[index]->start + offset;
 	}
+
+	if (dcp->disp_bw_doorbell_index) {
+		u32 index = dcp->disp_bw_doorbell_index;
+		rt_bw.reg_doorbell = dcp->disp_registers[index]->start;
+		rt_bw.doorbell_bit = REG_DOORBELL_BIT(dcp->index);
+		/*
+		 * This is most certainly not padding. t8103-dcp crashes without
+		 * setting this immediately during modeset on 12.3 and 13.5
+		 * firmware.
+		 */
+		rt_bw.padding[3] = 0x4;
+	}
+
+	return rt_bw;
 }
 
 static struct dcp_set_frame_sync_props_resp

From e9ea718c531a1faf1018f83b2f9c5b90bad75e6c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 17 Aug 2023 23:52:39 +0200
Subject: [PATCH 0742/1027] drm: apple: iomfb: Do not match/create PMU service
 for dcpext

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   |  3 +++
 drivers/gpu/drm/apple/dcp.c            |  2 ++
 drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++++++++++++
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  2 +-
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  2 +-
 5 files changed, 23 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 7fe2c3f98aa377..64025d23282d1c 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -183,6 +183,9 @@ struct apple_dcp {
 	/* clear all surfaces on init */
 	bool surfaces_cleared;
 
+	/* is dcpext / requires dptx */
+	bool is_dptx;
+
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index c00dc16125318a..cc0c6d887870f3 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -700,6 +700,8 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (IS_ERR(dcp->coproc_reg))
 		return PTR_ERR(dcp->coproc_reg);
 
+	dcp->is_dptx = dcp->phy != NULL;
+
 	of_property_read_u32(dev->of_node, "apple,dcp-index",
 					   &dcp->index);
 	of_property_read_u32(dev->of_node, "apple,dptx-phy",
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 3590bcffbdaacb..945e972a7c4679 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -135,6 +135,10 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v
 static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
 {
 	trace_iomfb_callback(dcp, tag, __func__);
+
+	if (dcp->is_dptx)
+		return true;
+
 	iomfb_a358_vi_set_temperature_hint(dcp, false,
 					   complete_vi_set_temperature_hint,
 					   NULL);
@@ -158,6 +162,12 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void
 {
 	trace_iomfb_callback(dcp, tag, __func__);
 
+	if (dcp->is_dptx) {
+		u8 *ret = out;
+		ret[0] = 1;
+		return true;
+	}
+
 	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
 				       out);
 
@@ -1044,6 +1054,11 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
 	};
 }
 
+static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp)
+{
+	return !dcp->is_dptx;
+}
+
 static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp)
 {
 	return dcp_has_panel(dcp);
@@ -1101,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
 	      struct iomfb_property);
 TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
 		 struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
+TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8);
 TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8);
 
 /*
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index abcd1e4aab3ff8..8b4d87ad9012bd 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[106] = trampoline_nop, /* remove_property */
 	[107] = trampoline_true, /* create_provider_service */
 	[108] = trampoline_true, /* create_product_service */
-	[109] = trampoline_true, /* create_pmu_service */
+	[109] = trampoline_create_pmu_service,
 	[110] = trampoline_true, /* create_iomfb_service */
 	[111] = trampoline_create_backlight_service,
 	[116] = dcpep_cb_boot_1,
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 9c692ba3c81b92..0689c0a593f784 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[107] = trampoline_nop, /* remove_property */
 	[108] = trampoline_true, /* create_provider_service */
 	[109] = trampoline_true, /* create_product_service */
-	[110] = trampoline_true, /* create_pmu_service */
+	[110] = trampoline_create_pmu_service,
 	[111] = trampoline_true, /* create_iomfb_service */
 	[112] = trampoline_create_backlight_service,
 	[113] = trampoline_true, /* create_nvram_servce? */

From 20aa8119544cb2a37bfbe04c9f9b686b0bab461f Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 9 Apr 2023 22:44:35 +0200
Subject: [PATCH 0743/1027] drm: apple: afk: Adapt to macOS 13.3 firmware

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 9 ++++++---
 drivers/gpu/drm/apple/afk.h | 2 ++
 2 files changed, 8 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index d577f4ec055b03..f1e8bdfcc319a2 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -495,7 +495,7 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
 	service = afk_epic_find_service(ep, channel);
 
 	if (!service) {
-		if (type != EPIC_TYPE_NOTIFY) {
+		if (type != EPIC_TYPE_NOTIFY && type != EPIC_TYPE_REPLY) {
 			dev_err(ep->dcp->dev,
 				"AFK[ep:%02x]: expected notify but got 0x%x on channel %d\n",
 				ep->endpoint, type, channel);
@@ -807,12 +807,15 @@ int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
 	eshdr = ep->txbfr.buf + wptr;
 	memset(eshdr, 0, sizeof(*eshdr));
 	eshdr->length = cpu_to_le32(payload_len);
-	eshdr->version = 3;
+	eshdr->version = 4;
 	eshdr->category = ecat;
 	eshdr->type = cpu_to_le16(stype);
 	eshdr->timestamp = cpu_to_le64(0);
 	eshdr->tag = cpu_to_le16(tag);
-	eshdr->inline_len = cpu_to_le16(0);
+	if (ecat == EPIC_CAT_REPLY)
+		eshdr->inline_len = cpu_to_le16(payload_len - 4);
+	else
+		eshdr->inline_len = cpu_to_le16(0);
 	wptr += sizeof(*eshdr);
 
 	memcpy(ep->txbfr.buf + wptr, payload, payload_len);
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index fe4ed35159ace0..1fdb4100352b25 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -106,6 +106,8 @@ struct epic_cmd {
 	__le64 txbuf;
 	__le32 rxlen;
 	__le32 txlen;
+	u8 rxcookie;
+	u8 txcookie;
 } __attribute__((packed));
 
 struct epic_service_call {

From 710df74e6218b83bc7556dd17ba5f3f8c861fb37 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 28 Apr 2023 22:24:59 +0200
Subject: [PATCH 0744/1027] drm: apple: dptx: Port APCALL to macOS 13.3
 firmware

The 13.3 firmware has an additional get_max_lane_count call inserted
with ID 10.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 23 +++++++++++++++++++++++
 drivers/gpu/drm/apple/dptxep.h | 29 +++++++++++++++--------------
 drivers/gpu/drm/apple/trace.h  |  2 ++
 3 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 2002f540d0e729..7179cc35991d3d 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -29,6 +29,13 @@ struct dptxport_apcall_link_rate {
 	u8 _unk1[12];
 } __attribute__((packed));
 
+struct dptxport_apcall_lane_count {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le64 lane_count;
+	u8 _unk1[8];
+} __attribute__((packed));
+
 struct dptxport_apcall_get_support {
 	__le32 retcode;
 	u8 _unk0[12];
@@ -158,6 +165,20 @@ static int dptxport_call_get_max_link_rate(struct apple_epic_service *service,
 	return 0;
 }
 
+static int dptxport_call_get_max_lane_count(struct apple_epic_service *service,
+					   void *reply_, size_t reply_size)
+{
+	struct dptxport_apcall_lane_count *reply = reply_;
+
+	if (reply_size < sizeof(*reply))
+		return -EINVAL;
+
+	reply->retcode = cpu_to_le32(0);
+	reply->lane_count = cpu_to_le64(4);
+
+	return 0;
+}
+
 static int dptxport_call_get_link_rate(struct apple_epic_service *service,
 				       void *reply_, size_t reply_size)
 {
@@ -311,6 +332,8 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 	case DPTX_APCALL_SET_LINK_RATE:
 		return dptxport_call_set_link_rate(service, data, data_size,
 						   reply, reply_size);
+	case DPTX_APCALL_GET_MAX_LANE_COUNT:
+		return dptxport_call_get_max_lane_count(service, reply, reply_size);
 	case DPTX_APCALL_GET_SUPPORTS_HPD:
 		return dptxport_call_get_supports_hpd(service, reply,
 						      reply_size);
diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h
index efd1d5005f56da..8f0483e7030b7a 100644
--- a/drivers/gpu/drm/apple/dptxep.h
+++ b/drivers/gpu/drm/apple/dptxep.h
@@ -15,20 +15,21 @@ enum dptx_apcall {
 	DPTX_APCALL_GET_MAX_LINK_RATE = 7,
 	DPTX_APCALL_GET_LINK_RATE = 8,
 	DPTX_APCALL_SET_LINK_RATE = 9,
-	DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 10,
-	DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 11,
-	DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 12,
-	DPTX_APCALL_GET_DOWN_SPREAD = 13,
-	DPTX_APCALL_SET_DOWN_SPREAD = 14,
-	DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 15,
-	DPTX_APCALL_SET_LANE_MAP = 16,
-	DPTX_APCALL_GET_SUPPORTS_HPD = 17,
-	DPTX_APCALL_FORCE_HOTPLUG_DETECT = 18,
-	DPTX_APCALL_INACTIVE_SINK_DETECTED = 19,
-	DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 20,
-	DPTX_APCALL_DEVICE_NOT_RESPONDING = 21,
-	DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 22,
-	DPTX_APCALL_DEVICE_NOT_STARTED = 23,
+	DPTX_APCALL_GET_MAX_LANE_COUNT = 10,
+	DPTX_APCALL_GET_ACTIVE_LANE_COUNT = 11,
+	DPTX_APCALL_SET_ACTIVE_LANE_COUNT = 12,
+	DPTX_APCALL_GET_SUPPORTS_DOWN_SPREAD = 13,
+	DPTX_APCALL_GET_DOWN_SPREAD = 14,
+	DPTX_APCALL_SET_DOWN_SPREAD = 15,
+	DPTX_APCALL_GET_SUPPORTS_LANE_MAPPING = 16,
+	DPTX_APCALL_SET_LANE_MAP = 17,
+	DPTX_APCALL_GET_SUPPORTS_HPD = 18,
+	DPTX_APCALL_FORCE_HOTPLUG_DETECT = 19,
+	DPTX_APCALL_INACTIVE_SINK_DETECTED = 20,
+	DPTX_APCALL_SET_TILED_DISPLAY_HINTS = 21,
+	DPTX_APCALL_DEVICE_NOT_RESPONDING = 22,
+	DPTX_APCALL_DEVICE_BUSY_TIMEOUT = 23,
+	DPTX_APCALL_DEVICE_NOT_STARTED = 24,
 };
 
 #define DCPDPTX_REMOTE_PORT_CORE GENMASK(3, 0)
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index 6edc9f1d5db919..814bc7f0864475 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -52,6 +52,8 @@
 		{ DPTX_APCALL_GET_MAX_LINK_RATE, "get_max_link_rate" },       \
 		{ DPTX_APCALL_GET_LINK_RATE, "get_link_rate" },               \
 		{ DPTX_APCALL_SET_LINK_RATE, "set_link_rate" },               \
+		{ DPTX_APCALL_GET_MAX_LANE_COUNT,                             \
+		  "get_max_lane_count" },                                     \
 		{ DPTX_APCALL_GET_ACTIVE_LANE_COUNT,                          \
 		  "get_active_lane_count" },                                  \
 		{ DPTX_APCALL_SET_ACTIVE_LANE_COUNT,                          \

From fc4561e70a3e6bcafd0f9d1c941f36b35f4b312a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 18 Aug 2023 00:05:15 +0200
Subject: [PATCH 0745/1027] drm: apple: dptx: port interface to macOS 13.5
 firmware

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 7179cc35991d3d..0ffcde99d0c070 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -65,7 +65,7 @@ int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
 
 	cmd.target = cpu_to_le32(target);
 	cmd.unk = cpu_to_le32(0x100);
-	ret = afk_service_call(service, 0, 14, &cmd, sizeof(cmd), 40, &resp,
+	ret = afk_service_call(service, 0, 12, &cmd, sizeof(cmd), 40, &resp,
 			       sizeof(resp), 40);
 	if (ret)
 		return ret;
@@ -93,7 +93,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
 
 	cmd.target = cpu_to_le32(target);
 	cmd.unk = cpu_to_le32(0x100);
-	ret = afk_service_call(service, 0, 13, &cmd, sizeof(cmd), 24, &resp,
+	ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp,
 			       sizeof(resp), 24);
 	if (ret)
 		return ret;
@@ -108,12 +108,12 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
 
 int dptxport_request_display(struct apple_epic_service *service)
 {
-	return afk_service_call(service, 0, 8, NULL, 0, 16, NULL, 0, 16);
+	return afk_service_call(service, 0, 6, NULL, 0, 16, NULL, 0, 16);
 }
 
 int dptxport_release_display(struct apple_epic_service *service)
 {
-	return afk_service_call(service, 0, 9, NULL, 0, 16, NULL, 0, 16);
+	return afk_service_call(service, 0, 7, NULL, 0, 16, NULL, 0, 16);
 }
 
 int dptxport_set_hpd(struct apple_epic_service *service, bool hpd)
@@ -126,7 +126,7 @@ int dptxport_set_hpd(struct apple_epic_service *service, bool hpd)
 	if (hpd)
 		cmd.unk = cpu_to_le32(1);
 
-	ret = afk_service_call(service, 8, 10, &cmd, sizeof(cmd), 12, &resp,
+	ret = afk_service_call(service, 8, 8, &cmd, sizeof(cmd), 12, &resp,
 			       sizeof(resp), 12);
 	if (ret)
 		return ret;

From ee880ca8e0cac0c6d2764208a7a0742ae9f6f9d4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 23:36:20 +0100
Subject: [PATCH 0746/1027] drm: apple: dptx: Add set_active_lanes APCALL

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 55 ++++++++++++++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 0ffcde99d0c070..23599f8c4c9c77 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -36,6 +36,13 @@ struct dptxport_apcall_lane_count {
 	u8 _unk1[8];
 } __attribute__((packed));
 
+struct dptxport_apcall_set_active_lane_count {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le64 lane_count;
+	u8 _unk1[8];
+} __packed;
+
 struct dptxport_apcall_get_support {
 	__le32 retcode;
 	u8 _unk0[12];
@@ -179,6 +186,51 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service,
 	return 0;
 }
 
+static int dptxport_call_set_active_lane_count(struct apple_epic_service *service,
+					       const void *data, size_t data_size,
+					       void *reply_, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	const struct dptxport_apcall_set_active_lane_count *request = data;
+	struct dptxport_apcall_set_active_lane_count *reply = reply_;
+	int ret = 0;
+	int retcode = 0;
+
+	if (reply_size < sizeof(*reply))
+		return -1;
+	if (data_size < sizeof(*request))
+		return -1;
+
+	u64 lane_count = cpu_to_le64(request->lane_count);
+
+	switch (lane_count) {
+	case 0 ... 2:
+	case 4:
+		dptx->phy_ops.dp.lanes = lane_count;
+		dptx->phy_ops.dp.set_lanes = 1;
+		break;
+	default:
+		dev_err(service->ep->dcp->dev, "set_active_lane_count: invalid lane count:%llu\n", lane_count);
+		retcode = 1;
+		lane_count = 0;
+		break;
+	}
+
+	if (dptx->phy_ops.dp.set_lanes) {
+		if (dptx->atcphy) {
+			ret = phy_configure(dptx->atcphy, &dptx->phy_ops);
+			if (ret)
+				return ret;
+		}
+		dptx->phy_ops.dp.set_lanes = 0;
+	}
+
+	reply->retcode = cpu_to_le32(retcode);
+	reply->lane_count = cpu_to_le64(lane_count);
+
+	return ret;
+}
+
 static int dptxport_call_get_link_rate(struct apple_epic_service *service,
 				       void *reply_, size_t reply_size)
 {
@@ -334,6 +386,9 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 						   reply, reply_size);
 	case DPTX_APCALL_GET_MAX_LANE_COUNT:
 		return dptxport_call_get_max_lane_count(service, reply, reply_size);
+        case DPTX_APCALL_SET_ACTIVE_LANE_COUNT:
+		return dptxport_call_set_active_lane_count(service, data, data_size,
+							   reply, reply_size);
 	case DPTX_APCALL_GET_SUPPORTS_HPD:
 		return dptxport_call_get_supports_hpd(service, reply,
 						      reply_size);

From 1cd2a274c54194a548400f9d256a5b4d309a186a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 23:37:27 +0100
Subject: [PATCH 0747/1027] drm: apple: dptx: Add DPTX_APCALL_ACTIVATE

Configures the phy to the correct dcp(ext) source by abusing submode in
the phy_set_mode_ext() call.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 22 +++++++++++++++++++++-
 1 file changed, 21 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 23599f8c4c9c77..a0f90f7153fccd 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -364,6 +364,24 @@ dptxport_call_get_supports_downspread(struct apple_epic_service *service,
 	return 0;
 }
 
+static int
+dptxport_call_activate(struct apple_epic_service *service,
+		       const void *data, size_t data_size,
+		       void *reply, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	const struct apple_dcp *dcp = service->ep->dcp;
+
+	// TODO: hack, use phy_set_mode to select the correct DCP(EXT) input
+	phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index);
+
+	memcpy(reply, data, min(reply_size, data_size));
+	if (reply_size > 4)
+		memset(reply, 0, 4);
+
+	return 0;
+}
+
 static int dptxport_call(struct apple_epic_service *service, u32 idx,
 			 const void *data, size_t data_size, void *reply,
 			 size_t reply_size)
@@ -398,13 +416,15 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 	case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS:
 		return dptxport_call_get_max_drive_settings(service, reply,
 							    reply_size);
+        case DPTX_APCALL_ACTIVATE:
+		return dptxport_call_activate(service, data, data_size,
+					      reply, reply_size);
 	default:
 		/* just try to ACK and hope for the best... */
 		dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n",
 			idx);
 		fallthrough;
 	/* we can silently ignore and just ACK these calls */
-	case DPTX_APCALL_ACTIVATE:
 	case DPTX_APCALL_DEACTIVATE:
 	case DPTX_APCALL_SET_DRIVE_SETTINGS:
 	case DPTX_APCALL_GET_DRIVE_SETTINGS:

From 6f2c106395591f337f0c219e681c8014fe1496d9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 23:44:08 +0100
Subject: [PATCH 0748/1027] drm: apple: dptx: Adapt dptxport_connect() to
 observed behavior

Adapt to behavior seen on j474s with dcp0 driving lpdptx-phy and
dp2hdmi using the macOS 13.5 firmware.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index a0f90f7153fccd..2c751c630a122d 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -90,6 +90,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
 {
 	struct dptx_port *dptx = service->cookie;
 	struct dcpdptx_connection_cmd cmd, resp;
+	u32 unk_field = 0x0; // seen as 0x100 under some conditions
 	int ret;
 	u32 target = FIELD_PREP(DCPDPTX_REMOTE_PORT_CORE, core) |
 		     FIELD_PREP(DCPDPTX_REMOTE_PORT_ATC, atc) |
@@ -99,7 +100,7 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
 	trace_dptxport_connect(dptx, core, atc, die);
 
 	cmd.target = cpu_to_le32(target);
-	cmd.unk = cpu_to_le32(0x100);
+	cmd.unk = cpu_to_le32(unk_field);
 	ret = afk_service_call(service, 0, 11, &cmd, sizeof(cmd), 24, &resp,
 			       sizeof(resp), 24);
 	if (ret)
@@ -107,8 +108,9 @@ int dptxport_connect(struct apple_epic_service *service, u8 core, u8 atc,
 
 	if (le32_to_cpu(resp.target) != target)
 		return -EINVAL;
-	if (le32_to_cpu(resp.unk) != 0x100)
-		return -EINVAL;
+	if (le32_to_cpu(resp.unk) != unk_field)
+		dev_notice(service->ep->dcp->dev, "unexpected unk field in reply: 0x%x (0x%x)\n",
+			  le32_to_cpu(resp.unk), unk_field);
 
 	return 0;
 }

From f7cd9b2ef1bee00587d2eaa916c81e5a6cad76f5 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Thu, 16 Nov 2023 19:38:49 +0900
Subject: [PATCH 0749/1027] drm: apple: afk: Clear commands before sending them

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/afk.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index f1e8bdfcc319a2..10255f2e15ee4d 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -861,6 +861,7 @@ int afk_send_command(struct apple_epic_service *service, u8 type,
 
 	memcpy(txbuf, payload, payload_len);
 
+	memset(&cmd, 0, sizeof(cmd));
 	cmd.retcode = cpu_to_le32(0);
 	cmd.rxbuf = cpu_to_le64(rxbuf_dma);
 	cmd.rxlen = cpu_to_le32(output_len);
@@ -951,6 +952,8 @@ int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
 		return -ENOMEM;
 
 	call = bfr;
+
+	memset(call, 0, sizeof(*call));
 	call->group = cpu_to_le16(group);
 	call->command = cpu_to_le32(command);
 	call->data_len = cpu_to_le32(data_len + data_pad);

From 6c935eec61e7704b9a36446f9ee829ed1510126e Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 17 Nov 2023 00:02:27 +0900
Subject: [PATCH 0750/1027] drm: apple: Fix missing unlock path in
 dcp_dptx_connect

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/dcp.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index cc0c6d887870f3..dbd06178614656 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -269,12 +269,14 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 	}
 
 	if (dcp->dptxport[port].connected)
-		return 0;
+		goto ret;
 
 	dcp->dptxport[port].atcphy = dcp->phy;
 	dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die);
 	dptxport_request_display(dcp->dptxport[port].service);
 	dcp->dptxport[port].connected = true;
+
+ret:
 	mutex_unlock(&dcp->hpd_mutex);
 
 	return 0;

From f2e5f3be370af65dfc03dc732106b49962588b70 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 17 Nov 2023 00:03:36 +0900
Subject: [PATCH 0751/1027] drm: apple: dptxep: Fix reply size check

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/dptxep.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 2c751c630a122d..50d14741e66da7 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -378,7 +378,7 @@ dptxport_call_activate(struct apple_epic_service *service,
 	phy_set_mode_ext(dptx->atcphy, PHY_MODE_DP, dcp->index);
 
 	memcpy(reply, data, min(reply_size, data_size));
-	if (reply_size > 4)
+	if (reply_size >= 4)
 		memset(reply, 0, 4);
 
 	return 0;
@@ -431,7 +431,7 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 	case DPTX_APCALL_SET_DRIVE_SETTINGS:
 	case DPTX_APCALL_GET_DRIVE_SETTINGS:
 		memcpy(reply, data, min(reply_size, data_size));
-		if (reply_size > 4)
+		if (reply_size >= 4)
 			memset(reply, 0, 4);
 		return 0;
 	}

From de692e6687721984cec83fe2950c48ea9ff7f377 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Fri, 17 Nov 2023 00:03:51 +0900
Subject: [PATCH 0752/1027] drm: apple: dptxep: Implement drive settings stuff

Just in case, for consistency with macOS.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/apple/dptxep.c | 75 +++++++++++++++++++++++++++++++++-
 drivers/gpu/drm/apple/dptxep.h |  1 +
 2 files changed, 74 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 50d14741e66da7..83d4a3925af0ac 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -57,6 +57,18 @@ struct dptxport_apcall_max_drive_settings {
 	u8 _unk1[8];
 };
 
+struct dptxport_apcall_drive_settings {
+	__le32 retcode;
+	u8 _unk0[12];
+	__le32 unk1;
+	__le32 unk2;
+	__le32 unk3;
+	__le32 unk4;
+	__le32 unk5;
+	__le32 unk6;
+	__le32 unk7;
+};
+
 int dptxport_validate_connection(struct apple_epic_service *service, u8 core,
 				 u8 atc, u8 die)
 {
@@ -160,6 +172,61 @@ dptxport_call_get_max_drive_settings(struct apple_epic_service *service,
 	return 0;
 }
 
+static int
+dptxport_call_get_drive_settings(struct apple_epic_service *service,
+				     const void *request_, size_t request_size,
+				     void *reply_, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	const struct dptxport_apcall_drive_settings *request = request_;
+	struct dptxport_apcall_drive_settings *reply = reply_;
+
+	if (reply_size < sizeof(*reply) || request_size < sizeof(*request))
+		return -EINVAL;
+
+	*reply = *request;
+
+	/* Clear the rest of the buffer */
+	memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply));
+
+	if (reply->retcode != 4)
+		dev_err(service->ep->dcp->dev,
+			"get_drive_settings: unexpected retcode %d\n",
+			reply->retcode);
+
+	reply->retcode = 4; /* Should already be 4? */
+	reply->unk5 = dptx->drive_settings[0];
+	reply->unk6 = 0;
+	reply->unk7 = dptx->drive_settings[1];
+
+	return 0;
+}
+
+static int
+dptxport_call_set_drive_settings(struct apple_epic_service *service,
+				     const void *request_, size_t request_size,
+				     void *reply_, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+	const struct dptxport_apcall_drive_settings *request = request_;
+	struct dptxport_apcall_drive_settings *reply = reply_;
+
+	if (reply_size < sizeof(*reply) || request_size < sizeof(*request))
+		return -EINVAL;
+
+	*reply = *request;
+	reply->retcode = cpu_to_le32(0);
+
+	dev_info(service->ep->dcp->dev, "set_drive_settings: %d:%d:%d:%d:%d:%d:%d\n",
+		 request->unk1, request->unk2, request->unk3, request->unk4,
+		 request->unk5, request->unk6, request->unk7);
+
+	dptx->drive_settings[0] = reply->unk5;
+	dptx->drive_settings[1] = reply->unk7;
+
+	return 0;
+}
+
 static int dptxport_call_get_max_link_rate(struct apple_epic_service *service,
 					   void *reply_, size_t reply_size)
 {
@@ -418,6 +485,12 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 	case DPTX_APCALL_GET_MAX_DRIVE_SETTINGS:
 		return dptxport_call_get_max_drive_settings(service, reply,
 							    reply_size);
+	case DPTX_APCALL_GET_DRIVE_SETTINGS:
+		return dptxport_call_get_drive_settings(service, data, data_size,
+							reply, reply_size);
+	case DPTX_APCALL_SET_DRIVE_SETTINGS:
+		return dptxport_call_set_drive_settings(service, data, data_size,
+							reply, reply_size);
         case DPTX_APCALL_ACTIVATE:
 		return dptxport_call_activate(service, data, data_size,
 					      reply, reply_size);
@@ -428,8 +501,6 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
 		fallthrough;
 	/* we can silently ignore and just ACK these calls */
 	case DPTX_APCALL_DEACTIVATE:
-	case DPTX_APCALL_SET_DRIVE_SETTINGS:
-	case DPTX_APCALL_GET_DRIVE_SETTINGS:
 		memcpy(reply, data, min(reply_size, data_size));
 		if (reply_size >= 4)
 			memset(reply, 0, 4);
diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h
index 8f0483e7030b7a..481ebbc97bf38d 100644
--- a/drivers/gpu/drm/apple/dptxep.h
+++ b/drivers/gpu/drm/apple/dptxep.h
@@ -55,6 +55,7 @@ struct dptx_port {
 	struct phy *atcphy;
 	struct mux_control *mux;
 	u32 link_rate, pending_link_rate;
+	u32 drive_settings[2];
 };
 
 int dptxport_validate_connection(struct apple_epic_service *service, u8 core,

From 881a8f405918d040cdfccc07e7bab7c1f19f1458 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 13:59:35 +0900
Subject: [PATCH 0753/1027] drm/apple: Add missing sound Kconfig dependencies

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/Kconfig | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index f0504641b5f641..fe69b04a912f93 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -3,10 +3,12 @@ config DRM_APPLE
 	tristate "DRM Support for Apple display controllers"
 	depends on DRM && OF && ARM64
 	depends on ARCH_APPLE || COMPILE_TEST
+	depends on SND
 	select DRM_KMS_HELPER
 	select DRM_KMS_DMA_HELPER
 	select DRM_GEM_DMA_HELPER
 	select VIDEOMODE_HELPERS
 	select MULTIPLEXER
+	select SND_PCM
 	help
 	  Say Y if you have an Apple Silicon chipset.

From 18bc23253ffc835979a5c48a5572891867939092 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 20 Nov 2023 22:43:48 +0100
Subject: [PATCH 0754/1027] drm: apple: HACK: Do not delete piodma platform
 device

of_platform_device_destroy() can trigger several NULL pointer
dereference which have been elusive so far.
Comment this for now since the oopses causes the shutdown to hang.
Since dcp can not be reloaded this leaks the platform device on shutdown
and reboot.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index dbd06178614656..1d7a628f348a76 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -819,7 +819,10 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 	if (dcp->piodma) {
 		iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev);
 		iommu_domain_free(dcp->iommu_dom);
-		of_platform_device_destroy(&dcp->piodma->dev, NULL);
+		/* TODO: the piodma platform device has to be destroyed but
+		 *       doing so leads to all kind of breakage.
+		 */
+		// of_platform_device_destroy(&dcp->piodma->dev, NULL);
 		dcp->piodma = NULL;
 	}
 

From 46221115564615a1b1a6a557b55b6a80d649fc7a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 6 Nov 2023 22:27:54 +0100
Subject: [PATCH 0755/1027] drm: apple: afk: Update read pointer before
 processing message

Avoids out of order messages and already unmapped buffers while tracing
with hv/trace_dcp.py.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 11 +++++++++--
 1 file changed, 9 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index 10255f2e15ee4d..fc90150bb7b5ab 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -613,8 +613,6 @@ static bool afk_recv(struct apple_dcp_afkep *ep)
 	channel = le32_to_cpu(hdr->channel);
 	type = le32_to_cpu(hdr->type);
 
-	afk_recv_handle(ep, channel, type, hdr->data, size);
-
 	rptr = ALIGN(rptr + sizeof(*hdr) + size, 1 << BLOCK_SHIFT);
 	if (WARN_ON(rptr > ep->rxbfr.bufsz))
 		rptr = 0;
@@ -626,6 +624,15 @@ static bool afk_recv(struct apple_dcp_afkep *ep)
 	ep->rxbfr.hdr->rptr = cpu_to_le32(rptr);
 	trace_afk_recv_rwptr_post(ep, rptr, wptr);
 
+	/*
+	 * TODO: this is theoretically unsafe since DCP could overwrite data
+	 *       after the read pointer was updated above. Do it anyway since
+	 *       it avoids 2 problems in the DCP tracer:
+	 *       1. the tracer sees replies before the the notifies from dcp
+	 *       2. the tracer tries to read buffers after they are unmapped.
+	 */
+	afk_recv_handle(ep, channel, type, hdr->data, size);
+
 	return true;
 }
 

From 3fbf2f14c0de343f064edee5830789a8c4b9283d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 19 Nov 2023 18:07:41 +0100
Subject: [PATCH 0756/1027] drm: apple: Implement D592 callback

This callback is occasionally seen around (failed) modesets. There seems
to be no need to handle it so just trace it.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c |  7 +++++++
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  1 +
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  1 +
 drivers/gpu/drm/apple/trace.h          | 17 +++++++++++++++++
 4 files changed, 26 insertions(+)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 945e972a7c4679..d60b143c040b9d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1044,6 +1044,12 @@ dcpep_cb_swap_complete_intent_gated(struct apple_dcp *dcp,
 		info->width, info->height);
 }
 
+static void
+dcpep_cb_abort_swap_ap_gated(struct apple_dcp *dcp, u32 *swap_id)
+{
+	trace_iomfb_abort_swap_ap_gated(dcp, *swap_id);
+}
+
 static struct dcpep_get_tiling_state_resp
 dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
 			  struct dcpep_get_tiling_state_req *req)
@@ -1110,6 +1116,7 @@ TRAMPOLINE_IN(trampoline_hotplug, dcpep_cb_hotplug, u64);
 TRAMPOLINE_IN(trampoline_swap_complete_intent_gated,
 	      dcpep_cb_swap_complete_intent_gated,
 	      struct dcp_swap_complete_intent_gated);
+TRAMPOLINE_IN(trampoline_abort_swap_ap_gated, dcpep_cb_abort_swap_ap_gated, u32);
 TRAMPOLINE_IN(trampoline_enable_backlight_message_ap_gated,
 	      iomfbep_cb_enable_backlight_message_ap_gated, u8);
 TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index 8b4d87ad9012bd..ad3cbf576cfdcf 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -89,6 +89,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
 	[589] = trampoline_swap_complete,
 	[591] = trampoline_swap_complete_intent_gated,
+	[592] = trampoline_abort_swap_ap_gated,
 	[593] = trampoline_enable_backlight_message_ap_gated,
 	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
 	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 0689c0a593f784..0311e1c8c39874 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -95,6 +95,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[588] = trampoline_nop, /* resize_default_fb_surface_gated */
 	[589] = trampoline_swap_complete,
 	[591] = trampoline_swap_complete_intent_gated,
+	[592] = trampoline_abort_swap_ap_gated,
 	[593] = trampoline_enable_backlight_message_ap_gated,
 	[594] = trampoline_nop, /* IOMobileFramebufferAP::setSystemConsoleMode */
 	[596] = trampoline_false, /* IOMobileFramebufferAP::isDFBAllocated */
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index 814bc7f0864475..e03bf8b199c88f 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -303,6 +303,23 @@ TRACE_EVENT(iomfb_swap_complete_intent_gated,
 	    )
 );
 
+TRACE_EVENT(iomfb_abort_swap_ap_gated,
+	    TP_PROTO(struct apple_dcp *dcp, u32 swap_id),
+	    TP_ARGS(dcp, swap_id),
+	    TP_STRUCT__entry(
+			     __field(u64, dcp)
+			     __field(u32, swap_id)
+	    ),
+	    TP_fast_assign(
+			   __entry->dcp = (u64)dcp;
+			   __entry->swap_id = swap_id;
+	    ),
+	    TP_printk("dcp=%llx, swap_id=%u",
+		      __entry->dcp,
+		      __entry->swap_id
+	    )
+);
+
 DECLARE_EVENT_CLASS(iomfb_parse_mode_template,
 	    TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
 	    TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score),

From 7dff643fa909aca2822adba9168c2a8bf8096817 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 19 Nov 2023 18:25:22 +0100
Subject: [PATCH 0757/1027] drm: apple: Keep information at which swap_id fb
 are still referenced

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   | 4 ++++
 drivers/gpu/drm/apple/iomfb_template.c | 6 ++++++
 2 files changed, 10 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 64025d23282d1c..5ac2fcfa455cec 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -75,6 +75,7 @@ struct dcp_channel {
 struct dcp_fb_reference {
 	struct list_head head;
 	struct drm_framebuffer *fb;
+	u32 swap_id;
 };
 
 #define MAX_NOTCH_HEIGHT 160
@@ -167,6 +168,9 @@ struct apple_dcp {
 		struct dcp_swap_submit_req_v13_3 v13_3;
 	} swap;
 
+	/* swap id of the last completed swap */
+	u32 last_swap_id;
+
 	/* Current display mode */
 	bool valid_mode;
 	struct dcp_set_digital_out_mode_req mode;
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index d60b143c040b9d..6d7c0c614b54b5 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -121,6 +121,7 @@ static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 				   struct DCP_FW_NAME(dc_swap_complete_resp) *resp)
 {
 	trace_iomfb_swap_complete(dcp, resp->swap_id);
+	dcp->last_swap_id = resp->swap_id;
 
 	dcp_drm_crtc_vblank(dcp->crtc);
 }
@@ -746,6 +747,8 @@ static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
 		struct dcp_fb_reference *entry;
 		entry = list_first_entry(&dcp->swapped_out_fbs,
 					 struct dcp_fb_reference, head);
+		if (entry->swap_id == dcp->last_swap_id)
+			break;
 		if (entry->fb)
 			drm_framebuffer_put(entry->fb);
 		list_del(&entry->head);
@@ -1145,6 +1148,8 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
 		struct dcp_fb_reference *entry;
 		entry = list_first_entry(&dcp->swapped_out_fbs,
 					 struct dcp_fb_reference, head);
+		if (entry->swap_id == dcp->last_swap_id)
+			break;
 		if (entry->fb)
 			drm_framebuffer_put(entry->fb);
 		list_del(&entry->head);
@@ -1252,6 +1257,7 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 				kzalloc(sizeof(*entry), GFP_KERNEL);
 			if (entry) {
 				entry->fb = old_state->fb;
+				entry->swap_id = dcp->last_swap_id;
 				list_add_tail(&entry->head,
 					      &dcp->swapped_out_fbs);
 			}

From ac23bfbf1443be935fe7c1ad0572df68fc868a4b Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 20 Nov 2023 20:09:25 +0100
Subject: [PATCH 0758/1027] Revert "drm: apple: iomfb: Do not match/create PMU
 service for dcpext"

This reverts commit ab69434d230f9951644e10c9142dbc43ea0516c4.
---
 drivers/gpu/drm/apple/dcp-internal.h   |  3 ---
 drivers/gpu/drm/apple/dcp.c            |  2 --
 drivers/gpu/drm/apple/iomfb_template.c | 16 ----------------
 drivers/gpu/drm/apple/iomfb_v12_3.c    |  2 +-
 drivers/gpu/drm/apple/iomfb_v13_3.c    |  2 +-
 5 files changed, 2 insertions(+), 23 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 5ac2fcfa455cec..1041aecb211822 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -187,9 +187,6 @@ struct apple_dcp {
 	/* clear all surfaces on init */
 	bool surfaces_cleared;
 
-	/* is dcpext / requires dptx */
-	bool is_dptx;
-
 	/* Modes valid for the connected display */
 	struct dcp_display_mode *modes;
 	unsigned int nr_modes;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 1d7a628f348a76..1b266d191b2383 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -702,8 +702,6 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (IS_ERR(dcp->coproc_reg))
 		return PTR_ERR(dcp->coproc_reg);
 
-	dcp->is_dptx = dcp->phy != NULL;
-
 	of_property_read_u32(dev->of_node, "apple,dcp-index",
 					   &dcp->index);
 	of_property_read_u32(dev->of_node, "apple,dptx-phy",
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 6d7c0c614b54b5..8ade4368b6a32c 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -136,10 +136,6 @@ static void complete_vi_set_temperature_hint(struct apple_dcp *dcp, void *out, v
 static bool iomfbep_cb_match_pmu_service(struct apple_dcp *dcp, int tag, void *out, void *in)
 {
 	trace_iomfb_callback(dcp, tag, __func__);
-
-	if (dcp->is_dptx)
-		return true;
-
 	iomfb_a358_vi_set_temperature_hint(dcp, false,
 					   complete_vi_set_temperature_hint,
 					   NULL);
@@ -163,12 +159,6 @@ static bool iomfbep_cb_match_pmu_service_2(struct apple_dcp *dcp, int tag, void
 {
 	trace_iomfb_callback(dcp, tag, __func__);
 
-	if (dcp->is_dptx) {
-		u8 *ret = out;
-		ret[0] = 1;
-		return true;
-	}
-
 	iomfb_a131_pmu_service_matched(dcp, false, complete_pmu_service_matched,
 				       out);
 
@@ -1063,11 +1053,6 @@ dcpep_cb_get_tiling_state(struct apple_dcp *dcp,
 	};
 }
 
-static u8 dcpep_cb_create_pmu_service(struct apple_dcp *dcp)
-{
-	return !dcp->is_dptx;
-}
-
 static u8 dcpep_cb_create_backlight_service(struct apple_dcp *dcp)
 {
 	return dcp_has_panel(dcp);
@@ -1126,7 +1111,6 @@ TRAMPOLINE_IN(trampoline_pr_publish, iomfb_cb_pr_publish,
 	      struct iomfb_property);
 TRAMPOLINE_INOUT(trampoline_get_tiling_state, dcpep_cb_get_tiling_state,
 		 struct dcpep_get_tiling_state_req, struct dcpep_get_tiling_state_resp);
-TRAMPOLINE_OUT(trampoline_create_pmu_service, dcpep_cb_create_pmu_service, u8);
 TRAMPOLINE_OUT(trampoline_create_backlight_service, dcpep_cb_create_backlight_service, u8);
 
 /*
diff --git a/drivers/gpu/drm/apple/iomfb_v12_3.c b/drivers/gpu/drm/apple/iomfb_v12_3.c
index ad3cbf576cfdcf..0fe08c42d64659 100644
--- a/drivers/gpu/drm/apple/iomfb_v12_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v12_3.c
@@ -48,7 +48,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[106] = trampoline_nop, /* remove_property */
 	[107] = trampoline_true, /* create_provider_service */
 	[108] = trampoline_true, /* create_product_service */
-	[109] = trampoline_create_pmu_service,
+	[109] = trampoline_true, /* create_pmu_service */
 	[110] = trampoline_true, /* create_iomfb_service */
 	[111] = trampoline_create_backlight_service,
 	[116] = dcpep_cb_boot_1,
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 0311e1c8c39874..1ee29112be4543 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -50,7 +50,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[107] = trampoline_nop, /* remove_property */
 	[108] = trampoline_true, /* create_provider_service */
 	[109] = trampoline_true, /* create_product_service */
-	[110] = trampoline_create_pmu_service,
+	[110] = trampoline_true, /* create_pmu_service */
 	[111] = trampoline_true, /* create_iomfb_service */
 	[112] = trampoline_create_backlight_service,
 	[113] = trampoline_true, /* create_nvram_servce? */

From de6010f5dff9f76b4106f6bce478a969837e5c71 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 20 Nov 2023 22:48:02 +0100
Subject: [PATCH 0759/1027] drm: apple: dptx: Implement APCALL_DEACTIVATE and
 reset the phy

This mirrors what macOS does and should make reconnections more
reliable.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 83d4a3925af0ac..328ff41aee7dd0 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -451,6 +451,23 @@ dptxport_call_activate(struct apple_epic_service *service,
 	return 0;
 }
 
+static int
+dptxport_call_deactivate(struct apple_epic_service *service,
+		       const void *data, size_t data_size,
+		       void *reply, size_t reply_size)
+{
+	struct dptx_port *dptx = service->cookie;
+
+	/* deactivate phy */
+	phy_set_mode_ext(dptx->atcphy, PHY_MODE_INVALID, 0);
+
+	memcpy(reply, data, min(reply_size, data_size));
+	if (reply_size >= 4)
+		memset(reply, 0, 4);
+
+	return 0;
+}
+
 static int dptxport_call(struct apple_epic_service *service, u32 idx,
 			 const void *data, size_t data_size, void *reply,
 			 size_t reply_size)
@@ -494,13 +511,13 @@ static int dptxport_call(struct apple_epic_service *service, u32 idx,
         case DPTX_APCALL_ACTIVATE:
 		return dptxport_call_activate(service, data, data_size,
 					      reply, reply_size);
+	case DPTX_APCALL_DEACTIVATE:
+		return dptxport_call_deactivate(service, data, data_size,
+						reply, reply_size);
 	default:
 		/* just try to ACK and hope for the best... */
 		dev_info(service->ep->dcp->dev, "DPTXPort: acking unhandled call %u\n",
 			idx);
-		fallthrough;
-	/* we can silently ignore and just ACK these calls */
-	case DPTX_APCALL_DEACTIVATE:
 		memcpy(reply, data, min(reply_size, data_size));
 		if (reply_size >= 4)
 			memset(reply, 0, 4);

From cfd1d3b8d83bab6e938be4f8af3cf4da95502c95 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 20 Nov 2023 22:56:43 +0100
Subject: [PATCH 0760/1027] drm: apple: Disconnect dptx When the CRTC is
 powered down

Seems to make disconnect / reconnect more reliable and almost fixes
suspend/resume. The drm device tries to modeset too early on resume
which leaves the screen blank.
This should reduce power consumption after disconnecting the HDMI port.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c   | 64 +++++++++++++++++++++++++++++++++++
 drivers/gpu/drm/apple/iomfb.c | 51 ----------------------------
 2 files changed, 64 insertions(+), 51 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 1b266d191b2383..015fd97937e57a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -287,6 +287,7 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
 	struct apple_connector *connector = dcp->connector;
 
 	mutex_lock(&dcp->hpd_mutex);
+
 	if (connector && connector->connected) {
 		dcp->valid_mode = false;
 		schedule_work(&connector->hotplug_wq);
@@ -407,6 +408,69 @@ int dcp_wait_ready(struct platform_device *pdev, u64 timeout)
 }
 EXPORT_SYMBOL(dcp_wait_ready);
 
+static void __maybe_unused dcp_sleep(struct apple_dcp *dcp)
+{
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_sleep_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_5:
+		iomfb_sleep_v13_3(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
+	}
+}
+
+void dcp_poweron(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	if (dcp->hdmi_hpd) {
+		bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+		dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
+
+		if (connected)
+			dcp_dptx_connect(dcp, 0);
+	}
+
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_poweron_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_5:
+		iomfb_poweron_v13_3(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
+	}
+}
+EXPORT_SYMBOL(dcp_poweron);
+
+void dcp_poweroff(struct platform_device *pdev)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		iomfb_poweroff_v12_3(dcp);
+		break;
+	case DCP_FIRMWARE_V_13_5:
+		iomfb_poweroff_v13_3(dcp);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
+		break;
+	}
+
+	if (dcp->phy)
+		dcp_dptx_disconnect(dcp, 0);
+
+}
+EXPORT_SYMBOL(dcp_poweroff);
+
 static void dcp_work_register_backlight(struct work_struct *work)
 {
 	int ret;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 70bfcdf4a96bc8..b9236794193f69 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -219,57 +219,6 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 			 dcpep_ack(context));
 }
 
-void dcp_sleep(struct apple_dcp *dcp)
-{
-	switch (dcp->fw_compat) {
-	case DCP_FIRMWARE_V_12_3:
-		iomfb_sleep_v12_3(dcp);
-		break;
-	case DCP_FIRMWARE_V_13_5:
-		iomfb_sleep_v13_3(dcp);
-		break;
-	default:
-		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
-		break;
-	}
-}
-
-void dcp_poweron(struct platform_device *pdev)
-{
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-
-	switch (dcp->fw_compat) {
-	case DCP_FIRMWARE_V_12_3:
-		iomfb_poweron_v12_3(dcp);
-		break;
-	case DCP_FIRMWARE_V_13_5:
-		iomfb_poweron_v13_3(dcp);
-		break;
-	default:
-		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
-		break;
-	}
-}
-EXPORT_SYMBOL(dcp_poweron);
-
-void dcp_poweroff(struct platform_device *pdev)
-{
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-
-	switch (dcp->fw_compat) {
-	case DCP_FIRMWARE_V_12_3:
-		iomfb_poweroff_v12_3(dcp);
-		break;
-	case DCP_FIRMWARE_V_13_5:
-		iomfb_poweroff_v13_3(dcp);
-		break;
-	default:
-		WARN_ONCE(true, "Unexpected firmware version: %u\n", dcp->fw_compat);
-		break;
-	}
-}
-EXPORT_SYMBOL(dcp_poweroff);
-
 /*
  * Helper to send a DRM hotplug event. The DCP is accessed from a single
  * (RTKit) thread. To handle hotplug callbacks, we need to call

From 30901c970c83e2ded7e0cde744e872486f9fe404 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 21 Nov 2023 23:32:07 +0100
Subject: [PATCH 0761/1027] drm: apple: dptx: Wait for completion of
 dptx_connect.

Makes connects more reliable.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c    | 17 ++++++++++++-----
 drivers/gpu/drm/apple/dptxep.c |  4 ++++
 drivers/gpu/drm/apple/dptxep.h |  1 +
 3 files changed, 17 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 015fd97937e57a..6a986abae05583 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -256,6 +256,8 @@ EXPORT_SYMBOL_GPL(dcp_get_connector_type);
 
 static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 {
+	int ret = 0;
+
 	if (!dcp->phy) {
 		dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n");
 		return -ENODEV;
@@ -264,22 +266,27 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 	mutex_lock(&dcp->hpd_mutex);
 	if (!dcp->dptxport[port].enabled) {
 		dev_warn(dcp->dev, "dcp_dptx_connect: dptx service for port %d not enabled\n", port);
-		mutex_unlock(&dcp->hpd_mutex);
-		return -ENODEV;
+		ret = -ENODEV;
+		goto out_unlock;
 	}
 
 	if (dcp->dptxport[port].connected)
-		goto ret;
+		goto out_unlock;
 
+	reinit_completion(&dcp->dptxport[port].linkcfg_completion);
 	dcp->dptxport[port].atcphy = dcp->phy;
 	dptxport_connect(dcp->dptxport[port].service, 0, dcp->dptx_phy, dcp->dptx_die);
 	dptxport_request_display(dcp->dptxport[port].service);
 	dcp->dptxport[port].connected = true;
 
-ret:
 	mutex_unlock(&dcp->hpd_mutex);
-
+	wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion,
+				    msecs_to_jiffies(1000));
 	return 0;
+
+out_unlock:
+	mutex_unlock(&dcp->hpd_mutex);
+	return ret;
 }
 
 static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 328ff41aee7dd0..0a3ab4abd074c6 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -330,8 +330,10 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service)
 static int
 dptxport_call_did_change_link_config(struct apple_epic_service *service)
 {
+	struct dptx_port *dptx = service->cookie;
 	/* assume the link config did change and wait a little bit */
 	mdelay(10);
+	complete(&dptx->linkcfg_completion);
 	return 0;
 }
 
@@ -573,6 +575,8 @@ int dptxep_init(struct apple_dcp *dcp)
 
 	init_completion(&dcp->dptxport[0].enable_completion);
 	init_completion(&dcp->dptxport[1].enable_completion);
+	init_completion(&dcp->dptxport[0].linkcfg_completion);
+	init_completion(&dcp->dptxport[1].linkcfg_completion);
 
 	dcp->dptxep = afk_init(dcp, DPTX_ENDPOINT, dptxep_ops);
 	if (IS_ERR(dcp->dptxep))
diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h
index 481ebbc97bf38d..4a0770d43c954c 100644
--- a/drivers/gpu/drm/apple/dptxep.h
+++ b/drivers/gpu/drm/apple/dptxep.h
@@ -49,6 +49,7 @@ struct apple_epic_service;
 struct dptx_port {
 	bool enabled, connected;
 	struct completion enable_completion;
+	struct completion linkcfg_completion;
 	u32 unit;
 	struct apple_epic_service *service;
 	union phy_configure_opts phy_ops;

From 4ae5ea193fd1ddfd4d876bce48e722148978af97 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 21 Nov 2023 23:50:49 +0100
Subject: [PATCH 0762/1027] drm: apple: HPD: Only act on connect IRQs

DCP notices the disconnects on its own and the parallel handling just
results in confusion (both on DRM and developer side).

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6a986abae05583..9931435871af92 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -314,12 +314,16 @@ static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data)
 	struct apple_dcp *dcp = data;
 	bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
 
-	dev_info(dcp->dev, "DP2HDMI HPD connected:%d\n", connected);
+	/* do nothing on disconnect and trust that dcp detects it itself.
+	 * Parallel disconnect HPDs result drm disabling the CRTC even when it
+	 * should not.
+	 * The interrupt should be changed to rising but for now the disconnect
+	 * IRQs might be helpful for debugging.
+	 */
+	dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected);
 
 	if (connected)
 		dcp_dptx_connect(dcp, 0);
-	else
-		dcp_dptx_disconnect(dcp, 0);
 
 	return IRQ_HANDLED;
 }

From d48ea8b17c492a0a23026b909a896a9edbf34788 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 21 Nov 2023 23:57:07 +0100
Subject: [PATCH 0763/1027] drm: apple: iomfb: Improve hotplug related logging

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c          | 3 ++-
 drivers/gpu/drm/apple/iomfb_template.c | 3 +++
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index b9236794193f69..5c6b968830fbec 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -236,7 +236,8 @@ void dcp_hotplug(struct work_struct *work)
 	dev = connector->base.dev;
 
 	dcp = platform_get_drvdata(connector->dcp);
-	dev_info(dcp->dev, "%s: connected: %d", __func__, connector->connected);
+	dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__,
+		 connector->connected, dcp->valid_mode);
 
 	/*
 	 * DCP defers link training until we set a display mode. But we set
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 8ade4368b6a32c..435c5a2bf9718c 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1014,6 +1014,9 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 	if (dcp->main_display)
 		return;
 
+	dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n",
+		 *connected, dcp->valid_mode);
+
 	/* Hotplug invalidates mode. DRM doesn't always handle this. */
 	if (!(*connected)) {
 		dcp->valid_mode = false;

From 1e6ca0e1093bf511cb765ca515314d87853eca6a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 22 Nov 2023 09:41:29 +0100
Subject: [PATCH 0764/1027] drm: apple: Extract modeset crtc's atomic_flush()

Triggering modesets from drm_connector_helper_funcs.atomic_check is more
in line with DRM/KMS' design and allows returning errors from failed
modesets.
Ignore hotplug callbacks from DCP during modeset. DCP always does
disconnected -> connected on (at least the initial) modeset. Shield drm
helpers from this.
This improves reliability with externel (dptx based) displays.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c      |   2 +
 drivers/gpu/drm/apple/dcp-internal.h   |   1 +
 drivers/gpu/drm/apple/dcp.h            |   2 +
 drivers/gpu/drm/apple/iomfb.c          |  41 ++++++++
 drivers/gpu/drm/apple/iomfb_template.c | 137 ++++++++++++++-----------
 drivers/gpu/drm/apple/iomfb_template.h |   2 +
 6 files changed, 124 insertions(+), 61 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 7d2c927628d6e8..9a332b6f48a266 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -300,6 +300,8 @@ static const struct drm_connector_funcs apple_connector_funcs = {
 static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
 	.get_modes		= dcp_get_modes,
 	.mode_valid		= dcp_mode_valid,
+	.atomic_check		= dcp_connector_atomic_check,
+
 };
 
 static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 1041aecb211822..e975ae5be371fd 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -172,6 +172,7 @@ struct apple_dcp {
 	u32 last_swap_id;
 
 	/* Current display mode */
+	bool during_modeset;
 	bool valid_mode;
 	struct dcp_set_digital_out_mode_req mode;
 
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index e0dc96109b9d2b..039dcf3ab319d8 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -57,6 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc);
 int dcp_get_modes(struct drm_connector *connector);
 int dcp_mode_valid(struct drm_connector *connector,
 		   struct drm_display_mode *mode);
+int dcp_connector_atomic_check(struct drm_connector *connector,
+			       struct drm_atomic_state *state);
 bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 			 const struct drm_display_mode *mode,
 			 struct drm_display_mode *adjusted_mode);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 5c6b968830fbec..5884f30426d6d6 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -422,6 +422,47 @@ int dcp_mode_valid(struct drm_connector *connector,
 }
 EXPORT_SYMBOL_GPL(dcp_mode_valid);
 
+int dcp_connector_atomic_check(struct drm_connector *connector,
+			       struct drm_atomic_state *state)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+	struct platform_device *pdev = apple_connector->dcp;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	struct drm_crtc *crtc = &dcp->crtc->base;
+	struct drm_crtc_state *crtc_state;
+	int ret = -EIO;
+	bool modeset;
+
+	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
+	if (!crtc_state)
+		return 0;
+
+	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
+
+	if (!modeset)
+		return 0;
+
+	/* ignore no mode, poweroff is handled elsewhere */
+	if (crtc_state->mode.hdisplay == 0 && crtc_state->mode.vdisplay == 0)
+		return 0;
+
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		ret = iomfb_modeset_v12_3(dcp, crtc_state);
+		break;
+	case DCP_FIRMWARE_V_13_5:
+		ret = iomfb_modeset_v13_3(dcp, crtc_state);
+		break;
+	default:
+		WARN_ONCE(true, "Unexpected firmware version: %u\n",
+			  dcp->fw_compat);
+		break;
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_connector_atomic_check);
+
 bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 			 const struct drm_display_mode *mode,
 			 struct drm_display_mode *adjusted_mode)
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 435c5a2bf9718c..0895efe3a8472d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1014,6 +1014,13 @@ static void dcpep_cb_hotplug(struct apple_dcp *dcp, u64 *connected)
 	if (dcp->main_display)
 		return;
 
+	if (dcp->during_modeset) {
+		dev_info(dcp->dev,
+			 "cb_hotplug() ignored during modeset connected:%llu\n",
+			 *connected);
+		return;
+	}
+
 	dev_info(dcp->dev, "cb_hotplug() connected:%llu, valid_mode:%d\n",
 		 *connected, dcp->valid_mode);
 
@@ -1178,6 +1185,75 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
 	}
 }
 
+int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
+			       struct drm_crtc_state *crtc_state)
+{
+	struct dcp_display_mode *mode;
+	struct dcp_wait_cookie *cookie;
+	int ret;
+
+	mode = lookup_mode(dcp, &crtc_state->mode);
+	if (!mode) {
+		dev_err(dcp->dev, "no match for " DRM_MODE_FMT "\n",
+			DRM_MODE_ARG(&crtc_state->mode));
+		return -EIO;
+	}
+
+	dev_info(dcp->dev,
+		 "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n",
+		 mode->color_mode_id, mode->timing_mode_id,
+		 DRM_MODE_ARG(&crtc_state->mode));
+	dcp->mode = (struct dcp_set_digital_out_mode_req){
+		.color_mode_id = mode->color_mode_id,
+		.timing_mode_id = mode->timing_mode_id
+	};
+
+	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
+	if (!cookie) {
+		return -ENOMEM;
+	}
+
+	init_completion(&cookie->done);
+	kref_init(&cookie->refcount);
+	/* increase refcount to ensure the receiver has a reference */
+	kref_get(&cookie->refcount);
+
+	dcp->during_modeset = true;
+
+	dcp_set_digital_out_mode(dcp, false, &dcp->mode,
+				 complete_set_digital_out_mode, cookie);
+
+	/*
+	 * The DCP firmware has an internal timeout of ~8 seconds for
+	 * modesets. Add an extra 500ms to safe side that the modeset
+	 * call has returned.
+	 */
+	dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
+	ret = wait_for_completion_timeout(&cookie->done,
+					  msecs_to_jiffies(8500));
+
+	kref_put(&cookie->refcount, release_wait_cookie);
+	dcp->during_modeset = false;
+	dev_info(dcp->dev, "set_digital_out_mode finished:%d\n", ret);
+
+	if (ret == 0) {
+		dev_info(dcp->dev, "set_digital_out_mode timed out\n");
+		return -EIO;
+	} else if (ret < 0) {
+		dev_info(dcp->dev,
+			 "waiting on set_digital_out_mode failed:%d\n", ret);
+		return -EIO;
+
+	} else if (ret > 0) {
+		dev_dbg(dcp->dev,
+			"set_digital_out_mode finished with %d to spare\n",
+			jiffies_to_msecs(ret));
+	}
+	dcp->valid_mode = true;
+
+	return 0;
+}
+
 void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state)
 {
 	struct drm_plane *plane;
@@ -1186,13 +1262,10 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 	struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap);
 	int plane_idx, l;
 	int has_surface = 0;
-	bool modeset;
 	dev_dbg(dcp->dev, "%s", __func__);
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 
-	modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
-
 	/* Reset to defaults */
 	memset(req, 0, sizeof(*req));
 	for (l = 0; l < SWAP_SURFACES; l++)
@@ -1305,64 +1378,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 		l += 1;
 	}
 
-	if (modeset) {
-		struct dcp_display_mode *mode;
-		struct dcp_wait_cookie *cookie;
-		int ret;
-
-		mode = lookup_mode(dcp, &crtc_state->mode);
-		if (!mode) {
-			dev_warn(dcp->dev, "no match for " DRM_MODE_FMT,
-				 DRM_MODE_ARG(&crtc_state->mode));
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		dev_info(dcp->dev, "set_digital_out_mode(color:%d timing:%d)",
-			 mode->color_mode_id, mode->timing_mode_id);
-		dcp->mode = (struct dcp_set_digital_out_mode_req){
-			.color_mode_id = mode->color_mode_id,
-			.timing_mode_id = mode->timing_mode_id
-		};
-
-		cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
-		if (!cookie) {
-			schedule_work(&dcp->vblank_wq);
-			return;
-		}
-
-		init_completion(&cookie->done);
-		kref_init(&cookie->refcount);
-		/* increase refcount to ensure the receiver has a reference */
-		kref_get(&cookie->refcount);
-
-		dcp_set_digital_out_mode(dcp, false, &dcp->mode,
-					 complete_set_digital_out_mode, cookie);
-
-		/*
-		 * The DCP firmware has an internal timeout of ~8 seconds for
-		 * modesets. Add an extra 500ms to safe side that the modeset
-		 * call has returned.
-		 */
-		dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
-		ret = wait_for_completion_timeout(&cookie->done,
-						  msecs_to_jiffies(8500));
-
-		kref_put(&cookie->refcount, release_wait_cookie);
-
-		if (ret == 0) {
-			dev_info(dcp->dev, "set_digital_out_mode timed out");
-			schedule_work(&dcp->vblank_wq);
-			return;
-		} else if (ret > 0) {
-			dev_dbg(dcp->dev,
-				"set_digital_out_mode finished with %d to spare",
-				jiffies_to_msecs(ret));
-		}
-
-		dcp->valid_mode = true;
-	}
-
 	if (!has_surface && !crtc_state->color_mgmt_changed) {
 		if (crtc_state->enable && crtc_state->active &&
 		    !crtc_state->planes_changed) {
diff --git a/drivers/gpu/drm/apple/iomfb_template.h b/drivers/gpu/drm/apple/iomfb_template.h
index e9c249609f46cb..f446a4d8f38b90 100644
--- a/drivers/gpu/drm/apple/iomfb_template.h
+++ b/drivers/gpu/drm/apple/iomfb_template.h
@@ -172,6 +172,8 @@ struct DCP_FW_NAME(dcp_map_reg_resp) {
 
 struct apple_dcp;
 
+int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
+			       struct drm_crtc_state *crtc_state);
 void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, struct drm_atomic_state *state);
 void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp);
 void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp);

From d476c437959b9662fddb13a639cef27476b76e9c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 22 Nov 2023 09:53:09 +0100
Subject: [PATCH 0765/1027] drm: apple: dptx: Log connect/disconnect calls

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 9931435871af92..af977563b75507 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -262,6 +262,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 		dev_warn(dcp->dev, "dcp_dptx_connect: missing phy\n");
 		return -ENODEV;
 	}
+	dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
 
 	mutex_lock(&dcp->hpd_mutex);
 	if (!dcp->dptxport[port].enabled) {
@@ -292,6 +293,7 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
 {
 	struct apple_connector *connector = dcp->connector;
+	dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
 
 	mutex_lock(&dcp->hpd_mutex);
 

From 2b33aa9ae27fb44c5ec09ce402fa69811ca92817 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 23 Nov 2023 22:58:16 +0100
Subject: [PATCH 0766/1027] drm: apple: Move modeset into drm_crtc's
 atomic_enable

squash! drm: apple: Extract modeset crtc's atomic_flush()
Fixes: 99d7bb861908 ("drm: apple: Extract modeset crtc's atomic_flush()")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  5 +++--
 drivers/gpu/drm/apple/dcp.h       |  4 ++--
 drivers/gpu/drm/apple/iomfb.c     | 12 +++++-------
 3 files changed, 10 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 9a332b6f48a266..162c4beb12f349 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -199,6 +199,9 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 		dcp_poweron(apple_crtc->dcp);
 		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
 	}
+
+	if (crtc_state->active)
+		dcp_crtc_atomic_modeset(crtc, state);
 }
 
 static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
@@ -300,8 +303,6 @@ static const struct drm_connector_funcs apple_connector_funcs = {
 static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
 	.get_modes		= dcp_get_modes,
 	.mode_valid		= dcp_mode_valid,
-	.atomic_check		= dcp_connector_atomic_check,
-
 };
 
 static const struct drm_crtc_helper_funcs apple_crtc_helper_funcs = {
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 039dcf3ab319d8..298520c0832e93 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -57,8 +57,8 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc);
 int dcp_get_modes(struct drm_connector *connector);
 int dcp_mode_valid(struct drm_connector *connector,
 		   struct drm_display_mode *mode);
-int dcp_connector_atomic_check(struct drm_connector *connector,
-			       struct drm_atomic_state *state);
+int dcp_crtc_atomic_modeset(struct drm_crtc *crtc,
+			    struct drm_atomic_state *state);
 bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 			 const struct drm_display_mode *mode,
 			 struct drm_display_mode *adjusted_mode);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 5884f30426d6d6..f0aecd92dae348 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -422,13 +422,11 @@ int dcp_mode_valid(struct drm_connector *connector,
 }
 EXPORT_SYMBOL_GPL(dcp_mode_valid);
 
-int dcp_connector_atomic_check(struct drm_connector *connector,
-			       struct drm_atomic_state *state)
+int dcp_crtc_atomic_modeset(struct drm_crtc *crtc,
+			    struct drm_atomic_state *state)
 {
-	struct apple_connector *apple_connector = to_apple_connector(connector);
-	struct platform_device *pdev = apple_connector->dcp;
-	struct apple_dcp *dcp = platform_get_drvdata(pdev);
-	struct drm_crtc *crtc = &dcp->crtc->base;
+	struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
+	struct apple_dcp *dcp = platform_get_drvdata(apple_crtc->dcp);
 	struct drm_crtc_state *crtc_state;
 	int ret = -EIO;
 	bool modeset;
@@ -461,7 +459,7 @@ int dcp_connector_atomic_check(struct drm_connector *connector,
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_connector_atomic_check);
+EXPORT_SYMBOL_GPL(dcp_crtc_atomic_modeset);
 
 bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 			 const struct drm_display_mode *mode,

From 009d55dfb26ca97d52d7fd416b4fe9403c231214 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 23 Nov 2023 22:58:51 +0100
Subject: [PATCH 0767/1027] drm: apple: Fix DPTX hotplug handling

- Do not trigger an hotplug event from disconnect. DCP/iomfb notices that
  itself.
- Check HPD status before disconnecting DPTX in the crtc disable
  path.
- disconnect on suspend to allow an orderly re-connect on resume

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index af977563b75507..36c844d2df88ba 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -292,16 +292,9 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 
 static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
 {
-	struct apple_connector *connector = dcp->connector;
 	dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
 
 	mutex_lock(&dcp->hpd_mutex);
-
-	if (connector && connector->connected) {
-		dcp->valid_mode = false;
-		schedule_work(&connector->hotplug_wq);
-	}
-
 	if (dcp->dptxport[port].enabled && dcp->dptxport[port].connected) {
 		dptxport_release_display(dcp->dptxport[port].service);
 		dcp->dptxport[port].connected = false;
@@ -478,9 +471,11 @@ void dcp_poweroff(struct platform_device *pdev)
 		break;
 	}
 
-	if (dcp->phy)
-		dcp_dptx_disconnect(dcp, 0);
-
+	if (dcp->hdmi_hpd) {
+		bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+		if (!connected)
+			dcp_dptx_disconnect(dcp, 0);
+	}
 }
 EXPORT_SYMBOL(dcp_poweroff);
 
@@ -1017,8 +1012,10 @@ static int dcp_platform_suspend(struct device *dev)
 {
 	struct apple_dcp *dcp = dev_get_drvdata(dev);
 
-	if (dcp->hdmi_hpd_irq)
+	if (dcp->hdmi_hpd_irq) {
 		disable_irq(dcp->hdmi_hpd_irq);
+		dcp_dptx_disconnect(dcp, 0);
+	}
 	/*
 	 * Set the device as a wakeup device, which forces its power
 	 * domains to stay on. We need this as we do not support full

From 3fe42c36ec937fedbdf824ba67b93e86f5f2e2fb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 23 Nov 2023 23:04:47 +0100
Subject: [PATCH 0768/1027] drm: apple: iomfb: Use
 drm_kms_helper_connector_hotplug_event

Avoid device wide hotplugs as DCP knowns the affected connector.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index f0aecd92dae348..cb2cb6a89a4133 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -229,11 +229,9 @@ void dcp_ack(struct apple_dcp *dcp, enum dcp_context_id context)
 void dcp_hotplug(struct work_struct *work)
 {
 	struct apple_connector *connector;
-	struct drm_device *dev;
 	struct apple_dcp *dcp;
 
 	connector = container_of(work, struct apple_connector, hotplug_wq);
-	dev = connector->base.dev;
 
 	dcp = platform_get_drvdata(connector->dcp);
 	dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__,
@@ -244,13 +242,11 @@ void dcp_hotplug(struct work_struct *work)
 	 * display modes from atomic_flush, so userspace needs to trigger a
 	 * flush, or the CRTC gets no signal.
 	 */
-	if (connector->base.state && !dcp->valid_mode && connector->connected) {
-		drm_connector_set_link_status_property(
-			&connector->base, DRM_MODE_LINK_STATUS_BAD);
-	}
+	if (connector->base.state && !dcp->valid_mode && connector->connected)
+		drm_connector_set_link_status_property(&connector->base,
+						       DRM_MODE_LINK_STATUS_BAD);
 
-	if (dev && dev->registered)
-		drm_kms_helper_hotplug_event(dev);
+	drm_kms_helper_connector_hotplug_event(&connector->base);
 }
 EXPORT_SYMBOL_GPL(dcp_hotplug);
 

From eda51303b78d9709932180b097f0e0de80d8bbac Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 26 Nov 2023 18:30:59 +0100
Subject: [PATCH 0769/1027] drm : apple: iomfb: Handle OOB ASYNC/CB context

Only observed with dcp/dptx in linux after initialisation and reset in
m1n1. On the initial startup dcp sends two D576 (hotPlug_notify_gated)
presumendly due to state confusion due to  the multiple dptx
connections.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 2 +-
 drivers/gpu/drm/apple/iomfb.c        | 4 ++++
 drivers/gpu/drm/apple/iomfb.h        | 3 +++
 3 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index e975ae5be371fd..24d23364e8f415 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -154,7 +154,7 @@ struct apple_dcp {
 	struct dcp_mem_descriptor memdesc[DCP_MAX_MAPPINGS];
 
 	struct dcp_channel ch_cmd, ch_oobcmd;
-	struct dcp_channel ch_cb, ch_oobcb, ch_async;
+	struct dcp_channel ch_cb, ch_oobcb, ch_async, ch_oobasync;
 
 	/* iomfb EP callback handlers */
 	const iomfb_cb_handler *cb_handlers;
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index cb2cb6a89a4133..eb475c7d2d441c 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -49,6 +49,8 @@ static int dcp_channel_offset(enum dcp_context_id id)
 	switch (id) {
 	case DCP_CONTEXT_ASYNC:
 		return 0x40000;
+	case DCP_CONTEXT_OOBASYNC:
+		return 0x48000;
 	case DCP_CONTEXT_CB:
 		return 0x60000;
 	case DCP_CONTEXT_OOBCB:
@@ -118,6 +120,8 @@ static struct dcp_channel *dcp_get_channel(struct apple_dcp *dcp,
 		return &dcp->ch_oobcmd;
 	case DCP_CONTEXT_ASYNC:
 		return &dcp->ch_async;
+	case DCP_CONTEXT_OOBASYNC:
+		return &dcp->ch_oobasync;
 	default:
 		return NULL;
 	}
diff --git a/drivers/gpu/drm/apple/iomfb.h b/drivers/gpu/drm/apple/iomfb.h
index 5d54a59c7ced45..8fb225e6169e42 100644
--- a/drivers/gpu/drm/apple/iomfb.h
+++ b/drivers/gpu/drm/apple/iomfb.h
@@ -28,6 +28,9 @@ enum dcp_context_id {
 	/* Out-of-band command */
 	DCP_CONTEXT_OOBCMD = 6,
 
+	/* Out-of-band Asynchronous */
+	DCP_CONTEXT_OOBASYNC = 7,
+
 	DCP_NUM_CONTEXTS
 };
 

From a174dae002077ba5860a1ecf3adbcd5515feb3c4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 26 Nov 2023 18:57:07 +0100
Subject: [PATCH 0770/1027] drm: apple: iomfb: Extend hotplug/mode parsing
 logging

Under unknown but slightly broken conditions dcp sends timing modes
without linked color modes. Log a warning when this happens and log the
number of valid modes before emitting HPD events.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb.c          | 4 ++--
 drivers/gpu/drm/apple/iomfb_template.c | 2 ++
 2 files changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index eb475c7d2d441c..b179c183f46529 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -238,8 +238,8 @@ void dcp_hotplug(struct work_struct *work)
 	connector = container_of(work, struct apple_connector, hotplug_wq);
 
 	dcp = platform_get_drvdata(connector->dcp);
-	dev_info(dcp->dev, "%s() connected:%d valid_mode:%d\n", __func__,
-		 connector->connected, dcp->valid_mode);
+	dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__,
+		 connector->connected, dcp->valid_mode, dcp->nr_modes);
 
 	/*
 	 * DCP defers link training until we set a display mode. But we set
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 0895efe3a8472d..52d97e380d5b3c 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -567,6 +567,8 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp,
 			dcp->nr_modes = 0;
 			return false;
 		}
+		if (dcp->nr_modes == 0)
+			dev_warn(dcp->dev, "TimingElements without valid modes!\n");
 	} else if (!strcmp(req->key, "DisplayAttributes")) {
 		/* DisplayAttributes are empty for integrated displays, use
 		 * display dimensions read from the devicetree

From 7eaf74d3cb8870a9ee6d7a983d3c3ab8e031b779 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 27 Nov 2023 00:11:12 +0100
Subject: [PATCH 0771/1027] drm: apple: Adjust startup sequence and timing for
 dptx

DPTX setup from an initialized connection and display with sleeping and
reset dcp is unfortunately quite fragile. The display connection has to
be stopped and reestablished. Goodbye flicker free boot.
If the IOMFB endpoint is started too early dcp might provide incomplete
timing modes which prevent modesets.
On display standby a HPD is triggered should result in a fully
initialized dcp. If not a display cable unplug and plug should help.
MacOS doesn't handle this at all and just gives up.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  8 +++-
 drivers/gpu/drm/apple/dcp.c       | 64 +++++++++++++++++--------------
 2 files changed, 43 insertions(+), 29 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 162c4beb12f349..907c052801d4e9 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -8,6 +8,7 @@
  */
 
 #include <linux/component.h>
+#include <linux/delay.h>
 #include <linux/dma-mapping.h>
 #include <linux/jiffies.h>
 #include <linux/module.h>
@@ -444,7 +445,10 @@ static int apple_drm_init_dcp(struct device *dev)
 	if (num_dcp < 1)
 		return -ENODEV;
 
-	timeout = get_jiffies_64() + msecs_to_jiffies(500);
+	/*
+	 * Starting DPTX might take some time.
+	 */
+	timeout = get_jiffies_64() + msecs_to_jiffies(3000);
 
 	for (i = 0; i < num_dcp; ++i) {
 		u64 jiffies = get_jiffies_64();
@@ -459,6 +463,8 @@ static int apple_drm_init_dcp(struct device *dev)
 		if (ret)
 			dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret);
 	}
+	/* HACK: Wait for dcp* to settle before a modeset */
+	msleep(100);
 
 	return 0;
 }
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 36c844d2df88ba..5752517c2abd2d 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -345,23 +345,40 @@ int dcp_start(struct platform_device *pdev)
 	if (ret)
 		dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret);
 
-	if (dcp->phy) {
-		if (dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
-			ret = ibootep_init(dcp);
-			if (ret)
-				dev_warn(dcp->dev,
-					 "Failed to start IBOOT endpoint: %d",
-					 ret);
-
-			ret = dptxep_init(dcp);
-			if (ret)
-				dev_warn(dcp->dev,
-					 "Failed to start DPTX endpoint: %d",
-					 ret);
-		} else
-			dev_warn(dcp->dev,
-				 "OS firmware incompatible with dptxport EP\n");
-	}
+	if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
+		ret = ibootep_init(dcp);
+		if (ret)
+			dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d",
+				 ret);
+
+		ret = dptxep_init(dcp);
+		if (ret)
+			dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d",
+				 ret);
+		else if (dcp->dptxport[0].enabled) {
+			bool connected;
+			/* force disconnect on start - necessary if the display
+			 * is already up from m1n1
+			 */
+			dptxport_set_hpd(dcp->dptxport[0].service, false);
+			dptxport_release_display(dcp->dptxport[0].service);
+			usleep_range(10 * USEC_PER_MSEC, 25 * USEC_PER_MSEC);
+
+			connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+			dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
+
+			// necessary on j473/j474 but not on j314c
+			if (connected)
+				dcp_dptx_connect(dcp, 0);
+			/*
+			 * Long sleep necessary to ensure dcp delivers timing
+			 * modes with matched color modes.
+			 * 400ms was sufficient on j473
+			 */
+			msleep(500);
+		}
+	} else if (dcp->phy)
+		dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n");
 
 	ret = iomfb_start_rtkit(dcp);
 	if (ret)
@@ -373,17 +390,8 @@ EXPORT_SYMBOL(dcp_start);
 
 static int dcp_enable_dp2hdmi_hpd(struct apple_dcp *dcp)
 {
-	if (dcp->hdmi_hpd) {
-		bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
-		dev_info(dcp->dev, "%s: DP2HDMI HPD connected:%d\n", __func__, connected);
-
-		// necessary on j473/j474 but not on j314c
-		if (connected)
-			dcp_dptx_connect(dcp, 0);
-
-		if (dcp->hdmi_hpd_irq)
-			enable_irq(dcp->hdmi_hpd_irq);
-	}
+	if (dcp->hdmi_hpd_irq)
+		enable_irq(dcp->hdmi_hpd_irq);
 
 	return 0;
 }

From ea2704f40131c24ea6279822701898fc19cc4f43 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 28 Nov 2023 14:27:18 +0100
Subject: [PATCH 0772/1027] drm: apple: dcp: Fix resume with DPTX based display
 outputs

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 5752517c2abd2d..ba785cab672570 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1041,6 +1041,13 @@ static int dcp_platform_resume(struct device *dev)
 	if (dcp->hdmi_hpd_irq)
 		enable_irq(dcp->hdmi_hpd_irq);
 
+	if (dcp->hdmi_hpd) {
+		bool connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+		dev_info(dcp->dev, "resume: HPD connected:%d\n", connected);
+		if (connected)
+			dcp_dptx_connect(dcp, 0);
+	}
+
 	return 0;
 }
 

From ec7635ec94815c5c1ddd7e218e20febae5fac5a9 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 2 Dec 2023 10:26:13 +0100
Subject: [PATCH 0773/1027] drm: apple: Be less noisy about teardown notifies
 without service

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index fc90150bb7b5ab..a11e9f1f5be4d3 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -507,6 +507,12 @@ static void afk_recv_handle(struct apple_dcp_afkep *ep, u32 channel, u32 type,
 				ep->endpoint, eshdr->category, channel);
 			return;
 		}
+		if (subtype == EPIC_SUBTYPE_TEARDOWN) {
+			dev_dbg(ep->dcp->dev,
+				"AFK[ep:%02x]: teardown without service on channel %d\n",
+				ep->endpoint, channel);
+			return;
+		}
 		if (subtype != EPIC_SUBTYPE_ANNOUNCE) {
 			dev_err(ep->dcp->dev,
 				"AFK[ep:%02x]: expected announce but got 0x%x on channel %d\n",

From a160666d8f4b81b10014ebdc451a97cf39fcf7da Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 3 Dec 2023 23:57:25 +0100
Subject: [PATCH 0774/1027] drm: apple: dptx: Wait for link config on connect

Should make connect more reliable by avoiding hardcoded waits which are
either to long or too short. In the second case the display can't be
brought up since dcp fails to report any modes during start.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c    | 22 ++++++++++++++--------
 drivers/gpu/drm/apple/dptxep.c |  8 ++++++--
 drivers/gpu/drm/apple/dptxep.h |  1 +
 3 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index ba785cab672570..01c051e7c19a25 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -10,6 +10,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/gpio/consumer.h>
 #include <linux/iommu.h>
+#include <linux/jiffies.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
@@ -254,6 +255,8 @@ int dcp_get_connector_type(struct platform_device *pdev)
 }
 EXPORT_SYMBOL_GPL(dcp_get_connector_type);
 
+#define DPTX_CONNECT_TIMEOUT msecs_to_jiffies(1000)
+
 static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 {
 	int ret = 0;
@@ -281,8 +284,17 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 	dcp->dptxport[port].connected = true;
 
 	mutex_unlock(&dcp->hpd_mutex);
-	wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion,
-				    msecs_to_jiffies(1000));
+	ret = wait_for_completion_timeout(&dcp->dptxport[port].linkcfg_completion,
+				    DPTX_CONNECT_TIMEOUT);
+	if (ret < 0)
+		dev_warn(dcp->dev, "dcp_dptx_connect: port %d link complete failed:%d\n",
+			 port, ret);
+	else
+		dev_dbg(dcp->dev, "dcp_dptx_connect: waited %d ms for link\n",
+			jiffies_to_msecs(DPTX_CONNECT_TIMEOUT - ret));
+
+	usleep_range(5, 10);
+
 	return 0;
 
 out_unlock:
@@ -370,12 +382,6 @@ int dcp_start(struct platform_device *pdev)
 			// necessary on j473/j474 but not on j314c
 			if (connected)
 				dcp_dptx_connect(dcp, 0);
-			/*
-			 * Long sleep necessary to ensure dcp delivers timing
-			 * modes with matched color modes.
-			 * 400ms was sufficient on j473
-			 */
-			msleep(500);
 		}
 	} else if (dcp->phy)
 		dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n");
diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 0a3ab4abd074c6..56b86966e807a7 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -294,9 +294,14 @@ static int dptxport_call_set_active_lane_count(struct apple_epic_service *servic
 		dptx->phy_ops.dp.set_lanes = 0;
 	}
 
+	dptx->lane_count = lane_count;
+
 	reply->retcode = cpu_to_le32(retcode);
 	reply->lane_count = cpu_to_le64(lane_count);
 
+	if (dptx->lane_count > 0)
+		complete(&dptx->linkcfg_completion);
+
 	return ret;
 }
 
@@ -330,10 +335,9 @@ dptxport_call_will_change_link_config(struct apple_epic_service *service)
 static int
 dptxport_call_did_change_link_config(struct apple_epic_service *service)
 {
-	struct dptx_port *dptx = service->cookie;
 	/* assume the link config did change and wait a little bit */
 	mdelay(10);
-	complete(&dptx->linkcfg_completion);
+
 	return 0;
 }
 
diff --git a/drivers/gpu/drm/apple/dptxep.h b/drivers/gpu/drm/apple/dptxep.h
index 4a0770d43c954c..0bf2534054fd7b 100644
--- a/drivers/gpu/drm/apple/dptxep.h
+++ b/drivers/gpu/drm/apple/dptxep.h
@@ -55,6 +55,7 @@ struct dptx_port {
 	union phy_configure_opts phy_ops;
 	struct phy *atcphy;
 	struct mux_control *mux;
+	u32 lane_count;
 	u32 link_rate, pending_link_rate;
 	u32 drive_settings[2];
 };

From 70c43e58fd2086bc45ca65de01e44b4ae356073c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Fri, 1 Dec 2023 23:41:53 +0100
Subject: [PATCH 0775/1027] drm: apple: Prefer RGB SDR modes

DCP color mode scoring seems to prefer high bit depth color modes even
when it it would require DSC. For example 12-bit 4k 60 Hz YCbCr 4:4:4
over a 600 MHz HDMI 2.0 link.
Prefer 8-/10-bit RGB or YCbCr 4:4:4 modes if available.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 16 ++++++
 drivers/gpu/drm/apple/parser.c         | 78 ++++++++++++++++++--------
 drivers/gpu/drm/apple/parser.h         | 68 ++++++++++++++++++++++
 3 files changed, 138 insertions(+), 24 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 52d97e380d5b3c..888659a18690c6 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -1192,6 +1192,7 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
 {
 	struct dcp_display_mode *mode;
 	struct dcp_wait_cookie *cookie;
+	struct dcp_color_mode *cmode = NULL;
 	int ret;
 
 	mode = lookup_mode(dcp, &crtc_state->mode);
@@ -1205,6 +1206,21 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
 		 "set_digital_out_mode(color:%d timing:%d) " DRM_MODE_FMT "\n",
 		 mode->color_mode_id, mode->timing_mode_id,
 		 DRM_MODE_ARG(&crtc_state->mode));
+	if (mode->color_mode_id == mode->sdr_rgb.id)
+		cmode = &mode->sdr_rgb;
+	else if (mode->color_mode_id == mode->sdr_444.id)
+		cmode = &mode->sdr_444;
+	else if (mode->color_mode_id == mode->sdr.id)
+		cmode = &mode->sdr;
+	else if (mode->color_mode_id == mode->best.id)
+		cmode = &mode->best;
+	if (cmode)
+		dev_info(dcp->dev,
+			"set_digital_out_mode() color mode depth:%hhu format:%u "
+			"colorimetry:%u eotf:%u range:%u\n", cmode->depth,
+			cmode->format, cmode->colorimetry, cmode->eotf,
+			cmode->range);
+
 	dcp->mode = (struct dcp_set_digital_out_mode_req){
 		.color_mode_id = mode->color_mode_id,
 		.timing_mode_id = mode->timing_mode_id
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index a837af1bd0a5e8..fcac87f0fadec0 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -313,14 +313,42 @@ struct color_mode {
 	s64 score;
 };
 
-static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id)
+static int fill_color_mode(struct dcp_color_mode *color,
+			   struct color_mode *cmode)
+{
+	if (color->score >= cmode->score)
+		return 0;
+
+	if (cmode->colorimetry < 0 || cmode->colorimetry >= DCP_COLORIMETRY_COUNT)
+		return -EINVAL;
+	if (cmode->depth < 8 || cmode->depth > 12)
+		return -EINVAL;
+	if (cmode->dynamic_range < 0 || cmode->dynamic_range >= DCP_COLOR_YCBCR_RANGE_COUNT)
+		return -EINVAL;
+	if (cmode->eotf < 0 || cmode->eotf >= DCP_EOTF_COUNT)
+		return -EINVAL;
+	if (cmode->pixel_encoding < 0 || cmode->pixel_encoding >= DCP_COLOR_FORMAT_COUNT)
+		return -EINVAL;
+
+	color->score = cmode->score;
+	color->id = cmode->id;
+	color->eotf = cmode->eotf;
+	color->format = cmode->pixel_encoding;
+	color->colorimetry = cmode->colorimetry;
+	color->range = cmode->dynamic_range;
+	color->depth = cmode->depth;
+
+	return 0;
+}
+
+static int parse_color_modes(struct dcp_parse_ctx *handle,
+			     struct dcp_display_mode *out)
 {
 	struct iterator outer_it;
 	int ret = 0;
-	s64 best_score = -1, best_score_sdr = -1;
-	s64 best_id = -1, best_id_sdr = -1;
-
-	*preferred_id = -1;
+	out->sdr_444.score = -1;
+	out->sdr_rgb.score = -1;
+	out->best.score = -1;
 
 	dcp_parse_foreach_in_array(handle, outer_it) {
 		struct iterator it;
@@ -367,25 +395,18 @@ static int parse_color_modes(struct dcp_parse_ctx *handle, s64 *preferred_id)
 				       cmode.eotf, cmode.dynamic_range,
 				       cmode.pixel_encoding);
 
-		if (cmode.eotf == 0) {
-			if (cmode.score > best_score_sdr) {
-				best_score_sdr = cmode.score;
-				best_id_sdr = cmode.id;
-			}
-		} else {
-			if (cmode.score > best_score) {
-				best_score = cmode.score;
-				best_id = cmode.id;
-			}
+		if (cmode.eotf == DCP_EOTF_SDR_GAMMA) {
+			if (cmode.pixel_encoding == DCP_COLOR_FORMAT_RGB &&
+				cmode.depth <= 10)
+				fill_color_mode(&out->sdr_rgb, &cmode);
+			else if (cmode.pixel_encoding == DCP_COLOR_FORMAT_YCBCR444 &&
+				cmode.depth <= 10)
+				fill_color_mode(&out->sdr_444, &cmode);
+			fill_color_mode(&out->sdr, &cmode);
 		}
+		fill_color_mode(&out->best, &cmode);
 	}
 
-	/* prefer SDR color modes as long as HDR is not supported */
-	if (best_score_sdr >= 0)
-		*preferred_id = best_id_sdr;
-	else if (best_score >= 0)
-		*preferred_id = best_id;
-
 	return 0;
 }
 
@@ -427,7 +448,7 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 		else if (!strcmp(key, "VerticalAttributes"))
 			ret = parse_dimension(it.handle, &vert);
 		else if (!strcmp(key, "ColorModes"))
-			ret = parse_color_modes(it.handle, &best_color_mode);
+			ret = parse_color_modes(it.handle, out);
 		else if (!strcmp(key, "ID"))
 			ret = parse_int(it.handle, &id);
 		else if (!strcmp(key, "IsVirtual"))
@@ -445,8 +466,17 @@ static int parse_mode(struct dcp_parse_ctx *handle,
 			return ret;
 		}
 	}
-
-	trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode, is_virtual, *score);
+	if (out->sdr_rgb.score >= 0)
+		best_color_mode = out->sdr_rgb.id;
+	else if (out->sdr_444.score >= 0)
+		best_color_mode = out->sdr_444.id;
+	else if (out->sdr.score >= 0)
+		best_color_mode = out->sdr.id;
+	else if (out->best.score >= 0)
+		best_color_mode = out->best.id;
+
+	trace_iomfb_parse_mode_success(id, &horiz, &vert, best_color_mode,
+				       is_virtual, *score);
 
 	/*
 	 * Reject modes without valid color mode.
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index f9cc3718fa84f7..f867416070e49d 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -15,6 +15,70 @@ struct dcp_parse_ctx {
 	u32 pos, len;
 };
 
+enum dcp_color_eotf {
+	DCP_EOTF_SDR_GAMMA = 0, // "SDR gamma"
+	DCP_EOTF_HDR_GAMMA = 1, // "HDR gamma"
+	DCP_EOTF_ST_2084   = 2, // "ST 2084 (PQ)"
+	DCP_EOTF_BT_2100   = 3, // "BT.2100 (HLG)"
+	DCP_EOTF_COUNT
+};
+
+enum dcp_color_format {
+	DCP_COLOR_FORMAT_RGB                 =  0, // "RGB"
+	DCP_COLOR_FORMAT_YCBCR420            =  1, // "YUV 4:2:0"
+	DCP_COLOR_FORMAT_YCBCR422            =  3, // "YUV 4:2:2"
+	DCP_COLOR_FORMAT_YCBCR444            =  2, // "YUV 4:4:4"
+	DCP_COLOR_FORMAT_DV_NATIVE           =  4, // "DolbyVision (native)"
+	DCP_COLOR_FORMAT_DV_HDMI             =  5, // "DolbyVision (HDMI)"
+	DCP_COLOR_FORMAT_YCBCR422_DP         =  6, // "YCbCr 4:2:2 (DP tunnel)"
+	DCP_COLOR_FORMAT_YCBCR422_HDMI       =  7, // "YCbCr 4:2:2 (HDMI tunnel)"
+	DCP_COLOR_FORMAT_DV_LL_YCBCR422      =  8, // "DolbyVision LL YCbCr 4:2:2"
+	DCP_COLOR_FORMAT_DV_LL_YCBCR422_DP   =  9, // "DolbyVision LL YCbCr 4:2:2 (DP)"
+	DCP_COLOR_FORMAT_DV_LL_YCBCR422_HDMI = 10, // "DolbyVision LL YCbCr 4:2:2 (HDMI)"
+	DCP_COLOR_FORMAT_DV_LL_YCBCR444      = 11, // "DolbyVision LL YCbCr 4:4:4"
+	DCP_COLOR_FORMAT_DV_LL_RGB422        = 12, // "DolbyVision LL RGB 4:2:2"
+	DCP_COLOR_FORMAT_GRGB_BLUE_422       = 13, // "GRGB as YCbCr422 (Even line blue)"
+	DCP_COLOR_FORMAT_GRGB_RED_422        = 14, // "GRGB as YCbCr422 (Even line red)"
+	DCP_COLOR_FORMAT_COUNT
+};
+
+enum dcp_colorimetry {
+	DCP_COLORIMETRY_BT601              =  0, // "SMPTE 170M/BT.601"
+	DCP_COLORIMETRY_BT709              =  1, // "BT.701"
+	DCP_COLORIMETRY_XVYCC_601          =  2, // "xvYCC601"
+	DCP_COLORIMETRY_XVYCC_709          =  3, // "xvYCC709"
+	DCP_COLORIMETRY_SYCC_601           =  4, // "sYCC601"
+	DCP_COLORIMETRY_ADOBE_YCC_601      =  5, // "AdobeYCC601"
+	DCP_COLORIMETRY_BT2020_CYCC        =  6, // "BT.2020 (c)"
+	DCP_COLORIMETRY_BT2020_YCC         =  7, // "BT.2020 (nc)"
+	DCP_COLORIMETRY_VSVDB              =  8, // "DolbyVision VSVDB"
+	DCP_COLORIMETRY_BT2020_RGB         =  9, // "BT.2020 (RGB)"
+	DCP_COLORIMETRY_SRGB               = 10, // "sRGB"
+	DCP_COLORIMETRY_SCRGB              = 11, // "scRGB"
+	DCP_COLORIMETRY_SCRGB_FIXED        = 12, // "scRGBfixed"
+	DCP_COLORIMETRY_ADOBE_RGB          = 13, // "AdobeRGB"
+	DCP_COLORIMETRY_DCI_P3_RGB_D65     = 14, // "DCI-P3 (D65)"
+	DCP_COLORIMETRY_DCI_P3_RGB_THEATER = 15, // "DCI-P3 (Theater)"
+	DCP_COLORIMETRY_RGB                = 16, // "Default RGB"
+	DCP_COLORIMETRY_COUNT
+};
+
+enum dcp_color_range {
+	DCP_COLOR_YCBCR_RANGE_FULL    = 0,
+	DCP_COLOR_YCBCR_RANGE_LIMITED = 1,
+	DCP_COLOR_YCBCR_RANGE_COUNT
+};
+
+struct dcp_color_mode {
+	s64 score;
+	u32 id;
+	enum dcp_color_eotf eotf;
+	enum dcp_color_format format;
+	enum dcp_colorimetry colorimetry;
+	enum dcp_color_range range;
+	u8 depth;
+};
+
 /*
  * Represents a single display mode. These mode objects are populated at
  * runtime based on the TimingElements dictionary sent by the DCP.
@@ -23,6 +87,10 @@ struct dcp_display_mode {
 	struct drm_display_mode mode;
 	u32 color_mode_id;
 	u32 timing_mode_id;
+	struct dcp_color_mode sdr_rgb;
+	struct dcp_color_mode sdr_444;
+	struct dcp_color_mode sdr;
+	struct dcp_color_mode best;
 };
 
 struct dimension {

From 06247e222b04c8b50dafb5c9d2c690cab97dce41 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 4 Dec 2023 23:27:29 +0100
Subject: [PATCH 0776/1027] drm: apple: iomfb: Always parse DisplayAttributes

Fixes missing physical display dimensions for HDMI display on Macbook
Pros.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 15 +++++----------
 1 file changed, 5 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 888659a18690c6..dc99a4d9f8694b 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -570,17 +570,12 @@ static bool dcpep_process_chunks(struct apple_dcp *dcp,
 		if (dcp->nr_modes == 0)
 			dev_warn(dcp->dev, "TimingElements without valid modes!\n");
 	} else if (!strcmp(req->key, "DisplayAttributes")) {
-		/* DisplayAttributes are empty for integrated displays, use
-		 * display dimensions read from the devicetree
-		 */
-		if (dcp->main_display) {
-			ret = parse_display_attributes(&ctx, &dcp->width_mm,
-						&dcp->height_mm);
+		ret = parse_display_attributes(&ctx, &dcp->width_mm,
+					&dcp->height_mm);
 
-			if (ret) {
-				dev_warn(dcp->dev, "failed to parse display attribs\n");
-				return false;
-			}
+		if (ret) {
+			dev_warn(dcp->dev, "failed to parse display attribs\n");
+			return false;
 		}
 
 		dcp_set_dimensions(dcp);

From 9706afaa4d3f5185956994d3f76e9f4e2d22546e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 10 Dec 2023 13:01:22 +0100
Subject: [PATCH 0777/1027] drm: apple: parser: constify parser data

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c | 40 +++++++++++++++++-----------------
 drivers/gpu/drm/apple/parser.h |  4 ++--
 2 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index fcac87f0fadec0..958b94dd1b0f3e 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -30,9 +30,9 @@ struct dcp_parse_tag {
 	bool last : 1;
 } __packed;
 
-static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count)
+static const void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count)
 {
-	void *ptr = ctx->blob + ctx->pos;
+	const void *ptr = ctx->blob + ctx->pos;
 
 	if (ctx->pos + count > ctx->len)
 		return ERR_PTR(-EINVAL);
@@ -41,14 +41,14 @@ static void *parse_bytes(struct dcp_parse_ctx *ctx, size_t count)
 	return ptr;
 }
 
-static u32 *parse_u32(struct dcp_parse_ctx *ctx)
+static const u32 *parse_u32(struct dcp_parse_ctx *ctx)
 {
 	return parse_bytes(ctx, sizeof(u32));
 }
 
-static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx)
+static const struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx)
 {
-	struct dcp_parse_tag *tag;
+	const struct dcp_parse_tag *tag;
 
 	/* Align to 32-bits */
 	ctx->pos = round_up(ctx->pos, 4);
@@ -64,10 +64,10 @@ static struct dcp_parse_tag *parse_tag(struct dcp_parse_ctx *ctx)
 	return tag;
 }
 
-static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx,
+static const struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx,
 					       enum dcp_parse_type type)
 {
-	struct dcp_parse_tag *tag = parse_tag(ctx);
+	const struct dcp_parse_tag *tag = parse_tag(ctx);
 
 	if (IS_ERR(tag))
 		return tag;
@@ -80,7 +80,7 @@ static struct dcp_parse_tag *parse_tag_of_type(struct dcp_parse_ctx *ctx,
 
 static int skip(struct dcp_parse_ctx *handle)
 {
-	struct dcp_parse_tag *tag = parse_tag(handle);
+	const struct dcp_parse_tag *tag = parse_tag(handle);
 	int ret = 0;
 	int i;
 
@@ -132,7 +132,7 @@ static int skip_pair(struct dcp_parse_ctx *handle)
 
 static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen)
 {
-	struct dcp_parse_tag *tag;
+	const struct dcp_parse_tag *tag;
 	const char *key;
 	ctx->pos = round_up(ctx->pos, 4);
 
@@ -155,7 +155,7 @@ static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen)
 /* Caller must free the result */
 static char *parse_string(struct dcp_parse_ctx *handle)
 {
-	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING);
+	const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_STRING);
 	const char *in;
 	char *out;
 
@@ -175,8 +175,8 @@ static char *parse_string(struct dcp_parse_ctx *handle)
 
 static int parse_int(struct dcp_parse_ctx *handle, s64 *value)
 {
-	void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64);
-	s64 *in;
+	const void *tag = parse_tag_of_type(handle, DCP_TYPE_INT64);
+	const s64 *in;
 
 	if (IS_ERR(tag))
 		return PTR_ERR(tag);
@@ -192,7 +192,7 @@ static int parse_int(struct dcp_parse_ctx *handle, s64 *value)
 
 static int parse_bool(struct dcp_parse_ctx *handle, bool *b)
 {
-	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL);
+	const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BOOL);
 
 	if (IS_ERR(tag))
 		return PTR_ERR(tag);
@@ -201,10 +201,10 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b)
 	return 0;
 }
 
-static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 **blob)
+static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob)
 {
-	struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB);
-	u8 *out;
+	const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB);
+	const u8 *out;
 
 	if (IS_ERR(tag))
 		return PTR_ERR(tag);
@@ -229,7 +229,7 @@ struct iterator {
 static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it,
 			  bool dict)
 {
-	struct dcp_parse_tag *tag;
+	const struct dcp_parse_tag *tag;
 	enum dcp_parse_type type = dict ? DCP_TYPE_DICTIONARY : DCP_TYPE_ARRAY;
 
 	*it = (struct iterator) {
@@ -250,9 +250,9 @@ static int iterator_begin(struct dcp_parse_ctx *handle, struct iterator *it,
 #define dcp_parse_foreach_in_dict(handle, it)                                  \
 	for (iterator_begin(handle, &it, true); it.idx < it.len; ++it.idx)
 
-int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx)
+int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx)
 {
-	u32 *header;
+	const u32 *header;
 
 	*ctx = (struct dcp_parse_ctx) {
 		.blob = blob,
@@ -912,7 +912,7 @@ static int parse_mode_in_avep_element(struct dcp_parse_ctx *handle,
 					return ret;
 			}
 		} else if (consume_string(it.handle, "ElementData")) {
-			u8 *blob;
+			const u8 *blob;
 
 			ret = parse_blob(it.handle, sizeof(*cookie), &blob);
 			if (ret)
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index f867416070e49d..a008e220cec3dd 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -11,7 +11,7 @@ struct apple_dcp;
 
 struct dcp_parse_ctx {
 	struct apple_dcp *dcp;
-	void *blob;
+	const void *blob;
 	u32 pos, len;
 };
 
@@ -98,7 +98,7 @@ struct dimension {
 	s64 precise_sync_rate;
 };
 
-int parse(void *blob, size_t size, struct dcp_parse_ctx *ctx);
+int parse(const void *blob, size_t size, struct dcp_parse_ctx *ctx);
 struct dcp_display_mode *enumerate_modes(struct dcp_parse_ctx *handle,
 					 unsigned int *count, int width_mm,
 					 int height_mm, unsigned notch_height);

From e07e7a3a638b754c37559a81d66f000807345f7a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 10 Dec 2023 13:27:12 +0100
Subject: [PATCH 0778/1027] drm: apple: epic: Pass full notfiy/report payload
 to handler

The payload is not necessarily epic_std_service_ap_call. The powerlog
service on the system endpoint passes serialized dictionaries as
payload.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 18 +++---------------
 drivers/gpu/drm/apple/afk.h |  4 +++-
 2 files changed, 6 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index a11e9f1f5be4d3..52a5bf5f8a6479 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -447,21 +447,9 @@ static void afk_recv_handle_std_service(struct apple_dcp_afkep *ep, u32 channel,
 	}
 
 	if (type == EPIC_TYPE_NOTIFY && eshdr->category == EPIC_CAT_REPORT) {
-		struct epic_std_service_ap_call *call = payload;
-		size_t call_size;
-
-		if (payload_size < sizeof(*call))
-			return;
-
-		call_size = le32_to_cpu(call->len);
-		if (payload_size < sizeof(*call) + call_size)
-			return;
-
-		if (!service->ops->report)
-			return;
-
-		service->ops->report(service, le32_to_cpu(call->type),
-				     payload + sizeof(*call), call_size);
+		if (service->ops->report)
+			service->ops->report(service, le16_to_cpu(eshdr->type),
+					     payload, payload_size);
 		return;
 	}
 
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index 1fdb4100352b25..737288b1346b28 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -49,6 +49,8 @@ struct apple_epic_service {
 	void *cookie;
 };
 
+enum epic_subtype;
+
 struct apple_epic_service_ops {
 	const char name[32];
 
@@ -57,7 +59,7 @@ struct apple_epic_service_ops {
 	int (*call)(struct apple_epic_service *service, u32 idx,
 		    const void *data, size_t data_size, void *reply,
 		    size_t reply_size);
-	int (*report)(struct apple_epic_service *service, u32 idx,
+	int (*report)(struct apple_epic_service *service, enum epic_subtype type,
 		      const void *data, size_t data_size);
 	void (*teardown)(struct apple_epic_service *service);
 };

From 58623cc7cfdbf0a2797ce7bcea313ba55cef0ec1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 10 Dec 2023 13:40:14 +0100
Subject: [PATCH 0779/1027] drm: apple: epic: systemep: Parse "mNits" log
 events

The 13.5 firmware has stopped updating the NITS property on backlight
brightness changes. Parse system log events instead which report
backlight's brightness in millinits.
Fixes the backlight device's "actual_brightness" property used by the
systemd backlight service to save and restore brightness.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/parser.c   | 48 ++++++++++++++++++++++++++++++++
 drivers/gpu/drm/apple/parser.h   |  9 ++++++
 drivers/gpu/drm/apple/systemep.c | 37 ++++++++++++++++++++++++
 3 files changed, 94 insertions(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 958b94dd1b0f3e..6d629bb0dfc45e 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -983,3 +983,51 @@ int parse_sound_mode(struct dcp_parse_ctx *handle,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(parse_sound_mode);
+
+int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry)
+{
+	struct iterator it;
+	int ret;
+	s64 mnits = -1;
+	s64 idac = -1;
+	s64 timestamp = -1;
+	bool type_match = false;
+
+	dcp_parse_foreach_in_dict(handle, it) {
+		char *key = parse_string(it.handle);
+		if (IS_ERR(key)) {
+			ret = PTR_ERR(key);
+		} else if (!strcmp(key, "mNits")) {
+			ret = parse_int(it.handle, &mnits);
+		} else if (!strcmp(key, "iDAC")) {
+			ret = parse_int(it.handle, &idac);
+		} else if (!strcmp(key, "logEvent")) {
+			const char * value = parse_string(it.handle);
+			if (!IS_ERR_OR_NULL(value)) {
+				type_match = strcmp(value, "Display (Event Forward)") == 0;
+				kfree(value);
+			}
+		} else if (!strcmp(key, "timestamp")) {
+			ret = parse_int(it.handle, &timestamp);
+		} else {
+			skip(it.handle);
+		}
+
+		if (!IS_ERR_OR_NULL(key))
+			kfree(key);
+
+		if (ret) {
+			pr_err("dcp parser: failed to parse mNits sys event\n");
+			return ret;
+		}
+	}
+
+	if (!type_match ||  mnits < 0 || idac < 0 || timestamp < 0)
+		return -EINVAL;
+
+	entry->millinits = mnits;
+	entry->idac = idac;
+	entry->timestamp = timestamp;
+
+	return 0;
+}
diff --git a/drivers/gpu/drm/apple/parser.h b/drivers/gpu/drm/apple/parser.h
index a008e220cec3dd..2f52e063bbd426 100644
--- a/drivers/gpu/drm/apple/parser.h
+++ b/drivers/gpu/drm/apple/parser.h
@@ -126,4 +126,13 @@ int parse_sound_mode(struct dcp_parse_ctx *handle,
 		     struct snd_pcm_chmap_elem *chmap,
 		     struct dcp_sound_cookie *cookie);
 
+struct dcp_system_ev_mnits {
+	u32 timestamp;
+	u32 millinits;
+	u32 idac;
+};
+
+int parse_system_log_mnits(struct dcp_parse_ctx *handle,
+			   struct dcp_system_ev_mnits *entry);
+
 #endif
diff --git a/drivers/gpu/drm/apple/systemep.c b/drivers/gpu/drm/apple/systemep.c
index 5383a83f1e6c28..9fe7a0ce495aab 100644
--- a/drivers/gpu/drm/apple/systemep.c
+++ b/drivers/gpu/drm/apple/systemep.c
@@ -5,6 +5,7 @@
 
 #include "afk.h"
 #include "dcp.h"
+#include "parser.h"
 
 static bool enable_verbose_logging;
 module_param(enable_verbose_logging, bool, 0644);
@@ -66,6 +67,41 @@ static void powerlog_init(struct apple_epic_service *service, const char *name,
 {
 }
 
+static int powerlog_report(struct apple_epic_service *service, enum epic_subtype type,
+			 const void *data, size_t data_size)
+{
+	struct dcp_system_ev_mnits mnits;
+	struct dcp_parse_ctx parse_ctx;
+	struct apple_dcp *dcp = service->ep->dcp;
+	int ret;
+
+	dev_dbg(dcp->dev, "systemep[ch:%u]: report type:%02x len:%zu\n",
+		service->channel, type, data_size);
+
+	if (type != EPIC_SUBTYPE_STD_SERVICE)
+		return 0;
+
+	ret = parse(data, data_size, &parse_ctx);
+	if (ret) {
+		dev_warn(service->ep->dcp->dev, "systemep: failed to parse report: %d\n", ret);
+		return ret;
+	}
+
+	ret = parse_system_log_mnits(&parse_ctx, &mnits);
+	if (ret) {
+		/* ignore parse errors in the case dcp sends unknown log events */
+		dev_dbg(dcp->dev, "systemep: failed to parse mNits event: %d\n", ret);
+		return 0;
+	}
+
+	dev_dbg(dcp->dev, "systemep: mNits event: Nits: %u.%03u, iDAC: %u\n",
+		mnits.millinits / 1000, mnits.millinits % 1000, mnits.idac);
+
+	dcp->brightness.nits = mnits.millinits / 1000;
+
+	return 0;
+}
+
 static const struct apple_epic_service_ops systemep_ops[] = {
 	{
 		.name = "system",
@@ -74,6 +110,7 @@ static const struct apple_epic_service_ops systemep_ops[] = {
 	{
 		.name = "powerlog-service",
 		.init = powerlog_init,
+		.report = powerlog_report,
 	},
 	{}
 };

From 02839a310424ef126fc2563f98b4e8439f84f40d Mon Sep 17 00:00:00 2001
From: Arnd Bergmann <arnd@arndb.de>
Date: Wed, 17 Jan 2024 11:44:10 +0100
Subject: [PATCH 0780/1027] drm: apple: mark local functions static

With linux-6.8, the kernel warns about functions that have no
extern declaration, so mark both of these static.

Fixes: 2d782b0d007d ("gpu: drm: apple: Add sound mode parsing")
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
---
 drivers/gpu/drm/apple/parser.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 6d629bb0dfc45e..4a4a6458ecebc9 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -680,7 +680,7 @@ int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name,
 	return ret;
 }
 
-int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
+static int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
 {
 	s64 rate;
 	int ret = parse_int(handle, &rate);
@@ -701,7 +701,7 @@ int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
 	return 0;
 }
 
-int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit)
+static int parse_sample_fmtbit(struct dcp_parse_ctx *handle, u64 *fmtbit)
 {
 	s64 sample_size;
 	int ret = parse_int(handle, &sample_size);

From 1414e96e2f12e5bc60f1c85b163fb907aadf9580 Mon Sep 17 00:00:00 2001
From: Alyssa Ross <hi@alyssa.is>
Date: Thu, 11 Jan 2024 11:39:10 +0100
Subject: [PATCH 0781/1027] drm/apple: Add missing RTKit Kconfig dependency

Signed-off-by: Alyssa Ross <hi@alyssa.is>
---
 drivers/gpu/drm/apple/Kconfig | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index fe69b04a912f93..1bffe0f48ebff5 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -3,6 +3,7 @@ config DRM_APPLE
 	tristate "DRM Support for Apple display controllers"
 	depends on DRM && OF && ARM64
 	depends on ARCH_APPLE || COMPILE_TEST
+	depends on APPLE_RTKIT
 	depends on SND
 	select DRM_KMS_HELPER
 	select DRM_KMS_DMA_HELPER

From 1a3a0d98fffbfb45f66ebdf4bc010f93e11ffec0 Mon Sep 17 00:00:00 2001
From: Jonathan Gray <jsg@jsg.id.au>
Date: Mon, 22 Jan 2024 18:54:31 +1100
Subject: [PATCH 0782/1027] drm/apple: spelling fixes

Signed-off-by: Jonathan Gray <jsg@jsg.id.au>
---
 drivers/gpu/drm/apple/apple_drv.c      | 2 +-
 drivers/gpu/drm/apple/dcp-internal.h   | 2 +-
 drivers/gpu/drm/apple/dcp.c            | 4 ++--
 drivers/gpu/drm/apple/iomfb.c          | 2 +-
 drivers/gpu/drm/apple/iomfb_template.c | 2 +-
 drivers/gpu/drm/apple/iomfb_v13_3.c    | 2 +-
 6 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 907c052801d4e9..dd2e0b6e4ae5bc 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -458,7 +458,7 @@ static int apple_drm_init_dcp(struct device *dev)
 		ret = dcp_wait_ready(dcp[i], wait);
 		/* There is nothing we can do if a dcp/dcpext does not boot
 		 * (successfully). Ignoring it should not do any harm now.
-		 * Needs to reevaluated whenn adding dcpext support.
+		 * Needs to reevaluated when adding dcpext support.
 		 */
 		if (ret)
 			dev_warn(dev, "DCP[%d] not ready: %d\n", i, ret);
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 24d23364e8f415..e625ea574b88ae 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -95,7 +95,7 @@ struct dcp_panel {
 	int width_mm;
 	/// panel height in millimeter
 	int height_mm;
-	/// panel has a mini-LED backllight
+	/// panel has a mini-LED backlight
 	bool has_mini_led;
 };
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 01c051e7c19a25..6cfb203f8a242e 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -808,7 +808,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	if (dcp->notch_height > 0)
 		dev_info(dev, "Detected display with notch of %u pixel\n", dcp->notch_height);
 
-	/* intialize brightness scale to a sensible default to avoid divide by 0*/
+	/* initialize brightness scale to a sensible default to avoid divide by 0*/
 	dcp->brightness.scale = 65536;
 	panel_np = of_get_compatible_child(dev->of_node, "apple,panel-mini-led");
 	if (panel_np)
@@ -877,7 +877,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops);
 	if (IS_ERR(dcp->rtk))
 		return dev_err_probe(dev, PTR_ERR(dcp->rtk),
-				     "Failed to intialize RTKit");
+				     "Failed to initialize RTKit");
 
 	ret = apple_rtkit_wake(dcp->rtk);
 	if (ret)
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index b179c183f46529..2944911ced770a 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -274,7 +274,7 @@ static void dcpep_handle_cb(struct apple_dcp *dcp, enum dcp_context_id context,
 	out = in + hdr->in_len;
 
 	// TODO: verify that in_len and out_len match our prototypes
-	// for now just clear the out data to have at least consistant results
+	// for now just clear the out data to have at least consistent results
 	if (hdr->out_len)
 		memset(out, 0, hdr->out_len);
 
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index dc99a4d9f8694b..d2139cd3db2e9d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -326,7 +326,7 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 
 /*
  * Allocate an IOVA contiguous buffer mapped to the DCP. The buffer need not be
- * physically contigiuous, however we should save the sgtable in case the
+ * physically contiguous, however we should save the sgtable in case the
  * buffer needs to be later mapped for PIODMA.
  */
 static struct dcp_allocate_buffer_resp
diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 1ee29112be4543..115490fd9cc6e3 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -53,7 +53,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[110] = trampoline_true, /* create_pmu_service */
 	[111] = trampoline_true, /* create_iomfb_service */
 	[112] = trampoline_create_backlight_service,
-	[113] = trampoline_true, /* create_nvram_servce? */
+	[113] = trampoline_true, /* create_nvram_service? */
 	[114] = trampoline_get_tiling_state,
 	[115] = trampoline_false, /* set_tiling_state */
 	[120] = dcpep_cb_boot_1,

From 58149165d6682bd957fa218a6081bae0e1ca49cb Mon Sep 17 00:00:00 2001
From: Mark Kettenis <kettenis@openbsd.org>
Date: Thu, 28 Dec 2023 11:41:55 +0100
Subject: [PATCH 0783/1027] drm: apple: backlight: force backlight update after
 resume

If the DCP firmware indicates that it didn't restore the
brightness, schedule an update.  Wait for 1 frame duration and
check if the brightness update has been taken care of by a swap
that happened in the meantime.
Fixes restoring the brightness after resume when running on a
dumb framebuffer where swaps may not happen for a very long time.

Signed-off-by: Mark Kettenis <kettenis@openbsd.org>
---
 drivers/gpu/drm/apple/dcp-internal.h   |  3 +++
 drivers/gpu/drm/apple/dcp.c            | 10 +++++++++
 drivers/gpu/drm/apple/dcp_backlight.c  | 31 +++++++++++++++-----------
 drivers/gpu/drm/apple/iomfb_template.c |  1 +
 4 files changed, 32 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index e625ea574b88ae..7fbca0b6bb9778 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -212,6 +212,8 @@ struct apple_dcp {
 	/* Workqueue for updating the initial initial brightness */
 	struct work_struct bl_register_wq;
 	struct mutex bl_register_mutex;
+	/* Workqueue for updating the brightness */
+	struct work_struct bl_update_wq;
 
 	/* integrated panel if present */
 	struct dcp_panel panel;
@@ -241,6 +243,7 @@ struct apple_dcp {
 };
 
 int dcp_backlight_register(struct apple_dcp *dcp);
+int dcp_backlight_update(struct apple_dcp *dcp);
 bool dcp_has_panel(struct apple_dcp *dcp);
 
 #define DCP_AUDIO_MAX_CHANS 15
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6cfb203f8a242e..3b09b367a17466 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -515,6 +515,15 @@ static void dcp_work_register_backlight(struct work_struct *work)
 	mutex_unlock(&dcp->bl_register_mutex);
 }
 
+static void dcp_work_update_backlight(struct work_struct *work)
+{
+	struct apple_dcp *dcp;
+
+	dcp = container_of(work, struct apple_dcp, bl_update_wq);
+
+	dcp_backlight_update(dcp);
+}
+
 static int dcp_create_piodma_iommu_dev(struct apple_dcp *dcp)
 {
 	int ret;
@@ -835,6 +844,7 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 		dcp->connector_type = DRM_MODE_CONNECTOR_eDP;
 		INIT_WORK(&dcp->bl_register_wq, dcp_work_register_backlight);
 		mutex_init(&dcp->bl_register_mutex);
+		INIT_WORK(&dcp->bl_update_wq, dcp_work_update_backlight);
 	} else if (of_property_match_string(dev->of_node, "apple,connector-type", "HDMI-A") >= 0)
 		dcp->connector_type = DRM_MODE_CONNECTOR_HDMIA;
 	else if (of_property_match_string(dev->of_node, "apple,connector-type", "DP") >= 0)
diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index 0eeb3d6d92c5a2..dfc78f3ce37b0d 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -172,20 +172,8 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp)
 	return ret;
 }
 
-static int dcp_set_brightness(struct backlight_device *bd)
+int dcp_backlight_update(struct apple_dcp *dcp)
 {
-	int ret = 0;
-	struct apple_dcp *dcp = bl_get_data(bd);
-	struct drm_modeset_acquire_ctx ctx;
-	int brightness = backlight_get_brightness(bd);
-
-	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
-
-	dcp->brightness.dac = calculate_dac(dcp, brightness);
-	dcp->brightness.update = true;
-
-	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
-
 	/*
 	 * Do not actively try to change brightness if no mode is set.
 	 * TODO: should this be reflected the in backlight's power property?
@@ -202,6 +190,23 @@ static int dcp_set_brightness(struct backlight_device *bd)
 	return drm_crtc_set_brightness(dcp);
 }
 
+static int dcp_set_brightness(struct backlight_device *bd)
+{
+	int ret = 0;
+	struct apple_dcp *dcp = bl_get_data(bd);
+	struct drm_modeset_acquire_ctx ctx;
+	int brightness = backlight_get_brightness(bd);
+
+	DRM_MODESET_LOCK_ALL_BEGIN(dcp->crtc->base.dev, ctx, 0, ret);
+
+	dcp->brightness.dac = calculate_dac(dcp, brightness);
+	dcp->brightness.update = true;
+
+	DRM_MODESET_LOCK_ALL_END(dcp->crtc->base.dev, ctx, ret);
+
+	return dcp_backlight_update(dcp);
+}
+
 static const struct backlight_ops dcp_backlight_ops = {
 	.options = BL_CORE_SUSPENDRESUME,
 	.get_brightness = dcp_get_brightness,
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index d2139cd3db2e9d..72685462a36d6d 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -497,6 +497,7 @@ static void iomfbep_cb_enable_backlight_message_ap_gated(struct apple_dcp *dcp,
 	 * syslog: "[BrightnessLCD.cpp:743][AFK]nitsToDBV: iDAC out of range"
 	 */
 	dcp->brightness.update = true;
+	schedule_work(&dcp->bl_update_wq);
 }
 
 /* Chunked data transfer for property dictionaries */

From 271d75dfbd536b0db9e0cc080a95301b56ec4b35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 22 Jan 2024 22:24:11 +0100
Subject: [PATCH 0784/1027] drm: apple: Fix/remove log messages

Add missing training '\n' and remove leftover dev_dbg() statements.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c            |  8 +++---
 drivers/gpu/drm/apple/apple_drv.c      |  4 ---
 drivers/gpu/drm/apple/dcp.c            | 30 +++++++++----------
 drivers/gpu/drm/apple/dcp_backlight.c  |  2 +-
 drivers/gpu/drm/apple/iomfb.c          |  4 +--
 drivers/gpu/drm/apple/iomfb_template.c | 40 ++++++++------------------
 6 files changed, 34 insertions(+), 54 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index 52a5bf5f8a6479..b3a5cf74e817e9 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -138,7 +138,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
 	u32 bufsz, end;
 
 	if (tag != ep->bfr_tag) {
-		dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x",
+		dev_err(ep->dcp->dev, "AFK[ep:%02x]: expected tag 0x%x but got 0x%x\n",
 			ep->endpoint, ep->bfr_tag, tag);
 		return;
 	}
@@ -151,7 +151,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
 
 	if (base >= ep->bfr_size) {
 		dev_err(ep->dcp->dev,
-			"AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx",
+			"AFK[ep:%02x]: requested base 0x%x >= max size 0x%lx\n",
 			ep->endpoint, base, ep->bfr_size);
 		return;
 	}
@@ -159,7 +159,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
 	end = base + size;
 	if (end > ep->bfr_size) {
 		dev_err(ep->dcp->dev,
-			"AFK[ep:%02x]: requested end 0x%x > max size 0x%lx",
+			"AFK[ep:%02x]: requested end 0x%x > max size 0x%lx\n",
 			ep->endpoint, end, ep->bfr_size);
 		return;
 	}
@@ -168,7 +168,7 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
 	bufsz = le32_to_cpu(bfr->hdr->bufsz);
 	if (bufsz + sizeof(*bfr->hdr) != size) {
 		dev_err(ep->dcp->dev,
-			"AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx",
+			"AFK[ep:%02x]: ring buffer size 0x%x != expected 0x%lx\n",
 			ep->endpoint, bufsz, sizeof(*bfr->hdr));
 		return;
 	}
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index dd2e0b6e4ae5bc..f96379d112204b 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -196,9 +196,7 @@ static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 
 	if (crtc_state->active_changed && crtc_state->active) {
 		struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
-		dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
 		dcp_poweron(apple_crtc->dcp);
-		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
 	}
 
 	if (crtc_state->active)
@@ -213,9 +211,7 @@ static void apple_crtc_atomic_disable(struct drm_crtc *crtc,
 
 	if (crtc_state->active_changed && !crtc_state->active) {
 		struct apple_crtc *apple_crtc = to_apple_crtc(crtc);
-		dev_dbg(&apple_crtc->dcp->dev, "%s", __func__);
 		dcp_poweroff(apple_crtc->dcp);
-		dev_dbg(&apple_crtc->dcp->dev, "%s finished", __func__);
 	}
 
 	if (crtc->state->event && !crtc->state->active) {
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 3b09b367a17466..36cdbceae5d08a 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -128,7 +128,7 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 		afk_receive_message(dcp->dptxep, message);
 		return;
 	default:
-		WARN(endpoint, "unknown DCP endpoint %hhu", endpoint);
+		WARN(endpoint, "unknown DCP endpoint %hhu\n", endpoint);
 	}
 }
 
@@ -137,7 +137,7 @@ static void dcp_rtk_crashed(void *cookie)
 	struct apple_dcp *dcp = cookie;
 
 	dcp->crashed = true;
-	dev_err(dcp->dev, "DCP has crashed");
+	dev_err(dcp->dev, "DCP has crashed\n");
 	if (dcp->connector) {
 		dcp->connector->connected = 0;
 		schedule_work(&dcp->connector->hotplug_wq);
@@ -169,7 +169,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 
 		bfr->is_mapped = true;
 		dev_info(dcp->dev,
-			 "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx",
+			 "shmem_setup: iova: %lx -> pa: %lx -> iomem: %lx\n",
 			 (uintptr_t)bfr->iova, (uintptr_t)phy_addr,
 			 (uintptr_t)bfr->buffer);
 	} else {
@@ -178,7 +178,7 @@ static int dcp_rtk_shmem_setup(void *cookie, struct apple_rtkit_shmem *bfr)
 		if (!bfr->buffer)
 			return -ENOMEM;
 
-		dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx",
+		dev_info(dcp->dev, "shmem_setup: iova: %lx, buffer: %lx\n",
 			 (uintptr_t)bfr->iova, (uintptr_t)bfr->buffer);
 	}
 
@@ -226,7 +226,7 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 	needs_modeset = drm_atomic_crtc_needs_modeset(crtc_state) || !dcp->valid_mode;
 	if (!needs_modeset && !dcp->connector->connected) {
-		dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset");
+		dev_err(dcp->dev, "crtc_atomic_check: disconnected but no modeset\n");
 		return -EINVAL;
 	}
 
@@ -239,7 +239,7 @@ int dcp_crtc_atomic_check(struct drm_crtc *crtc, struct drm_atomic_state *state)
 	}
 
 	if (plane_count > DCP_MAX_PLANES) {
-		dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!");
+		dev_err(dcp->dev, "crtc_atomic_check: Blend supports only 2 layers!\n");
 		return -EINVAL;
 	}
 
@@ -355,17 +355,17 @@ int dcp_start(struct platform_device *pdev)
 	/* start RTKit endpoints */
 	ret = systemep_init(dcp);
 	if (ret)
-		dev_warn(dcp->dev, "Failed to start system endpoint: %d", ret);
+		dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret);
 
 	if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
 		ret = ibootep_init(dcp);
 		if (ret)
-			dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d",
+			dev_warn(dcp->dev, "Failed to start IBOOT endpoint: %d\n",
 				 ret);
 
 		ret = dptxep_init(dcp);
 		if (ret)
-			dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d",
+			dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n",
 				 ret);
 		else if (dcp->dptxport[0].enabled) {
 			bool connected;
@@ -388,7 +388,7 @@ int dcp_start(struct platform_device *pdev)
 
 	ret = iomfb_start_rtkit(dcp);
 	if (ret)
-		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d", ret);
+		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret);
 
 	return ret;
 }
@@ -887,12 +887,12 @@ static int dcp_comp_bind(struct device *dev, struct device *main, void *data)
 	dcp->rtk = devm_apple_rtkit_init(dev, dcp, "mbox", 0, &rtkit_ops);
 	if (IS_ERR(dcp->rtk))
 		return dev_err_probe(dev, PTR_ERR(dcp->rtk),
-				     "Failed to initialize RTKit");
+				     "Failed to initialize RTKit\n");
 
 	ret = apple_rtkit_wake(dcp->rtk);
 	if (ret)
 		return dev_err_probe(dev, ret,
-				     "Failed to boot RTKit: %d", ret);
+				     "Failed to boot RTKit: %d\n", ret);
 	return ret;
 }
 
@@ -960,7 +960,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 
 	dcp->phy = devm_phy_optional_get(dev, "dp-phy");
 	if (IS_ERR(dcp->phy)) {
-		dev_err(dev, "Failed to get dp-phy: %ld", PTR_ERR(dcp->phy));
+		dev_err(dev, "Failed to get dp-phy: %ld\n", PTR_ERR(dcp->phy));
 		return PTR_ERR(dcp->phy);
 	}
 	if (dcp->phy) {
@@ -987,7 +987,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 						IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
 						"dp2hdmi-hpd-irq", dcp);
 			if (ret < 0) {
-				dev_err(dev, "failed to request HDMI hpd irq %d: %d",
+				dev_err(dev, "failed to request HDMI hpd irq %d: %d\n",
 					irq, ret);
 				return ret;
 			}
@@ -1010,7 +1010,7 @@ static int dcp_platform_probe(struct platform_device *pdev)
 		if (!ret) {
 			dcp->xbar = devm_mux_control_get(dev, "dp-xbar");
 			if (IS_ERR(dcp->xbar)) {
-				dev_err(dev, "Failed to get dp-xbar: %ld", PTR_ERR(dcp->xbar));
+				dev_err(dev, "Failed to get dp-xbar: %ld\n", PTR_ERR(dcp->xbar));
 				return PTR_ERR(dcp->xbar);
 			}
 			ret = mux_control_select(dcp->xbar, mux_index);
diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index dfc78f3ce37b0d..ed3b240ead8557 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -99,7 +99,7 @@ static u32 interpolate(int val, int min, int max, u32 *tbl, size_t tbl_size)
 
 	size_t index = interpolated / SCALE_FACTOR;
 
-	if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u", index, val))
+	if (WARN(index + 1 >= tbl_size, "invalid index %zu for brightness %u\n", index, val))
 		return tbl[tbl_size / 2];
 
 	frac = interpolated & (SCALE_FACTOR - 1);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 2944911ced770a..9fe8487053efeb 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -326,7 +326,7 @@ static void dcpep_got_msg(struct apple_dcp *dcp, u64 message)
 	channel_offset = dcp_channel_offset(ctx_id);
 
 	if (channel_offset < 0) {
-		dev_warn(dcp->dev, "invalid context received %u", ctx_id);
+		dev_warn(dcp->dev, "invalid context received %u\n", ctx_id);
 		return;
 	}
 
@@ -482,7 +482,7 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 	if (dcp_channel_busy(&dcp->ch_cmd))
 	{
-		dev_err(dcp->dev, "unexpected busy command channel");
+		dev_err(dcp->dev, "unexpected busy command channel\n");
 		/* HACK: issue a delayed vblank event to avoid timeouts in
 		 * drm_atomic_helper_wait_for_vblanks().
 		 */
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index 72685462a36d6d..d081a3c83dfebe 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -299,7 +299,7 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 	struct dcp_mem_descriptor *memdesc;
 
 	if (resp->buffer >= ARRAY_SIZE(dcp->memdesc)) {
-		dev_warn(dcp->dev, "unmap request for out of range buffer %llu",
+		dev_warn(dcp->dev, "unmap request for out of range buffer %llu\n",
 			 resp->buffer);
 		return;
 	}
@@ -308,14 +308,14 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 
 	if (!memdesc->buf) {
 		dev_warn(dcp->dev,
-			 "unmap for non-mapped buffer %llu iova:0x%08llx",
+			 "unmap for non-mapped buffer %llu iova:0x%08llx\n",
 			 resp->buffer, resp->dva);
 		return;
 	}
 
 	if (memdesc->dva != resp->dva) {
 		dev_warn(dcp->dev, "unmap buffer %llu address mismatch "
-			 "memdesc.dva:%llx dva:%llx", resp->buffer,
+			 "memdesc.dva:%llx dva:%llx\n", resp->buffer,
 			 memdesc->dva, resp->dva);
 		return;
 	}
@@ -343,7 +343,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
 		find_first_zero_bit(dcp->memdesc_map, DCP_MAX_MAPPINGS);
 
 	if (resp.mem_desc_id >= DCP_MAX_MAPPINGS) {
-		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring");
+		dev_warn(dcp->dev, "DCP overflowed mapping table, ignoring\n");
 		resp.dva_size = 0;
 		resp.mem_desc_id = 0;
 		return resp;
@@ -378,7 +378,7 @@ static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
 	}
 
 	if (!test_and_clear_bit(id, dcp->memdesc_map)) {
-		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u",
+		dev_warn(dcp->dev, "unmap request for unused mem_desc_id %u\n",
 			 id);
 		return 0;
 	}
@@ -428,7 +428,7 @@ dcpep_cb_map_physical(struct apple_dcp *dcp, struct dcp_map_physical_req *req)
 	u32 id;
 
 	if (!is_disp_register(dcp, req->paddr, req->paddr + size - 1)) {
-		dev_err(dcp->dev, "refusing to map phys address %llx size %llx",
+		dev_err(dcp->dev, "refusing to map phys address %llx size %llx\n",
 			req->paddr, req->size);
 		return (struct dcp_map_physical_resp){};
 	}
@@ -457,7 +457,7 @@ static struct DCP_FW_NAME(dcp_map_reg_resp) dcpep_cb_map_reg(struct apple_dcp *d
 						struct DCP_FW_NAME(dcp_map_reg_req) *req)
 {
 	if (req->index >= dcp->nr_disp_registers) {
-		dev_warn(dcp->dev, "attempted to read invalid reg index %u",
+		dev_warn(dcp->dev, "attempted to read invalid reg index %u\n",
 			 req->index);
 
 		return (struct DCP_FW_NAME(dcp_map_reg_resp)){ .ret = 1 };
@@ -602,7 +602,7 @@ static void boot_done(struct apple_dcp *dcp, void *out, void *cookie)
 {
 	struct dcp_channel *ch = &dcp->ch_cb;
 	u8 *succ = ch->output[ch->depth - 1];
-	dev_dbg(dcp->dev, "boot done");
+	dev_dbg(dcp->dev, "boot done\n");
 
 	*succ = true;
 	dcp_ack(dcp, DCP_CONTEXT_CB);
@@ -717,7 +717,6 @@ static void release_swap_cookie(struct kref *ref)
 static void dcp_swap_cleared(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct DCP_FW_NAME(dcp_swap_submit_resp) *resp = data;
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (cookie) {
 		struct dcp_swap_cookie *info = cookie;
@@ -748,7 +747,6 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
 				   void *cookie)
 {
 	struct dcp_swap_start_resp *resp = data;
-	dev_dbg(dcp->dev, "%s swap_id: %u", __func__, resp->swap_id);
 	DCP_FW_UNION(dcp->swap).swap.swap_id = resp->swap_id;
 
 	if (cookie) {
@@ -762,7 +760,6 @@ static void dcp_swap_clear_started(struct apple_dcp *dcp, void *data,
 static void dcp_on_final(struct apple_dcp *dcp, void *out, void *cookie)
 {
 	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (wait) {
 		complete(&wait->done);
@@ -775,7 +772,6 @@ static void dcp_on_set_power_state(struct apple_dcp *dcp, void *out, void *cooki
 	struct dcp_set_power_state_req req = {
 		.unklong = 1,
 	};
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	dcp_set_power_state(dcp, false, &req, dcp_on_final, cookie);
 }
@@ -791,7 +787,6 @@ static void dcp_on_set_parameter(struct apple_dcp *dcp, void *out, void *cookie)
 		.count = 1,
 #endif
 	};
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	dcp_set_parameter_dcp(dcp, false, &param, dcp_on_set_power_state, cookie);
 }
@@ -803,8 +798,6 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp)
 	u32 handle;
 	dev_info(dcp->dev, "dcp_poweron() starting\n");
 
-	dev_dbg(dcp->dev, "%s", __func__);
-
 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
 	if (!cookie)
 		return;
@@ -826,7 +819,7 @@ void DCP_FW_NAME(iomfb_poweron)(struct apple_dcp *dcp)
 	ret = wait_for_completion_timeout(&cookie->done, msecs_to_jiffies(500));
 
 	if (ret == 0)
-		dev_warn(dcp->dev, "wait for power timed out");
+		dev_warn(dcp->dev, "wait for power timed out\n");
 
 	kref_put(&cookie->refcount, release_wait_cookie);;
 
@@ -874,8 +867,6 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 	struct dcp_swap_start_req swap_req = { 0 };
 	struct DCP_FW_NAME(dcp_swap_submit_req) *swap = &DCP_FW_UNION(dcp->swap);
 
-	dev_dbg(dcp->dev, "%s", __func__);
-
 	cookie = kzalloc(sizeof(*cookie), GFP_KERNEL);
 	if (!cookie)
 		return;
@@ -923,7 +914,7 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 		return;
 	}
 
-	dev_dbg(dcp->dev, "%s: clear swap submitted: %u", __func__, swap_id);
+	dev_dbg(dcp->dev, "%s: clear swap submitted: %u\n", __func__, swap_id);
 
 	poff_cookie = kzalloc(sizeof(*poff_cookie), GFP_KERNEL);
 	if (!poff_cookie)
@@ -939,14 +930,13 @@ void DCP_FW_NAME(iomfb_poweroff)(struct apple_dcp *dcp)
 					  msecs_to_jiffies(1000));
 
 	if (ret == 0)
-		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms", 1000);
+		dev_warn(dcp->dev, "setPowerState(0) timeout %u ms\n", 1000);
 	else if (ret > 0)
 		dev_dbg(dcp->dev,
 			"setPowerState(0) finished with %d ms to spare",
 			jiffies_to_msecs(ret));
 
 	kref_put(&poff_cookie->refcount, release_wait_cookie);
-	dev_dbg(dcp->dev, "%s: setPowerState(0) done", __func__);
 
 	dev_info(dcp->dev, "dcp_poweroff() done\n");
 }
@@ -990,11 +980,9 @@ void DCP_FW_NAME(iomfb_sleep)(struct apple_dcp *dcp)
 					  msecs_to_jiffies(1000));
 
 	if (ret == 0)
-		dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms", 1000);
+		dev_warn(dcp->dev, "setDCPPower(0) timeout %u ms\n", 1000);
 
 	kref_put(&cookie->refcount, release_wait_cookie);
-	dev_dbg(dcp->dev, "%s: setDCPPower(0) done", __func__);
-
 	dev_info(dcp->dev, "dcp_sleep() done\n");
 }
 
@@ -1163,7 +1151,6 @@ static void dcp_swap_started(struct apple_dcp *dcp, void *data, void *cookie)
 static void do_swap(struct apple_dcp *dcp, void *data, void *cookie)
 {
 	struct dcp_swap_start_req start_req = { 0 };
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (dcp->connector && dcp->connector->connected)
 		dcp_swap_start(dcp, false, &start_req, dcp_swap_started, NULL);
@@ -1175,7 +1162,6 @@ static void complete_set_digital_out_mode(struct apple_dcp *dcp, void *data,
 					  void *cookie)
 {
 	struct dcp_wait_cookie *wait = cookie;
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	if (wait) {
 		complete(&wait->done);
@@ -1242,7 +1228,6 @@ int DCP_FW_NAME(iomfb_modeset)(struct apple_dcp *dcp,
 	 * modesets. Add an extra 500ms to safe side that the modeset
 	 * call has returned.
 	 */
-	dev_dbg(dcp->dev, "%s - wait for modeset", __func__);
 	ret = wait_for_completion_timeout(&cookie->done,
 					  msecs_to_jiffies(8500));
 
@@ -1276,7 +1261,6 @@ void DCP_FW_NAME(iomfb_flush)(struct apple_dcp *dcp, struct drm_crtc *crtc, stru
 	struct DCP_FW_NAME(dcp_swap_submit_req) *req = &DCP_FW_UNION(dcp->swap);
 	int plane_idx, l;
 	int has_surface = 0;
-	dev_dbg(dcp->dev, "%s", __func__);
 
 	crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
 

From e844e9bc9571b2b2d4c84ca3a86b4d839bf16731 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 26 Mar 2024 22:05:50 +0100
Subject: [PATCH 0785/1027] drm: apple: dptx: Debounce HPD by simple msleep()

Not necessarily only a debounce but 500ms sleep in the HPD interrupt
handler seems to make the modeset more reliable on M2* desktop devices.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 36cdbceae5d08a..6ccf1b03410e8b 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -329,6 +329,12 @@ static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data)
 	 */
 	dev_info(dcp->dev, "DP2HDMI HPD irq, connected:%d\n", connected);
 
+	if (connected) {
+		msleep(500);
+		connected = gpiod_get_value_cansleep(dcp->hdmi_hpd);
+		dev_info(dcp->dev, "DP2HDMI HPD irq, 500ms debounce: connected:%d\n", connected);
+	}
+
 	if (connected)
 		dcp_dptx_connect(dcp, 0);
 

From 50cdd33979362dfb4c4ab89c41be2700c2084a47 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 21 Jan 2024 14:47:56 +0100
Subject: [PATCH 0786/1027] drm: apple: Add Kconfig option for audio

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Kconfig  | 9 +++++++--
 drivers/gpu/drm/apple/parser.c | 8 ++++++++
 2 files changed, 15 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index 1bffe0f48ebff5..56b6e855d12176 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -4,12 +4,17 @@ config DRM_APPLE
 	depends on DRM && OF && ARM64
 	depends on ARCH_APPLE || COMPILE_TEST
 	depends on APPLE_RTKIT
-	depends on SND
 	select DRM_KMS_HELPER
 	select DRM_KMS_DMA_HELPER
 	select DRM_GEM_DMA_HELPER
 	select VIDEOMODE_HELPERS
 	select MULTIPLEXER
-	select SND_PCM
 	help
 	  Say Y if you have an Apple Silicon chipset.
+
+config DRM_APPLE_AUDIO
+	bool "DisplayPort/HDMI Audio support"
+	default y
+	depends on DRM_APPLE
+	depends on SND
+	select SND_PCM
diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 4a4a6458ecebc9..965fcf88a0f4b6 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -7,7 +7,9 @@
 #include <linux/string.h>
 #include <linux/slab.h>
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
 #include <sound/pcm.h> // for sound format masks
+#endif
 
 #include "parser.h"
 #include "trace.h"
@@ -119,6 +121,7 @@ static int skip(struct dcp_parse_ctx *handle)
 	}
 }
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
 static int skip_pair(struct dcp_parse_ctx *handle)
 {
 	int ret;
@@ -151,6 +154,7 @@ static bool consume_string(struct dcp_parse_ctx *ctx, const char *specimen)
 	skip(ctx);
 	return true;
 }
+#endif
 
 /* Caller must free the result */
 static char *parse_string(struct dcp_parse_ctx *handle)
@@ -201,6 +205,7 @@ static int parse_bool(struct dcp_parse_ctx *handle, bool *b)
 	return 0;
 }
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
 static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob)
 {
 	const struct dcp_parse_tag *tag = parse_tag_of_type(handle, DCP_TYPE_BLOB);
@@ -220,6 +225,7 @@ static int parse_blob(struct dcp_parse_ctx *handle, size_t size, u8 const **blob
 	*blob = out;
 	return 0;
 }
+#endif
 
 struct iterator {
 	struct dcp_parse_ctx *handle;
@@ -680,6 +686,7 @@ int parse_epic_service_init(struct dcp_parse_ctx *handle, const char **name,
 	return ret;
 }
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
 static int parse_sample_rate_bit(struct dcp_parse_ctx *handle, unsigned int *ratebit)
 {
 	s64 rate;
@@ -983,6 +990,7 @@ int parse_sound_mode(struct dcp_parse_ctx *handle,
 	return 0;
 }
 EXPORT_SYMBOL_GPL(parse_sound_mode);
+#endif
 
 int parse_system_log_mnits(struct dcp_parse_ctx *handle, struct dcp_system_ev_mnits *entry)
 {

From 9b624477a1b4993feaad1dc4cd00dd631ece619e Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 25 Dec 2023 18:03:37 +0100
Subject: [PATCH 0787/1027] drm: apple: iomfb: export property dicts in
 connector debugfs

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile         |   1 +
 drivers/gpu/drm/apple/apple_drv.c      |   2 +
 drivers/gpu/drm/apple/connector.c      | 122 +++++++++++++++++++++++++
 drivers/gpu/drm/apple/connector.h      |  39 ++++++++
 drivers/gpu/drm/apple/dcp.h            |  15 +--
 drivers/gpu/drm/apple/iomfb_template.c |   5 +-
 6 files changed, 168 insertions(+), 16 deletions(-)
 create mode 100644 drivers/gpu/drm/apple/connector.c
 create mode 100644 drivers/gpu/drm/apple/connector.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 50b7682ee4ccdb..f4ad014c0d21d4 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src)
 appledrm-y := apple_drv.o
 
 apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o
+apple_dcp-y += connector.o
 apple_dcp-y += ibootep.o
 apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index f96379d112204b..53e3924abff02a 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -295,6 +295,7 @@ static const struct drm_connector_funcs apple_connector_funcs = {
 	.atomic_duplicate_state	= drm_atomic_helper_connector_duplicate_state,
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 	.detect			= apple_connector_detect,
+	.debugfs_init		= apple_connector_debugfs_init,
 };
 
 static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
@@ -343,6 +344,7 @@ static int apple_probe_per_dcp(struct device *dev,
 	enc->base.possible_crtcs = drm_crtc_mask(&crtc->base);
 
 	connector = kzalloc(sizeof(*connector), GFP_KERNEL);
+	mutex_init(&connector->chunk_lock);
 	drm_connector_helper_add(&connector->base,
 				 &apple_connector_helper_funcs);
 
diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c
new file mode 100644
index 00000000000000..a39bd249697d90
--- /dev/null
+++ b/drivers/gpu/drm/apple/connector.c
@@ -0,0 +1,122 @@
+// SPDX-License-Identifier: GPL-2.0+ OR MIT
+/*
+ * Copyright (C) The Asahi Linux Contributors
+ */
+
+#include <linux/debugfs.h>
+#include <linux/module.h>
+#include <linux/seq_file.h>
+#include <linux/string_helpers.h>
+#include <linux/uaccess.h>
+
+#include <drm/drm_managed.h>
+
+#include "connector.h"
+#include "dcp-internal.h"
+
+enum dcp_chunk_type {
+	DCP_CHUNK_COLOR_ELEMENTS,
+	DCP_CHUNK_TIMING_ELELMENTS,
+	DCP_CHUNK_DISPLAY_ATTRIBUTES,
+	DCP_CHUNK_TRANSPORT,
+	DCP_CHUNK_NUM_TYPES,
+};
+
+static int chunk_show(struct seq_file *m,
+		      enum dcp_chunk_type chunk_type)
+{
+	struct apple_connector *apple_con = m->private;
+	struct dcp_chunks *chunk = NULL;
+
+	mutex_lock(&apple_con->chunk_lock);
+
+	switch (chunk_type) {
+	case DCP_CHUNK_COLOR_ELEMENTS:
+		chunk = &apple_con->color_elements;
+		break;
+	case DCP_CHUNK_TIMING_ELELMENTS:
+		chunk = &apple_con->timing_elements;
+		break;
+	case DCP_CHUNK_DISPLAY_ATTRIBUTES:
+		chunk = &apple_con->display_attributes;
+		break;
+	case DCP_CHUNK_TRANSPORT:
+		chunk = &apple_con->transport;
+		break;
+	default:
+		break;
+	}
+
+	if (chunk)
+                seq_write(m, chunk->data, chunk->length);
+
+	mutex_unlock(&apple_con->chunk_lock);
+
+	return 0;
+}
+
+#define CONNECTOR_DEBUGFS_ENTRY(name, type) \
+static int chunk_ ## name ## _show(struct seq_file *m, void *data) \
+{ \
+        return chunk_show(m, type); \
+} \
+static int chunk_ ## name ## _open(struct inode *inode, struct file *file) \
+{ \
+        return single_open(file,  chunk_ ## name ## _show, inode->i_private); \
+} \
+static const struct file_operations chunk_ ## name ## _fops = { \
+        .owner = THIS_MODULE, \
+        .open = chunk_ ## name ## _open, \
+        .read = seq_read, \
+        .llseek = seq_lseek, \
+        .release = single_release, \
+}
+
+CONNECTOR_DEBUGFS_ENTRY(color, DCP_CHUNK_COLOR_ELEMENTS);
+CONNECTOR_DEBUGFS_ENTRY(timing, DCP_CHUNK_TIMING_ELELMENTS);
+CONNECTOR_DEBUGFS_ENTRY(display_attribs, DCP_CHUNK_DISPLAY_ATTRIBUTES);
+CONNECTOR_DEBUGFS_ENTRY(transport, DCP_CHUNK_TRANSPORT);
+
+void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root)
+{
+	struct apple_connector *apple_con = to_apple_connector(connector);
+
+        debugfs_create_file("ColorElements", 0444, root, apple_con,
+                            &chunk_color_fops);
+        debugfs_create_file("TimingElements", 0444, root, apple_con,
+                            &chunk_timing_fops);
+        debugfs_create_file("DisplayAttributes", 0444, root, apple_con,
+                            &chunk_display_attribs_fops);
+        debugfs_create_file("Transport", 0444, root, apple_con,
+                            &chunk_transport_fops);
+}
+EXPORT_SYMBOL(apple_connector_debugfs_init);
+
+static void dcp_connector_set_dict(struct apple_connector *connector,
+				   struct dcp_chunks *dict,
+				   struct dcp_chunks *chunks)
+{
+	if (dict->data)
+		devm_kfree(&connector->dcp->dev, dict->data);
+
+	*dict = *chunks;
+}
+
+void dcp_connector_update_dict(struct apple_connector *connector, const char *key,
+			       struct dcp_chunks *chunks)
+{
+	mutex_lock(&connector->chunk_lock);
+	if (!strcmp(key, "ColorElements"))
+		dcp_connector_set_dict(connector, &connector->color_elements, chunks);
+	else if (!strcmp(key, "TimingElements"))
+		dcp_connector_set_dict(connector, &connector->timing_elements, chunks);
+	else if (!strcmp(key, "DisplayAttributes"))
+		dcp_connector_set_dict(connector, &connector->display_attributes, chunks);
+	else if (!strcmp(key, "Transport"))
+		dcp_connector_set_dict(connector, &connector->transport, chunks);
+
+	chunks->data = NULL;
+	chunks->length = 0;
+
+	mutex_unlock(&connector->chunk_lock);
+}
diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h
new file mode 100644
index 00000000000000..79a1eef8c32da8
--- /dev/null
+++ b/drivers/gpu/drm/apple/connector.h
@@ -0,0 +1,39 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* "Copyright" 2021 Alyssa Rosenzweig <alyssa@rosenzweig.io> */
+
+#ifndef __APPLE_CONNECTOR_H__
+#define __APPLE_CONNECTOR_H__
+
+#include <linux/workqueue.h>
+
+#include <drm/drm_atomic.h>
+#include "drm/drm_connector.h"
+
+#include "dcp-internal.h"
+
+void dcp_hotplug(struct work_struct *work);
+
+struct apple_connector {
+	struct drm_connector base;
+	bool connected;
+
+	struct platform_device *dcp;
+
+	/* Workqueue for sending hotplug events to the associated device */
+	struct work_struct hotplug_wq;
+
+	struct mutex chunk_lock;
+
+	struct dcp_chunks color_elements;
+	struct dcp_chunks timing_elements;
+	struct dcp_chunks display_attributes;
+	struct dcp_chunks transport;
+};
+
+#define to_apple_connector(x) container_of(x, struct apple_connector, base)
+
+void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root);
+
+void dcp_connector_update_dict(struct apple_connector *connector, const char *key,
+			       struct dcp_chunks *chunks);
+#endif
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 298520c0832e93..52c5c94a2a8bbe 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -8,6 +8,7 @@
 #include <drm/drm_encoder.h>
 #include <drm/drm_fourcc.h>
 
+#include "connector.h"
 #include "dcp-internal.h"
 #include "parser.h"
 
@@ -22,20 +23,6 @@ struct apple_crtc {
 
 #define to_apple_crtc(x) container_of(x, struct apple_crtc, base)
 
-void dcp_hotplug(struct work_struct *work);
-
-struct apple_connector {
-	struct drm_connector base;
-	bool connected;
-
-	struct platform_device *dcp;
-
-	/* Workqueue for sending hotplug events to the associated device */
-	struct work_struct hotplug_wq;
-};
-
-#define to_apple_connector(x) container_of(x, struct apple_connector, base)
-
 struct apple_encoder {
 	struct drm_encoder base;
 };
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index d081a3c83dfebe..d74f2b502821a1 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -590,9 +590,10 @@ static u8 dcpep_cb_prop_end(struct apple_dcp *dcp,
 {
 	u8 resp = dcpep_process_chunks(dcp, req);
 
-	/* Reset for the next transfer */
-	devm_kfree(dcp->dev, dcp->chunks.data);
+	/* move chunked data to connector to provide it via debugfs */
+	dcp_connector_update_dict(dcp->connector, req->key, &dcp->chunks);
 	dcp->chunks.data = NULL;
+	dcp->chunks.length = 0;
 
 	return resp;
 }

From 115ae878eeb9fb61e6ac3440b14028a686296851 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 13 Feb 2023 14:55:13 +0100
Subject: [PATCH 0788/1027] gpu: drm: apple: Expose injecting of EPIC calls via
 debugfs
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Kconfig        |   5 +
 drivers/gpu/drm/apple/afk.c          | 161 +++++++++++++++++++++++++++
 drivers/gpu/drm/apple/afk.h          |   8 ++
 drivers/gpu/drm/apple/connector.c    |  29 +++++
 drivers/gpu/drm/apple/dcp-internal.h |   3 +
 5 files changed, 206 insertions(+)

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index 56b6e855d12176..6cf972f8ce1162 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -18,3 +18,8 @@ config DRM_APPLE_AUDIO
 	depends on DRM_APPLE
 	depends on SND
 	select SND_PCM
+
+config DRM_APPLE_DEBUG
+	bool "Enable additional driver debugging"
+	depends on DRM_APPLE
+	depends on EXPERT # only for developers
diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index b3a5cf74e817e9..218c28dfe84249 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -2,7 +2,9 @@
 /* Copyright 2022 Sven Peter <sven@svenpeter.dev> */
 
 #include <linux/bitfield.h>
+#include <linux/debugfs.h>
 #include <linux/dma-mapping.h>
+#include <linux/kconfig.h>
 #include <linux/of_platform.h>
 #include <linux/slab.h>
 #include <linux/workqueue.h>
@@ -181,6 +183,18 @@ static void afk_init_rxtx(struct apple_dcp_afkep *ep, u64 message,
 		afk_send(ep, FIELD_PREP(RBEP_TYPE, RBEP_START));
 }
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG)
+static void afk_populate_service_debugfs(struct apple_epic_service *srv);
+static void afk_remove_service_debugfs(struct apple_epic_service *srv);
+#else
+static void afk_populate_service_debugfs(struct apple_epic_service *srv)
+{
+}
+static void afk_remove_service_debugfs(struct apple_epic_service *srv)
+{
+}
+#endif
+
 static const struct apple_epic_service_ops *
 afk_match_service(struct apple_dcp_afkep *ep, const char *name)
 {
@@ -284,6 +298,9 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
 	ops->init(&ep->services[ch_idx], epic_name, epic_class, epic_unit);
 	dev_info(ep->dcp->dev, "AFK[ep:%02x]: new service %s on channel %d\n",
 		 ep->endpoint, service_name, channel);
+
+	afk_populate_service_debugfs(&ep->services[ch_idx]);
+
 free:
 	kfree(epic_name);
 	kfree(epic_class);
@@ -302,6 +319,8 @@ static void afk_recv_handle_teardown(struct apple_dcp_afkep *ep, u32 channel)
 		return;
 	}
 
+	afk_remove_service_debugfs(service);
+
 	// TODO: think through what locking is necessary
 	spin_lock_irqsave(&service->lock, flags);
 	service->enabled = false;
@@ -989,3 +1008,145 @@ int afk_service_call(struct apple_epic_service *service, u16 group, u32 command,
 	kfree(bfr);
 	return ret;
 }
+
+#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG)
+
+#define AFK_DEBUGFS_MAX_REPLY 8192
+
+static ssize_t service_call_write_file(struct file *file, const char __user *user_buf,
+				       size_t count, loff_t *ppos)
+{
+	struct apple_epic_service *srv = file->private_data;
+	void *buf;
+	int ret;
+	struct {
+		u32 group;
+		u32 command;
+	} call_info;
+
+	if (count < sizeof(call_info))
+		return -EINVAL;
+	if (!srv->debugfs.scratch) {
+		srv->debugfs.scratch = \
+			devm_kzalloc(srv->ep->dcp->dev, AFK_DEBUGFS_MAX_REPLY, GFP_KERNEL);
+		if (!srv->debugfs.scratch)
+			return -ENOMEM;
+	}
+
+	ret = copy_from_user(&call_info, user_buf, sizeof(call_info));
+	if (ret == sizeof(call_info))
+		return -EFAULT;
+	user_buf += sizeof(call_info);
+	count -= sizeof(call_info);
+
+	buf = kmalloc(count, GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	ret = copy_from_user(buf, user_buf, count);
+	if (ret == count) {
+		kfree(buf);
+		return -EFAULT;
+	}
+
+	memset(srv->debugfs.scratch, 0, AFK_DEBUGFS_MAX_REPLY);
+	dma_mb();
+
+	ret = afk_service_call(srv, call_info.group, call_info.command, buf, count, 0,
+			       srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY, 0);
+	kfree(buf);
+
+	if (ret < 0)
+		return ret;
+
+	return count + sizeof(call_info);
+}
+
+static ssize_t service_call_read_file(struct file *file, char __user *user_buf,
+				      size_t count, loff_t *ppos)
+{
+	struct apple_epic_service *srv = file->private_data;
+
+	if (!srv->debugfs.scratch)
+		return -EINVAL;
+
+	return simple_read_from_buffer(user_buf, count, ppos,
+				       srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY);
+}
+
+static const struct file_operations service_call_fops = {
+	.open = simple_open,
+	.write = service_call_write_file,
+	.read = service_call_read_file,
+};
+
+static ssize_t service_raw_call_write_file(struct file *file, const char __user *user_buf,
+					   size_t count, loff_t *ppos)
+{
+	struct apple_epic_service *srv = file->private_data;
+	u32 retcode;
+	int ret;
+
+	if (!srv->debugfs.scratch) {
+		srv->debugfs.scratch = \
+			devm_kzalloc(srv->ep->dcp->dev, AFK_DEBUGFS_MAX_REPLY, GFP_KERNEL);
+		if (!srv->debugfs.scratch)
+			return -ENOMEM;
+	}
+
+	memset(srv->debugfs.scratch, 0, AFK_DEBUGFS_MAX_REPLY);
+	ret = copy_from_user(srv->debugfs.scratch, user_buf, count);
+	if (ret == count)
+		return -EFAULT;
+
+	ret = afk_send_command(srv, EPIC_SUBTYPE_STD_SERVICE, srv->debugfs.scratch, count,
+			       srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY, &retcode);
+	if (ret < 0)
+		return ret;
+	if (retcode)
+		return -EINVAL;
+
+	return count;
+}
+
+static ssize_t service_raw_call_read_file(struct file *file, char __user *user_buf,
+					  size_t count, loff_t *ppos)
+{
+	struct apple_epic_service *srv = file->private_data;
+
+	if (!srv->debugfs.scratch)
+		return -EINVAL;
+
+	return simple_read_from_buffer(user_buf, count, ppos,
+				       srv->debugfs.scratch, AFK_DEBUGFS_MAX_REPLY);
+}
+
+static const struct file_operations service_raw_call_fops = {
+	.open = simple_open,
+	.write = service_raw_call_write_file,
+	.read = service_raw_call_read_file,
+};
+
+static void afk_populate_service_debugfs(struct apple_epic_service *srv)
+{
+	if (!srv->ep->debugfs_entry || !srv->ops)
+		return;
+
+	if (strcmp(srv->ops->name, "DCPAVAudioInterface") == 0) {
+		srv->debugfs.entry = debugfs_create_dir(srv->ops->name,
+							srv->ep->debugfs_entry);
+		debugfs_create_file("call", 0600, srv->debugfs.entry, srv,
+				&service_call_fops);
+		debugfs_create_file("raw_call", 0600, srv->debugfs.entry, srv,
+				&service_raw_call_fops);
+	}
+}
+
+static void afk_remove_service_debugfs(struct apple_epic_service *srv)
+{
+	if (srv->debugfs.entry) {
+		debugfs_remove_recursive(srv->debugfs.entry);
+		srv->debugfs.entry = NULL;
+	}
+}
+
+#endif
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index 737288b1346b28..0f91f32e08e301 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -8,6 +8,7 @@
 #define _DRM_APPLE_DCP_AFK_H
 
 #include <linux/completion.h>
+#include <linux/kconfig.h>
 #include <linux/types.h>
 
 #include "dcp.h"
@@ -47,6 +48,11 @@ struct apple_epic_service {
 	bool enabled;
 
 	void *cookie;
+
+    struct {
+        struct dentry *entry;
+        u8 *scratch;
+    } debugfs;
 };
 
 enum epic_subtype;
@@ -174,6 +180,8 @@ struct apple_dcp_afkep {
 	const struct apple_epic_service_ops *ops;
 	struct apple_epic_service services[AFK_MAX_CHANNEL];
 	u32 num_channels;
+
+	struct dentry *debugfs_entry;
 };
 
 struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c
index a39bd249697d90..46de8e8756f1ed 100644
--- a/drivers/gpu/drm/apple/connector.c
+++ b/drivers/gpu/drm/apple/connector.c
@@ -3,6 +3,7 @@
  * Copyright (C) The Asahi Linux Contributors
  */
 
+#include "linux/err.h"
 #include <linux/debugfs.h>
 #include <linux/module.h>
 #include <linux/seq_file.h>
@@ -77,6 +78,25 @@ CONNECTOR_DEBUGFS_ENTRY(timing, DCP_CHUNK_TIMING_ELELMENTS);
 CONNECTOR_DEBUGFS_ENTRY(display_attribs, DCP_CHUNK_DISPLAY_ATTRIBUTES);
 CONNECTOR_DEBUGFS_ENTRY(transport, DCP_CHUNK_TRANSPORT);
 
+static void dcp_afk_debugfs_root(struct platform_device *pdev, int ep, struct dentry *root)
+{
+#if IS_ENABLED(CONFIG_DRM_APPLE_DEBUG)
+	struct dentry *entry = NULL;
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+
+	switch (ep) {
+	case AV_ENDPOINT:
+		entry = debugfs_create_dir("avep", root);
+		break;
+	default:
+		break;
+	}
+
+	if (!IS_ERR_OR_NULL(entry))
+		dcp->ep_debugfs[ep - 0x20] = entry;
+#endif
+}
+
 void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry *root)
 {
 	struct apple_connector *apple_con = to_apple_connector(connector);
@@ -89,6 +109,15 @@ void apple_connector_debugfs_init(struct drm_connector *connector, struct dentry
                             &chunk_display_attribs_fops);
         debugfs_create_file("Transport", 0444, root, apple_con,
                             &chunk_transport_fops);
+
+	switch (connector->connector_type) {
+	case DRM_MODE_CONNECTOR_DisplayPort:
+	case DRM_MODE_CONNECTOR_HDMIA:
+		dcp_afk_debugfs_root(apple_con->dcp, AV_ENDPOINT, root);
+		break;
+	default:
+		break;
+	}
 }
 EXPORT_SYMBOL(apple_connector_debugfs_init);
 
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 7fbca0b6bb9778..9f9832133e73ac 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -227,6 +227,9 @@ struct apple_dcp {
 
 	struct dptx_port dptxport[2];
 
+	/* debugfs entries */
+	struct dentry *ep_debugfs[0x20];
+
 	/* these fields are output port specific */
 	struct phy *phy;
 	struct mux_control *xbar;

From dfc07ccb4f33f8323674b0453dd944febf6d8586 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Mon, 13 Feb 2023 14:56:24 +0100
Subject: [PATCH 0789/1027] gpu: drm: apple: Set up client of AV endpoint
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/gpu/drm/apple/Makefile       |   1 +
 drivers/gpu/drm/apple/audio.h        |  26 +++
 drivers/gpu/drm/apple/av.c           | 284 +++++++++++++++++++++++++++
 drivers/gpu/drm/apple/av.h           |   9 +
 drivers/gpu/drm/apple/dcp-internal.h |   6 +
 drivers/gpu/drm/apple/dcp.c          |  16 ++
 drivers/gpu/drm/apple/dcp.h          |   2 +
 7 files changed, 344 insertions(+)
 create mode 100644 drivers/gpu/drm/apple/audio.h
 create mode 100644 drivers/gpu/drm/apple/av.c
 create mode 100644 drivers/gpu/drm/apple/av.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index f4ad014c0d21d4..5963d3cde31316 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src)
 appledrm-y := apple_drv.o
 
 apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o
+apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += av.o
 apple_dcp-y += connector.o
 apple_dcp-y += ibootep.o
 apple_dcp-y += iomfb_v12_3.o
diff --git a/drivers/gpu/drm/apple/audio.h b/drivers/gpu/drm/apple/audio.h
new file mode 100644
index 00000000000000..3cf4d31417694e
--- /dev/null
+++ b/drivers/gpu/drm/apple/audio.h
@@ -0,0 +1,26 @@
+#ifndef __AUDIO_H__
+#define __AUDIO_H__
+
+#include <linux/types.h>
+
+struct device;
+struct device_node;
+struct dcp_sound_cookie;
+
+typedef void (*dcp_audio_hotplug_callback)(struct device *dev, bool connected);
+
+struct dcp_audio_pdata {
+	struct device *dcp_dev;
+	struct device_node *dpaudio_node;
+};
+
+void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev,
+								 dcp_audio_hotplug_callback cb);
+int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie);
+int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie);
+int dcp_audiosrv_stoplink(struct device *dev);
+int dcp_audiosrv_unprepare(struct device *dev);
+int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize);
+int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize);
+
+#endif /* __AUDIO_H__ */
diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
new file mode 100644
index 00000000000000..bd4c7ec51bdb7d
--- /dev/null
+++ b/drivers/gpu/drm/apple/av.c
@@ -0,0 +1,284 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright 2023 Martin Povišer <povik+lin@cutebit.org> */
+
+// #define DEBUG
+
+#include <linux/debugfs.h>
+#include <linux/kconfig.h>
+#include <linux/rwsem.h>
+#include <linux/types.h>
+
+#include "audio.h"
+#include "afk.h"
+#include "dcp.h"
+
+struct audiosrv_data {
+	struct device *audio_dev;
+	dcp_audio_hotplug_callback hotplug_cb;
+	bool plugged;
+	struct mutex plug_lock;
+
+	struct apple_epic_service *srv;
+	struct rw_semaphore srv_rwsem;
+};
+
+static void av_interface_init(struct apple_epic_service *service, const char *name,
+			      const char *class, s64 unit)
+{
+}
+
+static void av_audiosrv_init(struct apple_epic_service *service, const char *name,
+			     const char *class, s64 unit)
+{
+	struct apple_dcp *dcp = service->ep->dcp;
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	int err;
+
+	mutex_lock(&asrv->plug_lock);
+
+	down_write(&asrv->srv_rwsem);
+	asrv->srv = service;
+	up_write(&asrv->srv_rwsem);
+
+	/* TODO: this must be done elsewhere */
+	err = afk_service_call(asrv->srv, 0, 6, NULL, 0, 32, NULL, 0, 32);
+	if (err)
+		dev_err(dcp->dev, "error opening audio service: %d\n", err);
+
+	asrv->plugged = true;
+	if (asrv->hotplug_cb)
+		asrv->hotplug_cb(asrv->audio_dev, true);
+
+	mutex_unlock(&asrv->plug_lock);
+}
+
+static void av_audiosrv_teardown(struct apple_epic_service *service)
+{
+	struct apple_dcp *dcp = service->ep->dcp;
+	struct audiosrv_data *asrv = dcp->audiosrv;
+
+	mutex_lock(&asrv->plug_lock);
+
+	down_write(&asrv->srv_rwsem);
+	asrv->srv = NULL;
+	up_write(&asrv->srv_rwsem);
+
+	asrv->plugged = false;
+	if (asrv->hotplug_cb)
+		asrv->hotplug_cb(asrv->audio_dev, false);
+
+	mutex_unlock(&asrv->plug_lock);
+}
+
+void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev,
+								 dcp_audio_hotplug_callback cb)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+
+	mutex_lock(&asrv->plug_lock);
+	asrv->audio_dev = audio_dev;
+	asrv->hotplug_cb = cb;
+
+	if (cb)
+		cb(audio_dev, asrv->plugged);
+	mutex_unlock(&asrv->plug_lock);
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_set_hotplug_cb);
+
+int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = afk_service_call(asrv->srv, 0, 8, cookie, sizeof(*cookie),
+			       64 - sizeof(*cookie), NULL, 0, 64);
+	up_write(&asrv->srv_rwsem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_prepare);
+
+int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = afk_service_call(asrv->srv, 0, 9, cookie, sizeof(*cookie),
+			       64 - sizeof(*cookie), NULL, 0, 64);
+	up_write(&asrv->srv_rwsem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_startlink);
+
+int dcp_audiosrv_stoplink(struct device *dev)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = afk_service_call(asrv->srv, 0, 12, NULL, 0, 64, NULL, 0, 64);
+	up_write(&asrv->srv_rwsem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_stoplink);
+
+int dcp_audiosrv_unprepare(struct device *dev)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = afk_service_call(asrv->srv, 0, 13, NULL, 0, 64, NULL, 0, 64);
+	up_write(&asrv->srv_rwsem);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_unprepare);
+
+static int
+dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group,
+			   u32 command, void *output, size_t output_maxsize,
+			   size_t *output_size)
+{
+	struct {
+		__le64 max_size;
+		u8 _pad1[24];
+		__le64 used_size;
+		u8 _pad2[8];
+	} __attribute__((packed)) *hdr;
+	static_assert(sizeof(*hdr) == 48);
+	size_t bfr_len = output_maxsize + sizeof(*hdr);
+	void *bfr;
+	int ret;
+
+	bfr = kzalloc(bfr_len, GFP_KERNEL);
+	if (!bfr)
+		return -ENOMEM;
+
+	hdr = bfr;
+	hdr->max_size = cpu_to_le64(output_maxsize);
+	ret = afk_service_call(service, group, command, hdr, sizeof(*hdr), output_maxsize,
+			       bfr, sizeof(*hdr) + output_maxsize, 0);
+	if (ret)
+		return ret;
+
+	if (output)
+		memcpy(output, bfr + sizeof(*hdr), output_maxsize);
+
+	if (output_size)
+		*output_size = le64_to_cpu(hdr->used_size);
+
+	return 0;
+}
+
+int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	size_t size;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 18, elements, maxsize, &size);
+	up_write(&asrv->srv_rwsem);
+
+	if (ret)
+		dev_err(dev, "audiosrv: error getting elements: %d\n", ret);
+	else
+		dev_dbg(dev, "audiosrv: got %zd bytes worth of elements\n", size);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_get_elements);
+
+int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize)
+{
+	struct apple_dcp *dcp = dev_get_drvdata(dev);
+	struct audiosrv_data *asrv = dcp->audiosrv;
+	size_t size;
+	int ret;
+
+	down_write(&asrv->srv_rwsem);
+	ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 20, attrs, maxsize, &size);
+	up_write(&asrv->srv_rwsem);
+
+	if (ret)
+		dev_err(dev, "audiosrv: error getting product attributes: %d\n", ret);
+	else
+		dev_dbg(dev, "audiosrv: got %zd bytes worth of product attributes\n", size);
+
+	return ret;
+}
+EXPORT_SYMBOL_GPL(dcp_audiosrv_get_product_attrs);
+
+static int av_audiosrv_report(struct apple_epic_service *service, u32 idx,
+						  const void *data, size_t data_size)
+{
+	dev_dbg(service->ep->dcp->dev, "got audio report %d size %zx\n", idx, data_size);
+#ifdef DEBUG
+	print_hex_dump(KERN_DEBUG, "audio report: ", DUMP_PREFIX_NONE, 16, 1, data, data_size, true);
+#endif
+
+	return 0;
+}
+
+static const struct apple_epic_service_ops avep_ops[] = {
+	{
+		.name = "DCPAVSimpleVideoInterface",
+		.init = av_interface_init,
+	},
+	{
+		.name = "DCPAVAudioInterface",
+		.init = av_audiosrv_init,
+		.report = av_audiosrv_report,
+		.teardown = av_audiosrv_teardown,
+	},
+	{}
+};
+
+int avep_init(struct apple_dcp *dcp)
+{
+	struct dcp_audio_pdata *audio_pdata;
+	struct platform_device *audio_pdev;
+	struct audiosrv_data *audiosrv_data;
+	struct device *dev = dcp->dev;
+
+	audiosrv_data = devm_kzalloc(dcp->dev, sizeof(*audiosrv_data), GFP_KERNEL);
+	audio_pdata = devm_kzalloc(dcp->dev, sizeof(*audio_pdata), GFP_KERNEL);
+	if (!audiosrv_data || !audio_pdata)
+		return -ENOMEM;
+	init_rwsem(&audiosrv_data->srv_rwsem);
+	mutex_init(&audiosrv_data->plug_lock);
+	dcp->audiosrv = audiosrv_data;
+
+	audio_pdata->dcp_dev = dcp->dev;
+	/* TODO: free OF reference */
+	audio_pdata->dpaudio_node = \
+			of_parse_phandle(dev->of_node, "apple,audio-xmitter", 0);
+	if (!audio_pdata->dpaudio_node ||
+	    !of_device_is_available(audio_pdata->dpaudio_node)) {
+		dev_info(dev, "No audio support\n");
+		return 0;
+	}
+
+	audio_pdev = platform_device_register_data(dev, "dcp-hdmi-audio",
+						   PLATFORM_DEVID_AUTO,
+						   audio_pdata, sizeof(*audio_pdata));
+	if (IS_ERR(audio_pdev))
+		return dev_err_probe(dev, PTR_ERR(audio_pdev), "registering audio device\n");
+
+	dcp->avep = afk_init(dcp, AV_ENDPOINT, avep_ops);
+	if (IS_ERR(dcp->avep))
+		return PTR_ERR(dcp->avep);
+	dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20];
+	return afk_start(dcp->avep);
+}
diff --git a/drivers/gpu/drm/apple/av.h b/drivers/gpu/drm/apple/av.h
new file mode 100644
index 00000000000000..b1f92fb5d07f90
--- /dev/null
+++ b/drivers/gpu/drm/apple/av.h
@@ -0,0 +1,9 @@
+#ifndef __AV_H__
+#define __AV_H__
+
+#include "parser.h"
+
+//int avep_audiosrv_startlink(struct apple_dcp *dcp, struct dcp_sound_cookie *cookie);
+//int avep_audiosrv_stoplink(struct apple_dcp *dcp);
+
+#endif /* __AV_H__ */
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 9f9832133e73ac..4c352fd7791dec 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -34,6 +34,7 @@ enum {
 	TEST_ENDPOINT = 0x21,
 	DCP_EXPERT_ENDPOINT = 0x22,
 	DISP0_ENDPOINT = 0x23,
+	AV_ENDPOINT = 0x29,
 	DPTX_ENDPOINT = 0x2a,
 	HDCP_ENDPOINT = 0x2b,
 	REMOTE_ALLOC_ENDPOINT = 0x2d,
@@ -89,6 +90,8 @@ struct dcp_brightness {
 	bool update;
 };
 
+struct audiosrv_data;
+
 /** laptop/AiO integrated panel parameters from DT */
 struct dcp_panel {
 	/// panel width in millimeter
@@ -223,6 +226,9 @@ struct apple_dcp {
 
 	struct apple_dcp_afkep *ibootep;
 
+	struct apple_dcp_afkep *avep;
+	struct audiosrv_data *audiosrv;
+
 	struct apple_dcp_afkep *dptxep;
 
 	struct dptx_port dptxport[2];
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 6ccf1b03410e8b..ec87c8f2a5d94d 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -11,6 +11,7 @@
 #include <linux/gpio/consumer.h>
 #include <linux/iommu.h>
 #include <linux/jiffies.h>
+#include <linux/kconfig.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/moduleparam.h>
@@ -45,6 +46,10 @@ static bool show_notch;
 module_param(show_notch, bool, 0644);
 MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch");
 
+static bool noaudio;
+module_param(noaudio, bool, 0644);
+MODULE_PARM_DESC(noaudio, "Skip audio support");
+
 /* HACK: moved here to avoid circular dependency between apple_drv and dcp */
 void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 {
@@ -118,6 +123,9 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 	switch (endpoint) {
 	case IOMFB_ENDPOINT:
 		return iomfb_recv_msg(dcp, message);
+	case AV_ENDPOINT:
+		afk_receive_message(dcp->avep, message);
+		return;
 	case SYSTEM_ENDPOINT:
 		afk_receive_message(dcp->systemep, message);
 		return;
@@ -363,6 +371,14 @@ int dcp_start(struct platform_device *pdev)
 	if (ret)
 		dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret);
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
+	if (!noaudio) {
+		ret = avep_init(dcp);
+		if (ret)
+			dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret);
+	}
+#endif
+
 	if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
 		ret = ibootep_init(dcp);
 		if (ret)
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 52c5c94a2a8bbe..0a1981040996dc 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -60,4 +60,6 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message);
 int systemep_init(struct apple_dcp *dcp);
 int dptxep_init(struct apple_dcp *dcp);
 int ibootep_init(struct apple_dcp *dcp);
+int avep_init(struct apple_dcp *dcp);
+
 #endif

From e50621be0b0ce4cfbe432b6f7d452e3980501aa5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 Nov 2023 21:59:46 +0100
Subject: [PATCH 0790/1027] drm: apple: av: Support macOS 12.3 and 13.5
 firmware APIs

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/av.c | 74 +++++++++++++++++++++++++++++++++-----
 1 file changed, 65 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index bd4c7ec51bdb7d..a00932476da3ab 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -11,6 +11,39 @@
 #include "audio.h"
 #include "afk.h"
 #include "dcp.h"
+#include "dcp-internal.h"
+
+struct dcp_av_audio_cmds {
+	/* commands in group 0*/
+	u32 open;
+	u32 prepare;
+	u32 start_link;
+	u32 stop_link;
+	u32 unprepare;
+	/* commands in group 1*/
+	u32 get_elements;
+	u32 get_product_attrs;
+};
+
+static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v12_3 = {
+	.open = 6,
+	.prepare = 8,
+	.start_link = 9,
+	.stop_link = 12,
+	.unprepare = 13,
+	.get_elements = 18,
+	.get_product_attrs = 20,
+};
+
+static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v13_5 = {
+	.open = 4,
+	.prepare = 6,
+	.start_link = 7,
+	.stop_link = 10,
+	.unprepare = 11,
+	.get_elements = 16,
+	.get_product_attrs = 18,
+};
 
 struct audiosrv_data {
 	struct device *audio_dev;
@@ -20,6 +53,8 @@ struct audiosrv_data {
 
 	struct apple_epic_service *srv;
 	struct rw_semaphore srv_rwsem;
+
+	struct dcp_av_audio_cmds cmds;
 };
 
 static void av_interface_init(struct apple_epic_service *service, const char *name,
@@ -41,7 +76,8 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam
 	up_write(&asrv->srv_rwsem);
 
 	/* TODO: this must be done elsewhere */
-	err = afk_service_call(asrv->srv, 0, 6, NULL, 0, 32, NULL, 0, 32);
+	err = afk_service_call(asrv->srv, 0, asrv->cmds.open, NULL, 0, 32, NULL,
+			       0, 32);
 	if (err)
 		dev_err(dcp->dev, "error opening audio service: %d\n", err);
 
@@ -93,8 +129,9 @@ int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie)
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = afk_service_call(asrv->srv, 0, 8, cookie, sizeof(*cookie),
-			       64 - sizeof(*cookie), NULL, 0, 64);
+	ret = afk_service_call(asrv->srv, 0, asrv->cmds.prepare, cookie,
+			       sizeof(*cookie), 64 - sizeof(*cookie), NULL, 0,
+			       64);
 	up_write(&asrv->srv_rwsem);
 
 	return ret;
@@ -108,8 +145,9 @@ int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie)
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = afk_service_call(asrv->srv, 0, 9, cookie, sizeof(*cookie),
-			       64 - sizeof(*cookie), NULL, 0, 64);
+	ret = afk_service_call(asrv->srv, 0, asrv->cmds.start_link, cookie,
+			       sizeof(*cookie), 64 - sizeof(*cookie), NULL, 0,
+			       64);
 	up_write(&asrv->srv_rwsem);
 
 	return ret;
@@ -123,7 +161,8 @@ int dcp_audiosrv_stoplink(struct device *dev)
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = afk_service_call(asrv->srv, 0, 12, NULL, 0, 64, NULL, 0, 64);
+	ret = afk_service_call(asrv->srv, 0, asrv->cmds.stop_link, NULL, 0, 64,
+			       NULL, 0, 64);
 	up_write(&asrv->srv_rwsem);
 
 	return ret;
@@ -137,7 +176,8 @@ int dcp_audiosrv_unprepare(struct device *dev)
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = afk_service_call(asrv->srv, 0, 13, NULL, 0, 64, NULL, 0, 64);
+	ret = afk_service_call(asrv->srv, 0, asrv->cmds.unprepare, NULL, 0, 64,
+			       NULL, 0, 64);
 	up_write(&asrv->srv_rwsem);
 
 	return ret;
@@ -188,7 +228,8 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 18, elements, maxsize, &size);
+	ret = dcp_audiosrv_osobject_call(asrv->srv, 1, asrv->cmds.get_elements,
+					 elements, maxsize, &size);
 	up_write(&asrv->srv_rwsem);
 
 	if (ret)
@@ -208,7 +249,9 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi
 	int ret;
 
 	down_write(&asrv->srv_rwsem);
-	ret = dcp_audiosrv_osobject_call(asrv->srv, 1, 20, attrs, maxsize, &size);
+	ret = dcp_audiosrv_osobject_call(asrv->srv, 1,
+					 asrv->cmds.get_product_attrs, attrs,
+					 maxsize, &size);
 	up_write(&asrv->srv_rwsem);
 
 	if (ret)
@@ -258,6 +301,19 @@ int avep_init(struct apple_dcp *dcp)
 		return -ENOMEM;
 	init_rwsem(&audiosrv_data->srv_rwsem);
 	mutex_init(&audiosrv_data->plug_lock);
+
+	switch (dcp->fw_compat) {
+	case DCP_FIRMWARE_V_12_3:
+		audiosrv_data->cmds = dcp_av_audio_cmds_v12_3;
+		break;
+	case DCP_FIRMWARE_V_13_5:
+		audiosrv_data->cmds = dcp_av_audio_cmds_v13_5;
+		break;
+	default:
+		dev_err(dcp->dev, "Audio not supported for firmware\n");
+		return -ENODEV;
+	}
+
 	dcp->audiosrv = audiosrv_data;
 
 	audio_pdata->dcp_dev = dcp->dev;

From 33fd23ccad5e014b71835d41fade0acda914415a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 Nov 2023 23:00:13 +0100
Subject: [PATCH 0791/1027] drm: apple: av: Do not open AV service from afk
 receive handler

Use a completion to do it from avep_init() instead.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/av.c | 37 +++++++++++++++++++++++++++----------
 1 file changed, 27 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index a00932476da3ab..5f3783221ac400 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -51,6 +51,7 @@ struct audiosrv_data {
 	bool plugged;
 	struct mutex plug_lock;
 
+	struct completion init_completion;
 	struct apple_epic_service *srv;
 	struct rw_semaphore srv_rwsem;
 
@@ -67,7 +68,6 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam
 {
 	struct apple_dcp *dcp = service->ep->dcp;
 	struct audiosrv_data *asrv = dcp->audiosrv;
-	int err;
 
 	mutex_lock(&asrv->plug_lock);
 
@@ -75,16 +75,8 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam
 	asrv->srv = service;
 	up_write(&asrv->srv_rwsem);
 
-	/* TODO: this must be done elsewhere */
-	err = afk_service_call(asrv->srv, 0, asrv->cmds.open, NULL, 0, 32, NULL,
-			       0, 32);
-	if (err)
-		dev_err(dcp->dev, "error opening audio service: %d\n", err);
-
+	complete(&asrv->init_completion);
 	asrv->plugged = true;
-	if (asrv->hotplug_cb)
-		asrv->hotplug_cb(asrv->audio_dev, true);
-
 	mutex_unlock(&asrv->plug_lock);
 }
 
@@ -313,6 +305,7 @@ int avep_init(struct apple_dcp *dcp)
 		dev_err(dcp->dev, "Audio not supported for firmware\n");
 		return -ENODEV;
 	}
+	init_completion(&audiosrv_data->init_completion);
 
 	dcp->audiosrv = audiosrv_data;
 
@@ -337,4 +330,28 @@ int avep_init(struct apple_dcp *dcp)
 		return PTR_ERR(dcp->avep);
 	dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20];
 	return afk_start(dcp->avep);
+
+	ret = wait_for_completion_timeout(&dcp->audiosrv->init_completion,
+					  msecs_to_jiffies(500));
+	if (ret < 0) {
+		dev_err(dcp->dev, "error waiting on audio service init: %d\n", ret);
+		return ret;
+	} else if (!ret) {
+		dev_err(dcp->dev, "timeout while waiting for audio service init\n");
+		return -ETIMEDOUT;
+	}
+
+	/* open AV audio service */
+	ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open,
+			       NULL, 0, 32, NULL, 0, 32);
+	if (ret) {
+		dev_err(dcp->dev, "error opening audio service: %d\n", ret);
+		return ret;
+	}
+
+	mutex_lock(&dcp->audiosrv->plug_lock);
+	if (dcp->audiosrv->hotplug_cb)
+		dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev,
+					  dcp->audiosrv->plugged);
+	mutex_unlock(&dcp->audiosrv->plug_lock);
 }

From 5b08c30695d979095e792c047a824557c63dab37 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 23 Feb 2023 12:49:43 +0100
Subject: [PATCH 0792/1027] gpu: drm: apple: Add DCP audio driver
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 drivers/gpu/drm/apple/Kconfig            |   1 +
 drivers/gpu/drm/apple/Makefile           |   5 +
 drivers/gpu/drm/apple/audio.c            | 608 +++++++++++++++++++++++
 drivers/gpu/drm/apple/hdmi-codec-chmap.h | 123 +++++
 4 files changed, 737 insertions(+)
 create mode 100644 drivers/gpu/drm/apple/audio.c
 create mode 100644 drivers/gpu/drm/apple/hdmi-codec-chmap.h

diff --git a/drivers/gpu/drm/apple/Kconfig b/drivers/gpu/drm/apple/Kconfig
index 6cf972f8ce1162..6b544a479d3264 100644
--- a/drivers/gpu/drm/apple/Kconfig
+++ b/drivers/gpu/drm/apple/Kconfig
@@ -18,6 +18,7 @@ config DRM_APPLE_AUDIO
 	depends on DRM_APPLE
 	depends on SND
 	select SND_PCM
+	select SND_DMAENGINE_PCM
 
 config DRM_APPLE_DEBUG
 	bool "Enable additional driver debugging"
diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 5963d3cde31316..88067ba9c32ec8 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -12,14 +12,19 @@ apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
+apple_dcp_audio-y := audio.o
 
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
 obj-$(CONFIG_DRM_APPLE) += apple_dcp.o
+ifeq ($(CONFIG_DRM_APPLE_AUDIO),y)
+obj-$(CONFIG_DRM_APPLE) += apple_dcp_audio.o
+endif
 
 # header test
 
 # exclude some broken headers from the test coverage
 no-header-test := \
+	hdmi-codec-chmap.h
 
 always-y += \
 	$(patsubst %.h,%.hdrtest, $(filter-out $(no-header-test), \
diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
new file mode 100644
index 00000000000000..223b033732216e
--- /dev/null
+++ b/drivers/gpu/drm/apple/audio.c
@@ -0,0 +1,608 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/*
+ * DCP Audio Bits
+ *
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * TODO:
+ *  - figure some nice identification of the sound card (in case
+ *    there's many DCP instances)
+ */
+
+#define DEBUG
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/of_dma.h>
+#include <linux/platform_device.h>
+#include <sound/dmaengine_pcm.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/initval.h>
+#include <sound/jack.h>
+
+#include "av.h"
+#include "audio.h"
+#include "parser.h"
+
+#define DCPAUD_ELEMENTS_MAXSIZE		16384
+#define DCPAUD_PRODUCTATTRS_MAXSIZE	1024
+
+#define DRV_NAME "dcp-hdmi-audio"
+
+struct dcp_audio {
+	struct device *dev;
+	struct dcp_audio_pdata *pdata;
+	struct dma_chan *chan;
+	struct snd_card *card;
+	struct snd_jack *jack;
+	struct snd_pcm_substream *substream;
+	unsigned int open_cookie;
+
+	struct mutex data_lock;
+	bool connected;
+	unsigned int connection_cookie;
+
+	struct snd_pcm_chmap_elem selected_chmap;
+	struct dcp_sound_cookie selected_cookie;
+	void *elements;
+	void *productattrs;
+
+	struct snd_pcm_chmap *chmap_info;
+};
+
+static const struct snd_pcm_hardware dcp_pcm_hw = {
+	.info	 = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+		   SNDRV_PCM_INFO_INTERLEAVED,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE | SNDRV_PCM_FMTBIT_S20_LE |
+		   SNDRV_PCM_FMTBIT_S24_LE | SNDRV_PCM_FMTBIT_S32_LE,
+	.rates			= SNDRV_PCM_RATE_CONTINUOUS,
+	.rate_min		= 0,
+	.rate_max		= UINT_MAX,
+	.channels_min		= 1,
+	.channels_max		= 16,
+	.buffer_bytes_max	= SIZE_MAX,
+	.period_bytes_min	= 4096, /* TODO */
+	.period_bytes_max	= SIZE_MAX,
+	.periods_min		= 2,
+	.periods_max		= UINT_MAX,
+};
+
+static int dcpaud_read_remote_info(struct dcp_audio *dcpaud)
+{
+	int ret;
+
+	ret = dcp_audiosrv_get_elements(dcpaud->pdata->dcp_dev, dcpaud->elements,
+					DCPAUD_ELEMENTS_MAXSIZE);
+	if (ret < 0)
+		return ret;
+
+	ret = dcp_audiosrv_get_product_attrs(dcpaud->pdata->dcp_dev, dcpaud->productattrs,
+					     DCPAUD_PRODUCTATTRS_MAXSIZE);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+static int dcpaud_interval_bitmask(struct snd_interval *i,
+				   unsigned int mask)
+{
+	struct snd_interval range;
+	if (!mask)
+		return -EINVAL;
+
+	snd_interval_any(&range);
+	range.min = __ffs(mask);
+	range.max = __fls(mask);
+	return snd_interval_refine(i, &range);
+}
+
+extern const struct snd_pcm_hw_constraint_list snd_pcm_known_rates;
+
+static void dcpaud_fill_fmt_sieve(struct snd_pcm_hw_params *params,
+				  struct dcp_sound_format_mask *sieve)
+{
+	struct snd_interval *c = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct snd_interval *r = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_RATE);
+	struct snd_mask *f = hw_param_mask(params,
+				SNDRV_PCM_HW_PARAM_FORMAT);
+	int i;
+
+	sieve->nchans = GENMASK(c->max, c->min);
+	sieve->formats = f->bits[0] | ((u64) f->bits[1]) << 32; /* TODO: don't open-code */
+
+	for (i = 0; i < snd_pcm_known_rates.count; i++) {
+		unsigned int rate = snd_pcm_known_rates.list[i];
+
+		if (snd_interval_test(r, rate))
+			sieve->rates |= 1u << i;
+	}
+}
+
+static void dcpaud_consult_elements(struct dcp_audio *dcpaud,
+				    struct snd_pcm_hw_params *params,
+				    struct dcp_sound_format_mask *hits)
+{
+	struct dcp_sound_format_mask sieve;
+	struct dcp_parse_ctx elements = {
+		.dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev),
+		.blob = dcpaud->elements + 4,
+		.len = DCPAUD_ELEMENTS_MAXSIZE - 4,
+		.pos = 0,
+	};
+
+	dcpaud_fill_fmt_sieve(params, &sieve);
+	dev_dbg(dcpaud->dev, "elements in: %llx %x %x\n", sieve.formats, sieve.nchans, sieve.rates);
+	parse_sound_constraints(&elements, &sieve, hits);
+	dev_dbg(dcpaud->dev, "elements out: %llx %x %x\n", hits->formats, hits->nchans, hits->rates);
+}
+
+static int dcpaud_select_cookie(struct dcp_audio *dcpaud,
+				 struct snd_pcm_hw_params *params)
+{
+	struct dcp_sound_format_mask sieve;
+	struct dcp_parse_ctx elements = {
+		.dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev),
+		.blob = dcpaud->elements + 4,
+		.len = DCPAUD_ELEMENTS_MAXSIZE - 4,
+		.pos = 0,
+	};
+
+	dcpaud_fill_fmt_sieve(params, &sieve);
+	return parse_sound_mode(&elements, &sieve, &dcpaud->selected_chmap,
+				&dcpaud->selected_cookie);
+}
+
+static int dcpaud_rule_channels(struct snd_pcm_hw_params *params,
+                                struct snd_pcm_hw_rule *rule)
+{
+	struct dcp_audio *dcpaud = rule->private;
+	struct snd_interval *c = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_CHANNELS);
+	struct dcp_sound_format_mask hits = {0, 0, 0};
+
+        dcpaud_consult_elements(dcpaud, params, &hits);
+
+        return dcpaud_interval_bitmask(c, hits.nchans);
+}
+
+static int dcpaud_refine_fmt_mask(struct snd_mask *m, u64 mask)
+{
+	struct snd_mask mask_mask;
+
+	if (!mask)
+		return -EINVAL;
+	mask_mask.bits[0] = mask;
+	mask_mask.bits[1] = mask >> 32;
+
+	return snd_mask_refine(m, &mask_mask);
+}
+
+static int dcpaud_rule_format(struct snd_pcm_hw_params *params,
+                               struct snd_pcm_hw_rule *rule)
+{
+	struct dcp_audio *dcpaud = rule->private;
+	struct snd_mask *f = hw_param_mask(params,
+				SNDRV_PCM_HW_PARAM_FORMAT);
+	struct dcp_sound_format_mask hits;
+
+        dcpaud_consult_elements(dcpaud, params, &hits);
+
+        return dcpaud_refine_fmt_mask(f, hits.formats);
+}
+
+static int dcpaud_rule_rate(struct snd_pcm_hw_params *params,
+                             struct snd_pcm_hw_rule *rule)
+{
+	struct dcp_audio *dcpaud = rule->private;
+	struct snd_interval *r = hw_param_interval(params,
+				SNDRV_PCM_HW_PARAM_RATE);
+	struct dcp_sound_format_mask hits;
+
+        dcpaud_consult_elements(dcpaud, params, &hits);
+
+        return snd_interval_rate_bits(r, hits.rates);
+}
+
+static int dcp_pcm_open(struct snd_pcm_substream *substream)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+	struct dma_chan *chan = dcpaud->chan;
+	struct snd_dmaengine_dai_dma_data dma_data = {
+		.flags = SND_DMAENGINE_PCM_DAI_FLAG_PACK,
+	};
+	struct snd_pcm_hardware hw;
+	int ret;
+
+	mutex_lock(&dcpaud->data_lock);
+	if (!dcpaud->connected) {
+		mutex_unlock(&dcpaud->data_lock);
+		return -ENXIO;
+	}
+	dcpaud->open_cookie = dcpaud->connection_cookie;
+	mutex_unlock(&dcpaud->data_lock);
+
+	ret = dcpaud_read_remote_info(dcpaud);
+	if (ret < 0)
+		return ret;
+
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_FORMAT,
+			    dcpaud_rule_format, dcpaud,
+			    SNDRV_PCM_HW_PARAM_CHANNELS, SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_CHANNELS,
+			    dcpaud_rule_channels, dcpaud,
+			    SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_RATE, -1);
+	snd_pcm_hw_rule_add(substream->runtime, 0, SNDRV_PCM_HW_PARAM_RATE,
+			    dcpaud_rule_rate, dcpaud,
+			    SNDRV_PCM_HW_PARAM_FORMAT, SNDRV_PCM_HW_PARAM_CHANNELS, -1);
+
+	hw = dcp_pcm_hw;
+	hw.info = SNDRV_PCM_INFO_MMAP | SNDRV_PCM_INFO_MMAP_VALID |
+			  SNDRV_PCM_INFO_INTERLEAVED;
+	hw.periods_min = 2;
+	hw.periods_max = UINT_MAX;
+	hw.period_bytes_min = 256;
+	hw.period_bytes_max = SIZE_MAX; // TODO dma_get_max_seg_size(dma_dev);
+	hw.buffer_bytes_max = SIZE_MAX;
+	hw.fifo_size = 16;
+	ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream, &dma_data,
+							&hw, chan);
+	if (ret)
+		return ret;
+	substream->runtime->hw = hw;
+
+	return snd_dmaengine_pcm_open(substream, chan);
+}
+
+static int dcp_pcm_close(struct snd_pcm_substream *substream)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+	dcpaud->selected_chmap.channels = 0;
+
+	return snd_dmaengine_pcm_close(substream);
+}
+
+static int dcpaud_connection_up(struct dcp_audio *dcpaud)
+{
+	bool ret;
+	mutex_lock(&dcpaud->data_lock);
+	ret = dcpaud->connected &&
+	      dcpaud->open_cookie == dcpaud->connection_cookie;
+	mutex_unlock(&dcpaud->data_lock);
+	return ret;
+}
+
+static int dcp_pcm_hw_params(struct snd_pcm_substream *substream,
+			     struct snd_pcm_hw_params *params)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+	struct dma_slave_config slave_config;
+	struct dma_chan *chan = snd_dmaengine_pcm_get_chan(substream);
+	int ret;
+
+	if (!dcpaud_connection_up(dcpaud))
+		return -ENXIO;
+
+	ret = dcpaud_select_cookie(dcpaud, params);
+	if (ret < 0)
+		return ret;
+	if (!ret)
+		return -EINVAL;
+
+	memset(&slave_config, 0, sizeof(slave_config));
+	ret = snd_hwparams_to_dma_slave_config(substream, params, &slave_config);
+	dev_info(dcpaud->dev, "snd_hwparams_to_dma_slave_config: %d\n", ret);
+	if (ret < 0)
+		return ret;
+
+	slave_config.direction = DMA_MEM_TO_DEV;
+	/*
+	 * The data entry from the DMA controller to the DPA peripheral
+	 * is 32-bit wide no matter the actual sample size.
+	 */
+	slave_config.dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES;
+
+	ret = dmaengine_slave_config(chan, &slave_config);
+	dev_info(dcpaud->dev, "dmaengine_slave_config: %d\n", ret);
+	return ret;
+}
+
+static int dcp_pcm_hw_free(struct snd_pcm_substream *substream)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+
+	if (!dcpaud_connection_up(dcpaud))
+		return 0;
+
+	return dcp_audiosrv_unprepare(dcpaud->pdata->dcp_dev);
+}
+
+static int dcp_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+
+	if (!dcpaud_connection_up(dcpaud))
+		return -ENXIO;
+
+	return dcp_audiosrv_prepare(dcpaud->pdata->dcp_dev,
+				    &dcpaud->selected_cookie);
+}
+
+static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+	struct dcp_audio *dcpaud = substream->pcm->private_data;
+	int ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		if (!dcpaud_connection_up(dcpaud))
+			return -ENXIO;
+
+		ret = dcp_audiosrv_startlink(dcpaud->pdata->dcp_dev,
+					     &dcpaud->selected_cookie);
+		if (ret < 0)
+			return ret;
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		break;
+
+	default:
+		return -EINVAL;
+	}
+
+	ret = snd_dmaengine_pcm_trigger(substream, cmd);
+	if (ret < 0)
+		return ret;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+		break;
+
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+		ret = dcp_audiosrv_stoplink(dcpaud->pdata->dcp_dev);
+		if (ret < 0)
+			return ret;
+		break;
+	}
+
+	return 0;
+}
+
+struct snd_pcm_ops dcp_playback_ops = {
+	.open = dcp_pcm_open,
+	.close = dcp_pcm_close,
+	.hw_params = dcp_pcm_hw_params,
+	.hw_free = dcp_pcm_hw_free,
+	.prepare = dcp_pcm_prepare,
+	.trigger = dcp_pcm_trigger,
+	.pointer = snd_dmaengine_pcm_pointer,
+};
+
+// Transitional workaround: for the chmap control TLV, advertise options
+// copied from hdmi-codec.c
+#include "hdmi-codec-chmap.h"
+
+static int dcpaud_chmap_ctl_get(struct snd_kcontrol *kcontrol,
+			        struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_pcm_chmap *info = snd_kcontrol_chip(kcontrol);
+	struct dcp_audio *dcpaud = info->private_data;
+	unsigned int i;
+
+	for (i = 0; i < info->max_channels; i++)
+		ucontrol->value.integer.value[i] = \
+				(i < dcpaud->selected_chmap.channels) ?
+				dcpaud->selected_chmap.map[i] : SNDRV_CHMAP_UNKNOWN;
+
+	return 0;
+}
+
+
+static int dcpaud_create_chmap_ctl(struct dcp_audio *dcpaud)
+{
+	struct snd_pcm *pcm = dcpaud->substream->pcm;
+	struct snd_pcm_chmap *chmap_info;
+	int ret;
+
+	ret = snd_pcm_add_chmap_ctls(pcm, SNDRV_PCM_STREAM_PLAYBACK, NULL,
+				     dcp_pcm_hw.channels_max, 0, &chmap_info);
+	if (ret < 0)
+		return ret;
+
+	chmap_info->kctl->get = dcpaud_chmap_ctl_get;
+	chmap_info->chmap = hdmi_codec_8ch_chmaps;
+	chmap_info->private_data = dcpaud;
+
+	return 0;
+}
+
+static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
+{
+	struct snd_card *card = dcpaud->card;
+	struct snd_pcm *pcm;
+	struct dma_chan *chan;
+	int ret;
+
+	chan = of_dma_request_slave_channel(dcpaud->pdata->dpaudio_node, "tx");
+	if (IS_ERR_OR_NULL(chan)) {
+		if (!chan)
+			return -EINVAL;
+
+		dev_err(dcpaud->dev, "can't request audio TX DMA channel: %pE\n", chan);
+		return PTR_ERR(chan);
+	}
+	dcpaud->chan = chan;
+
+#define NUM_PLAYBACK 1
+#define NUM_CAPTURE 0
+
+	ret = snd_pcm_new(card, card->shortname, 0, NUM_PLAYBACK, NUM_CAPTURE, &pcm);
+	if (ret)
+		return ret;
+
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops);
+	dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
+	snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM,
+				   chan->device->dev, 1024 * 1024,
+				   SIZE_MAX);
+
+	pcm->nonatomic = true;
+	pcm->private_data = dcpaud;
+	strscpy(pcm->name, card->shortname, sizeof(pcm->name));
+
+	return 0;
+}
+
+static void dcpaud_report_hotplug(struct device *dev, bool connected)
+{
+	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
+	struct snd_pcm_substream *substream = dcpaud->substream;
+
+	mutex_lock(&dcpaud->data_lock);
+	if (dcpaud->connected == connected) {
+		mutex_unlock(&dcpaud->data_lock);
+		return;
+	}
+
+	dcpaud->connected = connected;
+	if (connected)
+		dcpaud->connection_cookie++;
+	mutex_unlock(&dcpaud->data_lock);
+
+	snd_jack_report(dcpaud->jack, connected ? SND_JACK_AVOUT : 0);
+
+	if (!connected) {
+		snd_pcm_stream_lock(substream);
+		snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+		snd_pcm_stream_unlock(substream);
+	}
+}
+
+static int dcpaud_create_jack(struct dcp_audio *dcpaud)
+{
+	struct snd_card *card = dcpaud->card;
+
+	return snd_jack_new(card, "HDMI/DP", SND_JACK_AVOUT,
+			    &dcpaud->jack, true, false);
+}
+
+static void dcpaud_set_card_names(struct dcp_audio *dcpaud)
+{
+	struct snd_card *card = dcpaud->card;
+
+	strscpy(card->driver, "apple_dcp", sizeof(card->driver));
+	strscpy(card->longname, "Apple DisplayPort", sizeof(card->longname));
+	strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname));
+}
+
+#ifdef CONFIG_SND_DEBUG
+static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size)
+{
+	struct debugfs_blob_wrapper *wrapper;
+	wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL);
+	if (!wrapper)
+		return;
+	wrapper->data = base;
+	wrapper->size = size;
+	debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper);
+}
+#else
+static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {}
+#endif
+
+static int dcpaud_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct dcp_audio_pdata *pdata = dev->platform_data;
+	struct dcp_audio *dcpaud;
+	int ret;
+
+	dcpaud = devm_kzalloc(dev, sizeof(*dcpaud), GFP_KERNEL);
+	if (!dcpaud)
+		return -ENOMEM;
+	dcpaud->dev = dev;
+	dcpaud->pdata = pdata;
+	mutex_init(&dcpaud->data_lock);
+	platform_set_drvdata(pdev, dcpaud);
+
+	dcpaud->elements = devm_kzalloc(dev, DCPAUD_ELEMENTS_MAXSIZE,
+					GFP_KERNEL);
+	if (!dcpaud->elements)
+		return -ENOMEM;
+
+	dcpaud->productattrs = devm_kzalloc(dev, DCPAUD_PRODUCTATTRS_MAXSIZE,
+					    GFP_KERNEL);
+	if (!dcpaud->productattrs)
+		return -ENOMEM;
+
+	ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			   THIS_MODULE, 0, &dcpaud->card);
+	if (ret)
+		return ret;
+
+	dcpaud_set_card_names(dcpaud);
+
+	ret = dcpaud_create_pcm(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = dcpaud_create_chmap_ctl(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = dcpaud_create_jack(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = snd_card_register(dcpaud->card);
+	if (ret)
+		goto err_free_card;
+
+	dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie,
+				   sizeof(dcpaud->selected_cookie));
+	dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements,
+				   DCPAUD_ELEMENTS_MAXSIZE);
+	dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs,
+				   DCPAUD_PRODUCTATTRS_MAXSIZE);
+
+	dcp_audiosrv_set_hotplug_cb(pdata->dcp_dev, dev, dcpaud_report_hotplug);
+
+	return 0;
+
+err_free_card:
+	snd_card_free(dcpaud->card);
+	return ret;
+}
+
+static int dcpaud_remove(struct platform_device *dev)
+{
+	struct dcp_audio *dcpaud = platform_get_drvdata(dev);
+
+	dcp_audiosrv_set_hotplug_cb(dcpaud->pdata->dcp_dev, NULL, NULL);
+	snd_card_free(dcpaud->card);
+
+	return 0;
+}
+
+static struct platform_driver dcpaud_driver = {
+	.driver = {
+		.name = DRV_NAME,
+	},
+	.probe = dcpaud_probe,
+	.remove = dcpaud_remove,
+};
+
+module_platform_driver(dcpaud_driver);
+
+MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
+MODULE_DESCRIPTION("Apple DCP HDMI Audio Driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/gpu/drm/apple/hdmi-codec-chmap.h b/drivers/gpu/drm/apple/hdmi-codec-chmap.h
new file mode 100644
index 00000000000000..f98e1e86b89602
--- /dev/null
+++ b/drivers/gpu/drm/apple/hdmi-codec-chmap.h
@@ -0,0 +1,123 @@
+// copied from sound/soc/codecs/hdmi-codec.c
+
+#include <sound/pcm.h>
+
+/* Channel maps for multi-channel playbacks, up to 8 n_ch */
+static const struct snd_pcm_chmap_elem hdmi_codec_8ch_chmaps[] = {
+	{ .channels = 2, /* CA_ID 0x00 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR } },
+	{ .channels = 4, /* CA_ID 0x01 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA } },
+	{ .channels = 4, /* CA_ID 0x02 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC } },
+	{ .channels = 4, /* CA_ID 0x03 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC } },
+	{ .channels = 6, /* CA_ID 0x04 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 6, /* CA_ID 0x05 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 6, /* CA_ID 0x06 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 6, /* CA_ID 0x07 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 6, /* CA_ID 0x08 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 6, /* CA_ID 0x09 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 6, /* CA_ID 0x0A */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 6, /* CA_ID 0x0B */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR } },
+	{ .channels = 8, /* CA_ID 0x0C */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 8, /* CA_ID 0x0D */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 8, /* CA_ID 0x0E */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 8, /* CA_ID 0x0F */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RC, SNDRV_CHMAP_NA } },
+	{ .channels = 8, /* CA_ID 0x10 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } },
+	{ .channels = 8, /* CA_ID 0x11 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } },
+	{ .channels = 8, /* CA_ID 0x12 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } },
+	{ .channels = 8, /* CA_ID 0x13 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_RL, SNDRV_CHMAP_RR,
+		   SNDRV_CHMAP_RLC, SNDRV_CHMAP_RRC } },
+	{ .channels = 8, /* CA_ID 0x14 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x15 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x16 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x17 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x18 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x19 */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1A */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1B */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1C */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1D */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_NA, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1E */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ .channels = 8, /* CA_ID 0x1F */
+	  .map = { SNDRV_CHMAP_FL, SNDRV_CHMAP_FR, SNDRV_CHMAP_LFE,
+		   SNDRV_CHMAP_FC, SNDRV_CHMAP_NA, SNDRV_CHMAP_NA,
+		   SNDRV_CHMAP_FLC, SNDRV_CHMAP_FRC } },
+	{ }
+};

From cc3a3d899a5605aa781473a08cdf749f513ef01a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 14 Apr 2024 16:22:25 +0200
Subject: [PATCH 0793/1027] drm: apple: dptx: Remove DPTX disconnect/connect on
 init

This was only necessary for dcp0 on M2* devices presumably because the
reset in m1n1 doesn't work as intended.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 16 ++++++++++++----
 1 file changed, 12 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index ec87c8f2a5d94d..3fbbb662ecc892 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -386,10 +386,17 @@ int dcp_start(struct platform_device *pdev)
 				 ret);
 
 		ret = dptxep_init(dcp);
-		if (ret)
+		if (ret) {
 			dev_warn(dcp->dev, "Failed to start DPTX endpoint: %d\n",
 				 ret);
-		else if (dcp->dptxport[0].enabled) {
+#ifdef DCP_DPTX_DISCONNECT_ON_INIT
+		/*
+		 * This disconnect / connect cycle on init is only necessary
+		 * when using dcp0 on j473, j474s and presumedly j475c.
+		 * Since dcp0 is not used at the moment let's avoid this
+		 * since it is possibly the cause for startup issues.
+		 */
+		} else if (dcp->dptxport[0].enabled) {
 			bool connected;
 			/* force disconnect on start - necessary if the display
 			 * is already up from m1n1
@@ -404,10 +411,11 @@ int dcp_start(struct platform_device *pdev)
 			// necessary on j473/j474 but not on j314c
 			if (connected)
 				dcp_dptx_connect(dcp, 0);
+#endif
 		}
-	} else if (dcp->phy)
+	} else if (dcp->phy) {
 		dev_warn(dcp->dev, "OS firmware incompatible with dptxport EP\n");
-
+	}
 	ret = iomfb_start_rtkit(dcp);
 	if (ret)
 		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret);

From d829deaf9a64185d93c4de68aac0f6efe97568e6 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 14 Apr 2024 16:47:01 +0200
Subject: [PATCH 0794/1027] drm: apple: audio: init AV endpoint later

This seems to get rid of initialization timeouts / failures.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 3fbbb662ecc892..048a87473b6141 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -371,14 +371,6 @@ int dcp_start(struct platform_device *pdev)
 	if (ret)
 		dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret);
 
-#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
-	if (!noaudio) {
-		ret = avep_init(dcp);
-		if (ret)
-			dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret);
-	}
-#endif
-
 	if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
 		ret = ibootep_init(dcp);
 		if (ret)
@@ -420,6 +412,15 @@ int dcp_start(struct platform_device *pdev)
 	if (ret)
 		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret);
 
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
+	if (!noaudio) {
+		ret = avep_init(dcp);
+		if (ret)
+			dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret);
+		ret = 0;
+	}
+#endif
+
 	return ret;
 }
 EXPORT_SYMBOL(dcp_start);

From ad3a38fa866aa95fcd34954187d20193aca26fa7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 20 Apr 2024 21:00:37 +0200
Subject: [PATCH 0795/1027] drm: apple: av: Use a workqueue

Functionally a revert of
"drm: apple: av: Do not open AV service from afk receive handler"
with more workqueues.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/av.c | 63 ++++++++++++++++++++++----------------
 1 file changed, 36 insertions(+), 27 deletions(-)

diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index 5f3783221ac400..926c4b238227b1 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -7,6 +7,7 @@
 #include <linux/kconfig.h>
 #include <linux/rwsem.h>
 #include <linux/types.h>
+#include <linux/workqueue.h>
 
 #include "audio.h"
 #include "afk.h"
@@ -51,9 +52,10 @@ struct audiosrv_data {
 	bool plugged;
 	struct mutex plug_lock;
 
-	struct completion init_completion;
 	struct apple_epic_service *srv;
 	struct rw_semaphore srv_rwsem;
+	/* Workqueue for starting the audio service */
+	struct work_struct start_av_service_wq;
 
 	struct dcp_av_audio_cmds cmds;
 };
@@ -75,9 +77,9 @@ static void av_audiosrv_init(struct apple_epic_service *service, const char *nam
 	asrv->srv = service;
 	up_write(&asrv->srv_rwsem);
 
-	complete(&asrv->init_completion);
 	asrv->plugged = true;
 	mutex_unlock(&asrv->plug_lock);
+	schedule_work(&asrv->start_av_service_wq);
 }
 
 static void av_audiosrv_teardown(struct apple_epic_service *service)
@@ -280,6 +282,37 @@ static const struct apple_epic_service_ops avep_ops[] = {
 	{}
 };
 
+static void av_work_service_start(struct work_struct *work)
+{
+	int ret;
+	struct audiosrv_data *audiosrv_data;
+	struct apple_dcp *dcp;
+
+	audiosrv_data = container_of(work, struct audiosrv_data, start_av_service_wq);
+	if (!audiosrv_data->srv ||
+	    !audiosrv_data->srv->ep ||
+	    !audiosrv_data->srv->ep->dcp) {
+		pr_err("%s: dcp: av: NULL ptr during startup\n", __func__);
+		return;
+	}
+	dcp = audiosrv_data->srv->ep->dcp;
+
+	/* open AV audio service */
+	dev_info(dcp->dev, "%s: starting audio service\n", __func__);
+	ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open,
+			       NULL, 0, 32, NULL, 0, 32);
+	if (ret) {
+		dev_err(dcp->dev, "error opening audio service: %d\n", ret);
+		return;
+	}
+
+	mutex_lock(&dcp->audiosrv->plug_lock);
+	if (dcp->audiosrv->hotplug_cb)
+		dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev,
+					  dcp->audiosrv->plugged);
+	mutex_unlock(&dcp->audiosrv->plug_lock);
+}
+
 int avep_init(struct apple_dcp *dcp)
 {
 	struct dcp_audio_pdata *audio_pdata;
@@ -305,7 +338,7 @@ int avep_init(struct apple_dcp *dcp)
 		dev_err(dcp->dev, "Audio not supported for firmware\n");
 		return -ENODEV;
 	}
-	init_completion(&audiosrv_data->init_completion);
+	INIT_WORK(&audiosrv_data->start_av_service_wq, av_work_service_start);
 
 	dcp->audiosrv = audiosrv_data;
 
@@ -330,28 +363,4 @@ int avep_init(struct apple_dcp *dcp)
 		return PTR_ERR(dcp->avep);
 	dcp->avep->debugfs_entry = dcp->ep_debugfs[AV_ENDPOINT - 0x20];
 	return afk_start(dcp->avep);
-
-	ret = wait_for_completion_timeout(&dcp->audiosrv->init_completion,
-					  msecs_to_jiffies(500));
-	if (ret < 0) {
-		dev_err(dcp->dev, "error waiting on audio service init: %d\n", ret);
-		return ret;
-	} else if (!ret) {
-		dev_err(dcp->dev, "timeout while waiting for audio service init\n");
-		return -ETIMEDOUT;
-	}
-
-	/* open AV audio service */
-	ret = afk_service_call(dcp->audiosrv->srv, 0, dcp->audiosrv->cmds.open,
-			       NULL, 0, 32, NULL, 0, 32);
-	if (ret) {
-		dev_err(dcp->dev, "error opening audio service: %d\n", ret);
-		return ret;
-	}
-
-	mutex_lock(&dcp->audiosrv->plug_lock);
-	if (dcp->audiosrv->hotplug_cb)
-		dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev,
-					  dcp->audiosrv->plugged);
-	mutex_unlock(&dcp->audiosrv->plug_lock);
 }

From 4d5249e41bdc2fed6e59e0d82f068df5a3b3c5e1 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 20 Apr 2024 21:06:19 +0200
Subject: [PATCH 0796/1027] drm: apple: audio: move the audio driver into the
 DCP module

Those two drivers are closely linked and should always exists together.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile |  6 +-----
 drivers/gpu/drm/apple/audio.c  | 14 +++++++++-----
 drivers/gpu/drm/apple/dcp.c    | 22 +++++++++++++++++++++-
 drivers/gpu/drm/apple/dcp.h    |  4 ++++
 4 files changed, 35 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 88067ba9c32ec8..519303016b292a 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -5,6 +5,7 @@ CFLAGS_trace.o = -I$(src)
 appledrm-y := apple_drv.o
 
 apple_dcp-y := afk.o dcp.o dcp_backlight.o dptxep.o iomfb.o parser.o systemep.o
+apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += audio.o
 apple_dcp-$(CONFIG_DRM_APPLE_AUDIO) += av.o
 apple_dcp-y += connector.o
 apple_dcp-y += ibootep.o
@@ -12,13 +13,8 @@ apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
-apple_dcp_audio-y := audio.o
-
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
 obj-$(CONFIG_DRM_APPLE) += apple_dcp.o
-ifeq ($(CONFIG_DRM_APPLE_AUDIO),y)
-obj-$(CONFIG_DRM_APPLE) += apple_dcp_audio.o
-endif
 
 # header test
 
diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index 223b033732216e..e997a6deae7b69 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -600,9 +600,13 @@ static struct platform_driver dcpaud_driver = {
 	.remove = dcpaud_remove,
 };
 
-module_platform_driver(dcpaud_driver);
+void __init dcp_audio_register(void)
+{
+        platform_driver_register(&dcpaud_driver);
+}
+
+void __exit dcp_audio_unregister(void)
+{
+        platform_driver_unregister(&dcpaud_driver);
+}
 
-MODULE_AUTHOR("Martin Povišer <povik+lin@cutebit.org>");
-MODULE_DESCRIPTION("Apple DCP HDMI Audio Driver");
-MODULE_LICENSE("GPL");
-MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 048a87473b6141..8fcac8e8176674 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1138,7 +1138,27 @@ static struct platform_driver apple_platform_driver = {
 	},
 };
 
-drm_module_platform_driver(apple_platform_driver);
+static int __init apple_dcp_register(void)
+{
+	if (drm_firmware_drivers_only())
+		return -ENODEV;
+
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
+	dcp_audio_register();
+#endif
+	return platform_driver_register(&apple_platform_driver);
+}
+
+static void __exit apple_dcp_unregister(void)
+{
+	platform_driver_unregister(&apple_platform_driver);
+#if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
+	dcp_audio_unregister();
+#endif
+}
+
+module_init(apple_dcp_register);
+module_exit(apple_dcp_unregister);
 
 MODULE_AUTHOR("Alyssa Rosenzweig <alyssa@rosenzweig.io>");
 MODULE_DESCRIPTION("Apple Display Controller DRM driver");
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index 0a1981040996dc..c284a089b6e0bf 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -62,4 +62,8 @@ int dptxep_init(struct apple_dcp *dcp);
 int ibootep_init(struct apple_dcp *dcp);
 int avep_init(struct apple_dcp *dcp);
 
+
+void __init dcp_audio_register(void);
+void __exit dcp_audio_unregister(void);
+
 #endif

From 95ad97905d71de051589264a511bc79781f1d8f2 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 21 Apr 2024 15:47:20 +0200
Subject: [PATCH 0797/1027] drm: apple: audio: Make the DP/HDMI audio driver a
 full driver

The main advantage is that it allows runtime PM which would have been
manually implemented with the ad-hoc instantiated platform driver.
This also probes the devices as component of the DRM driver which allows
to simplify the the interface between the av endpoint and the audio
driver.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c |  20 +++-
 drivers/gpu/drm/apple/audio.c     | 149 +++++++++++++++++++++---------
 drivers/gpu/drm/apple/audio.h     |  14 +--
 drivers/gpu/drm/apple/av.c        |  71 ++++++--------
 4 files changed, 155 insertions(+), 99 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 53e3924abff02a..7abeb4ea5f458e 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -14,6 +14,7 @@
 #include <linux/module.h>
 #include <linux/of_address.h>
 #include <linux/of_device.h>
+#include <linux/of_graph.h>
 #include <linux/of_platform.h>
 
 #include <drm/drm_aperture.h>
@@ -574,7 +575,7 @@ const struct component_master_ops apple_drm_ops = {
 static int add_dcp_components(struct device *dev,
 			      struct component_match **matchptr)
 {
-	struct device_node *np;
+	struct device_node *np, *endpoint, *port;
 	int num = 0;
 
 	for_each_matching_node(np, apple_dcp_id_tbl) {
@@ -582,6 +583,23 @@ static int add_dcp_components(struct device *dev,
 			drm_of_component_match_add(dev, matchptr,
 						   component_compare_of, np);
 			num++;
+			for_each_endpoint_of_node(np, endpoint) {
+				port = of_graph_get_remote_port_parent(endpoint);
+				if (!port)
+					continue;
+
+#if !IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
+				if (of_device_is_compatible(port, "apple,dpaudio")) {
+					of_node_put(port);
+					continue;
+				}
+#endif
+				if (of_device_is_available(port))
+					drm_of_component_match_add(dev, matchptr,
+							   component_compare_of,
+							   port);
+				of_node_put(port);
+			}
 		}
 		of_node_put(np);
 	}
diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index e997a6deae7b69..b4a860d198c32b 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -11,9 +11,12 @@
 
 #define DEBUG
 
+#include <linux/component.h>
 #include <linux/debugfs.h>
 #include <linux/device.h>
 #include <linux/of_dma.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
 #include <linux/platform_device.h>
 #include <sound/dmaengine_pcm.h>
 #include <sound/pcm.h>
@@ -22,17 +25,16 @@
 #include <sound/jack.h>
 
 #include "av.h"
+#include "dcp.h"
 #include "audio.h"
 #include "parser.h"
 
 #define DCPAUD_ELEMENTS_MAXSIZE		16384
 #define DCPAUD_PRODUCTATTRS_MAXSIZE	1024
 
-#define DRV_NAME "dcp-hdmi-audio"
-
 struct dcp_audio {
 	struct device *dev;
-	struct dcp_audio_pdata *pdata;
+	struct device *dcp_dev;
 	struct dma_chan *chan;
 	struct snd_card *card;
 	struct snd_jack *jack;
@@ -72,12 +74,12 @@ static int dcpaud_read_remote_info(struct dcp_audio *dcpaud)
 {
 	int ret;
 
-	ret = dcp_audiosrv_get_elements(dcpaud->pdata->dcp_dev, dcpaud->elements,
+	ret = dcp_audiosrv_get_elements(dcpaud->dcp_dev, dcpaud->elements,
 					DCPAUD_ELEMENTS_MAXSIZE);
 	if (ret < 0)
 		return ret;
 
-	ret = dcp_audiosrv_get_product_attrs(dcpaud->pdata->dcp_dev, dcpaud->productattrs,
+	ret = dcp_audiosrv_get_product_attrs(dcpaud->dcp_dev, dcpaud->productattrs,
 					     DCPAUD_PRODUCTATTRS_MAXSIZE);
 	if (ret < 0)
 		return ret;
@@ -128,7 +130,7 @@ static void dcpaud_consult_elements(struct dcp_audio *dcpaud,
 {
 	struct dcp_sound_format_mask sieve;
 	struct dcp_parse_ctx elements = {
-		.dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev),
+		.dcp = dev_get_drvdata(dcpaud->dcp_dev),
 		.blob = dcpaud->elements + 4,
 		.len = DCPAUD_ELEMENTS_MAXSIZE - 4,
 		.pos = 0,
@@ -145,7 +147,7 @@ static int dcpaud_select_cookie(struct dcp_audio *dcpaud,
 {
 	struct dcp_sound_format_mask sieve;
 	struct dcp_parse_ctx elements = {
-		.dcp = dev_get_drvdata(dcpaud->pdata->dcp_dev),
+		.dcp = dev_get_drvdata(dcpaud->dcp_dev),
 		.blob = dcpaud->elements + 4,
 		.len = DCPAUD_ELEMENTS_MAXSIZE - 4,
 		.pos = 0,
@@ -317,7 +319,7 @@ static int dcp_pcm_hw_free(struct snd_pcm_substream *substream)
 	if (!dcpaud_connection_up(dcpaud))
 		return 0;
 
-	return dcp_audiosrv_unprepare(dcpaud->pdata->dcp_dev);
+	return dcp_audiosrv_unprepare(dcpaud->dcp_dev);
 }
 
 static int dcp_pcm_prepare(struct snd_pcm_substream *substream)
@@ -327,7 +329,7 @@ static int dcp_pcm_prepare(struct snd_pcm_substream *substream)
 	if (!dcpaud_connection_up(dcpaud))
 		return -ENXIO;
 
-	return dcp_audiosrv_prepare(dcpaud->pdata->dcp_dev,
+	return dcp_audiosrv_prepare(dcpaud->dcp_dev,
 				    &dcpaud->selected_cookie);
 }
 
@@ -342,7 +344,7 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 		if (!dcpaud_connection_up(dcpaud))
 			return -ENXIO;
 
-		ret = dcp_audiosrv_startlink(dcpaud->pdata->dcp_dev,
+		ret = dcp_audiosrv_startlink(dcpaud->dcp_dev,
 					     &dcpaud->selected_cookie);
 		if (ret < 0)
 			return ret;
@@ -367,7 +369,7 @@ static int dcp_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
 
 	case SNDRV_PCM_TRIGGER_STOP:
 	case SNDRV_PCM_TRIGGER_SUSPEND:
-		ret = dcp_audiosrv_stoplink(dcpaud->pdata->dcp_dev);
+		ret = dcp_audiosrv_stoplink(dcpaud->dcp_dev);
 		if (ret < 0)
 			return ret;
 		break;
@@ -431,7 +433,7 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 	struct dma_chan *chan;
 	int ret;
 
-	chan = of_dma_request_slave_channel(dcpaud->pdata->dpaudio_node, "tx");
+	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
 	if (IS_ERR_OR_NULL(chan)) {
 		if (!chan)
 			return -EINVAL;
@@ -461,9 +463,8 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 	return 0;
 }
 
-static void dcpaud_report_hotplug(struct device *dev, bool connected)
+static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected)
 {
-	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
 	struct snd_pcm_substream *substream = dcpaud->substream;
 
 	mutex_lock(&dcpaud->data_lock);
@@ -518,30 +519,44 @@ static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *nam
 static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {}
 #endif
 
-static int dcpaud_probe(struct platform_device *pdev)
+void dcpaud_connect(struct platform_device *pdev, bool connected)
 {
-	struct device *dev = &pdev->dev;
-	struct dcp_audio_pdata *pdata = dev->platform_data;
-	struct dcp_audio *dcpaud;
-	int ret;
+	struct dcp_audio *dcpaud = platform_get_drvdata(pdev);
+	dcpaud_report_hotplug(dcpaud, connected);
+}
 
-	dcpaud = devm_kzalloc(dev, sizeof(*dcpaud), GFP_KERNEL);
-	if (!dcpaud)
-		return -ENOMEM;
-	dcpaud->dev = dev;
-	dcpaud->pdata = pdata;
-	mutex_init(&dcpaud->data_lock);
-	platform_set_drvdata(pdev, dcpaud);
+void dcpaud_disconnect(struct platform_device *pdev)
+{
+	struct dcp_audio *dcpaud = platform_get_drvdata(pdev);
+	dcpaud_report_hotplug(dcpaud, false);
+}
 
-	dcpaud->elements = devm_kzalloc(dev, DCPAUD_ELEMENTS_MAXSIZE,
-					GFP_KERNEL);
-	if (!dcpaud->elements)
-		return -ENOMEM;
+static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
+{
+	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
+	struct device_node *endpoint, *dcp_node = NULL;
+	struct platform_device *dcp_pdev;
+	int ret;
 
-	dcpaud->productattrs = devm_kzalloc(dev, DCPAUD_PRODUCTATTRS_MAXSIZE,
-					    GFP_KERNEL);
-	if (!dcpaud->productattrs)
-		return -ENOMEM;
+	/* find linked DCP instance */
+	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+	if (endpoint) {
+		dcp_node = of_graph_get_remote_port_parent(endpoint);
+		of_node_put(endpoint);
+	}
+	if (!dcp_node || !of_device_is_available(dcp_node)) {
+		of_node_put(dcp_node);
+		dev_info(dev, "No audio support\n");
+		return 0;
+	}
+
+	dcp_pdev = of_find_device_by_node(dcp_node);
+	of_node_put(dcp_node);
+	if (!dcp_pdev) {
+		dev_info(dev, "No DP/HDMI audio device not ready\n");
+		return 0;
+	}
+	dcpaud->dcp_dev = &dcp_pdev->dev;
 
 	ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
 			   THIS_MODULE, 0, &dcpaud->card);
@@ -573,8 +588,6 @@ static int dcpaud_probe(struct platform_device *pdev)
 	dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs,
 				   DCPAUD_PRODUCTATTRS_MAXSIZE);
 
-	dcp_audiosrv_set_hotplug_cb(pdata->dcp_dev, dev, dcpaud_report_hotplug);
-
 	return 0;
 
 err_free_card:
@@ -582,22 +595,70 @@ static int dcpaud_probe(struct platform_device *pdev)
 	return ret;
 }
 
-static int dcpaud_remove(struct platform_device *dev)
+static void dcpaud_comp_unbind(struct device *dev, struct device *main,
+			       void *data)
 {
-	struct dcp_audio *dcpaud = platform_get_drvdata(dev);
+	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
 
-	dcp_audiosrv_set_hotplug_cb(dcpaud->pdata->dcp_dev, NULL, NULL);
-	snd_card_free(dcpaud->card);
+	/* snd_card_free_when_closed() checks for NULL */
+	snd_card_free_when_closed(dcpaud->card);
+}
 
-	return 0;
+static const struct component_ops dcpaud_comp_ops = {
+	.bind	= dcpaud_comp_bind,
+	.unbind	= dcpaud_comp_unbind,
+};
+
+static int dcpaud_probe(struct platform_device *pdev)
+{
+	struct dcp_audio *dcpaud;
+
+	dcpaud = devm_kzalloc(&pdev->dev, sizeof(*dcpaud), GFP_KERNEL);
+	if (!dcpaud)
+		return -ENOMEM;
+
+	dcpaud->elements = devm_kzalloc(&pdev->dev, DCPAUD_ELEMENTS_MAXSIZE,
+					GFP_KERNEL);
+	if (!dcpaud->elements)
+		return -ENOMEM;
+
+	dcpaud->productattrs = devm_kzalloc(&pdev->dev, DCPAUD_PRODUCTATTRS_MAXSIZE,
+					    GFP_KERNEL);
+	if (!dcpaud->productattrs)
+		return -ENOMEM;
+
+	dcpaud->dev = &pdev->dev;
+	mutex_init(&dcpaud->data_lock);
+	platform_set_drvdata(pdev, dcpaud);
+
+	return component_add(&pdev->dev, &dcpaud_comp_ops);
 }
 
+static void dcpaud_remove(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcpaud_comp_ops);
+}
+
+static void dcpaud_shutdown(struct platform_device *pdev)
+{
+	component_del(&pdev->dev, &dcpaud_comp_ops);
+}
+
+// static DEFINE_SIMPLE_DEV_PM_OPS(dcpaud_pm_ops, dcpaud_suspend, dcpaud_resume);
+
+static const struct of_device_id dcpaud_of_match[] = {
+	{ .compatible = "apple,dpaudio" },
+	{}
+};
+
 static struct platform_driver dcpaud_driver = {
 	.driver = {
-		.name = DRV_NAME,
+		.name = "dcp-dp-audio",
+		.of_match_table      = dcpaud_of_match,
 	},
-	.probe = dcpaud_probe,
-	.remove = dcpaud_remove,
+	.probe		= dcpaud_probe,
+	.remove		= dcpaud_remove,
+	.shutdown	= dcpaud_shutdown,
 };
 
 void __init dcp_audio_register(void)
diff --git a/drivers/gpu/drm/apple/audio.h b/drivers/gpu/drm/apple/audio.h
index 3cf4d31417694e..83b990dc6c343f 100644
--- a/drivers/gpu/drm/apple/audio.h
+++ b/drivers/gpu/drm/apple/audio.h
@@ -4,18 +4,9 @@
 #include <linux/types.h>
 
 struct device;
-struct device_node;
+struct platform_device;
 struct dcp_sound_cookie;
 
-typedef void (*dcp_audio_hotplug_callback)(struct device *dev, bool connected);
-
-struct dcp_audio_pdata {
-	struct device *dcp_dev;
-	struct device_node *dpaudio_node;
-};
-
-void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev,
-								 dcp_audio_hotplug_callback cb);
 int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie);
 int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie);
 int dcp_audiosrv_stoplink(struct device *dev);
@@ -23,4 +14,7 @@ int dcp_audiosrv_unprepare(struct device *dev);
 int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize);
 int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize);
 
+void dcpaud_connect(struct platform_device *pdev, bool connected);
+void dcpaud_disconnect(struct platform_device *pdev);
+
 #endif /* __AUDIO_H__ */
diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index 926c4b238227b1..66a99cb2ed7b0f 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -5,6 +5,8 @@
 
 #include <linux/debugfs.h>
 #include <linux/kconfig.h>
+#include <linux/of_graph.h>
+#include <linux/of_platform.h>
 #include <linux/rwsem.h>
 #include <linux/types.h>
 #include <linux/workqueue.h>
@@ -47,8 +49,7 @@ static const struct dcp_av_audio_cmds dcp_av_audio_cmds_v13_5 = {
 };
 
 struct audiosrv_data {
-	struct device *audio_dev;
-	dcp_audio_hotplug_callback hotplug_cb;
+	struct platform_device *audio_dev;
 	bool plugged;
 	struct mutex plug_lock;
 
@@ -94,28 +95,12 @@ static void av_audiosrv_teardown(struct apple_epic_service *service)
 	up_write(&asrv->srv_rwsem);
 
 	asrv->plugged = false;
-	if (asrv->hotplug_cb)
-		asrv->hotplug_cb(asrv->audio_dev, false);
+	if (asrv->audio_dev)
+		dcpaud_disconnect(asrv->audio_dev);
 
 	mutex_unlock(&asrv->plug_lock);
 }
 
-void dcp_audiosrv_set_hotplug_cb(struct device *dev, struct device *audio_dev,
-								 dcp_audio_hotplug_callback cb)
-{
-	struct apple_dcp *dcp = dev_get_drvdata(dev);
-	struct audiosrv_data *asrv = dcp->audiosrv;
-
-	mutex_lock(&asrv->plug_lock);
-	asrv->audio_dev = audio_dev;
-	asrv->hotplug_cb = cb;
-
-	if (cb)
-		cb(audio_dev, asrv->plugged);
-	mutex_unlock(&asrv->plug_lock);
-}
-EXPORT_SYMBOL_GPL(dcp_audiosrv_set_hotplug_cb);
-
 int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie)
 {
 	struct apple_dcp *dcp = dev_get_drvdata(dev);
@@ -130,7 +115,6 @@ int dcp_audiosrv_prepare(struct device *dev, struct dcp_sound_cookie *cookie)
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_prepare);
 
 int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie)
 {
@@ -146,7 +130,6 @@ int dcp_audiosrv_startlink(struct device *dev, struct dcp_sound_cookie *cookie)
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_startlink);
 
 int dcp_audiosrv_stoplink(struct device *dev)
 {
@@ -161,7 +144,6 @@ int dcp_audiosrv_stoplink(struct device *dev)
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_stoplink);
 
 int dcp_audiosrv_unprepare(struct device *dev)
 {
@@ -176,7 +158,6 @@ int dcp_audiosrv_unprepare(struct device *dev)
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_unprepare);
 
 static int
 dcp_audiosrv_osobject_call(struct apple_epic_service *service, u16 group,
@@ -233,7 +214,6 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_get_elements);
 
 int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsize)
 {
@@ -255,7 +235,6 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi
 
 	return ret;
 }
-EXPORT_SYMBOL_GPL(dcp_audiosrv_get_product_attrs);
 
 static int av_audiosrv_report(struct apple_epic_service *service, u32 idx,
 						  const void *data, size_t data_size)
@@ -307,22 +286,20 @@ static void av_work_service_start(struct work_struct *work)
 	}
 
 	mutex_lock(&dcp->audiosrv->plug_lock);
-	if (dcp->audiosrv->hotplug_cb)
-		dcp->audiosrv->hotplug_cb(dcp->audiosrv->audio_dev,
-					  dcp->audiosrv->plugged);
+	if (dcp->audiosrv->audio_dev)
+		dcpaud_connect(dcp->audiosrv->audio_dev, dcp->audiosrv->plugged);
 	mutex_unlock(&dcp->audiosrv->plug_lock);
 }
 
 int avep_init(struct apple_dcp *dcp)
 {
-	struct dcp_audio_pdata *audio_pdata;
-	struct platform_device *audio_pdev;
 	struct audiosrv_data *audiosrv_data;
+	struct platform_device *audio_pdev;
 	struct device *dev = dcp->dev;
+	struct device_node *endpoint, *audio_node = NULL;
 
 	audiosrv_data = devm_kzalloc(dcp->dev, sizeof(*audiosrv_data), GFP_KERNEL);
-	audio_pdata = devm_kzalloc(dcp->dev, sizeof(*audio_pdata), GFP_KERNEL);
-	if (!audiosrv_data || !audio_pdata)
+	if (!audiosrv_data)
 		return -ENOMEM;
 	init_rwsem(&audiosrv_data->srv_rwsem);
 	mutex_init(&audiosrv_data->plug_lock);
@@ -342,21 +319,27 @@ int avep_init(struct apple_dcp *dcp)
 
 	dcp->audiosrv = audiosrv_data;
 
-	audio_pdata->dcp_dev = dcp->dev;
-	/* TODO: free OF reference */
-	audio_pdata->dpaudio_node = \
-			of_parse_phandle(dev->of_node, "apple,audio-xmitter", 0);
-	if (!audio_pdata->dpaudio_node ||
-	    !of_device_is_available(audio_pdata->dpaudio_node)) {
+	endpoint = of_graph_get_endpoint_by_regs(dev->of_node, 0, 0);
+	if (endpoint) {
+		audio_node = of_graph_get_remote_port_parent(endpoint);
+		of_node_put(endpoint);
+	}
+	if (!audio_node || !of_device_is_available(audio_node)) {
+		of_node_put(audio_node);
 		dev_info(dev, "No audio support\n");
 		return 0;
 	}
 
-	audio_pdev = platform_device_register_data(dev, "dcp-hdmi-audio",
-						   PLATFORM_DEVID_AUTO,
-						   audio_pdata, sizeof(*audio_pdata));
-	if (IS_ERR(audio_pdev))
-		return dev_err_probe(dev, PTR_ERR(audio_pdev), "registering audio device\n");
+	audio_pdev = of_find_device_by_node(audio_node);
+	of_node_put(audio_node);
+	if (!audio_pdev) {
+		dev_info(dev, "No DP/HDMI audio device not ready\n");
+		return 0;
+	}
+	dcp->audiosrv->audio_dev = audio_pdev;
+
+	device_link_add(&audio_pdev->dev, dev,
+			DL_FLAG_STATELESS | DL_FLAG_PM_RUNTIME);
 
 	dcp->avep = afk_init(dcp, AV_ENDPOINT, avep_ops);
 	if (IS_ERR(dcp->avep))

From 532a9abaf6db3bbbc37a994d82f1b7cd1df20ff4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 22 Apr 2024 19:47:22 +0200
Subject: [PATCH 0798/1027] drm: apple: audio: Avoid probe errors

Now that the DP audio driver is a component of the display sub-system
probe errors will bring down the whole display initialization.
To prevent that the audio driver must not fail. Allow delayed sound card
initialization if the DMA controller is not ready, for example because
the apple-sio module is missing (at all or just in the initeramfs).
In the case apple-sio is available later provide as sysfs file
"probe_snd_card" to trigger initialization.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/audio.c | 147 ++++++++++++++++++++++++----------
 1 file changed, 105 insertions(+), 42 deletions(-)

diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index b4a860d198c32b..9266af8038083d 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -42,6 +42,7 @@ struct dcp_audio {
 	unsigned int open_cookie;
 
 	struct mutex data_lock;
+	bool dcp_connected; /// dcp status keep for delayed initialization
 	bool connected;
 	unsigned int connection_cookie;
 
@@ -430,19 +431,8 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 {
 	struct snd_card *card = dcpaud->card;
 	struct snd_pcm *pcm;
-	struct dma_chan *chan;
 	int ret;
 
-	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
-	if (IS_ERR_OR_NULL(chan)) {
-		if (!chan)
-			return -EINVAL;
-
-		dev_err(dcpaud->dev, "can't request audio TX DMA channel: %pE\n", chan);
-		return PTR_ERR(chan);
-	}
-	dcpaud->chan = chan;
-
 #define NUM_PLAYBACK 1
 #define NUM_CAPTURE 0
 
@@ -453,7 +443,7 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops);
 	dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
 	snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM,
-				   chan->device->dev, 1024 * 1024,
+				   dcpaud->chan->device->dev, 1024 * 1024,
 				   SIZE_MAX);
 
 	pcm->nonatomic = true;
@@ -463,12 +453,12 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 	return 0;
 }
 
+/* expects to be called with data_lock locked and unlocks it */
 static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected)
 {
 	struct snd_pcm_substream *substream = dcpaud->substream;
 
-	mutex_lock(&dcpaud->data_lock);
-	if (dcpaud->connected == connected) {
+	if (!dcpaud->card || dcpaud->connected == connected) {
 		mutex_unlock(&dcpaud->data_lock);
 		return;
 	}
@@ -504,6 +494,53 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud)
 	strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname));
 }
 
+static int dcpaud_init_snd_card(struct dcp_audio *dcpaud)
+{
+	int ret;
+	struct dma_chan *chan;
+
+	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
+	/* squelch dma channel request errors, the driver will try again alter */
+	if (!chan) {
+		dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n");
+		return 0;
+	} else if (IS_ERR(chan)) {
+		dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %pE\n", chan);
+		return 0;
+	}
+	dcpaud->chan = chan;
+
+	ret = snd_card_new(dcpaud->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
+			   THIS_MODULE, 0, &dcpaud->card);
+	if (ret)
+		return ret;
+
+	dcpaud_set_card_names(dcpaud);
+
+	ret = dcpaud_create_pcm(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = dcpaud_create_chmap_ctl(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = dcpaud_create_jack(dcpaud);
+	if (ret)
+		goto err_free_card;
+
+	ret = snd_card_register(dcpaud->card);
+	if (ret)
+		goto err_free_card;
+
+	return 0;
+err_free_card:
+	dev_warn(dcpaud->dev, "Failed to initialize sound card: %d\n", ret);
+	snd_card_free(dcpaud->card);
+	dcpaud->card = NULL;
+	return ret;
+}
+
 #ifdef CONFIG_SND_DEBUG
 static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size)
 {
@@ -522,15 +559,59 @@ static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *nam
 void dcpaud_connect(struct platform_device *pdev, bool connected)
 {
 	struct dcp_audio *dcpaud = platform_get_drvdata(pdev);
+
+	mutex_lock(&dcpaud->data_lock);
+
+	if (!dcpaud->chan) {
+		int ret = dcpaud_init_snd_card(dcpaud);
+		if (ret) {
+			dcpaud->dcp_connected = connected;
+			mutex_unlock(&dcpaud->data_lock);
+			return;
+		}
+	}
 	dcpaud_report_hotplug(dcpaud, connected);
 }
 
 void dcpaud_disconnect(struct platform_device *pdev)
 {
 	struct dcp_audio *dcpaud = platform_get_drvdata(pdev);
+
+	mutex_lock(&dcpaud->data_lock);
+
+	dcpaud->dcp_connected = false;
 	dcpaud_report_hotplug(dcpaud, false);
 }
 
+static ssize_t probe_snd_card_store(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	int ret;
+	bool connected = false;
+	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
+
+	mutex_lock(&dcpaud->data_lock);
+
+	if (!dcpaud->chan) {
+		ret = dcpaud_init_snd_card(dcpaud);
+		if (ret)
+			goto out_unlock;
+
+		connected = dcpaud->dcp_connected;
+		if (connected) {
+			dcpaud_report_hotplug(dcpaud, connected);
+			goto out;
+		}
+	}
+out_unlock:
+	mutex_unlock(&dcpaud->data_lock);
+out:
+	return count;
+}
+
+static const DEVICE_ATTR_WO(probe_snd_card);
+
 static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 {
 	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
@@ -553,34 +634,11 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 	dcp_pdev = of_find_device_by_node(dcp_node);
 	of_node_put(dcp_node);
 	if (!dcp_pdev) {
-		dev_info(dev, "No DP/HDMI audio device not ready\n");
+		dev_info(dev, "No DP/HDMI audio device, dcp not ready\n");
 		return 0;
 	}
 	dcpaud->dcp_dev = &dcp_pdev->dev;
 
-	ret = snd_card_new(dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
-			   THIS_MODULE, 0, &dcpaud->card);
-	if (ret)
-		return ret;
-
-	dcpaud_set_card_names(dcpaud);
-
-	ret = dcpaud_create_pcm(dcpaud);
-	if (ret)
-		goto err_free_card;
-
-	ret = dcpaud_create_chmap_ctl(dcpaud);
-	if (ret)
-		goto err_free_card;
-
-	ret = dcpaud_create_jack(dcpaud);
-	if (ret)
-		goto err_free_card;
-
-	ret = snd_card_register(dcpaud->card);
-	if (ret)
-		goto err_free_card;
-
 	dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie,
 				   sizeof(dcpaud->selected_cookie));
 	dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements,
@@ -588,11 +646,16 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 	dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs,
 				   DCPAUD_PRODUCTATTRS_MAXSIZE);
 
-	return 0;
+	mutex_lock(&dcpaud->data_lock);
+	/* ignore errors to prevent audio issues affecting the display side */
+	dcpaud_init_snd_card(dcpaud);
+	mutex_unlock(&dcpaud->data_lock);
 
-err_free_card:
-	snd_card_free(dcpaud->card);
-	return ret;
+	ret = device_create_file(dev, &dev_attr_probe_snd_card);
+        if (ret)
+		dev_info(dev, "creating force probe sysfs file failed: %d\n", ret);
+
+	return 0;
 }
 
 static void dcpaud_comp_unbind(struct device *dev, struct device *main,

From 75bd6f6d7758e64d1d5c2b7d42eb3dafcbf97826 Mon Sep 17 00:00:00 2001
From: Jonathan Gray <jsg@jsg.id.au>
Date: Sun, 21 Apr 2024 11:15:04 +1000
Subject: [PATCH 0799/1027] drm/apple: fix double words in comments

Signed-off-by: Jonathan Gray <jsg@jsg.id.au>
---
 drivers/gpu/drm/apple/afk.c          | 2 +-
 drivers/gpu/drm/apple/dcp-internal.h | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index 218c28dfe84249..d3e45a6180af69 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -641,7 +641,7 @@ static bool afk_recv(struct apple_dcp_afkep *ep)
 	 * TODO: this is theoretically unsafe since DCP could overwrite data
 	 *       after the read pointer was updated above. Do it anyway since
 	 *       it avoids 2 problems in the DCP tracer:
-	 *       1. the tracer sees replies before the the notifies from dcp
+	 *       1. the tracer sees replies before the notifies from dcp
 	 *       2. the tracer tries to read buffers after they are unmapped.
 	 */
 	afk_recv_handle(ep, channel, type, hdr->data, size);
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 4c352fd7791dec..8f1a55279597b3 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -128,7 +128,7 @@ struct apple_dcp {
 
 	/************* IOMFB **************************************************
 	 * everything below is mostly used inside IOMFB but it could make     *
-	 * sense keep some of the the members in apple_dcp.                   *
+	 * sense to keep some of the members in apple_dcp.                    *
 	 **********************************************************************/
 
 	/* clock rate request by dcp in */
@@ -212,7 +212,7 @@ struct apple_dcp {
 	struct list_head swapped_out_fbs;
 
 	struct dcp_brightness brightness;
-	/* Workqueue for updating the initial initial brightness */
+	/* Workqueue for updating the initial brightness */
 	struct work_struct bl_register_wq;
 	struct mutex bl_register_mutex;
 	/* Workqueue for updating the brightness */

From e35325b4d1bcaafe60cba046f853b45d0490f4c3 Mon Sep 17 00:00:00 2001
From: Caspar Schutijser <caspar@schutijser.com>
Date: Thu, 18 Apr 2024 22:26:58 +0100
Subject: [PATCH 0800/1027] drm: apple: backlight: release lock in error path

Signed-off-by: Caspar Schutijser <caspar@schutijser.com>
---
 drivers/gpu/drm/apple/dcp_backlight.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp_backlight.c b/drivers/gpu/drm/apple/dcp_backlight.c
index ed3b240ead8557..1397000c27935c 100644
--- a/drivers/gpu/drm/apple/dcp_backlight.c
+++ b/drivers/gpu/drm/apple/dcp_backlight.c
@@ -150,8 +150,10 @@ static int drm_crtc_set_brightness(struct apple_dcp *dcp)
 		goto done;
 
 	state = drm_atomic_state_alloc(crtc->dev);
-	if (!state)
-		return -ENOMEM;
+	if (!state) {
+		ret = -ENOMEM;
+		goto done;
+	}
 
 	state->acquire_ctx = &ctx;
 	crtc_state = drm_atomic_get_crtc_state(state, crtc);

From 2adb13024738b0759c54a3a537e36459bee959f5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 27 Apr 2024 17:55:25 +0200
Subject: [PATCH 0801/1027] drm: apple: Switch back to
 drm_atomic_helper_commit_tail_rpm()

The custom commit_tail implementation stopped making after "drm/apple:
Disable fake vblank IRQ machinery" which stopped calling
drm_vblank_init(). Revert back to the standard helper implementation.
Avoids or at least significantly reduces page flips taking approximately
one frame time in kwin_wayland 6.

Fixes: ("drm/apple: Switch to nonblocking commit handling")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/apple_drv.c | 22 +---------------------
 1 file changed, 1 insertion(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index 7abeb4ea5f458e..e1d55c1ee6d503 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -238,26 +238,6 @@ static void apple_crtc_atomic_begin(struct drm_crtc *crtc,
 	}
 }
 
-static void dcp_atomic_commit_tail(struct drm_atomic_state *old_state)
-{
-	struct drm_device *dev = old_state->dev;
-
-	drm_atomic_helper_commit_modeset_disables(dev, old_state);
-
-	drm_atomic_helper_commit_modeset_enables(dev, old_state);
-
-	drm_atomic_helper_commit_planes(dev, old_state,
-					DRM_PLANE_COMMIT_ACTIVE_ONLY);
-
-	drm_atomic_helper_fake_vblank(old_state);
-
-	drm_atomic_helper_commit_hw_done(old_state);
-
-	drm_atomic_helper_wait_for_flip_done(dev, old_state);
-
-	drm_atomic_helper_cleanup_planes(dev, old_state);
-}
-
 static void apple_crtc_cleanup(struct drm_crtc *crtc)
 {
 	drm_crtc_cleanup(crtc);
@@ -280,7 +260,7 @@ static const struct drm_mode_config_funcs apple_mode_config_funcs = {
 };
 
 static const struct drm_mode_config_helper_funcs apple_mode_config_helpers = {
-	.atomic_commit_tail	= dcp_atomic_commit_tail,
+	.atomic_commit_tail	= drm_atomic_helper_commit_tail_rpm,
 };
 
 static void appledrm_connector_cleanup(struct drm_connector *connector)

From a515c2833fe1f461189cc4fae086d8473a593ccc Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 May 2024 13:09:15 +0200
Subject: [PATCH 0802/1027] drm: apple: Fix broken
 MemDescRelay::release_descriptor callback number

Two callbacks for IOMFB::MemDescRelay seems to be dropped between 12.3
and 13.5 DCP firmware. This results in the renumbering of
MemDescRelay::release_descriptor from D456 to D454.
Noticed while when switching the display refresh rate to 50 Hz with a
14.5 system firmware on a M1 Max Macbook Pro.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_v13_3.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/iomfb_v13_3.c b/drivers/gpu/drm/apple/iomfb_v13_3.c
index 115490fd9cc6e3..0ac869d24eb01b 100644
--- a/drivers/gpu/drm/apple/iomfb_v13_3.c
+++ b/drivers/gpu/drm/apple/iomfb_v13_3.c
@@ -81,7 +81,7 @@ static const iomfb_cb_handler cb_handlers[IOMFB_MAX_CB] = {
 	[415] = trampoline_true, /* sr_set_property_bool */
 	[451] = trampoline_allocate_buffer,
 	[452] = trampoline_map_physical,
-	[456] = trampoline_release_mem_desc,
+	[454] = trampoline_release_mem_desc,
 	[552] = trampoline_true, /* set_property_dict_0 */
 	[561] = trampoline_true, /* set_property_dict */
 	[563] = trampoline_true, /* set_property_int */

From ad98e0cc5c220d6c3b4e32a8ae650b967666f238 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 May 2024 13:23:03 +0200
Subject: [PATCH 0803/1027] drm: apple: Reduce log spam about busy command
 channel

The most likely cause for this is an unexpected callback form which the
current driver doesn't recover. Warn only once about it.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h | 2 ++
 drivers/gpu/drm/apple/iomfb.c        | 7 ++++++-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 8f1a55279597b3..1c82201f6e934c 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -71,6 +71,8 @@ struct dcp_channel {
 
 	/* Current depth of the call stack. Less than DCP_MAX_CALL_DEPTH */
 	u8 depth;
+	/* Already warned about busy channel */
+	bool warned_busy;
 };
 
 struct dcp_fb_reference {
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 9fe8487053efeb..0941ff69873235 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -482,12 +482,17 @@ void dcp_flush(struct drm_crtc *crtc, struct drm_atomic_state *state)
 
 	if (dcp_channel_busy(&dcp->ch_cmd))
 	{
-		dev_err(dcp->dev, "unexpected busy command channel\n");
+		if (!dcp->ch_cmd.warned_busy) {
+			dev_err(dcp->dev, "unexpected busy command channel\n");
+			dcp->ch_cmd.warned_busy = true;
+		}
 		/* HACK: issue a delayed vblank event to avoid timeouts in
 		 * drm_atomic_helper_wait_for_vblanks().
 		 */
 		schedule_work(&dcp->vblank_wq);
 		return;
+	} else if (dcp->ch_cmd.warned_busy) {
+		dcp->ch_cmd.warned_busy = false;
 	}
 
 	switch (dcp->fw_compat) {

From bd73d466248892aa06eb8c4fca8f83103b607faf Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 4 May 2024 13:33:57 +0200
Subject: [PATCH 0804/1027] drm: apple: av: Warn only once about failed calls

Reduce log spam while errors are still likely due missing state checks.
---
 drivers/gpu/drm/apple/av.c | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index 66a99cb2ed7b0f..8a2c1126f5adea 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -59,6 +59,9 @@ struct audiosrv_data {
 	struct work_struct start_av_service_wq;
 
 	struct dcp_av_audio_cmds cmds;
+
+	bool warned_get_elements;
+	bool warned_get_product_attrs;
 };
 
 static void av_interface_init(struct apple_epic_service *service, const char *name,
@@ -207,10 +210,12 @@ int dcp_audiosrv_get_elements(struct device *dev, void *elements, size_t maxsize
 					 elements, maxsize, &size);
 	up_write(&asrv->srv_rwsem);
 
-	if (ret)
+	if (ret && asrv->warned_get_elements) {
 		dev_err(dev, "audiosrv: error getting elements: %d\n", ret);
-	else
+		asrv->warned_get_elements = true;
+	} else {
 		dev_dbg(dev, "audiosrv: got %zd bytes worth of elements\n", size);
+	}
 
 	return ret;
 }
@@ -228,10 +233,12 @@ int dcp_audiosrv_get_product_attrs(struct device *dev, void *attrs, size_t maxsi
 					 maxsize, &size);
 	up_write(&asrv->srv_rwsem);
 
-	if (ret)
+	if (ret && asrv->warned_get_product_attrs) {
 		dev_err(dev, "audiosrv: error getting product attributes: %d\n", ret);
-	else
+		asrv->warned_get_product_attrs = true;
+	} else {
 		dev_dbg(dev, "audiosrv: got %zd bytes worth of product attributes\n", size);
+	}
 
 	return ret;
 }

From 1e4965f1c3636ec44b99d14d584a8686d43532b5 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 8 May 2024 16:55:11 +0200
Subject: [PATCH 0805/1027] drm: apple: disable HDMI audio by default

Can be still enabled by adding `apple_dcp.hdmi_audio` the kernel command
line.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/audio.c | 5 +++++
 drivers/gpu/drm/apple/dcp.c   | 8 ++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index 9266af8038083d..923f5421298305 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -494,11 +494,16 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud)
 	strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname));
 }
 
+extern bool hdmi_audio;
+
 static int dcpaud_init_snd_card(struct dcp_audio *dcpaud)
 {
 	int ret;
 	struct dma_chan *chan;
 
+	if (!hdmi_audio)
+		return -ENODEV;
+
 	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
 	/* squelch dma channel request errors, the driver will try again alter */
 	if (!chan) {
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 8fcac8e8176674..aae81446ccc52e 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -46,9 +46,9 @@ static bool show_notch;
 module_param(show_notch, bool, 0644);
 MODULE_PARM_DESC(show_notch, "Use the full display height and shows the notch");
 
-static bool noaudio;
-module_param(noaudio, bool, 0644);
-MODULE_PARM_DESC(noaudio, "Skip audio support");
+bool hdmi_audio;
+module_param(hdmi_audio, bool, 0644);
+MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support");
 
 /* HACK: moved here to avoid circular dependency between apple_drv and dcp */
 void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
@@ -413,7 +413,7 @@ int dcp_start(struct platform_device *pdev)
 		dev_err(dcp->dev, "Failed to start IOMFB endpoint: %d\n", ret);
 
 #if IS_ENABLED(CONFIG_DRM_APPLE_AUDIO)
-	if (!noaudio) {
+	if (hdmi_audio) {
 		ret = avep_init(dcp);
 		if (ret)
 			dev_warn(dcp->dev, "Failed to start AV endpoint: %d", ret);

From a94de16782d36db0070dce8c234f72e73e5b09ee Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 12 May 2024 09:39:59 +0200
Subject: [PATCH 0806/1027] drm: apple: Override drm_vblank's page flip event
 handling [HACK]

Since we don't init/uses drm's vblank support our page flip timestamps
are CLOCK_MONOTONIC timestamps during the event generation. Since
compositors use the timestamp to schedule their next kms commit this is
timing sensitive sop move it under the drivers control.
Take the timestamp directly in the swap_complete callback.
Framebuffer swaps are unfortunately not fast with DCP. Measured time
from swap_submit to swap_complete is ~1.5 ms for dcp and ~2.3 ms for
dcpext. This warrants further investigation. Presentation timestamps
might help if delay on dcp firmware side occurs after the actual swap.
In the meantime doctor the time stamps and move the page flip completion
up to 1 ms earler. This fixes half rate refresh on external displays
displays using dcpext.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp-internal.h   |  3 +
 drivers/gpu/drm/apple/dcp.c            | 87 ++++++++++++++++++++++++++
 drivers/gpu/drm/apple/iomfb_template.c |  4 +-
 3 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 1c82201f6e934c..1a465138104d57 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -175,6 +175,7 @@ struct apple_dcp {
 
 	/* swap id of the last completed swap */
 	u32 last_swap_id;
+	ktime_t swap_start;
 
 	/* Current display mode */
 	bool during_modeset;
@@ -253,6 +254,8 @@ struct apple_dcp {
 	int hdmi_hpd_irq;
 };
 
+void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now);
+
 int dcp_backlight_register(struct apple_dcp *dcp);
 int dcp_backlight_update(struct apple_dcp *dcp);
 bool dcp_has_panel(struct apple_dcp *dcp);
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index aae81446ccc52e..68f9f42e066546 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -50,6 +50,76 @@ bool hdmi_audio;
 module_param(hdmi_audio, bool, 0644);
 MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support");
 
+/* copied and simplified from drm_vblank.c */
+static void send_vblank_event(struct drm_device *dev,
+		struct drm_pending_vblank_event *e,
+		u64 seq, ktime_t now)
+{
+	struct timespec64 tv;
+
+	if (e->event.base.type != DRM_EVENT_FLIP_COMPLETE)
+		return;
+
+	tv = ktime_to_timespec64(now);
+	e->event.vbl.sequence = seq;
+	/*
+		* e->event is a user space structure, with hardcoded unsigned
+		* 32-bit seconds/microseconds. This is safe as we always use
+		* monotonic timestamps since linux-4.15
+		*/
+	e->event.vbl.tv_sec = tv.tv_sec;
+	e->event.vbl.tv_usec = tv.tv_nsec / 1000;
+
+	/*
+	 * Use the same timestamp for any associated fence signal to avoid
+	 * mismatch in timestamps for vsync & fence events triggered by the
+	 * same HW event. Frameworks like SurfaceFlinger in Android expects the
+	 * retire-fence timestamp to match exactly with HW vsync as it uses it
+	 * for its software vsync modeling.
+	 */
+	drm_send_event_timestamp_locked(dev, &e->base, now);
+}
+
+/**
+ * dcp_crtc_send_page_flip_event - helper to send vblank event after pageflip
+ *
+ * Compensate for unknown slack between page flip and arrival of the
+ * swap_complete callback. Minimal observed duration on DCP with HDMI output
+ * was around 2.3 ms. If the fb swap was submitted closer to the expected
+ * swap_complete it gets a penalty of one frame duration. This is on the border
+ * of unreasonable considering that Apple advertises support for 240 Hz (frame
+ * duration of 4.167 ms).
+ * It is unreasonable considering kwin's kms commit scheduling. Kwin commits
+ * 1.5 ms + the mode's vblank time before the expected next page flip
+ * completion. This results in presenting at half the display's rate for HDMI
+ * outputs.
+ * This might be a difference between dcp and dcpext.
+ */
+static void dcp_crtc_send_page_flip_event(struct apple_crtc *crtc,
+					  struct drm_pending_vblank_event *e,
+					  ktime_t now, ktime_t start)
+{
+	struct drm_device *dev = crtc->base.dev;
+	u64 seq;
+	unsigned int pipe = drm_crtc_index(&crtc->base);
+	ktime_t flip;
+
+	seq = 0;
+	if (start != KTIME_MIN) {
+		s64 delta = ktime_us_delta(now, start);
+		if (delta <= 500)
+			flip = now;
+		else if (delta >= 2500)
+			flip = ktime_sub_us(now, 1000);
+		else
+			flip = ktime_sub_us(now, (delta - 500) / 2);
+	} else {
+		flip = now;
+	}
+	e->pipe = pipe;
+	send_vblank_event(dev, e, seq, flip);
+}
+
 /* HACK: moved here to avoid circular dependency between apple_drv and dcp */
 void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 {
@@ -63,6 +133,23 @@ void dcp_drm_crtc_vblank(struct apple_crtc *crtc)
 	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
 }
 
+void dcp_drm_crtc_page_flip(struct apple_dcp *dcp, ktime_t now)
+{
+	unsigned long flags;
+	struct apple_crtc *crtc = dcp->crtc;
+
+	spin_lock_irqsave(&crtc->base.dev->event_lock, flags);
+	if (crtc->event) {
+		if (crtc->event->event.base.type == DRM_EVENT_FLIP_COMPLETE)
+			dcp_crtc_send_page_flip_event(crtc, crtc->event, now, dcp->swap_start);
+		else
+			drm_crtc_send_vblank_event(&crtc->base, crtc->event);
+		crtc->event = NULL;
+		dcp->swap_start = KTIME_MIN;
+	}
+	spin_unlock_irqrestore(&crtc->base.dev->event_lock, flags);
+}
+
 void dcp_set_dimensions(struct apple_dcp *dcp)
 {
 	int i;
diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index d74f2b502821a1..bc3df16ebac542 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -120,10 +120,11 @@ static u32 dcpep_cb_zero(struct apple_dcp *dcp)
 static void dcpep_cb_swap_complete(struct apple_dcp *dcp,
 				   struct DCP_FW_NAME(dc_swap_complete_resp) *resp)
 {
+	ktime_t now = ktime_get();
 	trace_iomfb_swap_complete(dcp, resp->swap_id);
 	dcp->last_swap_id = resp->swap_id;
 
-	dcp_drm_crtc_vblank(dcp->crtc);
+	dcp_drm_crtc_page_flip(dcp, now);
 }
 
 /* special */
@@ -1124,6 +1125,7 @@ static void dcp_swapped(struct apple_dcp *dcp, void *data, void *cookie)
 		dcp_drm_crtc_vblank(dcp->crtc);
 		return;
 	}
+	dcp->swap_start = ktime_get();
 
 	while (!list_empty(&dcp->swapped_out_fbs)) {
 		struct dcp_fb_reference *entry;

From f5a3f9ad7adffa7cdc36045022d5d57a25e5e8ac Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 14:29:48 +0900
Subject: [PATCH 0807/1027] drm/apple: Explicitly stop AFK endpoints on
 shutdown

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/afk.c | 13 +++++++++++++
 drivers/gpu/drm/apple/afk.h |  1 +
 drivers/gpu/drm/apple/dcp.c | 31 ++++++++++++++++++++++++++++++-
 3 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index d3e45a6180af69..83afb51883048f 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -86,6 +86,19 @@ struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
 	return ERR_PTR(ret);
 }
 
+void afk_shutdown(struct apple_dcp_afkep *afkep)
+{
+	afk_send(afkep, FIELD_PREP(RBEP_TYPE, RBEP_SHUTDOWN));
+	int ret;
+
+	ret = wait_for_completion_timeout(&afkep->stopped, msecs_to_jiffies(1000));
+	if (ret <= 0) {
+		dev_err(afkep->dcp->dev, "Timed out shutting down AFK endpoint %02x", afkep->endpoint);
+	}
+
+	destroy_workqueue(afkep->wq);
+}
+
 int afk_start(struct apple_dcp_afkep *ep)
 {
 	int ret;
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index 0f91f32e08e301..be3f0b105de581 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -187,6 +187,7 @@ struct apple_dcp_afkep {
 struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,
 				 const struct apple_epic_service_ops *ops);
 int afk_start(struct apple_dcp_afkep *ep);
+void afk_shutdown(struct apple_dcp_afkep *ep);
 int afk_receive_message(struct apple_dcp_afkep *ep, u64 message);
 int afk_send_epic(struct apple_dcp_afkep *ep, u32 channel, u16 tag,
 		  enum epic_type etype, enum epic_category ecat, u8 stype,
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 68f9f42e066546..4519cb6e2a7504 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1022,10 +1022,33 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 {
 	struct apple_dcp *dcp = dev_get_drvdata(dev);
 
+	if (!dcp)
+		return;
+
 	if (dcp->hdmi_hpd_irq)
 		disable_irq(dcp->hdmi_hpd_irq);
 
-	if (dcp && dcp->shmem)
+	if (dcp->avep) {
+		afk_shutdown(dcp->avep);
+		dcp->avep = NULL;
+	}
+
+	if (dcp->dptxep) {
+		afk_shutdown(dcp->dptxep);
+		dcp->dptxep = NULL;
+	}
+
+	if (dcp->ibootep) {
+		afk_shutdown(dcp->ibootep);
+		dcp->ibootep = NULL;
+	}
+
+	if (dcp->systemep) {
+		afk_shutdown(dcp->systemep);
+		dcp->systemep = NULL;
+	}
+
+	if (dcp->shmem)
 		iomfb_shutdown(dcp);
 
 	if (dcp->piodma) {
@@ -1038,6 +1061,12 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 		dcp->piodma = NULL;
 	}
 
+	if (dcp->connector_type == DRM_MODE_CONNECTOR_eDP) {
+		cancel_work_sync(&dcp->bl_register_wq);
+		cancel_work_sync(&dcp->bl_update_wq);
+	}
+	cancel_work_sync(&dcp->vblank_wq);
+
 	devm_clk_put(dev, dcp->clk);
 	dcp->clk = NULL;
 }

From 894159a99df26c90d79b5e93499b3f8c9b877aac Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 19:51:12 +0900
Subject: [PATCH 0808/1027] drm/apple: audio: Create a device link to the DMA
 device

This works even before the DMA device probes. Might help deal with
runtime-pm ordering, though it doesn't solve the deferred ordering
problem (since we're creating the link while already probing)...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/audio.c | 67 ++++++++++++++++-------------------
 1 file changed, 31 insertions(+), 36 deletions(-)

diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index 923f5421298305..231309a1305f70 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -35,6 +35,8 @@
 struct dcp_audio {
 	struct device *dev;
 	struct device *dcp_dev;
+	struct device *dma_dev;
+	struct device_link *dma_link;
 	struct dma_chan *chan;
 	struct snd_card *card;
 	struct snd_jack *jack;
@@ -588,40 +590,13 @@ void dcpaud_disconnect(struct platform_device *pdev)
 	dcpaud_report_hotplug(dcpaud, false);
 }
 
-static ssize_t probe_snd_card_store(struct device *dev,
-				    struct device_attribute *attr,
-				    const char *buf, size_t count)
-{
-	int ret;
-	bool connected = false;
-	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
-
-	mutex_lock(&dcpaud->data_lock);
-
-	if (!dcpaud->chan) {
-		ret = dcpaud_init_snd_card(dcpaud);
-		if (ret)
-			goto out_unlock;
-
-		connected = dcpaud->dcp_connected;
-		if (connected) {
-			dcpaud_report_hotplug(dcpaud, connected);
-			goto out;
-		}
-	}
-out_unlock:
-	mutex_unlock(&dcpaud->data_lock);
-out:
-	return count;
-}
-
-static const DEVICE_ATTR_WO(probe_snd_card);
-
 static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 {
 	struct dcp_audio *dcpaud = dev_get_drvdata(dev);
 	struct device_node *endpoint, *dcp_node = NULL;
-	struct platform_device *dcp_pdev;
+	struct platform_device *dcp_pdev, *dma_pdev;
+	struct of_phandle_args dma_spec;
+	int index;
 	int ret;
 
 	/* find linked DCP instance */
@@ -636,6 +611,18 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 		return 0;
 	}
 
+	index = of_property_match_string(dev->of_node, "dma-names", "tx");
+	if (index < 0) {
+		dev_err(dev, "No dma-names property\n");
+		return 0;
+	}
+
+	if (of_parse_phandle_with_args(dev->of_node, "dmas", "#dma-cells", index,
+				       &dma_spec) || !dma_spec.np) {
+		dev_err(dev, "Failed to parse dmas property\n");
+		return 0;
+	}
+
 	dcp_pdev = of_find_device_by_node(dcp_node);
 	of_node_put(dcp_node);
 	if (!dcp_pdev) {
@@ -644,12 +631,17 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 	}
 	dcpaud->dcp_dev = &dcp_pdev->dev;
 
-	dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie,
-				   sizeof(dcpaud->selected_cookie));
-	dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements,
-				   DCPAUD_ELEMENTS_MAXSIZE);
-	dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs,
-				   DCPAUD_PRODUCTATTRS_MAXSIZE);
+
+	dma_pdev = of_find_device_by_node(dma_spec.np);
+	of_node_put(dma_spec.np);
+	if (!dma_pdev) {
+		dev_info(dev, "No DMA device\n");
+		return 0;
+	}
+	dcpaud->dma_dev = &dma_pdev->dev;
+
+	dcpaud->dma_link = device_link_add(dev, dcpaud->dma_dev, DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE |
+					   DL_FLAG_STATELESS);
 
 	mutex_lock(&dcpaud->data_lock);
 	/* ignore errors to prevent audio issues affecting the display side */
@@ -670,6 +662,9 @@ static void dcpaud_comp_unbind(struct device *dev, struct device *main,
 
 	/* snd_card_free_when_closed() checks for NULL */
 	snd_card_free_when_closed(dcpaud->card);
+
+	if (dcpaud->dma_link)
+		device_link_del(dcpaud->dma_link);
 }
 
 static const struct component_ops dcpaud_comp_ops = {

From ebb8a122f9e60a28564c289bf71b643c179f1e9d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 19:52:16 +0900
Subject: [PATCH 0809/1027] drm/apple: audio: Defer DMA channel acquisition to
 device open

Allow the DMA device driver to probe late, and still create the sound
device upfront. Instead try to request the DMA channel on first PCM
open. This should be safe as long as we bail early and don't allow the
process to continue to configuring buffers (since that requires the DMA
to be configured).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/audio.c | 105 ++++++++++++++++++----------------
 1 file changed, 57 insertions(+), 48 deletions(-)

diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index 231309a1305f70..f955efef48430d 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -212,10 +212,36 @@ static int dcpaud_rule_rate(struct snd_pcm_hw_params *params,
         return snd_interval_rate_bits(r, hits.rates);
 }
 
+static int dcpaud_init_dma(struct dcp_audio *dcpaud)
+{
+	struct dma_chan *chan;
+	if (dcpaud->chan)
+		return 0;
+
+	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
+	/* squelch dma channel request errors, the driver will try again alter */
+	if (!chan) {
+		dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n");
+		return -ENXIO;
+	} else if (chan == ERR_PTR(-EPROBE_DEFER)) {
+		dev_info(dcpaud->dev, "audio TX DMA channel is not ready yet\n");
+		return -ENXIO;
+	} else if (IS_ERR(chan)) {
+		dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %ld\n", PTR_ERR(chan));
+		return PTR_ERR(chan);
+	}
+	dcpaud->chan = chan;
+
+	snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM,
+				   dcpaud->chan->device->dev, 1024 * 1024,
+				   SIZE_MAX);
+
+	return 0;
+}
+
 static int dcp_pcm_open(struct snd_pcm_substream *substream)
 {
 	struct dcp_audio *dcpaud = substream->pcm->private_data;
-	struct dma_chan *chan = dcpaud->chan;
 	struct snd_dmaengine_dai_dma_data dma_data = {
 		.flags = SND_DMAENGINE_PCM_DAI_FLAG_PACK,
 	};
@@ -223,6 +249,10 @@ static int dcp_pcm_open(struct snd_pcm_substream *substream)
 	int ret;
 
 	mutex_lock(&dcpaud->data_lock);
+	ret = dcpaud_init_dma(dcpaud);
+	if (ret < 0)
+		return ret;
+
 	if (!dcpaud->connected) {
 		mutex_unlock(&dcpaud->data_lock);
 		return -ENXIO;
@@ -254,12 +284,12 @@ static int dcp_pcm_open(struct snd_pcm_substream *substream)
 	hw.buffer_bytes_max = SIZE_MAX;
 	hw.fifo_size = 16;
 	ret = snd_dmaengine_pcm_refine_runtime_hwparams(substream, &dma_data,
-							&hw, chan);
+							&hw, dcpaud->chan);
 	if (ret)
 		return ret;
 	substream->runtime->hw = hw;
 
-	return snd_dmaengine_pcm_open(substream, chan);
+	return snd_dmaengine_pcm_open(substream, dcpaud->chan);
 }
 
 static int dcp_pcm_close(struct snd_pcm_substream *substream)
@@ -444,10 +474,6 @@ static int dcpaud_create_pcm(struct dcp_audio *dcpaud)
 
 	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK, &dcp_playback_ops);
 	dcpaud->substream = pcm->streams[SNDRV_PCM_STREAM_PLAYBACK].substream;
-	snd_pcm_set_managed_buffer(dcpaud->substream, SNDRV_DMA_TYPE_DEV_IRAM,
-				   dcpaud->chan->device->dev, 1024 * 1024,
-				   SIZE_MAX);
-
 	pcm->nonatomic = true;
 	pcm->private_data = dcpaud;
 	strscpy(pcm->name, card->shortname, sizeof(pcm->name));
@@ -496,26 +522,29 @@ static void dcpaud_set_card_names(struct dcp_audio *dcpaud)
 	strscpy(card->shortname, "Apple DisplayPort", sizeof(card->shortname));
 }
 
+#ifdef CONFIG_SND_DEBUG
+static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size)
+{
+	struct debugfs_blob_wrapper *wrapper;
+	wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL);
+	if (!wrapper)
+		return;
+	wrapper->data = base;
+	wrapper->size = size;
+	debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper);
+}
+#else
+static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {}
+#endif
+
 extern bool hdmi_audio;
 
 static int dcpaud_init_snd_card(struct dcp_audio *dcpaud)
 {
 	int ret;
-	struct dma_chan *chan;
-
 	if (!hdmi_audio)
 		return -ENODEV;
 
-	chan = of_dma_request_slave_channel(dcpaud->dev->of_node, "tx");
-	/* squelch dma channel request errors, the driver will try again alter */
-	if (!chan) {
-		dev_warn(dcpaud->dev, "audio TX DMA channel request failed\n");
-		return 0;
-	} else if (IS_ERR(chan)) {
-		dev_warn(dcpaud->dev, "audio TX DMA channel request failed: %pE\n", chan);
-		return 0;
-	}
-	dcpaud->chan = chan;
 
 	ret = snd_card_new(dcpaud->dev, SNDRV_DEFAULT_IDX1, SNDRV_DEFAULT_STR1,
 			   THIS_MODULE, 0, &dcpaud->card);
@@ -548,35 +577,12 @@ static int dcpaud_init_snd_card(struct dcp_audio *dcpaud)
 	return ret;
 }
 
-#ifdef CONFIG_SND_DEBUG
-static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size)
-{
-	struct debugfs_blob_wrapper *wrapper;
-	wrapper = devm_kzalloc(dcpaud->dev, sizeof(*wrapper), GFP_KERNEL);
-	if (!wrapper)
-		return;
-	wrapper->data = base;
-	wrapper->size = size;
-	debugfs_create_blob(name, 0600, dcpaud->card->debugfs_root, wrapper);
-}
-#else
-static void dcpaud_expose_debugfs_blob(struct dcp_audio *dcpaud, const char *name, void *base, size_t size) {}
-#endif
-
 void dcpaud_connect(struct platform_device *pdev, bool connected)
 {
 	struct dcp_audio *dcpaud = platform_get_drvdata(pdev);
 
 	mutex_lock(&dcpaud->data_lock);
 
-	if (!dcpaud->chan) {
-		int ret = dcpaud_init_snd_card(dcpaud);
-		if (ret) {
-			dcpaud->dcp_connected = connected;
-			mutex_unlock(&dcpaud->data_lock);
-			return;
-		}
-	}
 	dcpaud_report_hotplug(dcpaud, connected);
 }
 
@@ -643,14 +649,17 @@ static int dcpaud_comp_bind(struct device *dev, struct device *main, void *data)
 	dcpaud->dma_link = device_link_add(dev, dcpaud->dma_dev, DL_FLAG_PM_RUNTIME | DL_FLAG_RPM_ACTIVE |
 					   DL_FLAG_STATELESS);
 
-	mutex_lock(&dcpaud->data_lock);
 	/* ignore errors to prevent audio issues affecting the display side */
-	dcpaud_init_snd_card(dcpaud);
-	mutex_unlock(&dcpaud->data_lock);
+	ret = dcpaud_init_snd_card(dcpaud);
 
-	ret = device_create_file(dev, &dev_attr_probe_snd_card);
-        if (ret)
-		dev_info(dev, "creating force probe sysfs file failed: %d\n", ret);
+	if (!ret) {
+		dcpaud_expose_debugfs_blob(dcpaud, "selected_cookie", &dcpaud->selected_cookie,
+					sizeof(dcpaud->selected_cookie));
+		dcpaud_expose_debugfs_blob(dcpaud, "elements", dcpaud->elements,
+					DCPAUD_ELEMENTS_MAXSIZE);
+		dcpaud_expose_debugfs_blob(dcpaud, "product_attrs", dcpaud->productattrs,
+					DCPAUD_PRODUCTATTRS_MAXSIZE);
+	}
 
 	return 0;
 }

From 9698e37a1a58398e1a601995b4f523977357b8f6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 21:12:46 +0900
Subject: [PATCH 0810/1027] drm/apple: audio: Fix hotplug notifications

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/audio.c |  4 ++--
 drivers/gpu/drm/apple/av.c    | 15 +++++++++++++++
 2 files changed, 17 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/audio.c b/drivers/gpu/drm/apple/audio.c
index f955efef48430d..97daff109bc96b 100644
--- a/drivers/gpu/drm/apple/audio.c
+++ b/drivers/gpu/drm/apple/audio.c
@@ -500,7 +500,8 @@ static void dcpaud_report_hotplug(struct dcp_audio *dcpaud, bool connected)
 
 	if (!connected) {
 		snd_pcm_stream_lock(substream);
-		snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
+		if (substream->runtime)
+			snd_pcm_stop(substream, SNDRV_PCM_STATE_DISCONNECTED);
 		snd_pcm_stream_unlock(substream);
 	}
 }
@@ -592,7 +593,6 @@ void dcpaud_disconnect(struct platform_device *pdev)
 
 	mutex_lock(&dcpaud->data_lock);
 
-	dcpaud->dcp_connected = false;
 	dcpaud_report_hotplug(dcpaud, false);
 }
 
diff --git a/drivers/gpu/drm/apple/av.c b/drivers/gpu/drm/apple/av.c
index 8a2c1126f5adea..586f39cc11ca11 100644
--- a/drivers/gpu/drm/apple/av.c
+++ b/drivers/gpu/drm/apple/av.c
@@ -69,6 +69,20 @@ static void av_interface_init(struct apple_epic_service *service, const char *na
 {
 }
 
+static void av_interface_teardown(struct apple_epic_service *service)
+{
+	struct apple_dcp *dcp = service->ep->dcp;
+	struct audiosrv_data *asrv = dcp->audiosrv;
+
+	mutex_lock(&asrv->plug_lock);
+
+	asrv->plugged = false;
+	if (asrv->audio_dev)
+		dcpaud_disconnect(asrv->audio_dev);
+
+	mutex_unlock(&asrv->plug_lock);
+}
+
 static void av_audiosrv_init(struct apple_epic_service *service, const char *name,
 			     const char *class, s64 unit)
 {
@@ -258,6 +272,7 @@ static const struct apple_epic_service_ops avep_ops[] = {
 	{
 		.name = "DCPAVSimpleVideoInterface",
 		.init = av_interface_init,
+		.teardown = av_interface_teardown,
 	},
 	{
 		.name = "DCPAVAudioInterface",

From 39bf1535a7cf2dfd93608ac4dc6cbba4631f17e2 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Sat, 5 Nov 2022 13:15:34 +0100
Subject: [PATCH 0811/1027] drm: apple: Add oob hotplug event

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/gpu/drm/apple/apple_drv.c | 17 +++++++++++++++++
 drivers/gpu/drm/apple/dcp.c       | 19 +++++++++++++++++++
 drivers/gpu/drm/apple/dcp.h       |  3 +++
 3 files changed, 39 insertions(+)

diff --git a/drivers/gpu/drm/apple/apple_drv.c b/drivers/gpu/drm/apple/apple_drv.c
index e1d55c1ee6d503..a93bb7f43c2a13 100644
--- a/drivers/gpu/drm/apple/apple_drv.c
+++ b/drivers/gpu/drm/apple/apple_drv.c
@@ -189,6 +189,22 @@ apple_connector_detect(struct drm_connector *connector, bool force)
 						  connector_status_disconnected;
 }
 
+static void apple_connector_oob_hotplug(struct drm_connector *connector,
+					enum drm_connector_status status)
+{
+	struct apple_connector *apple_connector = to_apple_connector(connector);
+
+	printk("#### oob_hotplug status:0x%x ####\n", (u32)status);
+
+	if (status == connector_status_connected)
+		dcp_dptx_connect_oob(apple_connector->dcp, 0);
+	else if (status == connector_status_disconnected)
+		dcp_dptx_disconnect_oob(apple_connector->dcp, 0);
+	else
+		dev_err(&apple_connector->dcp->dev, "unexpected connector status"
+			":0x%x in oob_hotplug event\n", (u32)status);
+}
+
 static void apple_crtc_atomic_enable(struct drm_crtc *crtc,
 				     struct drm_atomic_state *state)
 {
@@ -277,6 +293,7 @@ static const struct drm_connector_funcs apple_connector_funcs = {
 	.atomic_destroy_state	= drm_atomic_helper_connector_destroy_state,
 	.detect			= apple_connector_detect,
 	.debugfs_init		= apple_connector_debugfs_init,
+	.oob_hotplug_event	= apple_connector_oob_hotplug,
 };
 
 static const struct drm_connector_helper_funcs apple_connector_helper_funcs = {
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 4519cb6e2a7504..eda60b95340564 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -397,6 +397,17 @@ static int dcp_dptx_connect(struct apple_dcp *dcp, u32 port)
 	return ret;
 }
 
+int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	int err = dcp_dptx_connect(dcp, port);
+	if (err < 0)
+		return err;
+	dptxport_set_hpd(dcp->dptxport[port].service, true);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dcp_dptx_connect_oob);
+
 static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
 {
 	dev_info(dcp->dev, "%s(port=%d)\n", __func__, port);
@@ -411,6 +422,14 @@ static int dcp_dptx_disconnect(struct apple_dcp *dcp, u32 port)
 	return 0;
 }
 
+int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port)
+{
+	struct apple_dcp *dcp = platform_get_drvdata(pdev);
+	dptxport_set_hpd(dcp->dptxport[port].service, false);
+	return dcp_dptx_disconnect(dcp, port);
+}
+EXPORT_SYMBOL_GPL(dcp_dptx_disconnect_oob);
+
 static irqreturn_t dcp_dp2hdmi_hpd(int irq, void *data)
 {
 	struct apple_dcp *dcp = data;
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index c284a089b6e0bf..de322ed02c72d5 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -52,6 +52,9 @@ bool dcp_crtc_mode_fixup(struct drm_crtc *crtc,
 void dcp_set_dimensions(struct apple_dcp *dcp);
 void dcp_send_message(struct apple_dcp *dcp, u8 endpoint, u64 message);
 
+int dcp_dptx_connect_oob(struct platform_device *pdev, u32 port);
+int dcp_dptx_disconnect_oob(struct platform_device *pdev, u32 port);
+
 int iomfb_start_rtkit(struct apple_dcp *dcp);
 void iomfb_shutdown(struct apple_dcp *dcp);
 /* rtkit message handler for IOMFB messages */

From b515d61366a21cd412f0f41a27d0e4380400b68c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 9 Jun 2024 21:49:43 +0200
Subject: [PATCH 0812/1027] drm: apple: dptx: Fix get_drive_settings retcode

This appears to be lane count as "2" is observed for USB-C DP alt mode
in shared DP/USB3 mode.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index 56b86966e807a7..fd54f69b19919b 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -189,12 +189,16 @@ dptxport_call_get_drive_settings(struct apple_epic_service *service,
 	/* Clear the rest of the buffer */
 	memset(reply_ + sizeof(*reply), 0, reply_size - sizeof(*reply));
 
-	if (reply->retcode != 4)
+	/*
+	 * retcode appears to be lane count, seeing 2 for USB-C dp alt mode
+	 * with lanes splitted for DP/USB3.
+	 */
+	if (reply->retcode != dptx->lane_count)
 		dev_err(service->ep->dcp->dev,
 			"get_drive_settings: unexpected retcode %d\n",
 			reply->retcode);
 
-	reply->retcode = 4; /* Should already be 4? */
+	reply->retcode = dptx->lane_count;
 	reply->unk5 = dptx->drive_settings[0];
 	reply->unk6 = 0;
 	reply->unk7 = dptx->drive_settings[1];

From b439dad14bc178b58d63862e5a53024ee3e011f4 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 9 Jun 2024 22:01:59 +0200
Subject: [PATCH 0813/1027] drm: apple: dptxport: get_max_lane_count: Retrieve
 lane count from phy

This unfortunately doesn't work relieably with typec-altmode-displayport
since the oob hotplug notification arrives before atc-phy is configured
to the appropiate DP mode.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dptxep.c | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/drivers/gpu/drm/apple/dptxep.c b/drivers/gpu/drm/apple/dptxep.c
index fd54f69b19919b..b8cb7f00133760 100644
--- a/drivers/gpu/drm/apple/dptxep.c
+++ b/drivers/gpu/drm/apple/dptxep.c
@@ -249,6 +249,9 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service,
 					   void *reply_, size_t reply_size)
 {
 	struct dptxport_apcall_lane_count *reply = reply_;
+	struct dptx_port *dptx = service->cookie;
+	union phy_configure_opts phy_ops;
+	int ret;
 
 	if (reply_size < sizeof(*reply))
 		return -EINVAL;
@@ -256,6 +259,17 @@ static int dptxport_call_get_max_lane_count(struct apple_epic_service *service,
 	reply->retcode = cpu_to_le32(0);
 	reply->lane_count = cpu_to_le64(4);
 
+	ret = phy_validate(dptx->atcphy, PHY_MODE_DP, 0, &phy_ops);
+	if (ret < 0 || phy_ops.dp.lanes < 2) {
+		// phy_validate might return 0 lines if atc-phy is not yet
+		// switched to  DP alt mode
+		dev_dbg(service->ep->dcp->dev, "get_max_lane_count: "
+			"phy_validate ret:%d lanes:%d\n", ret, phy_ops.dp.lanes);
+	} else {
+		reply->retcode = cpu_to_le32(0);
+		reply->lane_count = cpu_to_le64(phy_ops.dp.lanes);
+	}
+
 	return 0;
 }
 

From 2095a65f220c3347ba38b8e2a74f7db1c7d079cd Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 17:01:56 +0900
Subject: [PATCH 0814/1027] drm/apple: Fix missing mode init (feel free to
 squash)

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/apple/parser.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/apple/parser.c b/drivers/gpu/drm/apple/parser.c
index 965fcf88a0f4b6..700a31883eac8e 100644
--- a/drivers/gpu/drm/apple/parser.c
+++ b/drivers/gpu/drm/apple/parser.c
@@ -354,6 +354,7 @@ static int parse_color_modes(struct dcp_parse_ctx *handle,
 	int ret = 0;
 	out->sdr_444.score = -1;
 	out->sdr_rgb.score = -1;
+	out->sdr.score = -1;
 	out->best.score = -1;
 
 	dcp_parse_foreach_in_array(handle, outer_it) {

From e8696f32b93c1784c8486cf43201db011eb84c55 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 14 Jul 2024 00:01:08 +0200
Subject: [PATCH 0815/1027] drm: apple: iomfb: Align buffer size on unmap/free
 as well

Fixes failure to unmap buffers in dcpep_cb_unmap_piodma() due to the
unaligned size. Further along this causes kernel log splat when DCP
tries to map the buffers again since thye IOVA is still in use.
This causes no apparent issue although map_piodma callback signals an
errror and returns 0 (unmapped as DVA).

It's not clear why this presents only randomly. Possibly some build or
uninitialized memory triggers this unmap/free and immediate allocate/map
cycle in the DCP firmware. I never notices this with a clang-built kernel
on j314c. It showed with gcc build with the Fedora config at least on
6.8.8 based kernels. This did not reproduce on j375d.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/iomfb_template.c | 11 +++++++----
 1 file changed, 7 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/iomfb_template.c b/drivers/gpu/drm/apple/iomfb_template.c
index bc3df16ebac542..5df04979f573eb 100644
--- a/drivers/gpu/drm/apple/iomfb_template.c
+++ b/drivers/gpu/drm/apple/iomfb_template.c
@@ -322,7 +322,10 @@ static void dcpep_cb_unmap_piodma(struct apple_dcp *dcp,
 	}
 
 	/* use the piodma iommu domain to unmap from the right IOMMU */
-	iommu_unmap(dcp->iommu_dom, memdesc->dva, memdesc->size);
+	/* HACK: expect size to be 16K aligned since the iommu API only maps
+	 *       full pages
+	 */
+	iommu_unmap(dcp->iommu_dom, memdesc->dva, ALIGN(memdesc->size, SZ_16K));
 }
 
 /*
@@ -370,6 +373,7 @@ dcpep_cb_allocate_buffer(struct apple_dcp *dcp,
 static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
 {
 	struct dcp_mem_descriptor *memdesc;
+	size_t size;
 	u32 id = *mem_desc_id;
 
 	if (id >= DCP_MAX_MAPPINGS) {
@@ -385,10 +389,9 @@ static u8 dcpep_cb_release_mem_desc(struct apple_dcp *dcp, u32 *mem_desc_id)
 	}
 
 	memdesc = &dcp->memdesc[id];
+	size = ALIGN(memdesc->size, SZ_16K);
 	if (memdesc->buf) {
-		dma_free_coherent(dcp->dev, memdesc->size, memdesc->buf,
-				  memdesc->dva);
-
+		dma_free_coherent(dcp->dev, size, memdesc->buf, memdesc->dva);
 		memdesc->buf = NULL;
 		memset(&memdesc->map, 0, sizeof(memdesc->map));
 	} else {

From cac9174fb8639fa8a59bdd3e07b5de3e6ecc24cb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Wed, 21 Aug 2024 21:51:11 +0200
Subject: [PATCH 0816/1027] Revert "drm: apple: HACK: Do not delete piodma
 platform device"

This reverts commit fa86f31f64a691eb65a217c66468b3e9e58cc9e1.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/dcp.c | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index eda60b95340564..a1d6ba35558d62 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -1073,10 +1073,7 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 	if (dcp->piodma) {
 		iommu_detach_device(dcp->iommu_dom, &dcp->piodma->dev);
 		iommu_domain_free(dcp->iommu_dom);
-		/* TODO: the piodma platform device has to be destroyed but
-		 *       doing so leads to all kind of breakage.
-		 */
-		// of_platform_device_destroy(&dcp->piodma->dev, NULL);
+		of_platform_device_destroy(&dcp->piodma->dev, NULL);
 		dcp->piodma = NULL;
 	}
 

From 810d6b7d61dd70b205c7dcfdb75225555d871a10 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 20 Aug 2024 23:04:29 +0200
Subject: [PATCH 0817/1027] drm: apple: afk: Optionally match against EPICName

The dpavserv endpoint uses various EPICProviderClass depending on the
connected display.

Observed values:
- "AppleDCPAgileCDIDPDisplay" (j134c, dcp, panel)
- "AppleDCPMCDP29XX" (j274, dcp, hdmi)
- "AppleDCPPS190" (j474s, dcpext0, hdmi)
- "DCPDPService" (j474s, dcpext1, typec)

So match against against EPICName which is consistent in all cases.
This also allows the distinction between 'dcpav-service-epic' and
'dcpdp-service-epic'. Not sure what the second EPIC service is used for.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/afk.c | 6 +++++-
 drivers/gpu/drm/apple/afk.h | 2 ++
 2 files changed, 7 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/apple/afk.c b/drivers/gpu/drm/apple/afk.c
index 83afb51883048f..bd1f16e8937c74 100644
--- a/drivers/gpu/drm/apple/afk.c
+++ b/drivers/gpu/drm/apple/afk.c
@@ -293,7 +293,11 @@ static void afk_recv_handle_init(struct apple_dcp_afkep *ep, u32 channel,
             service_name = name;
         }
 
-	ops = afk_match_service(ep, service_name);
+	if (ep->match_epic_name)
+		ops = afk_match_service(ep, epic_name);
+	else
+		ops = afk_match_service(ep, service_name);
+
 	if (!ops) {
 		dev_err(ep->dcp->dev,
 			"AFK[ep:%02x]: unable to match service %s on channel %d\n",
diff --git a/drivers/gpu/drm/apple/afk.h b/drivers/gpu/drm/apple/afk.h
index be3f0b105de581..5a286799835248 100644
--- a/drivers/gpu/drm/apple/afk.h
+++ b/drivers/gpu/drm/apple/afk.h
@@ -182,6 +182,8 @@ struct apple_dcp_afkep {
 	u32 num_channels;
 
 	struct dentry *debugfs_entry;
+
+	bool match_epic_name;
 };
 
 struct apple_dcp_afkep *afk_init(struct apple_dcp *dcp, u32 endpoint,

From 7b6cf2f2b48c434ebd40cab595a38122600a9c35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 3 Dec 2023 23:24:11 +0100
Subject: [PATCH 0818/1027] drm: apple: Add dcpav-service-ep

Known uses EDID retrieval and raw I2C access.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/Makefile          |   2 +
 drivers/gpu/drm/apple/connector.c       |   3 +-
 drivers/gpu/drm/apple/connector.h       |   2 +
 drivers/gpu/drm/apple/dcp-internal.h    |   6 +
 drivers/gpu/drm/apple/dcp.c             |  19 ++
 drivers/gpu/drm/apple/dcp.h             |   1 +
 drivers/gpu/drm/apple/epic/dpavservep.c | 230 ++++++++++++++++++++++++
 drivers/gpu/drm/apple/epic/dpavservep.h |  22 +++
 drivers/gpu/drm/apple/trace.h           |  12 ++
 9 files changed, 296 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/apple/epic/dpavservep.c
 create mode 100644 drivers/gpu/drm/apple/epic/dpavservep.h

diff --git a/drivers/gpu/drm/apple/Makefile b/drivers/gpu/drm/apple/Makefile
index 519303016b292a..045183c63bc129 100644
--- a/drivers/gpu/drm/apple/Makefile
+++ b/drivers/gpu/drm/apple/Makefile
@@ -11,6 +11,8 @@ apple_dcp-y += connector.o
 apple_dcp-y += ibootep.o
 apple_dcp-y += iomfb_v12_3.o
 apple_dcp-y += iomfb_v13_3.o
+apple_dcp-y += epic/dpavservep.o
+
 apple_dcp-$(CONFIG_TRACING) += trace.o
 
 obj-$(CONFIG_DRM_APPLE) += appledrm.o
diff --git a/drivers/gpu/drm/apple/connector.c b/drivers/gpu/drm/apple/connector.c
index 46de8e8756f1ed..9e786670893387 100644
--- a/drivers/gpu/drm/apple/connector.c
+++ b/drivers/gpu/drm/apple/connector.c
@@ -3,6 +3,8 @@
  * Copyright (C) The Asahi Linux Contributors
  */
 
+#include "connector.h"
+
 #include "linux/err.h"
 #include <linux/debugfs.h>
 #include <linux/module.h>
@@ -12,7 +14,6 @@
 
 #include <drm/drm_managed.h>
 
-#include "connector.h"
 #include "dcp-internal.h"
 
 enum dcp_chunk_type {
diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h
index 79a1eef8c32da8..616ad8fceefae4 100644
--- a/drivers/gpu/drm/apple/connector.h
+++ b/drivers/gpu/drm/apple/connector.h
@@ -9,6 +9,8 @@
 #include <drm/drm_atomic.h>
 #include "drm/drm_connector.h"
 
+struct apple_connector;
+
 #include "dcp-internal.h"
 
 void dcp_hotplug(struct work_struct *work);
diff --git a/drivers/gpu/drm/apple/dcp-internal.h b/drivers/gpu/drm/apple/dcp-internal.h
index 1a465138104d57..d762c6bffbdee4 100644
--- a/drivers/gpu/drm/apple/dcp-internal.h
+++ b/drivers/gpu/drm/apple/dcp-internal.h
@@ -17,12 +17,15 @@
 #include "iomfb.h"
 #include "iomfb_v12_3.h"
 #include "iomfb_v13_3.h"
+#include "epic/dpavservep.h"
 
 #define DCP_MAX_PLANES 2
 
 struct apple_dcp;
 struct apple_dcp_afkep;
 
+struct dcpav_service_epic;
+
 enum dcp_firmware_version {
 	DCP_FIRMWARE_UNKNOWN,
 	DCP_FIRMWARE_V_12_3,
@@ -34,6 +37,7 @@ enum {
 	TEST_ENDPOINT = 0x21,
 	DCP_EXPERT_ENDPOINT = 0x22,
 	DISP0_ENDPOINT = 0x23,
+	DPAVSERV_ENDPOINT = 0x28,
 	AV_ENDPOINT = 0x29,
 	DPTX_ENDPOINT = 0x2a,
 	HDCP_ENDPOINT = 0x2b,
@@ -228,6 +232,8 @@ struct apple_dcp {
 	struct completion systemep_done;
 
 	struct apple_dcp_afkep *ibootep;
+	struct apple_dcp_afkep *dcpavservep;
+	struct dcpavserv dcpavserv;
 
 	struct apple_dcp_afkep *avep;
 	struct audiosrv_data *audiosrv;
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index a1d6ba35558d62..235f331df0b951 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -50,6 +50,10 @@ bool hdmi_audio;
 module_param(hdmi_audio, bool, 0644);
 MODULE_PARM_DESC(hdmi_audio, "Enable unstable HDMI audio support");
 
+static bool unstable_edid;
+module_param(unstable_edid, bool, 0644);
+MODULE_PARM_DESC(unstable_edid, "Enable unstable EDID retrival support");
+
 /* copied and simplified from drm_vblank.c */
 static void send_vblank_event(struct drm_device *dev,
 		struct drm_pending_vblank_event *e,
@@ -219,6 +223,9 @@ static void dcp_recv_msg(void *cookie, u8 endpoint, u64 message)
 	case DISP0_ENDPOINT:
 		afk_receive_message(dcp->ibootep, message);
 		return;
+	case DPAVSERV_ENDPOINT:
+		afk_receive_message(dcp->dcpavservep, message);
+		return;
 	case DPTX_ENDPOINT:
 		afk_receive_message(dcp->dptxep, message);
 		return;
@@ -477,6 +484,13 @@ int dcp_start(struct platform_device *pdev)
 	if (ret)
 		dev_warn(dcp->dev, "Failed to start system endpoint: %d\n", ret);
 
+	if (unstable_edid && !dcp_has_panel(dcp)) {
+		ret = dpavservep_init(dcp);
+		if (ret)
+			dev_warn(dcp->dev, "Failed to start DPAVSERV endpoint: %d",
+				 ret);
+	}
+
 	if (dcp->phy && dcp->fw_compat >= DCP_FIRMWARE_V_13_5) {
 		ret = ibootep_init(dcp);
 		if (ret)
@@ -1067,6 +1081,11 @@ static void dcp_comp_unbind(struct device *dev, struct device *main, void *data)
 		dcp->systemep = NULL;
 	}
 
+	if (dcp->dcpavservep) {
+		afk_shutdown(dcp->dcpavservep);
+		dcp->dcpavservep = NULL;
+	}
+
 	if (dcp->shmem)
 		iomfb_shutdown(dcp);
 
diff --git a/drivers/gpu/drm/apple/dcp.h b/drivers/gpu/drm/apple/dcp.h
index de322ed02c72d5..0c33fac0ee28f0 100644
--- a/drivers/gpu/drm/apple/dcp.h
+++ b/drivers/gpu/drm/apple/dcp.h
@@ -63,6 +63,7 @@ void iomfb_recv_msg(struct apple_dcp *dcp, u64 message);
 int systemep_init(struct apple_dcp *dcp);
 int dptxep_init(struct apple_dcp *dcp);
 int ibootep_init(struct apple_dcp *dcp);
+int dpavservep_init(struct apple_dcp *dcp);
 int avep_init(struct apple_dcp *dcp);
 
 
diff --git a/drivers/gpu/drm/apple/epic/dpavservep.c b/drivers/gpu/drm/apple/epic/dpavservep.c
new file mode 100644
index 00000000000000..aa2cbc729a37d4
--- /dev/null
+++ b/drivers/gpu/drm/apple/epic/dpavservep.c
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#include "dpavservep.h"
+
+#include <drm/drm_edid.h>
+
+#include <linux/completion.h>
+#include <linux/device.h>
+#include <linux/types.h>
+
+#include "../afk.h"
+#include "../dcp.h"
+#include "../dcp-internal.h"
+#include "../trace.h"
+
+static void dcpavserv_init(struct apple_epic_service *service, const char *name,
+			  const char *class, s64 unit)
+{
+	struct apple_dcp *dcp = service->ep->dcp;
+	trace_dcpavserv_init(dcp, unit);
+
+	if (unit == 0 && name && !strcmp(name, "dcpav-service-epic")) {
+		if (dcp->dcpavserv.enabled) {
+			dev_err(dcp->dev,
+				"DCPAVSERV: unit %lld already exists\n", unit);
+			return;
+		}
+		dcp->dcpavserv.service = service;
+		dcp->dcpavserv.enabled = true;
+		service->cookie = &dcp->dcpavserv;
+		complete(&dcp->dcpavserv.enable_completion);
+	}
+}
+
+static void dcpavserv_teardown(struct apple_epic_service *service)
+{
+	struct apple_dcp *dcp = service->ep->dcp;
+	if (dcp->dcpavserv.enabled) {
+		dcp->dcpavserv.enabled = false;
+		dcp->dcpavserv.service = NULL;
+		service->cookie = NULL;
+		reinit_completion(&dcp->dcpavserv.enable_completion);
+	}
+}
+
+static void dcpdpserv_init(struct apple_epic_service *service, const char *name,
+			  const char *class, s64 unit)
+{
+}
+
+static void dcpdpserv_teardown(struct apple_epic_service *service)
+{
+}
+
+struct dcpavserv_status_report {
+	u32 unk00[4];
+	u8 flag0;
+	u8 flag1;
+	u8 flag2;
+	u8 flag3;
+	u32 unk14[3];
+	u32 status;
+	u32 unk24[3];
+} __packed;
+
+struct dpavserv_copy_edid_cmd {
+	__le64 max_size;
+	u8 _pad1[24];
+	__le64 used_size;
+	u8 _pad2[8];
+} __packed;
+
+#define EDID_LEADING_DATA_SIZE		8
+#define EDID_BLOCK_SIZE			128
+#define EDID_EXT_BLOCK_COUNT_OFFSET	0x7E
+#define EDID_MAX_SIZE			SZ_32K
+#define EDID_BUF_SIZE			(EDID_LEADING_DATA_SIZE + EDID_MAX_SIZE)
+
+struct dpavserv_copy_edid_resp {
+	__le64 max_size;
+	u8 _pad1[24];
+	__le64 used_size;
+	u8 _pad2[8];
+	u8 data[];
+} __packed;
+
+static int parse_report(struct apple_epic_service *service, enum epic_subtype type,
+			 const void *data, size_t data_size)
+{
+#if defined(DEBUG)
+	struct apple_dcp *dcp = service->ep->dcp;
+	const struct epic_service_call *call;
+	const void *payload;
+	size_t payload_size;
+
+	dev_dbg(dcp->dev, "dcpavserv[ch:%u]: report type:%02x len:%zu\n",
+		service->channel, type, data_size);
+
+	if (type != EPIC_SUBTYPE_STD_SERVICE)
+		return 0;
+
+	if (data_size < sizeof(*call))
+		return 0;
+
+	call = data;
+
+	if (le32_to_cpu(call->magic) != EPIC_SERVICE_CALL_MAGIC) {
+		dev_warn(dcp->dev, "dcpavserv[ch:%u]: report magic 0x%08x != 0x%08x\n",
+			service->channel, le32_to_cpu(call->magic), EPIC_SERVICE_CALL_MAGIC);
+		return 0;
+	}
+
+	payload_size = data_size - sizeof(*call);
+	if (payload_size < le32_to_cpu(call->data_len)) {
+		dev_warn(dcp->dev, "dcpavserv[ch:%u]: report payload size %zu call len %u\n",
+			service->channel, payload_size, le32_to_cpu(call->data_len));
+		return 0;
+	}
+	payload_size = le32_to_cpu(call->data_len);
+	payload = data + sizeof(*call);
+
+	if (le16_to_cpu(call->group) == 2 && le16_to_cpu(call->command) == 0) {
+		if (payload_size == sizeof(struct dcpavserv_status_report)) {
+			const struct dcpavserv_status_report *stat = payload;
+			dev_info(dcp->dev, "dcpavserv[ch:%u]: flags: 0x%02x,0x%02x,0x%02x,0x%02x status:%u\n",
+				service->channel, stat->flag0, stat->flag1,
+				stat->flag2, stat->flag3, stat->status);
+		} else {
+			dev_dbg(dcp->dev, "dcpavserv[ch:%u]: report payload size %zu\n", service->channel, payload_size);
+		}
+	} else {
+		print_hex_dump(KERN_DEBUG, "dcpavserv report: ", DUMP_PREFIX_NONE,
+			       16, 1, payload, payload_size, true);
+	}
+#endif
+
+	return 0;
+}
+
+static int dcpavserv_report(struct apple_epic_service *service,
+			    enum epic_subtype type, const void *data,
+			    size_t data_size)
+{
+	return parse_report(service, type, data, data_size);
+}
+
+static int dcpdpserv_report(struct apple_epic_service *service,
+			    enum epic_subtype type, const void *data,
+			    size_t data_size)
+{
+	return parse_report(service, type, data, data_size);
+}
+
+const struct drm_edid *dcpavserv_copy_edid(struct apple_epic_service *service)
+{
+	struct dpavserv_copy_edid_cmd cmd;
+	struct dpavserv_copy_edid_resp *resp __free(kfree) = NULL;
+	int num_blocks;
+	u64 data_size;
+	int ret;
+
+	memset(&cmd, 0, sizeof(cmd));
+	cmd.max_size = cpu_to_le64(EDID_BUF_SIZE);
+	resp = kzalloc(sizeof(*resp) + EDID_BUF_SIZE, GFP_KERNEL);
+	if (!resp)
+		return ERR_PTR(-ENOMEM);
+
+	ret = afk_service_call(service, 1, 7, &cmd, sizeof(cmd), EDID_BUF_SIZE, resp,
+			       sizeof(resp) + EDID_BUF_SIZE, 0);
+	if (ret < 0)
+		return ERR_PTR(ret);
+
+	if (le64_to_cpu(resp->max_size) != EDID_BUF_SIZE)
+		return ERR_PTR(-EIO);
+
+	// print_hex_dump(KERN_DEBUG, "dpavserv EDID cmd: ", DUMP_PREFIX_NONE,
+	// 	       16, 1, resp, 192, true);
+
+	data_size = le64_to_cpu(resp->used_size);
+	if (data_size < EDID_LEADING_DATA_SIZE + EDID_BLOCK_SIZE)
+		return ERR_PTR(-EIO);
+
+	num_blocks = resp->data[EDID_LEADING_DATA_SIZE + EDID_EXT_BLOCK_COUNT_OFFSET];
+	if ((1 + num_blocks) * EDID_BLOCK_SIZE != data_size - EDID_LEADING_DATA_SIZE)
+		return ERR_PTR(-EIO);
+
+	return drm_edid_alloc(resp->data + EDID_LEADING_DATA_SIZE,
+			      data_size - EDID_LEADING_DATA_SIZE);
+}
+
+static const struct apple_epic_service_ops dpavservep_ops[] = {
+	{
+		.name = "dcpav-service-epic",
+		.init = dcpavserv_init,
+		.teardown = dcpavserv_teardown,
+		.report = dcpavserv_report,
+	},
+	{
+		.name = "dcpdp-service-epic",
+		.init = dcpdpserv_init,
+		.teardown = dcpdpserv_teardown,
+		.report = dcpdpserv_report,
+	},
+	{},
+};
+
+int dpavservep_init(struct apple_dcp *dcp)
+{
+	int ret;
+
+	init_completion(&dcp->dcpavserv.enable_completion);
+
+	dcp->dcpavservep = afk_init(dcp, DPAVSERV_ENDPOINT, dpavservep_ops);
+	if (IS_ERR(dcp->dcpavservep))
+		return PTR_ERR(dcp->dcpavservep);
+
+	dcp->dcpavservep->match_epic_name = true;
+
+	ret = afk_start(dcp->dcpavservep);
+	if (ret)
+		return ret;
+
+	ret = wait_for_completion_timeout(&dcp->dcpavserv.enable_completion,
+					  msecs_to_jiffies(1000));
+	if (ret >= 0)
+		return 0;
+
+	return ret;
+}
diff --git a/drivers/gpu/drm/apple/epic/dpavservep.h b/drivers/gpu/drm/apple/epic/dpavservep.h
new file mode 100644
index 00000000000000..858ff14b0bd7be
--- /dev/null
+++ b/drivers/gpu/drm/apple/epic/dpavservep.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+/* Copyright The Asahi Linux Contributors */
+
+#ifndef _DRM_APPLE_EPIC_DPAVSERV_H
+#define _DRM_APPLE_EPIC_DPAVSERV_H
+
+#include <linux/completion.h>
+#include <linux/types.h>
+
+struct drm_edid;
+struct apple_epic_service;
+
+struct dcpavserv {
+	bool enabled;
+	struct completion enable_completion;
+	u32 unit;
+	struct apple_epic_service *service;
+};
+
+const struct drm_edid *dcpavserv_copy_edid(struct apple_epic_service *service);
+
+#endif /* _DRM_APPLE_EPIC_DPAVSERV_H */
diff --git a/drivers/gpu/drm/apple/trace.h b/drivers/gpu/drm/apple/trace.h
index e03bf8b199c88f..a13dd34fb7aab1 100644
--- a/drivers/gpu/drm/apple/trace.h
+++ b/drivers/gpu/drm/apple/trace.h
@@ -351,6 +351,18 @@ DEFINE_EVENT(iomfb_parse_mode_template, iomfb_parse_mode_fail,
 	    TP_PROTO(s64 id, struct dimension *horiz, struct dimension *vert, s64 best_color_mode, bool is_virtual, s64 score),
 	    TP_ARGS(id, horiz, vert, best_color_mode, is_virtual, score));
 
+TRACE_EVENT(dcpavserv_init, TP_PROTO(struct apple_dcp *dcp, u64 unit),
+	    TP_ARGS(dcp, unit),
+
+	    TP_STRUCT__entry(__string(devname, dev_name(dcp->dev))
+				     __field(u64, unit)),
+
+	    TP_fast_assign(__assign_str(devname);
+			   __entry->unit = unit;),
+
+	    TP_printk("%s: dcpav-service unit %lld initialized", __get_str(devname),
+		      __entry->unit));
+
 TRACE_EVENT(dptxport_init, TP_PROTO(struct apple_dcp *dcp, u64 unit),
 	    TP_ARGS(dcp, unit),
 

From d74fa5b1082bed3d84135fbf712e3f6c44b592ff Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 20 Aug 2024 22:39:06 +0200
Subject: [PATCH 0819/1027] drm: apple: iomfb: Provide the EDID as connector
 property

External display only since the EDID provided by integrated panels
holds no useful / correct information.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/apple/connector.h |  3 +++
 drivers/gpu/drm/apple/dcp.c       |  2 ++
 drivers/gpu/drm/apple/iomfb.c     | 20 ++++++++++++++++++++
 3 files changed, 25 insertions(+)

diff --git a/drivers/gpu/drm/apple/connector.h b/drivers/gpu/drm/apple/connector.h
index 616ad8fceefae4..ff643552c77d4c 100644
--- a/drivers/gpu/drm/apple/connector.h
+++ b/drivers/gpu/drm/apple/connector.h
@@ -8,6 +8,7 @@
 
 #include <drm/drm_atomic.h>
 #include "drm/drm_connector.h"
+#include "drm/drm_edid.h"
 
 struct apple_connector;
 
@@ -21,6 +22,8 @@ struct apple_connector {
 
 	struct platform_device *dcp;
 
+	const struct drm_edid *drm_edid;
+
 	/* Workqueue for sending hotplug events to the associated device */
 	struct work_struct hotplug_wq;
 
diff --git a/drivers/gpu/drm/apple/dcp.c b/drivers/gpu/drm/apple/dcp.c
index 235f331df0b951..f5239cd96c5a36 100644
--- a/drivers/gpu/drm/apple/dcp.c
+++ b/drivers/gpu/drm/apple/dcp.c
@@ -242,6 +242,8 @@ static void dcp_rtk_crashed(void *cookie)
 	dev_err(dcp->dev, "DCP has crashed\n");
 	if (dcp->connector) {
 		dcp->connector->connected = 0;
+		drm_edid_free(dcp->connector->drm_edid);
+		dcp->connector->drm_edid = NULL;
 		schedule_work(&dcp->connector->hotplug_wq);
 	}
 	complete(&dcp->start_done);
diff --git a/drivers/gpu/drm/apple/iomfb.c b/drivers/gpu/drm/apple/iomfb.c
index 0941ff69873235..398118933e801e 100644
--- a/drivers/gpu/drm/apple/iomfb.c
+++ b/drivers/gpu/drm/apple/iomfb.c
@@ -16,6 +16,7 @@
 #include <linux/slab.h>
 #include <linux/soc/apple/rtkit.h>
 
+#include <drm/drm_edid.h>
 #include <drm/drm_fb_dma_helper.h>
 #include <drm/drm_fourcc.h>
 #include <drm/drm_framebuffer.h>
@@ -241,6 +242,11 @@ void dcp_hotplug(struct work_struct *work)
 	dev_info(dcp->dev, "%s() connected:%d valid_mode:%d nr_modes:%u\n", __func__,
 		 connector->connected, dcp->valid_mode, dcp->nr_modes);
 
+	if (!connector->connected) {
+		drm_edid_free(connector->drm_edid);
+		connector->drm_edid = NULL;
+	}
+
 	/*
 	 * DCP defers link training until we set a display mode. But we set
 	 * display modes from atomic_flush, so userspace needs to trigger a
@@ -391,6 +397,20 @@ int dcp_get_modes(struct drm_connector *connector)
 		drm_mode_probed_add(connector, mode);
 	}
 
+	if (dcp->nr_modes && dcp->dcpavserv.enabled &&
+	    !apple_connector->drm_edid) {
+		const struct drm_edid *edid;
+		edid = dcpavserv_copy_edid(dcp->dcpavserv.service);
+		if (IS_ERR_OR_NULL(edid)) {
+			dev_info(dcp->dev, "copy_edid failed: %pe\n", edid);
+		} else {
+			drm_edid_free(apple_connector->drm_edid);
+			apple_connector->drm_edid = edid;
+		}
+	}
+	if (dcp->nr_modes && apple_connector->drm_edid)
+		drm_edid_connector_update(connector, apple_connector->drm_edid);
+
 	return dcp->nr_modes;
 }
 EXPORT_SYMBOL_GPL(dcp_get_modes);

From 978787a93263226e3de380423b12b5afab91c7f5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Martin=20Povi=C5=A1er?= <povik+lin@cutebit.org>
Date: Thu, 23 Feb 2023 12:34:28 +0100
Subject: [PATCH 0820/1027] ALSA: Introduce 'snd_interval_rate_bits'
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Martin Povišer <povik+lin@cutebit.org>
---
 include/sound/pcm.h  |  1 +
 sound/core/pcm_lib.c | 37 +++++++++++++++++++++++++++++++++++++
 2 files changed, 38 insertions(+)

diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index ac8f3aef920523..2d7d7db9aa5e2e 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -1066,6 +1066,7 @@ int snd_interval_ranges(struct snd_interval *i, unsigned int count,
 int snd_interval_ratnum(struct snd_interval *i,
 			unsigned int rats_count, const struct snd_ratnum *rats,
 			unsigned int *nump, unsigned int *denp);
+int snd_interval_rate_bits(struct snd_interval *i, unsigned int rate_bits);
 
 void _snd_pcm_hw_params_any(struct snd_pcm_hw_params *params);
 void _snd_pcm_hw_param_setempty(struct snd_pcm_hw_params *params, snd_pcm_hw_param_t var);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index 6e7905749c4a3b..c322b3ee2884f8 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -1146,6 +1146,43 @@ static int snd_interval_step(struct snd_interval *i, unsigned int step)
 	return changed;
 }
 
+/**
+ * snd_interval_rate_bits - refine the rate interval from a rate bitmask
+ * @i: the rate interval to refine
+ * @mask: the rate bitmask
+ *
+ * Refines the interval value, assumed to be the sample rate, according to
+ * a bitmask of available rates (an ORed combination of SNDRV_PCM_RATE_*).
+ *
+ * Return: Positive if the value is changed, zero if it's not changed, or a
+ * negative error code.
+ */
+int snd_interval_rate_bits(struct snd_interval *i, unsigned int mask)
+{
+	unsigned int k;
+	struct snd_interval mask_range;
+
+	if (!mask)
+		return -EINVAL;
+
+	snd_interval_any(&mask_range);
+	mask_range.min = UINT_MAX;
+	mask_range.max = 0;
+	for (k = 0; k < snd_pcm_known_rates.count; k++) {
+		unsigned int rate = snd_pcm_known_rates.list[k];
+		if (!(mask & (1 << k)))
+			continue;
+
+		if (rate > mask_range.max)
+			mask_range.max = rate;
+
+		if (rate < mask_range.min)
+			mask_range.min = rate;
+	}
+	return snd_interval_refine(i, &mask_range);
+}
+EXPORT_SYMBOL(snd_interval_rate_bits);
+
 /* Info constraints helpers */
 
 /**

From 9444b796c60905d0d85dc3f08109657a1a1f2413 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 5 Mar 2023 23:40:22 +0900
Subject: [PATCH 0821/1027] rust: helpers: Fix spinlock helper for various
 spinlock modes

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 rust/helpers/spinlock.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/rust/helpers/spinlock.c b/rust/helpers/spinlock.c
index acc1376b833c78..61fa5fa65c32d8 100644
--- a/rust/helpers/spinlock.c
+++ b/rust/helpers/spinlock.c
@@ -8,6 +8,9 @@ void rust_helper___spin_lock_init(spinlock_t *lock, const char *name,
 {
 #ifdef CONFIG_DEBUG_SPINLOCK
 	__raw_spin_lock_init(spinlock_check(lock), name, key, LD_WAIT_CONFIG);
+#elif defined(CONFIG_PREEMPT_RT)
+	rt_mutex_base_init(&lock->lock);
+	__rt_spin_lock_init(lock, name, key, false);
 #else
 	spin_lock_init(lock);
 #endif

From 6431666aa8d29d93d6ee535dab05b78b8d8d356f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 28 Apr 2023 20:11:26 +0900
Subject: [PATCH 0822/1027] rust: Enable type_alias_impl_trait

This is required to make PinInit<T> work as a return value from a
trait function. We indirect via an associated type to avoid
return_position_impl_trait_in_trait, which is an incomplete feature.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs     | 1 +
 scripts/Makefile.build | 2 +-
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 274bdc1b0a824a..5ea21d249168d6 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -16,6 +16,7 @@
 #![feature(dispatch_from_dyn)]
 #![feature(new_uninit)]
 #![feature(receiver_trait)]
+#![feature(type_alias_impl_trait)]
 #![feature(unsize)]
 
 // Ensure conditional compilation based on the kernel configuration works;
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index a5ac8ed1936fe8..e0a515ff7b5459 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -263,7 +263,7 @@ $(obj)/%.lst: $(obj)/%.c FORCE
 # Compile Rust sources (.rs)
 # ---------------------------------------------------------------------------
 
-rust_allowed_features := new_uninit
+rust_allowed_features := new_uninit,type_alias_impl_trait
 
 # `--out-dir` is required to avoid temporaries being created by `rustc` in the
 # current working directory, which may be not accessible in the out-of-tree

From 27d62627010be34f71e86a3a5c75a05e2d083af6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 28 Apr 2023 20:12:35 +0900
Subject: [PATCH 0823/1027] rust: kernel: lock: Add Lock::pin_init()

This allows initializing a lock using pin_init!(), instead of requiring
the inner data to be passed through the stack.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync/lock.rs       | 28 +++++++++++++++++++++++++++-
 rust/kernel/sync/lock/mutex.rs | 13 +++++++++++++
 2 files changed, 40 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index f6c34ca4d819f8..4f43f3140e28b2 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -6,7 +6,7 @@
 //! spinlocks, raw spinlocks) to be provided with minimal effort.
 
 use super::LockClassKey;
-use crate::{init::PinInit, pin_init, str::CStr, types::Opaque, types::ScopeGuard};
+use crate::{init::PinInit, pin_init, str::CStr, try_pin_init, types::Opaque, types::ScopeGuard};
 use core::{cell::UnsafeCell, marker::PhantomData, marker::PhantomPinned};
 use macros::pin_data;
 
@@ -94,6 +94,7 @@ pub struct Lock<T: ?Sized, B: Backend> {
     _pin: PhantomPinned,
 
     /// The data protected by the lock.
+    #[pin]
     pub(crate) data: UnsafeCell<T>,
 }
 
@@ -117,6 +118,31 @@ impl<T, B: Backend> Lock<T, B> {
             }),
         })
     }
+
+    /// Constructs a new lock initialiser taking an initialiser.
+    pub fn pin_init<E>(
+        t: impl PinInit<T, E>,
+        name: &'static CStr,
+        key: &'static LockClassKey,
+    ) -> impl PinInit<Self, E>
+    where
+        E: core::convert::From<core::convert::Infallible>,
+    {
+        try_pin_init!(Self {
+            // SAFETY: We are just forwarding the initialization across a
+            // cast away from UnsafeCell, so the pin_init_from_closure and
+            // __pinned_init() requirements are in sync.
+            data <- unsafe { crate::init::pin_init_from_closure(move |slot: *mut UnsafeCell<T>| {
+                t.__pinned_init(slot as *mut T)
+            })},
+            _pin: PhantomPinned,
+            // SAFETY: `slot` is valid while the closure is called and both `name` and `key` have
+            // static lifetimes so they live indefinitely.
+            state <- Opaque::ffi_init(|slot| unsafe {
+                B::init(slot, name.as_char_ptr(), key.as_ptr())
+            }),
+        }? E)
+    }
 }
 
 impl<T: ?Sized, B: Backend> Lock<T, B> {
diff --git a/rust/kernel/sync/lock/mutex.rs b/rust/kernel/sync/lock/mutex.rs
index 30632070ee6709..14725b5d16203c 100644
--- a/rust/kernel/sync/lock/mutex.rs
+++ b/rust/kernel/sync/lock/mutex.rs
@@ -17,6 +17,19 @@ macro_rules! new_mutex {
 }
 pub use new_mutex;
 
+/// Creates a [`Mutex`] initialiser with the given name and a newly-created lock class,
+/// given an initialiser for the inner type.
+///
+/// It uses the name if one is given, otherwise it generates one based on the file name and line
+/// number.
+#[macro_export]
+macro_rules! new_mutex_pinned {
+    ($inner:expr $(, $name:literal)? $(,)?) => {
+        $crate::sync::Mutex::pin_init(
+            $inner, $crate::optional_name!($($name)?), $crate::static_lock_class!())
+    };
+}
+
 /// A mutual exclusion primitive.
 ///
 /// Exposes the kernel's [`struct mutex`]. When multiple threads attempt to lock the same mutex,

From 5af954b58b58cb09226cb2db4230b44d769a7673 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 19 May 2023 15:24:04 +0900
Subject: [PATCH 0824/1027] rust: init: Implement Zeroable::zeroed()

By analogy to Default::default(), this just returns the zeroed
representation of the type directly. init::zeroed() is the version that
returns an initializer.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init.rs | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index 495c09ebe3a38f..dee4f79a350c10 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -1257,7 +1257,14 @@ pub unsafe trait PinnedDrop: __internal::HasPinData {
 /// ```rust,ignore
 /// let val: Self = unsafe { core::mem::zeroed() };
 /// ```
-pub unsafe trait Zeroable {}
+pub unsafe trait Zeroable: core::marker::Sized {
+    /// Create a new zeroed T.
+    ///
+    /// Directly returns a zeroed T, analogous to Default::default().
+    fn zeroed() -> Self {
+        unsafe { core::mem::zeroed() }
+    }
+}
 
 /// Create a new zeroed T.
 ///

From 7a0116d36a93b84204d57981ca83d00927431d53 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 19 May 2023 16:22:09 +0900
Subject: [PATCH 0825/1027] rust: init: Support type paths in pin_init!() and
 friends

This makes directly initializing structures with a type name that isn't
in the current scope work properly.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init.rs | 24 ++++++++++++------------
 1 file changed, 12 insertions(+), 12 deletions(-)

diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index dee4f79a350c10..0f4a954ca7af55 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -555,12 +555,12 @@ macro_rules! stack_try_pin_init {
 // module `__internal` inside of `init/__internal.rs`.
 #[macro_export]
 macro_rules! pin_init {
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)?),
+            @typ($t $(::$p)* $(::<$($generics),*>)?),
             @fields($($fields)*),
             @error(::core::convert::Infallible),
             @data(PinData, use_data),
@@ -612,12 +612,12 @@ macro_rules! pin_init {
 // module `__internal` inside of `init/__internal.rs`.
 #[macro_export]
 macro_rules! try_pin_init {
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)? ),
+            @typ($t $(::$p)* $(::<$($generics),*>)? ),
             @fields($($fields)*),
             @error($crate::error::Error),
             @data(PinData, use_data),
@@ -626,12 +626,12 @@ macro_rules! try_pin_init {
             @munch_fields($($fields)*),
         )
     };
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }? $err:ty) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)? ),
+            @typ($t $(::$p)* $(::<$($generics),*>)? ),
             @fields($($fields)*),
             @error($err),
             @data(PinData, use_data),
@@ -661,12 +661,12 @@ macro_rules! try_pin_init {
 // module `__internal` inside of `init/__internal.rs`.
 #[macro_export]
 macro_rules! init {
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)?),
+            @typ($t $(::$p)* $(::<$($generics),*>)?),
             @fields($($fields)*),
             @error(::core::convert::Infallible),
             @data(InitData, /*no use_data*/),
@@ -712,12 +712,12 @@ macro_rules! init {
 // module `__internal` inside of `init/__internal.rs`.
 #[macro_export]
 macro_rules! try_init {
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)?),
+            @typ($t $(::$p)* $(::<$($generics),*>)?),
             @fields($($fields)*),
             @error($crate::error::Error),
             @data(InitData, /*no use_data*/),
@@ -726,12 +726,12 @@ macro_rules! try_init {
             @munch_fields($($fields)*),
         )
     };
-    ($(&$this:ident in)? $t:ident $(::<$($generics:ty),* $(,)?>)? {
+    ($(&$this:ident in)? $t:ident $(::$p:ident)* $(::<$($generics:ty),* $(,)?>)? {
         $($fields:tt)*
     }? $err:ty) => {
         $crate::__init_internal!(
             @this($($this)?),
-            @typ($t $(::<$($generics),*>)?),
+            @typ($t $(::$p)* $(::<$($generics),*>)?),
             @fields($($fields)*),
             @error($err),
             @data(InitData, /*no use_data*/),

From 5ec36460e5da0a7cd97e5a41fc3511a7f84c58ef Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 22:30:51 +0900
Subject: [PATCH 0826/1027] rust: types: Add Opaque::zeroed()

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/types.rs | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs
index bd189d646adb9a..96b47e2f3ccead 100644
--- a/rust/kernel/types.rs
+++ b/rust/kernel/types.rs
@@ -250,6 +250,14 @@ impl<T> Opaque<T> {
         }
     }
 
+    /// Creates a zeroed value.
+    pub fn zeroed() -> Self {
+        Self {
+            value: UnsafeCell::new(MaybeUninit::zeroed()),
+            _pin: PhantomPinned,
+        }
+    }
+
     /// Creates a pin-initializer from the given initializer closure.
     ///
     /// The returned initializer calls the given closure with the pointer to the inner `T` of this

From 44a8ed2771092a452e40ef7324aaab6f5a175f44 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 20:13:42 +0900
Subject: [PATCH 0827/1027] rust: Use absolute paths to build Rust objects

We want to use caller_location to uniquely identify callsites, to
automatically create lockdep classes without macros. The location
filename in local code uses the relative path passed to the compiler,
but if that code is generic and instantiated from another crate, the
path becomes absolute.

To make this work and keep the paths consistent, always pass an absolute
path to the compiler. Then the Location path is always identical
regardless of how the code is being compiled.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/Makefile          | 2 +-
 scripts/Makefile.build | 8 ++++----
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/rust/Makefile b/rust/Makefile
index 2aa93007aacae0..5d1624db247a80 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -354,7 +354,7 @@ quiet_cmd_rustc_library = $(if $(skip_clippy),RUSTC,$(RUSTC_OR_CLIPPY_QUIET)) L
 		--emit=dep-info=$(depfile) --emit=obj=$@ \
 		--emit=metadata=$(dir $@)$(patsubst %.o,lib%.rmeta,$(notdir $@)) \
 		--crate-type rlib -L$(objtree)/$(obj) \
-		--crate-name $(patsubst %.o,%,$(notdir $@)) $< \
+		--crate-name $(patsubst %.o,%,$(notdir $@)) $(abspath $<) \
 		--sysroot=/dev/null \
 	$(if $(rustc_objcopy),;$(OBJCOPY) $(rustc_objcopy) $@)
 
diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index e0a515ff7b5459..ccf1fd65cb48ea 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -288,27 +288,27 @@ rust_common_cmd = \
 # would not match each other.
 
 quiet_cmd_rustc_o_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $<
+      cmd_rustc_o_rs = $(rust_common_cmd) --emit=obj=$@ $(abspath $<)
 
 $(obj)/%.o: $(obj)/%.rs FORCE
 	+$(call if_changed_dep,rustc_o_rs)
 
 quiet_cmd_rustc_rsi_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
       cmd_rustc_rsi_rs = \
-	$(rust_common_cmd) -Zunpretty=expanded $< >$@; \
+	$(rust_common_cmd) -Zunpretty=expanded $(abspath $<) >$@; \
 	command -v $(RUSTFMT) >/dev/null && $(RUSTFMT) $@
 
 $(obj)/%.rsi: $(obj)/%.rs FORCE
 	+$(call if_changed_dep,rustc_rsi_rs)
 
 quiet_cmd_rustc_s_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_s_rs = $(rust_common_cmd) --emit=asm=$@ $<
+      cmd_rustc_s_rs = $(rust_common_cmd) --emit=asm=$@ $(abspath $<)
 
 $(obj)/%.s: $(obj)/%.rs FORCE
 	+$(call if_changed_dep,rustc_s_rs)
 
 quiet_cmd_rustc_ll_rs = $(RUSTC_OR_CLIPPY_QUIET) $(quiet_modtag) $@
-      cmd_rustc_ll_rs = $(rust_common_cmd) --emit=llvm-ir=$@ $<
+      cmd_rustc_ll_rs = $(rust_common_cmd) --emit=llvm-ir=$@ $(abspath $<)
 
 $(obj)/%.ll: $(obj)/%.rs FORCE
 	+$(call if_changed_dep,rustc_ll_rs)

From 1b14c93da8901355d0be9c943a77dd0cbf9a6fcb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Oct 2024 08:35:53 +0200
Subject: [PATCH 0828/1027] fixup! mux: apple dp crossbar: Support t602x DP
 cross bar variant

---
 drivers/mux/apple-display-crossbar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index cb172cf7815bb9..854c210c3e298f 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -405,7 +405,7 @@ static int apple_dpxbar_probe(struct platform_device *pdev)
 	return 0;
 }
 
-const static struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
+static const struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
 	.n_ufp = 2,
 	.tunable = 0,
 	.ops = &apple_dpxbar_ops,

From 2d3f0e0b0fe9d6fb8a356388eb1bd8aa50bd4c9d Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Oct 2024 08:35:53 +0200
Subject: [PATCH 0829/1027] fixup! mux: apple dp crossbar: Support t602x DP
 cross bar variant

---
 drivers/mux/apple-display-crossbar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index 854c210c3e298f..ccc9348fc3729f 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -411,7 +411,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t8103 = {
 	.ops = &apple_dpxbar_ops,
 };
 
-const static struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = {
+static const struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = {
 	.n_ufp = 4,
 	.tunable = 4278196325,
 	.ops = &apple_dpxbar_ops,

From 40f857aa74bf922971c636e2eb98433e8d65cd15 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Oct 2024 08:35:53 +0200
Subject: [PATCH 0830/1027] fixup! mux: apple dp crossbar: Support t602x DP
 cross bar variant

---
 drivers/mux/apple-display-crossbar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index ccc9348fc3729f..51d1a6d36678c1 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -417,7 +417,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t8112 = {
 	.ops = &apple_dpxbar_ops,
 };
 
-const static struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
+static const struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
 	.n_ufp = 9,
 	.tunable = 5,
 	.ops = &apple_dpxbar_ops,

From 0fc91dacba367da598fdb226057c5eab8ac55d45 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 15 Oct 2024 08:35:53 +0200
Subject: [PATCH 0831/1027] fixup! mux: apple dp crossbar: Support t602x DP
 cross bar variant

---
 drivers/mux/apple-display-crossbar.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/mux/apple-display-crossbar.c b/drivers/mux/apple-display-crossbar.c
index 51d1a6d36678c1..ecff67236a4350 100644
--- a/drivers/mux/apple-display-crossbar.c
+++ b/drivers/mux/apple-display-crossbar.c
@@ -423,7 +423,7 @@ static const struct apple_dpxbar_hw apple_dpxbar_hw_t6000 = {
 	.ops = &apple_dpxbar_ops,
 };
 
-const static struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = {
+static const struct apple_dpxbar_hw apple_dpxbar_hw_t6020 = {
 	.n_ufp = 9,
 	.ops = &apple_dpxbar_t602x_ops,
 };

From 8c85122b9472c8d161ec96c2d95ef1c9bf4913ba Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 20:17:33 +0900
Subject: [PATCH 0832/1027] rust: kernel: Add simple siphash abstraction

This allows Rust code to use the Hasher interface with the kernel
siphash implementation.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/helpers/helpers.c          |  1 +
 rust/helpers/siphash.c          |  9 ++++++++
 rust/kernel/lib.rs              |  1 +
 rust/kernel/siphash.rs          | 40 +++++++++++++++++++++++++++++++++
 5 files changed, 52 insertions(+)
 create mode 100644 rust/helpers/siphash.c
 create mode 100644 rust/kernel/siphash.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index b940a577733045..6d4b15144208c6 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -17,6 +17,7 @@
 #include <linux/mdio.h>
 #include <linux/phy.h>
 #include <linux/refcount.h>
+#include <linux/siphash.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/wait.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 173533616c917c..48e553071a8482 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -17,6 +17,7 @@
 #include "page.c"
 #include "refcount.c"
 #include "signal.c"
+#include "siphash.c"
 #include "slab.c"
 #include "spinlock.c"
 #include "task.c"
diff --git a/rust/helpers/siphash.c b/rust/helpers/siphash.c
new file mode 100644
index 00000000000000..1eed3989953fe8
--- /dev/null
+++ b/rust/helpers/siphash.c
@@ -0,0 +1,9 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/siphash.h>
+
+u64 rust_helper_siphash(const void *data, size_t len,
+			const siphash_key_t *key)
+{
+	    return siphash(data, len, key);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 5ea21d249168d6..c6908cf1753f6f 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -44,6 +44,7 @@ pub mod net;
 pub mod page;
 pub mod prelude;
 pub mod print;
+pub mod siphash;
 mod static_assert;
 #[doc(hidden)]
 pub mod std_vendor;
diff --git a/rust/kernel/siphash.rs b/rust/kernel/siphash.rs
new file mode 100644
index 00000000000000..2f14b57b589ca7
--- /dev/null
+++ b/rust/kernel/siphash.rs
@@ -0,0 +1,40 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! A core::hash::Hasher wrapper for the kernel siphash implementation.
+//!
+//! This module allows Rust code to use the kernel's siphash implementation
+//! to hash Rust objects.
+
+use core::hash::Hasher;
+
+/// A Hasher implementation that uses the kernel siphash implementation.
+#[derive(Default)]
+pub struct SipHasher {
+    // SipHash state is 4xu64, but the Linux implementation
+    // doesn't expose incremental hashing so let's just chain
+    // individual SipHash calls for now, which return a u64
+    // hash.
+    state: u64,
+}
+
+impl SipHasher {
+    /// Create a new SipHasher with zeroed state.
+    pub fn new() -> Self {
+        SipHasher { state: 0 }
+    }
+}
+
+impl Hasher for SipHasher {
+    fn finish(&self) -> u64 {
+        self.state
+    }
+
+    fn write(&mut self, bytes: &[u8]) {
+        let key = bindings::siphash_key_t {
+            key: [self.state, 0],
+        };
+
+        // SAFETY: Safe to call on a valid slice
+        self.state = unsafe { bindings::siphash(bytes.as_ptr() as *const _, bytes.len(), &key) };
+    }
+}

From 041f3818f208e4d885529fd65f81fa9e1495ffd8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 18:40:11 +0900
Subject: [PATCH 0833/1027] rust: sync: Add dummy LockClassKey implementation
 for !CONFIG_LOCKDEP

Lock classes aren't used without lockdep. The C side declares the key
as an empty struct in that case, but let's make it an explicit ZST in
Rust, implemented in a separate module. This allows us to more easily
guarantee that the lockdep code will be trivially optimized out without
CONFIG_LOCKDEP, including LockClassKey arguments that are passed around.

Depending on whether CONFIG_LOCKDEP is enabled or not, we then import
the real lockdep implementation or the dummy one.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync.rs            | 29 ++++++++---------------------
 rust/kernel/sync/lockdep.rs    | 27 +++++++++++++++++++++++++++
 rust/kernel/sync/no_lockdep.rs | 19 +++++++++++++++++++
 3 files changed, 54 insertions(+), 21 deletions(-)
 create mode 100644 rust/kernel/sync/lockdep.rs
 create mode 100644 rust/kernel/sync/no_lockdep.rs

diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
index 0ab20975a3b5db..4fc44c60d22c9a 100644
--- a/rust/kernel/sync.rs
+++ b/rust/kernel/sync.rs
@@ -5,38 +5,25 @@
 //! This module contains the kernel APIs related to synchronisation that have been ported or
 //! wrapped for usage by Rust code in the kernel.
 
-use crate::types::Opaque;
-
 mod arc;
 mod condvar;
 pub mod lock;
 mod locked_by;
 
+#[cfg(CONFIG_LOCKDEP)]
+mod lockdep;
+#[cfg(not(CONFIG_LOCKDEP))]
+mod no_lockdep;
+#[cfg(not(CONFIG_LOCKDEP))]
+use no_lockdep as lockdep;
+
 pub use arc::{Arc, ArcBorrow, UniqueArc};
 pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult};
 pub use lock::mutex::{new_mutex, Mutex};
 pub use lock::spinlock::{new_spinlock, SpinLock};
+pub use lockdep::LockClassKey;
 pub use locked_by::LockedBy;
 
-/// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
-#[repr(transparent)]
-pub struct LockClassKey(Opaque<bindings::lock_class_key>);
-
-// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and
-// provides its own synchronization.
-unsafe impl Sync for LockClassKey {}
-
-impl LockClassKey {
-    /// Creates a new lock class key.
-    pub const fn new() -> Self {
-        Self(Opaque::uninit())
-    }
-
-    pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
-        self.0.get()
-    }
-}
-
 impl Default for LockClassKey {
     fn default() -> Self {
         Self::new()
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
new file mode 100644
index 00000000000000..cb68b18dc0adde
--- /dev/null
+++ b/rust/kernel/sync/lockdep.rs
@@ -0,0 +1,27 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Lockdep utilities.
+//!
+//! This module abstracts the parts of the kernel lockdep API relevant to Rust
+//! modules, including lock classes.
+
+use crate::types::Opaque;
+
+/// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
+#[repr(transparent)]
+pub struct LockClassKey(Opaque<bindings::lock_class_key>);
+
+impl LockClassKey {
+    /// Creates a new lock class key.
+    pub const fn new() -> Self {
+        Self(Opaque::uninit())
+    }
+
+    pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
+        self.0.get()
+    }
+}
+
+// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and
+// provides its own synchronization.
+unsafe impl Sync for LockClassKey {}
diff --git a/rust/kernel/sync/no_lockdep.rs b/rust/kernel/sync/no_lockdep.rs
new file mode 100644
index 00000000000000..69d42e8d980123
--- /dev/null
+++ b/rust/kernel/sync/no_lockdep.rs
@@ -0,0 +1,19 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Dummy lockdep utilities.
+//!
+//! Takes the place of the `lockdep` module when lockdep is disabled.
+
+/// A dummy, zero-sized lock class.
+pub struct LockClassKey();
+
+impl LockClassKey {
+    /// Creates a new dummy lock class key.
+    pub const fn new() -> Self {
+        Self()
+    }
+
+    pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
+        core::ptr::null_mut()
+    }
+}

From 35aab78f6fd94bd395db1b6570733e5d62ce6990 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 19:38:57 +0900
Subject: [PATCH 0834/1027] rust: sync: Replace static LockClassKey refs with a
 pointer wrapper

We want to be able to handle dynamic lock class creation and using
pointers to things that aren't a real lock_class_key as lock classes.
Doing this by casting around Rust references is difficult without
accidentally invoking UB.

Instead, switch LockClassKey to being a raw pointer wrapper around a
lock_class_key, which means there is no UB possible on the Rust side
just by creating and consuming these objects. The C code also should
never actually dereference lock classes, only use their address
(possibly with an offset).

We still provide a dummy ZST version of this wrapper, to be used when
lockdep is disabled.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync.rs            |  8 ++++----
 rust/kernel/sync/condvar.rs    |  2 +-
 rust/kernel/sync/lock.rs       |  4 ++--
 rust/kernel/sync/lockdep.rs    | 27 ++++++++++++++++++++++-----
 rust/kernel/sync/no_lockdep.rs | 15 +++++++++++++--
 rust/kernel/workqueue.rs       |  2 +-
 6 files changed, 43 insertions(+), 15 deletions(-)

diff --git a/rust/kernel/sync.rs b/rust/kernel/sync.rs
index 4fc44c60d22c9a..45f883c221249b 100644
--- a/rust/kernel/sync.rs
+++ b/rust/kernel/sync.rs
@@ -21,10 +21,10 @@ pub use arc::{Arc, ArcBorrow, UniqueArc};
 pub use condvar::{new_condvar, CondVar, CondVarTimeoutResult};
 pub use lock::mutex::{new_mutex, Mutex};
 pub use lock::spinlock::{new_spinlock, SpinLock};
-pub use lockdep::LockClassKey;
+pub use lockdep::{LockClassKey, StaticLockClassKey};
 pub use locked_by::LockedBy;
 
-impl Default for LockClassKey {
+impl Default for StaticLockClassKey {
     fn default() -> Self {
         Self::new()
     }
@@ -35,8 +35,8 @@ impl Default for LockClassKey {
 #[macro_export]
 macro_rules! static_lock_class {
     () => {{
-        static CLASS: $crate::sync::LockClassKey = $crate::sync::LockClassKey::new();
-        &CLASS
+        static CLASS: $crate::sync::StaticLockClassKey = $crate::sync::StaticLockClassKey::new();
+        CLASS.key()
     }};
 }
 
diff --git a/rust/kernel/sync/condvar.rs b/rust/kernel/sync/condvar.rs
index 2b306afbe56d96..a90275a4a32933 100644
--- a/rust/kernel/sync/condvar.rs
+++ b/rust/kernel/sync/condvar.rs
@@ -102,7 +102,7 @@ unsafe impl Sync for CondVar {}
 
 impl CondVar {
     /// Constructs a new condvar initialiser.
-    pub fn new(name: &'static CStr, key: &'static LockClassKey) -> impl PinInit<Self> {
+    pub fn new(name: &'static CStr, key: LockClassKey) -> impl PinInit<Self> {
         pin_init!(Self {
             _pin: PhantomPinned,
             // SAFETY: `slot` is valid while the closure is called and both `name` and `key` have
diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index 4f43f3140e28b2..251ef617caa6ca 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -107,7 +107,7 @@ unsafe impl<T: ?Sized + Send, B: Backend> Sync for Lock<T, B> {}
 
 impl<T, B: Backend> Lock<T, B> {
     /// Constructs a new lock initialiser.
-    pub fn new(t: T, name: &'static CStr, key: &'static LockClassKey) -> impl PinInit<Self> {
+    pub fn new(t: T, name: &'static CStr, key: LockClassKey) -> impl PinInit<Self> {
         pin_init!(Self {
             data: UnsafeCell::new(t),
             _pin: PhantomPinned,
@@ -123,7 +123,7 @@ impl<T, B: Backend> Lock<T, B> {
     pub fn pin_init<E>(
         t: impl PinInit<T, E>,
         name: &'static CStr,
-        key: &'static LockClassKey,
+        key: LockClassKey,
     ) -> impl PinInit<Self, E>
     where
         E: core::convert::From<core::convert::Infallible>,
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
index cb68b18dc0adde..d8328f4275fb7b 100644
--- a/rust/kernel/sync/lockdep.rs
+++ b/rust/kernel/sync/lockdep.rs
@@ -9,19 +9,36 @@ use crate::types::Opaque;
 
 /// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
 #[repr(transparent)]
-pub struct LockClassKey(Opaque<bindings::lock_class_key>);
+pub struct StaticLockClassKey(Opaque<bindings::lock_class_key>);
 
-impl LockClassKey {
+impl StaticLockClassKey {
     /// Creates a new lock class key.
     pub const fn new() -> Self {
         Self(Opaque::uninit())
     }
 
+    /// Returns the lock class key reference for this static lock class.
+    pub const fn key(&self) -> LockClassKey {
+        LockClassKey(self.0.get())
+    }
+}
+
+// SAFETY: `bindings::lock_class_key` just represents an opaque memory location, and is never
+// actually dereferenced.
+unsafe impl Sync for StaticLockClassKey {}
+
+/// A reference to a lock class key. This is a raw pointer to a lock_class_key,
+/// which is required to have a static lifetime.
+#[derive(Copy, Clone)]
+pub struct LockClassKey(*mut bindings::lock_class_key);
+
+impl LockClassKey {
     pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
-        self.0.get()
+        self.0
     }
 }
 
-// SAFETY: `bindings::lock_class_key` is designed to be used concurrently from multiple threads and
-// provides its own synchronization.
+// SAFETY: `bindings::lock_class_key` just represents an opaque memory location, and is never
+// actually dereferenced.
+unsafe impl Send for LockClassKey {}
 unsafe impl Sync for LockClassKey {}
diff --git a/rust/kernel/sync/no_lockdep.rs b/rust/kernel/sync/no_lockdep.rs
index 69d42e8d980123..518ec0bf9a7d1e 100644
--- a/rust/kernel/sync/no_lockdep.rs
+++ b/rust/kernel/sync/no_lockdep.rs
@@ -5,14 +5,25 @@
 //! Takes the place of the `lockdep` module when lockdep is disabled.
 
 /// A dummy, zero-sized lock class.
-pub struct LockClassKey();
+pub struct StaticLockClassKey();
 
-impl LockClassKey {
+impl StaticLockClassKey {
     /// Creates a new dummy lock class key.
     pub const fn new() -> Self {
         Self()
     }
 
+    /// Returns the lock class key reference for this static lock class.
+    pub const fn key(&self) -> LockClassKey {
+        LockClassKey()
+    }
+}
+
+/// A dummy reference to a lock class key.
+#[derive(Copy, Clone)]
+pub struct LockClassKey();
+
+impl LockClassKey {
     pub(crate) fn as_ptr(&self) -> *mut bindings::lock_class_key {
         core::ptr::null_mut()
     }
diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 553a5cba2adcb5..7ae66a9f8a8abc 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -367,7 +367,7 @@ impl<T: ?Sized, const ID: u64> Work<T, ID> {
     /// Creates a new instance of [`Work`].
     #[inline]
     #[allow(clippy::new_ret_no_self)]
-    pub fn new(name: &'static CStr, key: &'static LockClassKey) -> impl PinInit<Self>
+    pub fn new(name: &'static CStr, key: LockClassKey) -> impl PinInit<Self>
     where
         T: WorkItem<ID>,
     {

From c6015d55ee992b745d12c08b4ab590633918fdf7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 20:21:07 +0900
Subject: [PATCH 0835/1027] rust: sync: Implement dynamic lockdep class
 creation

Using macros to create lock classes all over the place is unergonomic,
and makes it impossible to add new features that require lock classes to
code such as Arc<> without changing all callers.

Rust has the ability to track the caller's identity by file/line/column
number, and we can use that to dynamically generate lock classes
instead.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync/lockdep.rs    | 156 ++++++++++++++++++++++++++++++++-
 rust/kernel/sync/no_lockdep.rs |   8 ++
 2 files changed, 163 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
index d8328f4275fb7b..7c0cdd0e37ae5c 100644
--- a/rust/kernel/sync/lockdep.rs
+++ b/rust/kernel/sync/lockdep.rs
@@ -5,7 +5,20 @@
 //! This module abstracts the parts of the kernel lockdep API relevant to Rust
 //! modules, including lock classes.
 
-use crate::types::Opaque;
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*, vec_ext::VecExt},
+    c_str, fmt,
+    init::InPlaceInit,
+    new_mutex,
+    prelude::{Box, Result, Vec},
+    str::{CStr, CString},
+    sync::Mutex,
+    types::Opaque,
+};
+
+use core::hash::{Hash, Hasher};
+use core::pin::Pin;
+use core::sync::atomic::{AtomicPtr, Ordering};
 
 /// Represents a lockdep class. It's a wrapper around C's `lock_class_key`.
 #[repr(transparent)]
@@ -42,3 +55,144 @@ impl LockClassKey {
 // actually dereferenced.
 unsafe impl Send for LockClassKey {}
 unsafe impl Sync for LockClassKey {}
+
+// Location is 'static but not really, since module unloads will
+// invalidate existing static Locations within that module.
+// To avoid breakage, we maintain our own location struct which is
+// dynamically allocated on first reference. We store a hash of the
+// whole location (including the filename string), as well as the
+// line and column separately. The assumption is that this whole
+// struct is highly unlikely to ever collide with a reasonable
+// hash (this saves us from having to check the filename string
+// itself).
+#[derive(PartialEq, Debug)]
+struct LocationKey {
+    hash: u64,
+    line: u32,
+    column: u32,
+}
+
+struct DynLockClassKey {
+    key: Opaque<bindings::lock_class_key>,
+    loc: LocationKey,
+    name: CString,
+}
+
+impl LocationKey {
+    fn new(loc: &'static core::panic::Location<'static>) -> Self {
+        let mut hasher = crate::siphash::SipHasher::new();
+        loc.hash(&mut hasher);
+
+        LocationKey {
+            hash: hasher.finish(),
+            line: loc.line(),
+            column: loc.column(),
+        }
+    }
+}
+
+impl DynLockClassKey {
+    fn key(&'static self) -> LockClassKey {
+        LockClassKey(self.key.get())
+    }
+
+    fn name(&'static self) -> &CStr {
+        &self.name
+    }
+}
+
+const LOCK_CLASS_BUCKETS: usize = 1024;
+
+#[track_caller]
+fn caller_lock_class_inner() -> Result<&'static DynLockClassKey> {
+    // This is just a hack to make the below static array initialization work.
+    #[allow(clippy::declare_interior_mutable_const)]
+    const ATOMIC_PTR: AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>> =
+        AtomicPtr::new(core::ptr::null_mut());
+
+    #[allow(clippy::complexity)]
+    static LOCK_CLASSES: [AtomicPtr<Mutex<Vec<&'static DynLockClassKey>>>; LOCK_CLASS_BUCKETS] =
+        [ATOMIC_PTR; LOCK_CLASS_BUCKETS];
+
+    let loc = core::panic::Location::caller();
+    let loc_key = LocationKey::new(loc);
+
+    let index = (loc_key.hash % (LOCK_CLASS_BUCKETS as u64)) as usize;
+    let slot = &LOCK_CLASSES[index];
+
+    let mut ptr = slot.load(Ordering::Relaxed);
+    if ptr.is_null() {
+        let new_element = Box::pin_init(new_mutex!(Vec::new()), GFP_KERNEL)?;
+
+        // SAFETY: We never move out of this Box
+        let raw = Box::into_raw(unsafe { Pin::into_inner_unchecked(new_element) });
+
+        if slot
+            .compare_exchange(
+                core::ptr::null_mut(),
+                raw,
+                Ordering::Relaxed,
+                Ordering::Relaxed,
+            )
+            .is_err()
+        {
+            // SAFETY: We just got this pointer from `into_raw()`
+            unsafe { drop(Box::from_raw(raw)) };
+        }
+
+        ptr = slot.load(Ordering::Relaxed);
+        assert!(!ptr.is_null());
+    }
+
+    // SAFETY: This mutex was either just created above or previously allocated,
+    // and we never free these objects so the pointer is guaranteed to be valid.
+    let mut guard = unsafe { (*ptr).lock() };
+
+    for i in guard.iter() {
+        if i.loc == loc_key {
+            return Ok(i);
+        }
+    }
+
+    // We immediately leak the class, so it becomes 'static
+    let new_class = Box::leak(Box::new(
+        DynLockClassKey {
+            key: Opaque::zeroed(),
+            loc: loc_key,
+            name: CString::try_from_fmt(fmt!("{}:{}:{}", loc.file(), loc.line(), loc.column()))?,
+        },
+        GFP_KERNEL,
+    )?);
+
+    // SAFETY: This is safe to call with a pointer to a dynamically allocated lockdep key,
+    // and we never free the objects so it is safe to never unregister the key.
+    unsafe { bindings::lockdep_register_key(new_class.key.get()) };
+
+    guard.push(new_class, GFP_KERNEL)?;
+
+    Ok(new_class)
+}
+
+#[track_caller]
+pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
+    match caller_lock_class_inner() {
+        Ok(a) => (a.key(), a.name()),
+        Err(_) => {
+            crate::pr_err!(
+                "Failed to dynamically allocate lock class, lockdep may be unreliable.\n"
+            );
+
+            let loc = core::panic::Location::caller();
+            // SAFETY: LockClassKey is opaque and the lockdep implementation only needs
+            // unique addresses for statically allocated keys, so it is safe to just cast
+            // the Location reference directly into a LockClassKey. However, this will
+            // result in multiple keys for the same callsite due to monomorphization,
+            // as well as spuriously destroyed keys when the static key is allocated in
+            // the wrong module, which is what makes this unreliable.
+            (
+                LockClassKey(loc as *const _ as *mut _),
+                c_str!("fallback_lock_class"),
+            )
+        }
+    }
+}
diff --git a/rust/kernel/sync/no_lockdep.rs b/rust/kernel/sync/no_lockdep.rs
index 518ec0bf9a7d1e..de53c4de7fbe53 100644
--- a/rust/kernel/sync/no_lockdep.rs
+++ b/rust/kernel/sync/no_lockdep.rs
@@ -4,6 +4,8 @@
 //!
 //! Takes the place of the `lockdep` module when lockdep is disabled.
 
+use crate::{c_str, str::CStr};
+
 /// A dummy, zero-sized lock class.
 pub struct StaticLockClassKey();
 
@@ -28,3 +30,9 @@ impl LockClassKey {
         core::ptr::null_mut()
     }
 }
+
+pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
+    static DUMMY_LOCK_CLASS: StaticLockClassKey = StaticLockClassKey::new();
+
+    (DUMMY_LOCK_CLASS.key(), c_str!("dummy"))
+}

From 0b349858c6bff766df32903be00e036d4b1aea49 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 20:23:02 +0900
Subject: [PATCH 0836/1027] rust: sync: Classless Lock::new() and pin_init()

Use the new automagic lock class code to remove the lock class and name
parameters from Lock::new() and Lock::pin_init(). The old functions
are renamed to new_with_class() and pin_init_with_class() respectively.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync/lock.rs          | 42 ++++++++++++++++++++++++++++---
 rust/kernel/sync/lock/mutex.rs    |  4 +--
 rust/kernel/sync/lock/spinlock.rs |  2 +-
 3 files changed, 41 insertions(+), 7 deletions(-)

diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index 251ef617caa6ca..1a9e99fcd6eba2 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -5,7 +5,7 @@
 //! It contains a generic Rust lock and guard that allow for different backends (e.g., mutexes,
 //! spinlocks, raw spinlocks) to be provided with minimal effort.
 
-use super::LockClassKey;
+use super::{lockdep::caller_lock_class, LockClassKey};
 use crate::{init::PinInit, pin_init, str::CStr, try_pin_init, types::Opaque, types::ScopeGuard};
 use core::{cell::UnsafeCell, marker::PhantomData, marker::PhantomPinned};
 use macros::pin_data;
@@ -107,7 +107,40 @@ unsafe impl<T: ?Sized + Send, B: Backend> Sync for Lock<T, B> {}
 
 impl<T, B: Backend> Lock<T, B> {
     /// Constructs a new lock initialiser.
-    pub fn new(t: T, name: &'static CStr, key: LockClassKey) -> impl PinInit<Self> {
+    #[track_caller]
+    pub fn new(t: T) -> impl PinInit<Self> {
+        let (key, name) = caller_lock_class();
+        Self::new_with_key(t, name, key)
+    }
+
+    /// Constructs a new lock initialiser taking an initialiser/
+    pub fn pin_init<E>(t: impl PinInit<T, E>) -> impl PinInit<Self, E>
+    where
+        E: core::convert::From<core::convert::Infallible>,
+    {
+        let (key, name) = caller_lock_class();
+        Self::pin_init_with_key(t, name, key)
+    }
+
+    /// Constructs a new lock initialiser.
+    #[allow(clippy::new_ret_no_self)]
+    #[track_caller]
+    pub fn new_named(t: T, name: &'static CStr) -> impl PinInit<Self> {
+        let (key, _) = caller_lock_class();
+        Self::new_with_key(t, name, key)
+    }
+
+    /// Constructs a new lock initialiser taking an initialiser/
+    pub fn pin_init_named<E>(t: impl PinInit<T, E>, name: &'static CStr) -> impl PinInit<Self, E>
+    where
+        E: core::convert::From<core::convert::Infallible>,
+    {
+        let (key, _) = caller_lock_class();
+        Self::pin_init_with_key(t, name, key)
+    }
+
+    /// Constructs a new lock initialiser given a particular name and lock class key.
+    pub fn new_with_key(t: T, name: &'static CStr, key: LockClassKey) -> impl PinInit<Self> {
         pin_init!(Self {
             data: UnsafeCell::new(t),
             _pin: PhantomPinned,
@@ -119,8 +152,9 @@ impl<T, B: Backend> Lock<T, B> {
         })
     }
 
-    /// Constructs a new lock initialiser taking an initialiser.
-    pub fn pin_init<E>(
+    /// Constructs a new lock initialiser taking an initialiser given a particular
+    /// name and lock class key.
+    pub fn pin_init_with_key<E>(
         t: impl PinInit<T, E>,
         name: &'static CStr,
         key: LockClassKey,
diff --git a/rust/kernel/sync/lock/mutex.rs b/rust/kernel/sync/lock/mutex.rs
index 14725b5d16203c..9d2402eeb7bfb1 100644
--- a/rust/kernel/sync/lock/mutex.rs
+++ b/rust/kernel/sync/lock/mutex.rs
@@ -11,7 +11,7 @@
 #[macro_export]
 macro_rules! new_mutex {
     ($inner:expr $(, $name:literal)? $(,)?) => {
-        $crate::sync::Mutex::new(
+        $crate::sync::Mutex::new_with_key(
             $inner, $crate::optional_name!($($name)?), $crate::static_lock_class!())
     };
 }
@@ -25,7 +25,7 @@ pub use new_mutex;
 #[macro_export]
 macro_rules! new_mutex_pinned {
     ($inner:expr $(, $name:literal)? $(,)?) => {
-        $crate::sync::Mutex::pin_init(
+        $crate::sync::Mutex::pin_init_with_key(
             $inner, $crate::optional_name!($($name)?), $crate::static_lock_class!())
     };
 }
diff --git a/rust/kernel/sync/lock/spinlock.rs b/rust/kernel/sync/lock/spinlock.rs
index ea5c5bc1ce12ed..de19b6a98ca957 100644
--- a/rust/kernel/sync/lock/spinlock.rs
+++ b/rust/kernel/sync/lock/spinlock.rs
@@ -11,7 +11,7 @@
 #[macro_export]
 macro_rules! new_spinlock {
     ($inner:expr $(, $name:literal)? $(,)?) => {
-        $crate::sync::SpinLock::new(
+        $crate::sync::SpinLock::new_with_class(
             $inner, $crate::optional_name!($($name)?), $crate::static_lock_class!())
     };
 }

From ee0d0a7a31e792c45732f4382e90fa953376fb54 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 20:34:22 +0900
Subject: [PATCH 0837/1027] rust: init: Update documentation for new mutex init
 style

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init.rs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index 0f4a954ca7af55..f6dfe68e74ff9a 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -46,7 +46,7 @@
 //! }
 //!
 //! let foo = pin_init!(Foo {
-//!     a <- new_mutex!(42, "Foo::a"),
+//!     a <- Mutex::new_named(42, "Foo::a"),
 //!     b: 24,
 //! });
 //! ```
@@ -65,7 +65,7 @@
 //! #     b: u32,
 //! # }
 //! # let foo = pin_init!(Foo {
-//! #     a <- new_mutex!(42, "Foo::a"),
+//! #     a <- Mutex::new_named(42, "Foo::a"),
 //! #     b: 24,
 //! # });
 //! let foo: Result<Pin<Box<Foo>>> = Box::pin_init(foo, GFP_KERNEL);
@@ -99,7 +99,7 @@
 //! impl DriverData {
 //!     fn new() -> impl PinInit<Self, Error> {
 //!         try_pin_init!(Self {
-//!             status <- new_mutex!(0, "DriverData::status"),
+//!             status <- Mutex::new_named(0, "DriverData::status"),
 //!             buffer: Box::init(kernel::init::zeroed(), GFP_KERNEL)?,
 //!         })
 //!     }
@@ -253,7 +253,7 @@ pub mod macros;
 /// }
 ///
 /// stack_pin_init!(let foo = pin_init!(Foo {
-///     a <- new_mutex!(42),
+///     a <- Mutex::new(42),
 ///     b: Bar {
 ///         x: 64,
 ///     },
@@ -305,7 +305,7 @@ macro_rules! stack_pin_init {
 /// }
 ///
 /// stack_try_pin_init!(let foo: Result<Pin<&mut Foo>, AllocError> = pin_init!(Foo {
-///     a <- new_mutex!(42),
+///     a <- Mutex::new(42),
 ///     b: Box::new(Bar {
 ///         x: 64,
 ///     }, GFP_KERNEL)?,
@@ -331,7 +331,7 @@ macro_rules! stack_pin_init {
 /// }
 ///
 /// stack_try_pin_init!(let foo: Pin<&mut Foo> =? pin_init!(Foo {
-///     a <- new_mutex!(42),
+///     a <- Mutex::new(42),
 ///     b: Box::new(Bar {
 ///         x: 64,
 ///     }, GFP_KERNEL)?,

From c1a2a2c77bed9a1741e5b3fa40b1c048dc6d52c8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 21:21:06 +0900
Subject: [PATCH 0838/1027] rust: sync: Add LockdepMap abstraction

This allows Rust code to explicitly integrate types with lockdep.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/helpers/helpers.c          |  1 +
 rust/helpers/lockdep.c          | 16 ++++++++++++
 rust/kernel/sync/lockdep.rs     | 43 +++++++++++++++++++++++++++++++++
 4 files changed, 61 insertions(+)
 create mode 100644 rust/helpers/lockdep.c

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6d4b15144208c6..70898ddb69b56e 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -16,6 +16,7 @@
 #include <linux/jiffies.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
+#include <linux/lockdep.h>
 #include <linux/refcount.h>
 #include <linux/siphash.h>
 #include <linux/sched.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 48e553071a8482..a3b5ac2acecea4 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -13,6 +13,7 @@
 #include "build_bug.c"
 #include "err.c"
 #include "kunit.c"
+#include "lockdep.c"
 #include "mutex.c"
 #include "page.c"
 #include "refcount.c"
diff --git a/rust/helpers/lockdep.c b/rust/helpers/lockdep.c
new file mode 100644
index 00000000000000..c3178001f3a5cc
--- /dev/null
+++ b/rust/helpers/lockdep.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/instruction_pointer.h>
+#include <linux/lockdep.h>
+
+void rust_helper_lock_acquire_ret(struct lockdep_map *lock, unsigned int subclass,
+				  int trylock, int read, int check,
+				  struct lockdep_map *nest_lock)
+{
+	lock_acquire(lock, subclass, trylock, read, check, nest_lock, _RET_IP_);
+}
+
+void rust_helper_lock_release_ret(struct lockdep_map *lock)
+{
+	lock_release(lock, _RET_IP_);
+}
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
index 7c0cdd0e37ae5c..0b480ab35a4ba0 100644
--- a/rust/kernel/sync/lockdep.rs
+++ b/rust/kernel/sync/lockdep.rs
@@ -196,3 +196,46 @@ pub(crate) fn caller_lock_class() -> (LockClassKey, &'static CStr) {
         }
     }
 }
+
+pub(crate) struct LockdepMap(Opaque<bindings::lockdep_map>);
+pub(crate) struct LockdepGuard<'a>(&'a LockdepMap);
+
+#[allow(dead_code)]
+impl LockdepMap {
+    #[track_caller]
+    pub(crate) fn new() -> Self {
+        let map = Opaque::uninit();
+        let (key, name) = caller_lock_class();
+
+        // SAFETY: Just calling the C API
+        unsafe {
+            bindings::lockdep_init_map_type(
+                map.get(),
+                name.as_char_ptr(),
+                key.as_ptr(),
+                0,
+                bindings::lockdep_wait_type_LD_WAIT_INV as _,
+                bindings::lockdep_wait_type_LD_WAIT_INV as _,
+                bindings::lockdep_lock_type_LD_LOCK_NORMAL as _,
+            )
+        };
+
+        LockdepMap(map)
+    }
+
+    #[inline(always)]
+    pub(crate) fn lock(&self) -> LockdepGuard<'_> {
+        // SAFETY: Just calling the C API
+        unsafe { bindings::lock_acquire_ret(self.0.get(), 0, 0, 1, 1, core::ptr::null_mut()) };
+
+        LockdepGuard(self)
+    }
+}
+
+impl<'a> Drop for LockdepGuard<'a> {
+    #[inline(always)]
+    fn drop(&mut self) {
+        // SAFETY: Just calling the C API
+        unsafe { bindings::lock_release_ret(self.0 .0.get()) };
+    }
+}

From 7437136deda5fe26b476ec4bd0af4e1691883d67 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 May 2023 21:22:03 +0900
Subject: [PATCH 0839/1027] rust: sync: arc: Add lockdep integration

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 lib/Kconfig.debug       |  8 +++++
 rust/kernel/init.rs     |  6 ++++
 rust/kernel/sync/arc.rs | 77 +++++++++++++++++++++++++++++++++++++++--
 3 files changed, 88 insertions(+), 3 deletions(-)

diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug
index a30c03a6617263..a1ccd363c084be 100644
--- a/lib/Kconfig.debug
+++ b/lib/Kconfig.debug
@@ -3048,6 +3048,14 @@ config RUST_KERNEL_DOCTESTS
 
 	  If unsure, say N.
 
+config RUST_EXTRA_LOCKDEP
+	bool "Extra lockdep checking"
+	depends on RUST && PROVE_LOCKING
+	help
+	  Enabled additional lockdep integration with certain Rust types.
+
+	  If unsure, say N.
+
 endmenu # "Rust"
 
 endmenu # Kernel hacking
diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index f6dfe68e74ff9a..83130c22dd0edc 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -1111,6 +1111,7 @@ pub trait InPlaceInit<T>: Sized {
     /// type.
     ///
     /// If `T: !Unpin` it will not be able to move afterwards.
+    #[track_caller]
     fn try_pin_init<E>(init: impl PinInit<T, E>, flags: Flags) -> Result<Pin<Self>, E>
     where
         E: From<AllocError>;
@@ -1119,6 +1120,7 @@ pub trait InPlaceInit<T>: Sized {
     /// type.
     ///
     /// If `T: !Unpin` it will not be able to move afterwards.
+    #[track_caller]
     fn pin_init<E>(init: impl PinInit<T, E>, flags: Flags) -> error::Result<Pin<Self>>
     where
         Error: From<E>,
@@ -1131,11 +1133,13 @@ pub trait InPlaceInit<T>: Sized {
     }
 
     /// Use the given initializer to in-place initialize a `T`.
+    #[track_caller]
     fn try_init<E>(init: impl Init<T, E>, flags: Flags) -> Result<Self, E>
     where
         E: From<AllocError>;
 
     /// Use the given initializer to in-place initialize a `T`.
+    #[track_caller]
     fn init<E>(init: impl Init<T, E>, flags: Flags) -> error::Result<Self>
     where
         Error: From<E>,
@@ -1180,6 +1184,7 @@ impl<T> InPlaceInit<T> for Box<T> {
 
 impl<T> InPlaceInit<T> for UniqueArc<T> {
     #[inline]
+    #[track_caller]
     fn try_pin_init<E>(init: impl PinInit<T, E>, flags: Flags) -> Result<Pin<Self>, E>
     where
         E: From<AllocError>,
@@ -1194,6 +1199,7 @@ impl<T> InPlaceInit<T> for UniqueArc<T> {
     }
 
     #[inline]
+    #[track_caller]
     fn try_init<E>(init: impl Init<T, E>, flags: Flags) -> Result<Self, E>
     where
         E: From<AllocError>,
diff --git a/rust/kernel/sync/arc.rs b/rust/kernel/sync/arc.rs
index 3673496c236300..fa740f24352a00 100644
--- a/rust/kernel/sync/arc.rs
+++ b/rust/kernel/sync/arc.rs
@@ -34,6 +34,9 @@ use core::{
 };
 use macros::pin_data;
 
+#[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+use crate::sync::lockdep::LockdepMap;
+
 mod std_vendor;
 
 /// A reference-counted pointer to an instance of `T`.
@@ -130,6 +133,17 @@ pub struct Arc<T: ?Sized> {
     _p: PhantomData<ArcInner<T>>,
 }
 
+#[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+#[pin_data]
+#[repr(C)]
+struct ArcInner<T: ?Sized> {
+    refcount: Opaque<bindings::refcount_t>,
+    lockdep_map: LockdepMap,
+    data: T,
+}
+
+// FIXME: pin_data does not work well with cfg attributes within the struct definition.
+#[cfg(not(CONFIG_RUST_EXTRA_LOCKDEP))]
 #[pin_data]
 #[repr(C)]
 struct ArcInner<T: ?Sized> {
@@ -195,11 +209,14 @@ unsafe impl<T: ?Sized + Sync + Send> Sync for Arc<T> {}
 
 impl<T> Arc<T> {
     /// Constructs a new reference counted instance of `T`.
+    #[track_caller]
     pub fn new(contents: T, flags: Flags) -> Result<Self, AllocError> {
         // INVARIANT: The refcount is initialised to a non-zero value.
         let value = ArcInner {
             // SAFETY: There are no safety requirements for this FFI call.
             refcount: Opaque::new(unsafe { bindings::REFCOUNT_INIT(1) }),
+            #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+            lockdep_map: LockdepMap::new(),
             data: contents,
         };
 
@@ -214,6 +231,7 @@ impl<T> Arc<T> {
     ///
     /// If `T: !Unpin` it will not be able to move afterwards.
     #[inline]
+    #[track_caller]
     pub fn pin_init<E>(init: impl PinInit<T, E>, flags: Flags) -> error::Result<Self>
     where
         Error: From<E>,
@@ -225,6 +243,7 @@ impl<T> Arc<T> {
     ///
     /// This is equivalent to [`Arc<T>::pin_init`], since an [`Arc`] is always pinned.
     #[inline]
+    #[track_caller]
     pub fn init<E>(init: impl Init<T, E>, flags: Flags) -> error::Result<Self>
     where
         Error: From<E>,
@@ -415,15 +434,50 @@ impl<T: ?Sized> Drop for Arc<T> {
         // freed/invalid memory as long as it is never dereferenced.
         let refcount = unsafe { self.ptr.as_ref() }.refcount.get();
 
+        #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+        // SAFETY: By the type invariant, there is necessarily a reference to the object.
+        // We cannot hold the map lock across the reference decrement, as we might race
+        // another thread. Therefore, we lock and immediately drop the guard here. This
+        // only serves to inform lockdep of the dependency up the call stack.
+        unsafe { self.ptr.as_ref() }.lockdep_map.lock();
+
         // INVARIANT: If the refcount reaches zero, there are no other instances of `Arc`, and
         // this instance is being dropped, so the broken invariant is not observable.
         // SAFETY: Also by the type invariant, we are allowed to decrement the refcount.
         let is_zero = unsafe { bindings::refcount_dec_and_test(refcount) };
+
         if is_zero {
             // The count reached zero, we must free the memory.
-            //
-            // SAFETY: The pointer was initialised from the result of `Box::leak`.
-            unsafe { drop(Box::from_raw(self.ptr.as_ptr())) };
+
+            #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+            // SAFETY: If we get this far, we had the last reference to the object.
+            // That means we are responsible for freeing it, so we can safely lock
+            // the fake lock again. This wraps dropping the inner object, which
+            // informs lockdep of the dependencies down the call stack.
+            let guard = unsafe { self.ptr.as_ref() }.lockdep_map.lock();
+
+            // SAFETY: The pointer was initialised from the result of `Box::leak`,
+            // and the value is valid.
+            unsafe { core::ptr::drop_in_place(&mut self.ptr.as_mut().data) };
+
+            // We need to drop the lock guard before freeing the LockdepMap itself
+            #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+            core::mem::drop(guard);
+
+            #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+            // SAFETY: The pointer was initialised from the result of `Box::leak`,
+            // and the lockdep map is valid.
+            unsafe {
+                core::ptr::drop_in_place(&mut self.ptr.as_mut().lockdep_map)
+            };
+
+            // SAFETY: The pointer was initialised from the result of `Box::leak`, and
+            // a ManuallyDrop<T> is compatible. We already dropped the contents above.
+            unsafe {
+                drop(Box::from_raw(
+                    self.ptr.as_ptr() as *mut ManuallyDrop<ArcInner<T>>
+                ))
+            };
         }
     }
 }
@@ -658,6 +712,7 @@ pub struct UniqueArc<T: ?Sized> {
 
 impl<T> UniqueArc<T> {
     /// Tries to allocate a new [`UniqueArc`] instance.
+    #[track_caller]
     pub fn new(value: T, flags: Flags) -> Result<Self, AllocError> {
         Ok(Self {
             // INVARIANT: The newly-created object has a refcount of 1.
@@ -666,8 +721,24 @@ impl<T> UniqueArc<T> {
     }
 
     /// Tries to allocate a new [`UniqueArc`] instance whose contents are not initialised yet.
+    #[track_caller]
     pub fn new_uninit(flags: Flags) -> Result<UniqueArc<MaybeUninit<T>>, AllocError> {
         // INVARIANT: The refcount is initialised to a non-zero value.
+        #[cfg(CONFIG_RUST_EXTRA_LOCKDEP)]
+        let inner = {
+            let map = LockdepMap::new();
+            Box::try_init::<AllocError>(
+                try_init!(ArcInner {
+                // SAFETY: There are no safety requirements for this FFI call.
+                refcount: Opaque::new(unsafe { bindings::REFCOUNT_INIT(1) }),
+                lockdep_map: map,
+                data <- init::uninit::<T, AllocError>(),
+            }? AllocError),
+                flags,
+            )?
+        };
+        // FIXME: try_init!() does not work with cfg attributes.
+        #[cfg(not(CONFIG_RUST_EXTRA_LOCKDEP))]
         let inner = Box::try_init::<AllocError>(
             try_init!(ArcInner {
                 // SAFETY: There are no safety requirements for this FFI call.

From 53cf15f5308092f6d796f3be93d26765b81c8d95 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 19 Oct 2022 20:56:03 +0900
Subject: [PATCH 0840/1027] rust: time: New module for timekeeping functions

This module is intended to contain functions related to kernel
timekeeping and time. Initially, this just wraps ktime_get() and
ktime_get_boottime() and returns them as core::time::Duration instances.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   2 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/timekeeping.c      |  15 ++++
 rust/kernel/time.rs             | 151 +++++++++++++++++++++++++++++++-
 4 files changed, 168 insertions(+), 1 deletion(-)
 create mode 100644 rust/helpers/timekeeping.c

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 70898ddb69b56e..948a3e7e843e8f 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -16,11 +16,13 @@
 #include <linux/jiffies.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
+#include <linux/ktime.h>
 #include <linux/lockdep.h>
 #include <linux/refcount.h>
 #include <linux/siphash.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/timekeeping.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a3b5ac2acecea4..6b7fd0504fb292 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -22,6 +22,7 @@
 #include "slab.c"
 #include "spinlock.c"
 #include "task.c"
+#include "timekeeping.c"
 #include "uaccess.c"
 #include "wait.c"
 #include "workqueue.c"
diff --git a/rust/helpers/timekeeping.c b/rust/helpers/timekeeping.c
new file mode 100644
index 00000000000000..6c130e845dcee0
--- /dev/null
+++ b/rust/helpers/timekeeping.c
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/timekeeping.h>
+
+ktime_t rust_helper_ktime_get_real(void) {
+	return ktime_get_real();
+}
+
+ktime_t rust_helper_ktime_get_boottime(void) {
+	return ktime_get_boottime();
+}
+
+ktime_t rust_helper_ktime_get_clocktai(void) {
+	return ktime_get_clocktai();
+}
diff --git a/rust/kernel/time.rs b/rust/kernel/time.rs
index e3bb5e89f88dac..3dcb58fccda44b 100644
--- a/rust/kernel/time.rs
+++ b/rust/kernel/time.rs
@@ -1,12 +1,13 @@
 // SPDX-License-Identifier: GPL-2.0
 
-//! Time related primitives.
+//! Time related primitives and functions.
 //!
 //! This module contains the kernel APIs related to time and timers that
 //! have been ported or wrapped for usage by Rust code in the kernel.
 //!
 //! C header: [`include/linux/jiffies.h`](srctree/include/linux/jiffies.h).
 //! C header: [`include/linux/ktime.h`](srctree/include/linux/ktime.h).
+//! C header: [`include/linux/timekeeping.h`](srctree/include/linux/timekeeping.h)
 
 /// The number of nanoseconds per millisecond.
 pub const NSEC_PER_MSEC: i64 = bindings::NSEC_PER_MSEC as i64;
@@ -81,3 +82,151 @@ impl core::ops::Sub for Ktime {
         }
     }
 }
+
+use crate::{bindings, pr_err};
+use core::marker::PhantomData;
+use core::time::Duration;
+
+/// Represents a clock, that is, a unique time source.
+pub trait Clock: Sized {}
+
+/// A time source that can be queried for the current time.
+pub trait Now: Clock {
+    /// Returns the current time for this clock.
+    fn now() -> Instant<Self>;
+}
+
+/// Marker trait for clock sources that are guaranteed to be monotonic.
+pub trait Monotonic {}
+
+/// Marker trait for clock sources that represent a calendar (wall clock)
+/// relative to the UNIX epoch.
+pub trait WallTime {}
+
+/// An instant in time associated with a given clock source.
+#[derive(Debug)]
+pub struct Instant<T: Clock> {
+    nanoseconds: i64,
+    _type: PhantomData<T>,
+}
+
+impl<T: Clock> Clone for Instant<T> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<T: Clock> Copy for Instant<T> {}
+
+impl<T: Clock> Instant<T> {
+    fn new(nanoseconds: i64) -> Self {
+        Instant {
+            nanoseconds,
+            _type: PhantomData,
+        }
+    }
+
+    /// Returns the time elapsed since an earlier Instant<t>, or
+    /// None if the argument is a later Instant.
+    pub fn since(&self, earlier: Instant<T>) -> Option<Duration> {
+        if earlier.nanoseconds > self.nanoseconds {
+            None
+        } else {
+            // Casting to u64 and subtracting is guaranteed to give the right
+            // result for all inputs, as long as the condition we checked above
+            // holds.
+            Some(Duration::from_nanos(
+                self.nanoseconds as u64 - earlier.nanoseconds as u64,
+            ))
+        }
+    }
+}
+
+impl<T: Clock + Now + Monotonic> Instant<T> {
+    /// Returns the time elapsed since this Instant<T>.
+    ///
+    /// This is guaranteed to return a positive result, since
+    /// it is only implemented for monotonic clocks.
+    pub fn elapsed(&self) -> Duration {
+        T::now().since(*self).unwrap_or_else(|| {
+            pr_err!(
+                "Monotonic clock {} went backwards!",
+                core::any::type_name::<T>()
+            );
+            Duration::ZERO
+        })
+    }
+}
+
+/// Contains the various clock source types available to the kernel.
+pub mod clock {
+    use super::*;
+
+    /// A clock representing the default kernel time source.
+    ///
+    /// This is `CLOCK_MONOTONIC` (though it is not the only
+    /// monotonic clock) and also the default clock used by
+    /// `ktime_get()` in the C API.
+    ///
+    /// This is like `BootTime`, but does not include time
+    /// spent sleeping.
+
+    pub struct KernelTime;
+
+    impl Clock for KernelTime {}
+    impl Monotonic for KernelTime {}
+    impl Now for KernelTime {
+        fn now() -> Instant<Self> {
+            // SAFETY: Always safe to call
+            Instant::<Self>::new(unsafe { bindings::ktime_get() })
+        }
+    }
+
+    /// A clock representing the time elapsed since boot.
+    ///
+    /// This is `CLOCK_MONOTONIC` (though it is not the only
+    /// monotonic clock) and also the default clock used by
+    /// `ktime_get()` in the C API.
+    ///
+    /// This is like `KernelTime`, but does include time
+    /// spent sleeping.
+    pub struct BootTime;
+
+    impl Clock for BootTime {}
+    impl Monotonic for BootTime {}
+    impl Now for BootTime {
+        fn now() -> Instant<Self> {
+            // SAFETY: Always safe to call
+            Instant::<Self>::new(unsafe { bindings::ktime_get_boottime() })
+        }
+    }
+
+    /// A clock representing TAI time.
+    ///
+    /// This clock is not monotonic and can be changed from userspace.
+    /// However, it is not affected by leap seconds.
+    pub struct TaiTime;
+
+    impl Clock for TaiTime {}
+    impl WallTime for TaiTime {}
+    impl Now for TaiTime {
+        fn now() -> Instant<Self> {
+            // SAFETY: Always safe to call
+            Instant::<Self>::new(unsafe { bindings::ktime_get_clocktai() })
+        }
+    }
+
+    /// A clock representing wall clock time.
+    ///
+    /// This clock is not monotonic and can be changed from userspace.
+    pub struct RealTime;
+
+    impl Clock for RealTime {}
+    impl WallTime for RealTime {}
+    impl Now for RealTime {
+        fn now() -> Instant<Self> {
+            // SAFETY: Always safe to call
+            Instant::<Self>::new(unsafe { bindings::ktime_get_real() })
+        }
+    }
+}

From afab7674036a593586007d2ede199fb91c9b1862 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 11 Jan 2023 19:54:58 +0900
Subject: [PATCH 0841/1027] rust: xarray: Add an abstraction for XArray

The XArray is an abstract data type which behaves like a very large
array of pointers. Add a Rust abstraction for this data type.

The initial implementation uses explicit locking on get operations and
returns a guard which blocks mutation, ensuring that the referenced
object remains alive. To avoid excessive serialization, users are
expected to use an inner type that can be efficiently cloned (such as
Arc<T>), and eagerly clone and drop the guard to unblock other users
after a lookup.

Future variants may support using RCU instead to avoid mutex locking.

This abstraction also introduces a reservation mechanism, which can be
used by alloc-capable XArrays to reserve a free slot without immediately
filling it, and then do so at a later time. If the reservation is
dropped without being filled, the slot is freed again for other users,
which eliminates the need for explicit cleanup code.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---

Hi everyone!

This abstraction is part of the set of dependencies for the drm/asahi
Apple M1/M2 GPU driver.

The branch at [1] contains the full series of patches rebased on
upstream leading to the complete driver, for reference on how it is
intended to be used.

Thank you everyone who helped review this on GitHub [2]! I hope I didn't
forget any CCs...

Note that I dropped the convenience `Deref` impl for `Guard`, since I
couldn't figure out how to do it safely. Suggestions welcome, or we can
leave it for a future improvement ^^

[1] https://github.com/AsahiLinux/linux/tree/gpu/rebase-20230224
[2] https://github.com/Rust-for-Linux/linux/pull/952

Changes in v3:
- Updated to the error v2/v3 series API.
- Renamed `err` to `ret` for consistency with the other instance.
- Link to v2: https://lore.kernel.org/r/20230224-rust-xarray-v2-1-4eeb0134944c@asahilina.net
Changes in v2:
- Added Pin requirement for all XArray operations, to close a
  soundness hole due to the lock in the XArray (locks are not safe to
  move while locked). Creation does not require pinning in place, since
  the lock cannot be acquired at that point.
- Added safety note to Drop impl about why we don't need to do the lock
  unlock dance to ensure soundness in case of a dropped lock guard.
- Downstream drm/asahi driver was also rebased on this version to prove
  it works (previously it was still on a pre-v1 version).
- This still depends on the Error series (v1). v2 of that will need a
  trivial rename of Error::from_kernel_errno -> Error::from_errno. If
  this version of XArray ends up looking good, I'll send a trivial v4 of
  XArray with the rename, after sending the v2 of the Error series.
- Link to v1: https://lore.kernel.org/r/20230224-rust-xarray-v1-1-80f0904ce5d3@asahilina.net
---
 rust/bindings/bindings_helper.h |  17 ++
 rust/helpers/helpers.c          |   1 +
 rust/helpers/xarray.c           |  34 ++++
 rust/kernel/lib.rs              |   1 +
 rust/kernel/xarray.rs           | 339 ++++++++++++++++++++++++++++++++
 5 files changed, 392 insertions(+)
 create mode 100644 rust/helpers/xarray.c
 create mode 100644 rust/kernel/xarray.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 948a3e7e843e8f..27761c4caf71a2 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -25,6 +25,7 @@
 #include <linux/timekeeping.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
+#include <linux/xarray.h>
 
 /* `bindgen` gets confused at certain things. */
 const size_t RUST_CONST_HELPER_ARCH_SLAB_MINALIGN = ARCH_SLAB_MINALIGN;
@@ -36,3 +37,19 @@ const gfp_t RUST_CONST_HELPER_GFP_NOWAIT = GFP_NOWAIT;
 const gfp_t RUST_CONST_HELPER___GFP_ZERO = __GFP_ZERO;
 const gfp_t RUST_CONST_HELPER___GFP_HIGHMEM = ___GFP_HIGHMEM;
 const blk_features_t RUST_CONST_HELPER_BLK_FEAT_ROTATIONAL = BLK_FEAT_ROTATIONAL;
+
+const gfp_t BINDINGS_XA_FLAGS_LOCK_IRQ = XA_FLAGS_LOCK_IRQ;
+const gfp_t BINDINGS_XA_FLAGS_LOCK_BH = XA_FLAGS_LOCK_BH;
+const gfp_t BINDINGS_XA_FLAGS_TRACK_FREE = XA_FLAGS_TRACK_FREE;
+const gfp_t BINDINGS_XA_FLAGS_ZERO_BUSY = XA_FLAGS_ZERO_BUSY;
+const gfp_t BINDINGS_XA_FLAGS_ALLOC_WRAPPED = XA_FLAGS_ALLOC_WRAPPED;
+const gfp_t BINDINGS_XA_FLAGS_ACCOUNT = XA_FLAGS_ACCOUNT;
+const gfp_t BINDINGS_XA_FLAGS_ALLOC = XA_FLAGS_ALLOC;
+const gfp_t BINDINGS_XA_FLAGS_ALLOC1 = XA_FLAGS_ALLOC1;
+
+const xa_mark_t BINDINGS_XA_MARK_0 = XA_MARK_0;
+const xa_mark_t BINDINGS_XA_MARK_1 = XA_MARK_1;
+const xa_mark_t BINDINGS_XA_MARK_2 = XA_MARK_2;
+const xa_mark_t BINDINGS_XA_PRESENT = XA_PRESENT;
+const xa_mark_t BINDINGS_XA_MARK_MAX = XA_MARK_MAX;
+const xa_mark_t BINDINGS_XA_FREE_MARK = XA_FREE_MARK;
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 6b7fd0504fb292..5c778bf65e7b9b 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -26,3 +26,4 @@
 #include "uaccess.c"
 #include "wait.c"
 #include "workqueue.c"
+#include "xarray.c"
diff --git a/rust/helpers/xarray.c b/rust/helpers/xarray.c
new file mode 100644
index 00000000000000..13d50f4bdf49db
--- /dev/null
+++ b/rust/helpers/xarray.c
@@ -0,0 +1,34 @@
+
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/xarray.h>
+
+void rust_helper_xa_init_flags(struct xarray *xa, gfp_t flags)
+{
+	xa_init_flags(xa, flags);
+}
+
+bool rust_helper_xa_empty(struct xarray *xa)
+{
+	return xa_empty(xa);
+}
+
+int rust_helper_xa_alloc(struct xarray *xa, u32 *id, void *entry, struct xa_limit limit, gfp_t gfp)
+{
+	return xa_alloc(xa, id, entry, limit, gfp);
+}
+
+void rust_helper_xa_lock(struct xarray *xa)
+{
+	xa_lock(xa);
+}
+
+void rust_helper_xa_unlock(struct xarray *xa)
+{
+	xa_unlock(xa);
+}
+
+int rust_helper_xa_err(void *entry)
+{
+	return xa_err(entry);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index c6908cf1753f6f..85425604627388 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -55,6 +55,7 @@ pub mod time;
 pub mod types;
 pub mod uaccess;
 pub mod workqueue;
+pub mod xarray;
 
 #[doc(hidden)]
 pub use bindings;
diff --git a/rust/kernel/xarray.rs b/rust/kernel/xarray.rs
new file mode 100644
index 00000000000000..6e6d377fef56e4
--- /dev/null
+++ b/rust/kernel/xarray.rs
@@ -0,0 +1,339 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! XArray abstraction.
+//!
+//! C header: [`include/linux/xarray.h`](../../include/linux/xarray.h)
+
+use crate::{
+    bindings,
+    error::{Error, Result},
+    types::{ForeignOwnable, Opaque, ScopeGuard},
+};
+use core::{
+    marker::{PhantomData, PhantomPinned},
+    pin::Pin,
+    ptr::NonNull,
+};
+
+/// Flags passed to `XArray::new` to configure the `XArray`.
+type Flags = bindings::gfp_t;
+
+/// Flag values passed to `XArray::new` to configure the `XArray`.
+pub mod flags {
+    /// Use IRQ-safe locking.
+    pub const LOCK_IRQ: super::Flags = bindings::BINDINGS_XA_FLAGS_LOCK_IRQ;
+    /// Use softirq-safe locking.
+    pub const LOCK_BH: super::Flags = bindings::BINDINGS_XA_FLAGS_LOCK_BH;
+    /// Track which entries are free (distinct from None).
+    pub const TRACK_FREE: super::Flags = bindings::BINDINGS_XA_FLAGS_TRACK_FREE;
+    /// Initialize array index 0 as busy.
+    pub const ZERO_BUSY: super::Flags = bindings::BINDINGS_XA_FLAGS_ZERO_BUSY;
+    /// Use GFP_ACCOUNT for internal memory allocations.
+    pub const ACCOUNT: super::Flags = bindings::BINDINGS_XA_FLAGS_ACCOUNT;
+    /// Create an allocating `XArray` starting at index 0.
+    pub const ALLOC: super::Flags = bindings::BINDINGS_XA_FLAGS_ALLOC;
+    /// Create an allocating `XArray` starting at index 1.
+    pub const ALLOC1: super::Flags = bindings::BINDINGS_XA_FLAGS_ALLOC1;
+}
+
+/// Wrapper for a value owned by the `XArray` which holds the `XArray` lock until dropped.
+pub struct Guard<'a, T: ForeignOwnable>(NonNull<T>, Pin<&'a XArray<T>>);
+
+impl<'a, T: ForeignOwnable> Guard<'a, T> {
+    /// Borrow the underlying value wrapped by the `Guard`.
+    ///
+    /// Returns a `T::Borrowed` type for the owned `ForeignOwnable` type.
+    pub fn borrow(&self) -> T::Borrowed<'_> {
+        // SAFETY: The value is owned by the `XArray`, the lifetime it is borrowed for must not
+        // outlive the `XArray` itself, nor the Guard that holds the lock ensuring the value
+        // remains in the `XArray`.
+        unsafe { T::borrow(self.0.as_ptr() as _) }
+    }
+}
+
+impl<'a, T: ForeignOwnable> Drop for Guard<'a, T> {
+    fn drop(&mut self) {
+        // SAFETY: The XArray we have a reference to owns the C xarray object.
+        unsafe { bindings::xa_unlock(self.1.xa.get()) };
+    }
+}
+
+/// Represents a reserved slot in an `XArray`, which does not yet have a value but has an assigned
+/// index and may not be allocated by any other user. If the Reservation is dropped without
+/// being filled, the entry is marked as available again.
+///
+/// Users must ensure that reserved slots are not filled by other mechanisms, or otherwise their
+/// contents may be dropped and replaced (which will print a warning).
+pub struct Reservation<'a, T: ForeignOwnable>(Pin<&'a XArray<T>>, usize, PhantomData<T>);
+
+impl<'a, T: ForeignOwnable> Reservation<'a, T> {
+    /// Stores a value into the reserved slot.
+    pub fn store(self, value: T) -> Result<usize> {
+        if self.0.replace(self.1, value)?.is_some() {
+            crate::pr_err!("XArray: Reservation stored but the entry already had data!\n");
+            // Consider it a success anyway, not much we can do
+        }
+        let index = self.1;
+        // The reservation is now fulfilled, so do not run our destructor.
+        core::mem::forget(self);
+        Ok(index)
+    }
+
+    /// Returns the index of this reservation.
+    pub fn index(&self) -> usize {
+        self.1
+    }
+}
+
+impl<'a, T: ForeignOwnable> Drop for Reservation<'a, T> {
+    fn drop(&mut self) {
+        if self.0.remove(self.1).is_some() {
+            crate::pr_err!("XArray: Reservation dropped but the entry was not empty!\n");
+        }
+    }
+}
+
+/// An array which efficiently maps sparse integer indices to owned objects.
+///
+/// This is similar to a `Vec<Option<T>>`, but more efficient when there are holes in the
+/// index space, and can be efficiently grown.
+///
+/// This structure is expected to often be used with an inner type that can either be efficiently
+/// cloned, such as an `Arc<T>`.
+pub struct XArray<T: ForeignOwnable> {
+    xa: Opaque<bindings::xarray>,
+    _p: PhantomData<T>,
+    _q: PhantomPinned,
+}
+
+impl<T: ForeignOwnable> XArray<T> {
+    /// The maximum supported index
+    pub const MAX: usize = core::ffi::c_ulong::MAX as usize;
+
+    /// Creates a new `XArray` with the given flags.
+    pub fn new(flags: Flags) -> XArray<T> {
+        let xa = Opaque::uninit();
+
+        // SAFETY: We have just created `xa`. This data structure does not require
+        // pinning.
+        unsafe { bindings::xa_init_flags(xa.get(), flags) };
+
+        // INVARIANT: Initialize the `XArray` with a valid `xa`.
+        XArray {
+            xa,
+            _p: PhantomData,
+            _q: PhantomPinned,
+        }
+    }
+
+    /// Replaces an entry with a new value, returning the old value (if any).
+    pub fn replace(self: Pin<&Self>, index: usize, value: T) -> Result<Option<T>> {
+        let new = value.into_foreign();
+        // SAFETY: `new` just came from into_foreign(), and we dismiss this guard if
+        // the xa_store operation succeeds and takes ownership of the pointer.
+        let guard = ScopeGuard::new(|| unsafe {
+            T::from_foreign(new);
+        });
+
+        // SAFETY: `self.xa` is always valid by the type invariant, and we are storing
+        // a `T::into_foreign()` result which upholds the later invariants.
+        let old = unsafe {
+            bindings::xa_store(
+                self.xa.get(),
+                index.try_into()?,
+                new as *mut _,
+                bindings::GFP_KERNEL,
+            )
+        };
+
+        // SAFETY: `xa_err` is safe to call on any pointer
+        let ret = unsafe { bindings::xa_err(old) };
+        if ret != 0 {
+            Err(Error::from_errno(ret))
+        } else if old.is_null() {
+            guard.dismiss();
+            Ok(None)
+        } else {
+            guard.dismiss();
+            // SAFETY: The old value must have been stored by either this function or
+            // `alloc_limits_opt`, both of which ensure non-NULL entries are valid
+            // ForeignOwnable pointers.
+            Ok(Some(unsafe { T::from_foreign(old) }))
+        }
+    }
+
+    /// Replaces an entry with a new value, dropping the old value (if any).
+    pub fn set(self: Pin<&Self>, index: usize, value: T) -> Result {
+        self.replace(index, value)?;
+        Ok(())
+    }
+
+    /// Looks up and returns a reference to an entry in the array, returning a `Guard` if it
+    /// exists.
+    ///
+    /// This guard blocks all other actions on the `XArray`. Callers are expected to drop the
+    /// `Guard` eagerly to avoid blocking other users, such as by taking a clone of the value.
+    pub fn get(self: Pin<&Self>, index: usize) -> Option<Guard<'_, T>> {
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        unsafe { bindings::xa_lock(self.xa.get()) };
+
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        let guard = ScopeGuard::new(|| unsafe { bindings::xa_unlock(self.xa.get()) });
+
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        let p = unsafe { bindings::xa_load(self.xa.get(), index.try_into().ok()?) };
+
+        NonNull::new(p as *mut T).map(|p| {
+            guard.dismiss();
+            Guard(p, self)
+        })
+    }
+
+    /// Looks up and returns a reference to the lowest entry in the array between index and max,
+    /// returning a tuple of its index and a `Guard` if one exists.
+    ///
+    /// This guard blocks all other actions on the `XArray`. Callers are expected to drop the
+    /// `Guard` eagerly to avoid blocking other users, such as by taking a clone of the value.
+    pub fn find(self: Pin<&Self>, index: usize, max: usize) -> Option<(usize, Guard<'_, T>)> {
+        let mut index: core::ffi::c_ulong = index.try_into().ok()?;
+
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        unsafe { bindings::xa_lock(self.xa.get()) };
+
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        let guard = ScopeGuard::new(|| unsafe { bindings::xa_unlock(self.xa.get()) });
+
+        // SAFETY: `self.xa` is always valid by the type invariant.
+        let p = unsafe {
+            bindings::xa_find(
+                self.xa.get(),
+                &mut index,
+                max.try_into().ok()?,
+                bindings::BINDINGS_XA_PRESENT,
+            )
+        };
+
+        NonNull::new(p as *mut T).map(|p| {
+            guard.dismiss();
+            (index as usize, Guard(p, self))
+        })
+    }
+
+    /// Removes and returns an entry, returning it if it existed.
+    pub fn remove(self: Pin<&Self>, index: usize) -> Option<T> {
+        // SAFETY: self.xa is always valid and pinned.
+        let p = unsafe { bindings::xa_erase(self.xa.get(), index.try_into().ok()?) };
+        if p.is_null() {
+            None
+        } else {
+            // SAFETY: Pointers stored in the xarray are always T types.
+            Some(unsafe { T::from_foreign(p) })
+        }
+    }
+
+    /// Allocates a new index in the array, optionally storing a new value into it, with
+    /// configurable bounds for the index range to allocate from.
+    ///
+    /// If `value` is `None`, then the index is reserved from further allocation but remains
+    /// free for storing a value into it.
+    fn alloc_limits_opt(self: Pin<&Self>, value: Option<T>, min: u32, max: u32) -> Result<usize> {
+        let new = value.map_or(core::ptr::null(), |a| a.into_foreign());
+        let mut id: u32 = 0;
+
+        // SAFETY: `self.xa` is always valid by the type invariant. If this succeeds, it
+        // takes ownership of the passed `T` (if any). If it fails, we must drop the
+        // `T` again.
+        let ret = unsafe {
+            bindings::xa_alloc(
+                self.xa.get(),
+                &mut id,
+                new as *mut _,
+                bindings::xa_limit { min, max },
+                bindings::GFP_KERNEL,
+            )
+        };
+
+        if ret < 0 {
+            // Make sure to drop the value we failed to store
+            if !new.is_null() {
+                // SAFETY: If `new` is not NULL, it came from the `ForeignOwnable` we got
+                // from the caller.
+                unsafe { T::from_foreign(new) };
+            }
+            Err(Error::from_errno(ret))
+        } else {
+            Ok(id as usize)
+        }
+    }
+
+    /// Allocates a new index in the array, storing a new value into it, with configurable
+    /// bounds for the index range to allocate from.
+    pub fn alloc_limits(self: Pin<&Self>, value: T, min: u32, max: u32) -> Result<usize> {
+        self.alloc_limits_opt(Some(value), min, max)
+    }
+
+    /// Allocates a new index in the array, storing a new value into it.
+    pub fn alloc(self: Pin<&Self>, value: T) -> Result<usize> {
+        self.alloc_limits(value, 0, u32::MAX)
+    }
+
+    /// Reserves a new index in the array within configurable bounds for the index.
+    ///
+    /// Returns a `Reservation` object, which can then be used to store a value at this index or
+    /// otherwise free it for reuse.
+    pub fn reserve_limits(self: Pin<&Self>, min: u32, max: u32) -> Result<Reservation<'_, T>> {
+        Ok(Reservation(
+            self,
+            self.alloc_limits_opt(None, min, max)?,
+            PhantomData,
+        ))
+    }
+
+    /// Reserves a new index in the array.
+    ///
+    /// Returns a `Reservation` object, which can then be used to store a value at this index or
+    /// otherwise free it for reuse.
+    pub fn reserve(self: Pin<&Self>) -> Result<Reservation<'_, T>> {
+        Ok(Reservation(
+            self,
+            self.alloc_limits_opt(None, 0, u32::MAX)?,
+            PhantomData,
+        ))
+    }
+}
+
+impl<T: ForeignOwnable> Drop for XArray<T> {
+    fn drop(&mut self) {
+        // SAFETY: `self.xa` is valid by the type invariant, and as we have the only reference to
+        // the `XArray` we can safely iterate its contents and drop everything.
+        unsafe {
+            let mut index: core::ffi::c_ulong = 0;
+            let mut entry = bindings::xa_find(
+                self.xa.get(),
+                &mut index,
+                core::ffi::c_ulong::MAX,
+                bindings::BINDINGS_XA_PRESENT,
+            );
+            while !entry.is_null() {
+                T::from_foreign(entry);
+                entry = bindings::xa_find_after(
+                    self.xa.get(),
+                    &mut index,
+                    core::ffi::c_ulong::MAX,
+                    bindings::BINDINGS_XA_PRESENT,
+                );
+            }
+
+            // Locked locks are not safe to drop. Normally we would want to try_lock()/unlock() here
+            // for safety or something similar, but in this case xa_destroy() is guaranteed to
+            // acquire the lock anyway. This will deadlock if a lock guard was improperly dropped,
+            // but that is not UB, so it's sufficient for soundness purposes.
+            bindings::xa_destroy(self.xa.get());
+        }
+    }
+}
+
+// SAFETY: XArray is thread-safe and all mutation operations are internally locked.
+unsafe impl<T: Send + ForeignOwnable> Send for XArray<T> {}
+// SAFETY: XArray is thread-safe and all mutation operations are internally locked.
+unsafe impl<T: Sync + ForeignOwnable> Sync for XArray<T> {}

From 31d9fb0c93339a745bdb8047e7f3e7809df5f6c8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 20:29:01 +0900
Subject: [PATCH 0842/1027] rust: Add a Sealed trait

Some traits exposed by the kernel crate may not be intended to be
implemented by downstream modules. Add a Sealed trait to allow avoiding
this using the sealed trait pattern.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 85425604627388..33f51afe8b6921 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -65,6 +65,11 @@ pub use uapi;
 #[doc(hidden)]
 pub use build_error::build_error;
 
+pub(crate) mod private {
+    #[allow(unreachable_pub)]
+    pub trait Sealed {}
+}
+
 /// Prefix to appear before log messages printed from within the `kernel` crate.
 const __LOG_PREFIX: &[u8] = b"rust_kernel\0";
 

From 6ab628724273618ca023b760958ccc60312707cd Mon Sep 17 00:00:00 2001
From: Wedson Almeida Filho <wedsonaf@google.com>
Date: Thu, 18 Nov 2021 00:30:04 +0000
Subject: [PATCH 0843/1027] rust: device: Add RawDevice (based on "Add an
 abstraction for devices")

Add a RawDevice trait which can be implemented by any type representing
a device class (such as a PlatformDevice), and a Device type which
represents an owned reference to a generic struct device.

Lina: Rewrote commit message, dropped the Amba bits, and squashed in
simple changes to the core RawDevice/Device code from latter commits
in rust-for-linux/rust. Also include the rust_helper_dev_get_drvdata
helper which will be needed by consumers later on.

Co-developed-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Miguel Ojeda <ojeda@kernel.org>
Signed-off-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/helpers/device.c           | 16 +++++++++
 rust/helpers/helpers.c          |  1 +
 rust/kernel/device.rs           | 59 +++++++++++++++++++++++++++++++++
 4 files changed, 77 insertions(+)
 create mode 100644 rust/helpers/device.c

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 27761c4caf71a2..b08002e93549bc 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -10,6 +10,7 @@
 #include <linux/blk_types.h>
 #include <linux/blk-mq.h>
 #include <linux/blkdev.h>
+#include <linux/device.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
 #include <linux/firmware.h>
diff --git a/rust/helpers/device.c b/rust/helpers/device.c
new file mode 100644
index 00000000000000..70a7cd2135858b
--- /dev/null
+++ b/rust/helpers/device.c
@@ -0,0 +1,16 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/device.h>
+#include <linux/export.h>
+
+void *rust_helper_dev_get_drvdata(struct device *dev)
+{
+	return dev_get_drvdata(dev);
+}
+EXPORT_SYMBOL_GPL(rust_helper_dev_get_drvdata);
+
+const char *rust_helper_dev_name(const struct device *dev)
+{
+	return dev_name(dev);
+}
+EXPORT_SYMBOL_GPL(rust_helper_dev_name);
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 5c778bf65e7b9b..074c6994e30eb0 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -11,6 +11,7 @@
 #include "bug.c"
 #include "build_assert.c"
 #include "build_bug.c"
+#include "device.c"
 #include "err.c"
 #include "kunit.c"
 #include "lockdep.c"
diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index 851018eef885e7..0aba52b92839db 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -6,10 +6,41 @@
 
 use crate::{
     bindings,
+    str::CStr,
     types::{ARef, Opaque},
 };
 use core::ptr;
 
+/// A raw device.
+///
+/// # Safety
+///
+/// Implementers must ensure that the `*mut device` returned by [`RawDevice::raw_device`] is
+/// related to `self`, that is, actions on it will affect `self`. For example, if one calls
+/// `get_device`, then the refcount on the device represented by `self` will be incremented.
+///
+/// Additionally, implementers must ensure that the device is never renamed. Commit a5462516aa99
+/// ("driver-core: document restrictions on device_rename()") has details on why `device_rename`
+/// should not be used.
+pub unsafe trait RawDevice {
+    /// Returns the raw `struct device` related to `self`.
+    fn raw_device(&self) -> *mut bindings::device;
+
+    /// Returns the name of the device.
+    fn name(&self) -> &CStr {
+        let ptr = self.raw_device();
+
+        // SAFETY: `ptr` is valid because `self` keeps it alive.
+        let name = unsafe { bindings::dev_name(ptr) };
+
+        // SAFETY: The name of the device remains valid while it is alive (because the device is
+        // never renamed, per the safety requirement of this trait). This is guaranteed to be the
+        // case because the reference to `self` outlives the one of the returned `CStr` (enforced
+        // by the compiler because of their lifetimes).
+        unsafe { CStr::from_char_ptr(name) }
+    }
+}
+
 /// A reference-counted device.
 ///
 /// This structure represents the Rust abstraction for a C `struct device`. This implementation
@@ -82,6 +113,13 @@ impl Device {
         // SAFETY: Guaranteed by the safety requirements of the function.
         unsafe { &*ptr.cast() }
     }
+
+    /// Creates a new device instance from an existing [`RawDevice`] instance.
+    pub fn from_dev(dev: &dyn RawDevice) -> ARef<Self> {
+        // SAFETY: The requirements are satisfied by the existence of `RawDevice` and its safety
+        // requirements.
+        unsafe { Self::from_raw(dev.raw_device()) }
+    }
 }
 
 // SAFETY: Instances of `Device` are always reference-counted.
@@ -97,6 +135,27 @@ unsafe impl crate::types::AlwaysRefCounted for Device {
     }
 }
 
+// SAFETY: The device returned by `raw_device` is the one for which we hold a reference.
+unsafe impl RawDevice for Device {
+    fn raw_device(&self) -> *mut bindings::device {
+        self.ptr
+    }
+}
+
+impl Drop for Device {
+    fn drop(&mut self) {
+        // SAFETY: By the type invariants, we know that `self` owns a reference, so it is safe to
+        // relinquish it now.
+        unsafe { bindings::put_device(self.ptr) };
+    }
+}
+
+impl Clone for Device {
+    fn clone(&self) -> Self {
+        Device::from_dev(self)
+    }
+}
+
 // SAFETY: As by the type invariant `Device` can be sent to any thread.
 unsafe impl Send for Device {}
 

From 7bf8b7ca950535e724c4887563607b63e6acaf52 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 7 Sep 2022 17:39:44 +0900
Subject: [PATCH 0844/1027] rust: io_pgtable: Add io_pgtable abstraction

The io_pgtable subsystem implements page table management for various
IOMMU page table formats. This abstraction allows Rust drivers for
devices with an embedded MMU to use this shared code.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/io_pgtable.rs       | 351 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   1 +
 3 files changed, 353 insertions(+)
 create mode 100644 rust/kernel/io_pgtable.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index b08002e93549bc..664cd8fecf0c0f 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -17,6 +17,7 @@
 #include <linux/jiffies.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
+#include <linux/io-pgtable.h>
 #include <linux/ktime.h>
 #include <linux/lockdep.h>
 #include <linux/refcount.h>
diff --git a/rust/kernel/io_pgtable.rs b/rust/kernel/io_pgtable.rs
new file mode 100644
index 00000000000000..c967ea90cc00e5
--- /dev/null
+++ b/rust/kernel/io_pgtable.rs
@@ -0,0 +1,351 @@
+// SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
+
+//! IOMMU page table management
+//!
+//! C header: [`include/io-pgtable.h`](../../../../include/io-pgtable.h)
+
+use crate::{
+    bindings, device,
+    error::{code::*, to_result, Result},
+    types::{ForeignOwnable, ScopeGuard},
+};
+
+use core::marker::PhantomData;
+use core::mem;
+use core::num::NonZeroU64;
+
+/// Protection flags used with IOMMU mappings.
+pub mod prot {
+    /// Read access.
+    pub const READ: u32 = bindings::IOMMU_READ;
+    /// Write access.
+    pub const WRITE: u32 = bindings::IOMMU_WRITE;
+    /// Request cache coherency.
+    pub const CACHE: u32 = bindings::IOMMU_CACHE;
+    /// Request no-execute permission.
+    pub const NOEXEC: u32 = bindings::IOMMU_NOEXEC;
+    /// MMIO peripheral mapping.
+    pub const MMIO: u32 = bindings::IOMMU_MMIO;
+    /// Privileged mapping.
+    pub const PRIV: u32 = bindings::IOMMU_PRIV;
+}
+
+/// Represents a requested io_pgtable configuration.
+pub struct Config {
+    /// Quirk bitmask (type-specific).
+    pub quirks: usize,
+    /// Valid page sizes, as a bitmask of powers of two.
+    pub pgsize_bitmap: usize,
+    /// Input address space size in bits.
+    pub ias: usize,
+    /// Output address space size in bits.
+    pub oas: usize,
+    /// IOMMU uses coherent accesses for page table walks.
+    pub coherent_walk: bool,
+}
+
+/// IOMMU callbacks for TLB and page table management.
+///
+/// Users must implement this trait to perform the TLB flush actions for this IOMMU, if
+/// required.
+pub trait FlushOps {
+    /// User-specified type owned by the IOPagetable that will be passed to TLB operations.
+    type Data: ForeignOwnable + Send + Sync;
+
+    /// Synchronously invalidate the entire TLB context.
+    fn tlb_flush_all(data: <Self::Data as ForeignOwnable>::Borrowed<'_>);
+
+    /// Synchronously invalidate all intermediate TLB state (sometimes referred to as the "walk
+    /// cache") for a virtual address range.
+    fn tlb_flush_walk(
+        data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        iova: usize,
+        size: usize,
+        granule: usize,
+    );
+
+    /// Optional callback to queue up leaf TLB invalidation for a single page.
+    ///
+    /// IOMMUs that cannot batch TLB invalidation operations efficiently will typically issue
+    /// them here, but others may decide to update the iommu_iotlb_gather structure and defer
+    /// the invalidation until iommu_iotlb_sync() instead.
+    ///
+    /// TODO: Implement the gather argument for batching.
+    fn tlb_add_page(
+        data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        iova: usize,
+        granule: usize,
+    );
+}
+
+/// Inner page table info shared across all table types.
+/// # Invariants
+///
+///   - [`self.ops`] is valid and non-null.
+///   - [`self.cfg`] is valid and non-null.
+#[doc(hidden)]
+pub struct IoPageTableInner {
+    ops: *mut bindings::io_pgtable_ops,
+    cfg: bindings::io_pgtable_cfg,
+    data: *mut core::ffi::c_void,
+}
+
+/// Helper trait to get the config type for a single page table type from the union.
+pub trait GetConfig {
+    /// Returns the specific output configuration for this page table type.
+    fn cfg(iopt: &impl IoPageTable) -> &Self
+    where
+        Self: Sized;
+}
+
+/// A generic IOMMU page table
+pub trait IoPageTable: crate::private::Sealed {
+    #[doc(hidden)]
+    const FLUSH_OPS: bindings::iommu_flush_ops;
+
+    #[doc(hidden)]
+    fn new_fmt<T: FlushOps>(
+        dev: &dyn device::RawDevice,
+        format: u32,
+        config: Config,
+        data: T::Data,
+    ) -> Result<IoPageTableInner> {
+        let ptr = data.into_foreign() as *mut _;
+        let guard = ScopeGuard::new(|| {
+            // SAFETY: `ptr` came from a previous call to `into_foreign`.
+            unsafe { T::Data::from_foreign(ptr) };
+        });
+
+        let mut raw_cfg = bindings::io_pgtable_cfg {
+            quirks: config.quirks.try_into()?,
+            pgsize_bitmap: config.pgsize_bitmap.try_into()?,
+            ias: config.ias.try_into()?,
+            oas: config.oas.try_into()?,
+            coherent_walk: config.coherent_walk,
+            tlb: &Self::FLUSH_OPS,
+            iommu_dev: dev.raw_device(),
+            alloc: None,
+            free: None,
+            __bindgen_anon_1: unsafe { mem::zeroed() },
+        };
+
+        let ops = unsafe {
+            bindings::alloc_io_pgtable_ops(format as bindings::io_pgtable_fmt, &mut raw_cfg, ptr)
+        };
+
+        if ops.is_null() {
+            return Err(EINVAL);
+        }
+
+        guard.dismiss();
+        Ok(IoPageTableInner {
+            ops,
+            cfg: raw_cfg,
+            data: ptr,
+        })
+    }
+
+    /// Map a range of pages.
+    fn map_pages(
+        &mut self,
+        iova: usize,
+        paddr: usize,
+        pgsize: usize,
+        pgcount: usize,
+        prot: u32,
+    ) -> Result<usize> {
+        let mut mapped: usize = 0;
+
+        to_result(unsafe {
+            (*self.inner_mut().ops).map_pages.unwrap()(
+                self.inner_mut().ops,
+                iova as u64,
+                paddr as u64,
+                pgsize,
+                pgcount,
+                prot as i32,
+                bindings::GFP_KERNEL,
+                &mut mapped,
+            )
+        })?;
+
+        Ok(mapped)
+    }
+
+    /// Unmap a range of pages.
+    fn unmap_pages(
+        &mut self,
+        iova: usize,
+        pgsize: usize,
+        pgcount: usize,
+        // TODO: gather: *mut iommu_iotlb_gather,
+    ) -> usize {
+        unsafe {
+            (*self.inner_mut().ops).unmap_pages.unwrap()(
+                self.inner_mut().ops,
+                iova as u64,
+                pgsize,
+                pgcount,
+                core::ptr::null_mut(),
+            )
+        }
+    }
+
+    /// Translate an IOVA to the corresponding physical address, if mapped.
+    fn iova_to_phys(&mut self, iova: usize) -> Option<NonZeroU64> {
+        NonZeroU64::new(unsafe {
+            (*self.inner_mut().ops).iova_to_phys.unwrap()(self.inner_mut().ops, iova as u64)
+        })
+    }
+
+    #[doc(hidden)]
+    fn inner_mut(&mut self) -> &mut IoPageTableInner;
+
+    #[doc(hidden)]
+    fn inner(&self) -> &IoPageTableInner;
+
+    #[doc(hidden)]
+    fn raw_cfg(&self) -> &bindings::io_pgtable_cfg {
+        &self.inner().cfg
+    }
+}
+
+unsafe impl Send for IoPageTableInner {}
+unsafe impl Sync for IoPageTableInner {}
+
+unsafe extern "C" fn tlb_flush_all_callback<T: FlushOps>(cookie: *mut core::ffi::c_void) {
+    T::tlb_flush_all(unsafe { T::Data::borrow(cookie) });
+}
+
+unsafe extern "C" fn tlb_flush_walk_callback<T: FlushOps>(
+    iova: core::ffi::c_ulong,
+    size: usize,
+    granule: usize,
+    cookie: *mut core::ffi::c_void,
+) {
+    T::tlb_flush_walk(
+        unsafe { T::Data::borrow(cookie) },
+        iova as usize,
+        size,
+        granule,
+    );
+}
+
+unsafe extern "C" fn tlb_add_page_callback<T: FlushOps>(
+    _gather: *mut bindings::iommu_iotlb_gather,
+    iova: core::ffi::c_ulong,
+    granule: usize,
+    cookie: *mut core::ffi::c_void,
+) {
+    T::tlb_add_page(unsafe { T::Data::borrow(cookie) }, iova as usize, granule);
+}
+
+macro_rules! iopt_cfg {
+    ($name:ident, $field:ident, $type:ident) => {
+        /// An IOMMU page table configuration for a specific kind of pagetable.
+        pub type $name = bindings::$type;
+
+        impl GetConfig for $name {
+            fn cfg(iopt: &impl IoPageTable) -> &$name {
+                unsafe { &iopt.raw_cfg().__bindgen_anon_1.$field }
+            }
+        }
+    };
+}
+
+impl GetConfig for () {
+    fn cfg(_iopt: &impl IoPageTable) -> &() {
+        &()
+    }
+}
+
+macro_rules! iopt_type {
+    ($type:ident, $cfg:ty, $fmt:ident) => {
+        /// Represents an IOPagetable of this type.
+        pub struct $type<T: FlushOps>(IoPageTableInner, PhantomData<T>);
+
+        impl<T: FlushOps> $type<T> {
+            /// Creates a new IOPagetable implementation of this type.
+            pub fn new(dev: &dyn device::RawDevice, config: Config, data: T::Data) -> Result<Self> {
+                Ok(Self(
+                    <Self as IoPageTable>::new_fmt::<T>(dev, bindings::$fmt, config, data)?,
+                    PhantomData,
+                ))
+            }
+
+            /// Get the configuration for this IOPagetable.
+            pub fn cfg(&self) -> &$cfg {
+                <$cfg as GetConfig>::cfg(self)
+            }
+        }
+
+        impl<T: FlushOps> crate::private::Sealed for $type<T> {}
+
+        impl<T: FlushOps> IoPageTable for $type<T> {
+            const FLUSH_OPS: bindings::iommu_flush_ops = bindings::iommu_flush_ops {
+                tlb_flush_all: Some(tlb_flush_all_callback::<T>),
+                tlb_flush_walk: Some(tlb_flush_walk_callback::<T>),
+                tlb_add_page: Some(tlb_add_page_callback::<T>),
+            };
+
+            fn inner(&self) -> &IoPageTableInner {
+                &self.0
+            }
+
+            fn inner_mut(&mut self) -> &mut IoPageTableInner {
+                &mut self.0
+            }
+        }
+
+        impl<T: FlushOps> Drop for $type<T> {
+            fn drop(&mut self) {
+                // SAFETY: The pointer is valid by the type invariant.
+                unsafe { bindings::free_io_pgtable_ops(self.0.ops) };
+
+                // Free context data.
+                //
+                // SAFETY: This matches the call to `into_foreign` from `new` in the success case.
+                unsafe { T::Data::from_foreign(self.0.data) };
+            }
+        }
+    };
+}
+
+// Ew...
+iopt_cfg!(
+    ARMLPAES1Cfg,
+    arm_lpae_s1_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_1
+);
+iopt_cfg!(
+    ARMLPAES2Cfg,
+    arm_lpae_s2_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_2
+);
+iopt_cfg!(
+    ARMv7SCfg,
+    arm_v7s_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_3
+);
+iopt_cfg!(
+    ARMMaliLPAECfg,
+    arm_mali_lpae_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_4
+);
+iopt_cfg!(
+    AppleDARTCfg,
+    apple_dart_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_5
+);
+
+iopt_type!(ARM32LPAES1, ARMLPAES1Cfg, io_pgtable_fmt_ARM_32_LPAE_S1);
+iopt_type!(ARM32LPAES2, ARMLPAES2Cfg, io_pgtable_fmt_ARM_32_LPAE_S2);
+iopt_type!(ARM64LPAES1, ARMLPAES1Cfg, io_pgtable_fmt_ARM_64_LPAE_S1);
+iopt_type!(ARM64LPAES2, ARMLPAES2Cfg, io_pgtable_fmt_ARM_64_LPAE_S2);
+iopt_type!(ARMv7S, ARMv7SCfg, io_pgtable_fmt_ARM_V7S);
+iopt_type!(ARMMaliLPAE, ARMMaliLPAECfg, io_pgtable_fmt_ARM_MALI_LPAE);
+iopt_type!(AMDIOMMUV1, (), io_pgtable_fmt_AMD_IOMMU_V1);
+iopt_type!(AppleDART, AppleDARTCfg, io_pgtable_fmt_APPLE_DART);
+iopt_type!(AppleDART2, AppleDARTCfg, io_pgtable_fmt_APPLE_DART2);
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 33f51afe8b6921..dd9526e44a386d 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -36,6 +36,7 @@ pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
 pub mod init;
+pub mod io_pgtable;
 pub mod ioctl;
 #[cfg(CONFIG_KUNIT)]
 pub mod kunit;

From 98d6f57ca1a4ec24ed88670df7bd563e8c0368b2 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 18 Aug 2022 02:13:54 +0900
Subject: [PATCH 0845/1027] rust: soc: apple: rtkit: Add Apple RTKit
 abstraction

RTKit is Apple's proprietary real-time operating system framework, used
across many subdevices on Apple Silicon platforms including NVMe, system
management, GPU, etc. Add Rust abstractions for this subsystem, so that
it can be used by upcoming Rust drivers.

Note: Although ARM64 support is not yet merged, this can be built on amd64
with CONFIG_COMPILE_TEST=y.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/lib.rs              |   1 +
 rust/kernel/soc/apple/mod.rs    |   6 +
 rust/kernel/soc/apple/rtkit.rs  | 262 ++++++++++++++++++++++++++++++++
 rust/kernel/soc/mod.rs          |   5 +
 5 files changed, 275 insertions(+)
 create mode 100644 rust/kernel/soc/apple/mod.rs
 create mode 100644 rust/kernel/soc/apple/rtkit.rs
 create mode 100644 rust/kernel/soc/mod.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 664cd8fecf0c0f..6adedd06de6b22 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -24,6 +24,7 @@
 #include <linux/siphash.h>
 #include <linux/sched.h>
 #include <linux/slab.h>
+#include <linux/soc/apple/rtkit.h>
 #include <linux/timekeeping.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index dd9526e44a386d..ada8feb20513fd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -46,6 +46,7 @@ pub mod page;
 pub mod prelude;
 pub mod print;
 pub mod siphash;
+pub mod soc;
 mod static_assert;
 #[doc(hidden)]
 pub mod std_vendor;
diff --git a/rust/kernel/soc/apple/mod.rs b/rust/kernel/soc/apple/mod.rs
new file mode 100644
index 00000000000000..dd69db63677dd6
--- /dev/null
+++ b/rust/kernel/soc/apple/mod.rs
@@ -0,0 +1,6 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Apple SoC drivers
+
+#[cfg(CONFIG_APPLE_RTKIT = "y")]
+pub mod rtkit;
diff --git a/rust/kernel/soc/apple/rtkit.rs b/rust/kernel/soc/apple/rtkit.rs
new file mode 100644
index 00000000000000..e92f9024aea692
--- /dev/null
+++ b/rust/kernel/soc/apple/rtkit.rs
@@ -0,0 +1,262 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Support for Apple RTKit coprocessors.
+//!
+//! C header: [`include/linux/soc/apple/rtkit.h`](../../../../include/linux/gpio/driver.h)
+
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*},
+    bindings, device,
+    error::{code::*, from_err_ptr, from_result, to_result, Result},
+    str::CStr,
+    types::{ForeignOwnable, ScopeGuard},
+};
+
+use alloc::boxed::Box;
+use core::marker::PhantomData;
+use core::ptr;
+use macros::vtable;
+
+/// Trait to represent allocatable buffers for the RTKit core.
+///
+/// Users must implement this trait for their own representation of those allocations.
+pub trait Buffer {
+    /// Returns the IOVA (virtual address) of the buffer from RTKit's point of view, or an error if
+    /// unavailable.
+    fn iova(&self) -> Result<usize>;
+
+    /// Returns a mutable byte slice of the buffer contents, or an
+    /// error if unavailable.
+    fn buf(&mut self) -> Result<&mut [u8]>;
+}
+
+/// Callback operations for an RTKit client.
+#[vtable]
+pub trait Operations {
+    /// Arbitrary user context type.
+    type Data: ForeignOwnable + Send + Sync;
+
+    /// Type representing an allocated buffer for RTKit.
+    type Buffer: Buffer;
+
+    /// Called when RTKit crashes.
+    fn crashed(_data: <Self::Data as ForeignOwnable>::Borrowed<'_>) {}
+
+    /// Called when a message was received on a non-system endpoint. Called in non-IRQ context.
+    fn recv_message(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _endpoint: u8,
+        _message: u64,
+    ) {
+    }
+
+    /// Called in IRQ context when a message was received on a non-system endpoint.
+    ///
+    /// Must return `true` if the message is handled, or `false` to process it in
+    /// the handling thread.
+    fn recv_message_early(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _endpoint: u8,
+        _message: u64,
+    ) -> bool {
+        false
+    }
+
+    /// Allocate a buffer for use by RTKit.
+    fn shmem_alloc(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _size: usize,
+    ) -> Result<Self::Buffer> {
+        Err(EINVAL)
+    }
+
+    /// Map an existing buffer used by RTKit at a device-specified virtual address.
+    fn shmem_map(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _iova: usize,
+        _size: usize,
+    ) -> Result<Self::Buffer> {
+        Err(EINVAL)
+    }
+}
+
+/// Represents `struct apple_rtkit *`.
+///
+/// # Invariants
+///
+/// The rtk pointer is valid.
+/// The data pointer is a valid pointer from T::Data::into_foreign().
+pub struct RtKit<T: Operations> {
+    rtk: *mut bindings::apple_rtkit,
+    data: *mut core::ffi::c_void,
+    _p: PhantomData<T>,
+}
+
+unsafe extern "C" fn crashed_callback<T: Operations>(cookie: *mut core::ffi::c_void) {
+    // SAFETY: cookie is always a T::Data in this API
+    T::crashed(unsafe { T::Data::borrow(cookie) });
+}
+
+unsafe extern "C" fn recv_message_callback<T: Operations>(
+    cookie: *mut core::ffi::c_void,
+    endpoint: u8,
+    message: u64,
+) {
+    // SAFETY: cookie is always a T::Data in this API
+    T::recv_message(unsafe { T::Data::borrow(cookie) }, endpoint, message);
+}
+
+unsafe extern "C" fn recv_message_early_callback<T: Operations>(
+    cookie: *mut core::ffi::c_void,
+    endpoint: u8,
+    message: u64,
+) -> bool {
+    // SAFETY: cookie is always a T::Data in this API
+    T::recv_message_early(unsafe { T::Data::borrow(cookie) }, endpoint, message)
+}
+
+unsafe extern "C" fn shmem_setup_callback<T: Operations>(
+    cookie: *mut core::ffi::c_void,
+    bfr: *mut bindings::apple_rtkit_shmem,
+) -> core::ffi::c_int {
+    // SAFETY: `bfr` is a valid buffer
+    let bfr_mut = unsafe { &mut *bfr };
+
+    from_result(|| {
+        let mut buf = if bfr_mut.iova != 0 {
+            bfr_mut.is_mapped = true;
+            T::shmem_map(
+                // SAFETY: `cookie` came from a previous call to `into_foreign`.
+                unsafe { T::Data::borrow(cookie) },
+                bfr_mut.iova as usize,
+                bfr_mut.size,
+            )?
+        } else {
+            bfr_mut.is_mapped = false;
+            // SAFETY: `cookie` came from a previous call to `into_foreign`.
+            T::shmem_alloc(unsafe { T::Data::borrow(cookie) }, bfr_mut.size)?
+        };
+
+        let iova = buf.iova()?;
+        let slice = buf.buf()?;
+
+        if slice.len() < bfr_mut.size {
+            return Err(ENOMEM);
+        }
+
+        bfr_mut.iova = iova as u64;
+        bfr_mut.buffer = slice.as_mut_ptr() as *mut _;
+
+        // Now box the returned buffer type and stash it in the private pointer of the
+        // `apple_rtkit_shmem` struct for safekeeping.
+        let boxed = Box::new(buf, GFP_KERNEL)?;
+        bfr_mut.private = Box::into_raw(boxed) as *mut _;
+        Ok(0)
+    })
+}
+
+unsafe extern "C" fn shmem_destroy_callback<T: Operations>(
+    _cookie: *mut core::ffi::c_void,
+    bfr: *mut bindings::apple_rtkit_shmem,
+) {
+    // SAFETY: `bfr` is a valid buffer
+    let bfr_mut = unsafe { &mut *bfr };
+    if !bfr_mut.private.is_null() {
+        // SAFETY: Per shmem_setup_callback, this has to be a pointer to a Buffer if it is set.
+        unsafe {
+            core::mem::drop(Box::from_raw(bfr_mut.private as *mut T::Buffer));
+        }
+        bfr_mut.private = core::ptr::null_mut();
+    }
+}
+
+impl<T: Operations> RtKit<T> {
+    const VTABLE: bindings::apple_rtkit_ops = bindings::apple_rtkit_ops {
+        crashed: Some(crashed_callback::<T>),
+        recv_message: Some(recv_message_callback::<T>),
+        recv_message_early: Some(recv_message_early_callback::<T>),
+        shmem_setup: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP {
+            Some(shmem_setup_callback::<T>)
+        } else {
+            None
+        },
+        shmem_destroy: if T::HAS_SHMEM_ALLOC || T::HAS_SHMEM_MAP {
+            Some(shmem_destroy_callback::<T>)
+        } else {
+            None
+        },
+    };
+
+    /// Creates a new RTKit client for a given device and optional mailbox name or index.
+    pub fn new(
+        dev: &dyn device::RawDevice,
+        mbox_name: Option<&'static CStr>,
+        mbox_idx: usize,
+        data: T::Data,
+    ) -> Result<Self> {
+        let ptr = data.into_foreign() as *mut _;
+        let guard = ScopeGuard::new(|| {
+            // SAFETY: `ptr` came from a previous call to `into_foreign`.
+            unsafe { T::Data::from_foreign(ptr) };
+        });
+        // SAFETY: This just calls the C init function.
+        let rtk = unsafe {
+            from_err_ptr(bindings::apple_rtkit_init(
+                dev.raw_device(),
+                ptr,
+                match mbox_name {
+                    Some(s) => s.as_char_ptr(),
+                    None => ptr::null(),
+                },
+                mbox_idx.try_into()?,
+                &Self::VTABLE,
+            ))
+        }?;
+
+        guard.dismiss();
+        // INVARIANT: `rtk` and `data` are valid here.
+        Ok(Self {
+            rtk,
+            data: ptr,
+            _p: PhantomData,
+        })
+    }
+
+    /// Boots (wakes up) the RTKit coprocessor.
+    pub fn boot(&mut self) -> Result {
+        // SAFETY: `rtk` is valid per the type invariant.
+        to_result(unsafe { bindings::apple_rtkit_boot(self.rtk) })
+    }
+
+    /// Starts a non-system endpoint.
+    pub fn start_endpoint(&mut self, endpoint: u8) -> Result {
+        // SAFETY: `rtk` is valid per the type invariant.
+        to_result(unsafe { bindings::apple_rtkit_start_ep(self.rtk, endpoint) })
+    }
+
+    /// Sends a message to a given endpoint.
+    pub fn send_message(&mut self, endpoint: u8, message: u64) -> Result {
+        // SAFETY: `rtk` is valid per the type invariant.
+        to_result(unsafe {
+            bindings::apple_rtkit_send_message(self.rtk, endpoint, message, ptr::null_mut(), false)
+        })
+    }
+}
+
+// SAFETY: `RtKit` operations require a mutable reference
+unsafe impl<T: Operations> Sync for RtKit<T> {}
+
+// SAFETY: `RtKit` operations require a mutable reference
+unsafe impl<T: Operations> Send for RtKit<T> {}
+
+impl<T: Operations> Drop for RtKit<T> {
+    fn drop(&mut self) {
+        // SAFETY: The pointer is valid by the type invariant.
+        unsafe { bindings::apple_rtkit_free(self.rtk) };
+
+        // Free context data.
+        //
+        // SAFETY: This matches the call to `into_foreign` from `new` in the success case.
+        unsafe { T::Data::from_foreign(self.data) };
+    }
+}
diff --git a/rust/kernel/soc/mod.rs b/rust/kernel/soc/mod.rs
new file mode 100644
index 00000000000000..e3024042e74f0d
--- /dev/null
+++ b/rust/kernel/soc/mod.rs
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! SoC drivers
+
+pub mod apple;

From e86b066fce83751a96c9428c079b90995b6f9194 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 16:23:27 +0900
Subject: [PATCH 0846/1027] *RFL import: kernel::types::Bool

Commit reference: 3dfc5ebff103
---
 rust/kernel/types.rs | 82 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 82 insertions(+)

diff --git a/rust/kernel/types.rs b/rust/kernel/types.rs
index 96b47e2f3ccead..b5ab6b51520d76 100644
--- a/rust/kernel/types.rs
+++ b/rust/kernel/types.rs
@@ -481,3 +481,85 @@ unsafe impl AsBytes for str {}
 // does not have any uninitialized portions either.
 unsafe impl<T: AsBytes> AsBytes for [T] {}
 unsafe impl<T: AsBytes, const N: usize> AsBytes for [T; N] {}
+
+/// A trait for boolean types.
+///
+/// This is meant to be used in type states to allow boolean constraints in implementation blocks.
+/// In the example below, the implementation containing `MyType::set_value` could _not_ be
+/// constrained to type states containing `Writable = true` if `Writable` were a constant instead
+/// of a type.
+///
+/// # Safety
+///
+/// No additional implementations of [`Bool`] should be provided, as [`True`] and [`False`] are
+/// already provided.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::{Bool, False, True};
+/// use core::marker::PhantomData;
+///
+/// // Type state specifies whether the type is writable.
+/// trait MyTypeState {
+///     type Writable: Bool;
+/// }
+///
+/// // In state S1, the type is writable.
+/// struct S1;
+/// impl MyTypeState for S1 {
+///     type Writable = True;
+/// }
+///
+/// // In state S2, the type is not writable.
+/// struct S2;
+/// impl MyTypeState for S2 {
+///     type Writable = False;
+/// }
+///
+/// struct MyType<T: MyTypeState> {
+///     value: u32,
+///     _p: PhantomData<T>,
+/// }
+///
+/// impl<T: MyTypeState> MyType<T> {
+///     fn new(value: u32) -> Self {
+///         Self {
+///             value,
+///             _p: PhantomData,
+///         }
+///     }
+/// }
+///
+/// // This implementation block only applies if the type state is writable.
+/// impl<T> MyType<T>
+/// where
+///     T: MyTypeState<Writable = True>,
+/// {
+///     fn set_value(&mut self, v: u32) {
+///         self.value = v;
+///     }
+/// }
+///
+/// let mut x = MyType::<S1>::new(10);
+/// let mut y = MyType::<S2>::new(20);
+///
+/// x.set_value(30);
+
+///
+/// // The code below fails to compile because `S2` is not writable.
+/// // y.set_value(40);
+/// ```
+pub unsafe trait Bool {}
+
+/// Represents the `true` value for types with [`Bool`] bound.
+pub struct True;
+
+// SAFETY: This is one of the only two implementations of `Bool`.
+unsafe impl Bool for True {}
+
+/// Represents the `false` value for types wth [`Bool`] bound.
+pub struct False;
+
+// SAFETY: This is one of the only two implementations of `Bool`.
+unsafe impl Bool for False {}

From 686d8120f22507201c208a80b67cb15563de2e04 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 15:51:49 +0900
Subject: [PATCH 0847/1027] *RFL import: kernel::io_buffer

Commit reference: 3dfc5ebff103
---
 rust/kernel/io_buffer.rs | 157 +++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs       |   1 +
 2 files changed, 158 insertions(+)
 create mode 100644 rust/kernel/io_buffer.rs

diff --git a/rust/kernel/io_buffer.rs b/rust/kernel/io_buffer.rs
new file mode 100644
index 00000000000000..abfc1f2e5faa3b
--- /dev/null
+++ b/rust/kernel/io_buffer.rs
@@ -0,0 +1,157 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Buffers used in IO.
+
+use crate::alloc::{flags::*, vec_ext::VecExt};
+use crate::error::Result;
+use alloc::vec::Vec;
+use core::mem::{size_of, MaybeUninit};
+
+/// Represents a buffer to be read from during IO.
+pub trait IoBufferReader {
+    /// Returns the number of bytes left to be read from the io buffer.
+    ///
+    /// Note that even reading less than this number of bytes may fail.
+    fn len(&self) -> usize;
+
+    /// Returns `true` if no data is available in the io buffer.
+    fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Reads raw data from the io buffer into a raw kernel buffer.
+    ///
+    /// # Safety
+    ///
+    /// The output buffer must be valid.
+    unsafe fn read_raw(&mut self, out: *mut u8, len: usize) -> Result;
+
+    /// Reads all data remaining in the io buffer.
+    ///
+    /// Returns `EFAULT` if the address does not currently point to mapped, readable memory.
+    fn read_all(&mut self) -> Result<Vec<u8>> {
+        let mut data = Vec::<u8>::with_capacity(self.len(), GFP_KERNEL)?;
+        // FIXME? data.resize(self.len(), 0);
+        for _ in 0..self.len() {
+            data.push(0, GFP_KERNEL)?
+        }
+
+        // SAFETY: The output buffer is valid as we just allocated it.
+        unsafe { self.read_raw(data.as_mut_ptr(), data.len())? };
+        Ok(data)
+    }
+
+    /// Reads a byte slice from the io buffer.
+    ///
+    /// Returns `EFAULT` if the byte slice is bigger than the remaining size of the user slice or
+    /// if the address does not currently point to mapped, readable memory.
+    fn read_slice(&mut self, data: &mut [u8]) -> Result {
+        // SAFETY: The output buffer is valid as it's coming from a live reference.
+        unsafe { self.read_raw(data.as_mut_ptr(), data.len()) }
+    }
+
+    /// Reads the contents of a plain old data (POD) type from the io buffer.
+    fn read<T: ReadableFromBytes>(&mut self) -> Result<T> {
+        let mut out = MaybeUninit::<T>::uninit();
+        // SAFETY: The buffer is valid as it was just allocated.
+        unsafe { self.read_raw(out.as_mut_ptr() as _, size_of::<T>()) }?;
+        // SAFETY: We just initialised the data.
+        Ok(unsafe { out.assume_init() })
+    }
+}
+
+/// Represents a buffer to be written to during IO.
+pub trait IoBufferWriter {
+    /// Returns the number of bytes left to be written into the io buffer.
+    ///
+    /// Note that even writing less than this number of bytes may fail.
+    fn len(&self) -> usize;
+
+    /// Returns `true` if the io buffer cannot hold any additional data.
+    fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+
+    /// Writes zeroes to the io buffer.
+    ///
+    /// Differently from the other write functions, `clear` will zero as much as it can and update
+    /// the writer internal state to reflect this. It will, however, return an error if it cannot
+    /// clear `len` bytes.
+    ///
+    /// For example, if a caller requests that 100 bytes be cleared but a segfault happens after
+    /// 20 bytes, then EFAULT is returned and the writer is advanced by 20 bytes.
+    fn clear(&mut self, len: usize) -> Result;
+
+    /// Writes a byte slice into the io buffer.
+    ///
+    /// Returns `EFAULT` if the byte slice is bigger than the remaining size of the io buffer or if
+    /// the address does not currently point to mapped, writable memory.
+    fn write_slice(&mut self, data: &[u8]) -> Result {
+        // SAFETY: The input buffer is valid as it's coming from a live reference.
+        unsafe { self.write_raw(data.as_ptr(), data.len()) }
+    }
+
+    /// Writes raw data to the io buffer from a raw kernel buffer.
+    ///
+    /// # Safety
+    ///
+    /// The input buffer must be valid.
+    unsafe fn write_raw(&mut self, data: *const u8, len: usize) -> Result;
+
+    /// Writes the contents of the given data into the io buffer.
+    fn write<T: WritableToBytes>(&mut self, data: &T) -> Result {
+        // SAFETY: The input buffer is valid as it's coming from a live
+        // reference to a type that implements `WritableToBytes`.
+        unsafe { self.write_raw(data as *const T as _, size_of::<T>()) }
+    }
+}
+
+/// Specifies that a type is safely readable from byte slices.
+///
+/// Not all types can be safely read from byte slices; examples from
+/// <https://doc.rust-lang.org/reference/behavior-considered-undefined.html> include `bool`
+/// that must be either `0` or `1`, and `char` that cannot be a surrogate or above `char::MAX`.
+///
+/// # Safety
+///
+/// Implementers must ensure that the type is made up only of types that can be safely read from
+/// arbitrary byte sequences (e.g., `u32`, `u64`, etc.).
+pub unsafe trait ReadableFromBytes {}
+
+// SAFETY: All bit patterns are acceptable values of the types below.
+unsafe impl ReadableFromBytes for u8 {}
+unsafe impl ReadableFromBytes for u16 {}
+unsafe impl ReadableFromBytes for u32 {}
+unsafe impl ReadableFromBytes for u64 {}
+unsafe impl ReadableFromBytes for usize {}
+unsafe impl ReadableFromBytes for i8 {}
+unsafe impl ReadableFromBytes for i16 {}
+unsafe impl ReadableFromBytes for i32 {}
+unsafe impl ReadableFromBytes for i64 {}
+unsafe impl ReadableFromBytes for isize {}
+
+/// Specifies that a type is safely writable to byte slices.
+///
+/// This means that we don't read undefined values (which leads to UB) in preparation for writing
+/// to the byte slice. It also ensures that no potentially sensitive information is leaked into the
+/// byte slices.
+///
+/// # Safety
+///
+/// A type must not include padding bytes and must be fully initialised to safely implement
+/// [`WritableToBytes`] (i.e., it doesn't contain [`MaybeUninit`] fields). A composition of
+/// writable types in a structure is not necessarily writable because it may result in padding
+/// bytes.
+pub unsafe trait WritableToBytes {}
+
+// SAFETY: Initialised instances of the following types have no uninitialised portions.
+unsafe impl WritableToBytes for u8 {}
+unsafe impl WritableToBytes for u16 {}
+unsafe impl WritableToBytes for u32 {}
+unsafe impl WritableToBytes for u64 {}
+unsafe impl WritableToBytes for usize {}
+unsafe impl WritableToBytes for i8 {}
+unsafe impl WritableToBytes for i16 {}
+unsafe impl WritableToBytes for i32 {}
+unsafe impl WritableToBytes for i64 {}
+unsafe impl WritableToBytes for isize {}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index ada8feb20513fd..c54455f284a1f7 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -36,6 +36,7 @@ pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
 pub mod init;
+pub mod io_buffer;
 pub mod io_pgtable;
 pub mod ioctl;
 #[cfg(CONFIG_KUNIT)]

From 930f4602a3d9b394d9d5e896976865ba0418daa8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 15:54:30 +0900
Subject: [PATCH 0848/1027] *RFL import: kernel::user_ptr

Commit reference: 3dfc5ebff103
---
 rust/helpers/uaccess.c  |   5 ++
 rust/kernel/lib.rs      |   1 +
 rust/kernel/user_ptr.rs | 175 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 181 insertions(+)
 create mode 100644 rust/kernel/user_ptr.rs

diff --git a/rust/helpers/uaccess.c b/rust/helpers/uaccess.c
index f49076f813cd64..f3dbe117fc3ed5 100644
--- a/rust/helpers/uaccess.c
+++ b/rust/helpers/uaccess.c
@@ -2,6 +2,11 @@
 
 #include <linux/uaccess.h>
 
+unsigned long rust_helper_clear_user(void __user *to, unsigned long n)
+{
+	return clear_user(to, n);
+}
+
 unsigned long rust_helper_copy_from_user(void *to, const void __user *from,
 					 unsigned long n)
 {
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index c54455f284a1f7..0224475ff87c6f 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -57,6 +57,7 @@ pub mod task;
 pub mod time;
 pub mod types;
 pub mod uaccess;
+pub mod user_ptr;
 pub mod workqueue;
 pub mod xarray;
 
diff --git a/rust/kernel/user_ptr.rs b/rust/kernel/user_ptr.rs
new file mode 100644
index 00000000000000..084535675c4a31
--- /dev/null
+++ b/rust/kernel/user_ptr.rs
@@ -0,0 +1,175 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! User pointers.
+//!
+//! C header: [`include/linux/uaccess.h`](../../../../include/linux/uaccess.h)
+
+use crate::{
+    bindings,
+    error::code::*,
+    error::Result,
+    io_buffer::{IoBufferReader, IoBufferWriter},
+};
+use alloc::vec::Vec;
+
+/// A reference to an area in userspace memory, which can be either
+/// read-only or read-write.
+///
+/// All methods on this struct are safe: invalid pointers return
+/// `EFAULT`. Concurrent access, *including data races to/from userspace
+/// memory*, is permitted, because fundamentally another userspace
+/// thread/process could always be modifying memory at the same time
+/// (in the same way that userspace Rust's [`std::io`] permits data races
+/// with the contents of files on disk). In the presence of a race, the
+/// exact byte values read/written are unspecified but the operation is
+/// well-defined. Kernelspace code should validate its copy of data
+/// after completing a read, and not expect that multiple reads of the
+/// same address will return the same value.
+///
+/// All APIs enforce the invariant that a given byte of memory from userspace
+/// may only be read once. By preventing double-fetches we avoid TOCTOU
+/// vulnerabilities. This is accomplished by taking `self` by value to prevent
+/// obtaining multiple readers on a given [`UserSlicePtr`], and the readers
+/// only permitting forward reads.
+///
+/// Constructing a [`UserSlicePtr`] performs no checks on the provided
+/// address and length, it can safely be constructed inside a kernel thread
+/// with no current userspace process. Reads and writes wrap the kernel APIs
+/// `copy_from_user` and `copy_to_user`, which check the memory map of the
+/// current process and enforce that the address range is within the user
+/// range (no additional calls to `access_ok` are needed).
+///
+/// [`std::io`]: https://doc.rust-lang.org/std/io/index.html
+pub struct UserSlicePtr(*mut core::ffi::c_void, usize);
+
+impl UserSlicePtr {
+    /// Constructs a user slice from a raw pointer and a length in bytes.
+    ///
+    /// # Safety
+    ///
+    /// Callers must be careful to avoid time-of-check-time-of-use
+    /// (TOCTOU) issues. The simplest way is to create a single instance of
+    /// [`UserSlicePtr`] per user memory block as it reads each byte at
+    /// most once.
+    pub unsafe fn new(ptr: *mut core::ffi::c_void, length: usize) -> Self {
+        UserSlicePtr(ptr, length)
+    }
+
+    /// Reads the entirety of the user slice.
+    ///
+    /// Returns `EFAULT` if the address does not currently point to
+    /// mapped, readable memory.
+    pub fn read_all(self) -> Result<Vec<u8>> {
+        self.reader().read_all()
+    }
+
+    /// Constructs a [`UserSlicePtrReader`].
+    pub fn reader(self) -> UserSlicePtrReader {
+        UserSlicePtrReader(self.0, self.1)
+    }
+
+    /// Writes the provided slice into the user slice.
+    ///
+    /// Returns `EFAULT` if the address does not currently point to
+    /// mapped, writable memory (in which case some data from before the
+    /// fault may be written), or `data` is larger than the user slice
+    /// (in which case no data is written).
+    pub fn write_all(self, data: &[u8]) -> Result {
+        self.writer().write_slice(data)
+    }
+
+    /// Constructs a [`UserSlicePtrWriter`].
+    pub fn writer(self) -> UserSlicePtrWriter {
+        UserSlicePtrWriter(self.0, self.1)
+    }
+
+    /// Constructs both a [`UserSlicePtrReader`] and a [`UserSlicePtrWriter`].
+    pub fn reader_writer(self) -> (UserSlicePtrReader, UserSlicePtrWriter) {
+        (
+            UserSlicePtrReader(self.0, self.1),
+            UserSlicePtrWriter(self.0, self.1),
+        )
+    }
+}
+
+/// A reader for [`UserSlicePtr`].
+///
+/// Used to incrementally read from the user slice.
+pub struct UserSlicePtrReader(*mut core::ffi::c_void, usize);
+
+impl IoBufferReader for UserSlicePtrReader {
+    /// Returns the number of bytes left to be read from this.
+    ///
+    /// Note that even reading less than this number of bytes may fail.
+    fn len(&self) -> usize {
+        self.1
+    }
+
+    /// Reads raw data from the user slice into a raw kernel buffer.
+    ///
+    /// # Safety
+    ///
+    /// The output buffer must be valid.
+    unsafe fn read_raw(&mut self, out: *mut u8, len: usize) -> Result {
+        if len > self.1 || len > u32::MAX as usize {
+            return Err(EFAULT);
+        }
+        let res = unsafe { bindings::copy_from_user(out as _, self.0, len as _) };
+        if res != 0 {
+            return Err(EFAULT);
+        }
+        // Since this is not a pointer to a valid object in our program,
+        // we cannot use `add`, which has C-style rules for defined
+        // behavior.
+        self.0 = self.0.wrapping_add(len);
+        self.1 -= len;
+        Ok(())
+    }
+}
+
+/// A writer for [`UserSlicePtr`].
+///
+/// Used to incrementally write into the user slice.
+pub struct UserSlicePtrWriter(*mut core::ffi::c_void, usize);
+
+impl IoBufferWriter for UserSlicePtrWriter {
+    fn len(&self) -> usize {
+        self.1
+    }
+
+    fn clear(&mut self, mut len: usize) -> Result {
+        let mut ret = Ok(());
+        if len > self.1 {
+            ret = Err(EFAULT);
+            len = self.1;
+        }
+
+        // SAFETY: The buffer will be validated by `clear_user`. We ensure that `len` is within
+        // bounds in the check above.
+        let left = unsafe { bindings::clear_user(self.0, len as _) } as usize;
+        if left != 0 {
+            ret = Err(EFAULT);
+            len -= left;
+        }
+
+        self.0 = self.0.wrapping_add(len);
+        self.1 -= len;
+        ret
+    }
+
+    unsafe fn write_raw(&mut self, data: *const u8, len: usize) -> Result {
+        if len > self.1 || len > u32::MAX as usize {
+            return Err(EFAULT);
+        }
+        let res = unsafe { bindings::copy_to_user(self.0, data as _, len as _) };
+        if res != 0 {
+            return Err(EFAULT);
+        }
+        // Since this is not a pointer to a valid object in our program,
+        // we cannot use `add`, which has C-style rules for defined
+        // behavior.
+        self.0 = self.0.wrapping_add(len);
+        self.1 -= len;
+        Ok(())
+    }
+}

From b96b61a9cca538f072eb5669441c362a00c6e021 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 16:29:46 +0900
Subject: [PATCH 0849/1027] MISSING SIGNOFFS: rust: Add PAGE_SIZE constant to
 kernel crate

Computed based on the PAGE_SHIFT macro from C.

// Co-authored-by: Adam Bratschi-Kaye <ark.email@gmail.com>
// Co-authored-by: Miguel Ojeda <ojeda@kernel.org>
// Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 0224475ff87c6f..6c8036f00c893c 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -74,6 +74,11 @@ pub(crate) mod private {
     pub trait Sealed {}
 }
 
+/// Page size defined in terms of the `PAGE_SHIFT` macro from C.
+///
+/// [`PAGE_SHIFT`]: ../../../include/asm-generic/page.h
+pub const PAGE_SIZE: usize = 1 << bindings::PAGE_SHIFT;
+
 /// Prefix to appear before log messages printed from within the `kernel` crate.
 const __LOG_PREFIX: &[u8] = b"rust_kernel\0";
 

From c72650c9911c8e17a1ca075f11b278137becb2bd Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 16:32:20 +0900
Subject: [PATCH 0850/1027] rust: Enable const_mut_refs feature for the kernel
 crate

Needed by the rust-for-linux/rust module_param module.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 6c8036f00c893c..af7452fc415830 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -13,6 +13,7 @@
 
 #![no_std]
 #![feature(coerce_unsized)]
+#![feature(const_mut_refs)]
 #![feature(dispatch_from_dyn)]
 #![feature(new_uninit)]
 #![feature(receiver_trait)]

From cadea1813b21c64ba597735ff205fc719bc1995e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 16:33:34 +0900
Subject: [PATCH 0851/1027] *RFL import: kernel::module_param

Commit reference: 3dfc5ebff103
---
 rust/kernel/lib.rs          |   1 +
 rust/kernel/module_param.rs | 500 ++++++++++++++++++++++++++++++++++++
 2 files changed, 501 insertions(+)
 create mode 100644 rust/kernel/module_param.rs

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index af7452fc415830..c22a41da6b21e2 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -42,6 +42,7 @@ pub mod io_pgtable;
 pub mod ioctl;
 #[cfg(CONFIG_KUNIT)]
 pub mod kunit;
+pub mod module_param;
 #[cfg(CONFIG_NET)]
 pub mod net;
 pub mod page;
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
new file mode 100644
index 00000000000000..9268f2b7b5acc3
--- /dev/null
+++ b/rust/kernel/module_param.rs
@@ -0,0 +1,500 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Types for module parameters.
+//!
+//! C header: [`include/linux/moduleparam.h`](../../../include/linux/moduleparam.h)
+
+use crate::alloc::{flags::*, vec_ext::VecExt};
+use crate::error::{code::*, from_result};
+use crate::str::{CStr, Formatter};
+use core::fmt::Write;
+
+/// Types that can be used for module parameters.
+///
+/// Note that displaying the type in `sysfs` will fail if
+/// [`alloc::string::ToString::to_string`] (as implemented through the
+/// [`core::fmt::Display`] trait) writes more than [`PAGE_SIZE`]
+/// bytes (including an additional null terminator).
+///
+/// [`PAGE_SIZE`]: `crate::PAGE_SIZE`
+pub trait ModuleParam: core::fmt::Display + core::marker::Sized {
+    /// The `ModuleParam` will be used by the kernel module through this type.
+    ///
+    /// This may differ from `Self` if, for example, `Self` needs to track
+    /// ownership without exposing it or allocate extra space for other possible
+    /// parameter values. See [`StringParam`] or [`ArrayParam`] for examples.
+    type Value: ?Sized;
+
+    /// Whether the parameter is allowed to be set without an argument.
+    ///
+    /// Setting this to `true` allows the parameter to be passed without an
+    /// argument (e.g. just `module.param` instead of `module.param=foo`).
+    const NOARG_ALLOWED: bool;
+
+    /// Convert a parameter argument into the parameter value.
+    ///
+    /// `None` should be returned when parsing of the argument fails.
+    /// `arg == None` indicates that the parameter was passed without an
+    /// argument. If `NOARG_ALLOWED` is set to `false` then `arg` is guaranteed
+    /// to always be `Some(_)`.
+    ///
+    /// Parameters passed at boot time will be set before [`kmalloc`] is
+    /// available (even if the module is loaded at a later time). However, in
+    /// this case, the argument buffer will be valid for the entire lifetime of
+    /// the kernel. So implementations of this method which need to allocate
+    /// should first check that the allocator is available (with
+    /// [`crate::bindings::slab_is_available`]) and when it is not available
+    /// provide an alternative implementation which doesn't allocate. In cases
+    /// where the allocator is not available it is safe to save references to
+    /// `arg` in `Self`, but in other cases a copy should be made.
+    ///
+    /// [`kmalloc`]: ../../../include/linux/slab.h
+    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self>;
+
+    /// Get the current value of the parameter for use in the kernel module.
+    ///
+    /// This function should not be used directly. Instead use the wrapper
+    /// `read` which will be generated by [`macros::module`].
+    fn value(&self) -> &Self::Value;
+
+    /// Set the module parameter from a string.
+    ///
+    /// Used to set the parameter value when loading the module or when set
+    /// through `sysfs`.
+    ///
+    /// # Safety
+    ///
+    /// If `val` is non-null then it must point to a valid null-terminated
+    /// string. The `arg` field of `param` must be an instance of `Self`.
+    unsafe extern "C" fn set_param(
+        val: *const core::ffi::c_char,
+        param: *const crate::bindings::kernel_param,
+    ) -> core::ffi::c_int {
+        let arg = if val.is_null() {
+            None
+        } else {
+            Some(unsafe { CStr::from_char_ptr(val).as_bytes() })
+        };
+        match Self::try_from_param_arg(arg) {
+            Some(new_value) => {
+                let old_value = unsafe { (*param).__bindgen_anon_1.arg as *mut Self };
+                let _ = unsafe { core::ptr::replace(old_value, new_value) };
+                0
+            }
+            None => EINVAL.to_errno(),
+        }
+    }
+
+    /// Write a string representation of the current parameter value to `buf`.
+    ///
+    /// Used for displaying the current parameter value in `sysfs`.
+    ///
+    /// # Safety
+    ///
+    /// `buf` must be a buffer of length at least `kernel::PAGE_SIZE` that is
+    /// writeable. The `arg` field of `param` must be an instance of `Self`.
+    unsafe extern "C" fn get_param(
+        buf: *mut core::ffi::c_char,
+        param: *const crate::bindings::kernel_param,
+    ) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: The C contracts guarantees that the buffer is at least `PAGE_SIZE` bytes.
+            let mut f = unsafe { Formatter::from_buffer(buf.cast(), crate::PAGE_SIZE) };
+            unsafe { write!(f, "{}\0", *((*param).__bindgen_anon_1.arg as *mut Self)) }?;
+            Ok(f.bytes_written().try_into()?)
+        })
+    }
+
+    /// Drop the parameter.
+    ///
+    /// Called when unloading a module.
+    ///
+    /// # Safety
+    ///
+    /// The `arg` field of `param` must be an instance of `Self`.
+    unsafe extern "C" fn free(arg: *mut core::ffi::c_void) {
+        unsafe { core::ptr::drop_in_place(arg as *mut Self) };
+    }
+}
+
+/// Trait for parsing integers.
+///
+/// Strings beginning with `0x`, `0o`, or `0b` are parsed as hex, octal, or
+/// binary respectively. Strings beginning with `0` otherwise are parsed as
+/// octal. Anything else is parsed as decimal. A leading `+` or `-` is also
+/// permitted. Any string parsed by [`kstrtol()`] or [`kstrtoul()`] will be
+/// successfully parsed.
+///
+/// [`kstrtol()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtol
+/// [`kstrtoul()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtoul
+trait ParseInt: Sized {
+    fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError>;
+    fn checked_neg(self) -> Option<Self>;
+
+    fn from_str_unsigned(src: &str) -> Result<Self, core::num::ParseIntError> {
+        let (radix, digits) = if let Some(n) = src.strip_prefix("0x") {
+            (16, n)
+        } else if let Some(n) = src.strip_prefix("0X") {
+            (16, n)
+        } else if let Some(n) = src.strip_prefix("0o") {
+            (8, n)
+        } else if let Some(n) = src.strip_prefix("0O") {
+            (8, n)
+        } else if let Some(n) = src.strip_prefix("0b") {
+            (2, n)
+        } else if let Some(n) = src.strip_prefix("0B") {
+            (2, n)
+        } else if src.starts_with('0') {
+            (8, src)
+        } else {
+            (10, src)
+        };
+        Self::from_str_radix(digits, radix)
+    }
+
+    fn from_str(src: &str) -> Option<Self> {
+        match src.bytes().next() {
+            None => None,
+            Some(b'-') => Self::from_str_unsigned(&src[1..]).ok()?.checked_neg(),
+            Some(b'+') => Some(Self::from_str_unsigned(&src[1..]).ok()?),
+            Some(_) => Some(Self::from_str_unsigned(src).ok()?),
+        }
+    }
+}
+
+macro_rules! impl_parse_int {
+    ($ty:ident) => {
+        impl ParseInt for $ty {
+            fn from_str_radix(src: &str, radix: u32) -> Result<Self, core::num::ParseIntError> {
+                $ty::from_str_radix(src, radix)
+            }
+
+            fn checked_neg(self) -> Option<Self> {
+                self.checked_neg()
+            }
+        }
+    };
+}
+
+impl_parse_int!(i8);
+impl_parse_int!(u8);
+impl_parse_int!(i16);
+impl_parse_int!(u16);
+impl_parse_int!(i32);
+impl_parse_int!(u32);
+impl_parse_int!(i64);
+impl_parse_int!(u64);
+impl_parse_int!(isize);
+impl_parse_int!(usize);
+
+macro_rules! impl_module_param {
+    ($ty:ident) => {
+        impl ModuleParam for $ty {
+            type Value = $ty;
+
+            const NOARG_ALLOWED: bool = false;
+
+            fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
+                let bytes = arg?;
+                let utf8 = core::str::from_utf8(bytes).ok()?;
+                <$ty as crate::module_param::ParseInt>::from_str(utf8)
+            }
+
+            fn value(&self) -> &Self::Value {
+                self
+            }
+        }
+    };
+}
+
+#[doc(hidden)]
+#[macro_export]
+/// Generate a static [`kernel_param_ops`](../../../include/linux/moduleparam.h) struct.
+///
+/// # Examples
+///
+/// ```ignore
+/// make_param_ops!(
+///     /// Documentation for new param ops.
+///     PARAM_OPS_MYTYPE, // Name for the static.
+///     MyType // A type which implements [`ModuleParam`].
+/// );
+/// ```
+macro_rules! make_param_ops {
+    ($ops:ident, $ty:ty) => {
+        $crate::make_param_ops!(
+            #[doc=""]
+            $ops,
+            $ty
+        );
+    };
+    ($(#[$meta:meta])* $ops:ident, $ty:ty) => {
+        $(#[$meta])*
+        ///
+        /// Static [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+        /// struct generated by [`make_param_ops`].
+        pub static $ops: $crate::bindings::kernel_param_ops = $crate::bindings::kernel_param_ops {
+            flags: if <$ty as $crate::module_param::ModuleParam>::NOARG_ALLOWED {
+                $crate::bindings::KERNEL_PARAM_OPS_FL_NOARG
+            } else {
+                0
+            },
+            set: Some(<$ty as $crate::module_param::ModuleParam>::set_param),
+            get: Some(<$ty as $crate::module_param::ModuleParam>::get_param),
+            free: Some(<$ty as $crate::module_param::ModuleParam>::free),
+        };
+    };
+}
+
+impl_module_param!(i8);
+impl_module_param!(u8);
+impl_module_param!(i16);
+impl_module_param!(u16);
+impl_module_param!(i32);
+impl_module_param!(u32);
+impl_module_param!(i64);
+impl_module_param!(u64);
+impl_module_param!(isize);
+impl_module_param!(usize);
+
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`i8`].
+    PARAM_OPS_I8,
+    i8
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`u8`].
+    PARAM_OPS_U8,
+    u8
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`i16`].
+    PARAM_OPS_I16,
+    i16
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`u16`].
+    PARAM_OPS_U16,
+    u16
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`i32`].
+    PARAM_OPS_I32,
+    i32
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`u32`].
+    PARAM_OPS_U32,
+    u32
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`i64`].
+    PARAM_OPS_I64,
+    i64
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`u64`].
+    PARAM_OPS_U64,
+    u64
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`isize`].
+    PARAM_OPS_ISIZE,
+    isize
+);
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`usize`].
+    PARAM_OPS_USIZE,
+    usize
+);
+
+impl ModuleParam for bool {
+    type Value = bool;
+
+    const NOARG_ALLOWED: bool = true;
+
+    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
+        match arg {
+            None => Some(true),
+            Some(b"y") | Some(b"Y") | Some(b"1") | Some(b"true") => Some(true),
+            Some(b"n") | Some(b"N") | Some(b"0") | Some(b"false") => Some(false),
+            _ => None,
+        }
+    }
+
+    fn value(&self) -> &Self::Value {
+        self
+    }
+}
+
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`bool`].
+    PARAM_OPS_BOOL,
+    bool
+);
+
+/// An array of at __most__ `N` values.
+///
+/// # Invariant
+///
+/// The first `self.used` elements of `self.values` are initialized.
+pub struct ArrayParam<T, const N: usize> {
+    values: [core::mem::MaybeUninit<T>; N],
+    used: usize,
+}
+
+impl<T, const N: usize> ArrayParam<T, { N }> {
+    fn values(&self) -> &[T] {
+        // SAFETY: The invariant maintained by `ArrayParam` allows us to cast
+        // the first `self.used` elements to `T`.
+        unsafe {
+            &*(&self.values[0..self.used] as *const [core::mem::MaybeUninit<T>] as *const [T])
+        }
+    }
+}
+
+impl<T: Copy, const N: usize> ArrayParam<T, { N }> {
+    const fn new() -> Self {
+        // INVARIANT: The first `self.used` elements of `self.values` are
+        // initialized.
+        ArrayParam {
+            values: [core::mem::MaybeUninit::uninit(); N],
+            used: 0,
+        }
+    }
+
+    const fn push(&mut self, val: T) {
+        if self.used < N {
+            // INVARIANT: The first `self.used` elements of `self.values` are
+            // initialized.
+            self.values[self.used] = core::mem::MaybeUninit::new(val);
+            self.used += 1;
+        }
+    }
+
+    /// Create an instance of `ArrayParam` initialized with `vals`.
+    ///
+    /// This function is only meant to be used in the [`module::module`] macro.
+    pub const fn create(vals: &[T]) -> Self {
+        let mut result = ArrayParam::new();
+        let mut i = 0;
+        while i < vals.len() {
+            result.push(vals[i]);
+            i += 1;
+        }
+        result
+    }
+}
+
+impl<T: core::fmt::Display, const N: usize> core::fmt::Display for ArrayParam<T, { N }> {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        for val in self.values() {
+            write!(f, "{},", val)?;
+        }
+        Ok(())
+    }
+}
+
+impl<T: Copy + core::fmt::Display + ModuleParam, const N: usize> ModuleParam
+    for ArrayParam<T, { N }>
+{
+    type Value = [T];
+
+    const NOARG_ALLOWED: bool = false;
+
+    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
+        arg.and_then(|args| {
+            let mut result = Self::new();
+            for arg in args.split(|b| *b == b',') {
+                result.push(T::try_from_param_arg(Some(arg))?);
+            }
+            Some(result)
+        })
+    }
+
+    fn value(&self) -> &Self::Value {
+        self.values()
+    }
+}
+
+/// A C-style string parameter.
+///
+/// The Rust version of the [`charp`] parameter. This type is meant to be
+/// used by the [`macros::module`] macro, not handled directly. Instead use the
+/// `read` method generated by that macro.
+///
+/// [`charp`]: ../../../include/linux/moduleparam.h
+pub enum StringParam {
+    /// A borrowed parameter value.
+    ///
+    /// Either the default value (which is static in the module) or borrowed
+    /// from the original argument buffer used to set the value.
+    Ref(&'static [u8]),
+
+    /// A value that was allocated when the parameter was set.
+    ///
+    /// The value needs to be freed when the parameter is reset or the module is
+    /// unloaded.
+    Owned(alloc::vec::Vec<u8>),
+}
+
+impl StringParam {
+    fn bytes(&self) -> &[u8] {
+        match self {
+            StringParam::Ref(bytes) => bytes,
+            StringParam::Owned(vec) => &vec[..],
+        }
+    }
+}
+
+impl core::fmt::Display for StringParam {
+    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+        let bytes = self.bytes();
+        match core::str::from_utf8(bytes) {
+            Ok(utf8) => write!(f, "{}", utf8),
+            Err(_) => write!(f, "{:?}", bytes),
+        }
+    }
+}
+
+impl ModuleParam for StringParam {
+    type Value = [u8];
+
+    const NOARG_ALLOWED: bool = false;
+
+    fn try_from_param_arg(arg: Option<&'static [u8]>) -> Option<Self> {
+        // SAFETY: It is always safe to call [`slab_is_available`](../../../include/linux/slab.h).
+        let slab_available = unsafe { crate::bindings::slab_is_available() };
+        arg.and_then(|arg| {
+            if slab_available {
+                let mut vec = alloc::vec::Vec::new();
+                vec.extend_from_slice(arg, GFP_KERNEL).ok()?;
+                Some(StringParam::Owned(vec))
+            } else {
+                Some(StringParam::Ref(arg))
+            }
+        })
+    }
+
+    fn value(&self) -> &Self::Value {
+        self.bytes()
+    }
+}
+
+make_param_ops!(
+    /// Rust implementation of [`kernel_param_ops`](../../../include/linux/moduleparam.h)
+    /// for [`StringParam`].
+    PARAM_OPS_STR,
+    StringParam
+);

From c1cf30478f061133b79ab71b1baa45c1128a6e25 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 21 Oct 2022 16:42:26 +0900
Subject: [PATCH 0852/1027] rust: module_param: Tolerate a trailing newline
 when parsing

This is the same behavior as kstrtol/kstrtoul, and allows simple
`echo 0 > /sys/module/foo/parameters/bar` commands to work.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/module_param.rs | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 9268f2b7b5acc3..96ab23af44b11e 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -122,8 +122,8 @@ pub trait ModuleParam: core::fmt::Display + core::marker::Sized {
 /// Strings beginning with `0x`, `0o`, or `0b` are parsed as hex, octal, or
 /// binary respectively. Strings beginning with `0` otherwise are parsed as
 /// octal. Anything else is parsed as decimal. A leading `+` or `-` is also
-/// permitted. Any string parsed by [`kstrtol()`] or [`kstrtoul()`] will be
-/// successfully parsed.
+/// permitted. The string may contain a trailing newline. Any string parsed
+/// by [`kstrtol()`] or [`kstrtoul()`] will be successfully parsed.
 ///
 /// [`kstrtol()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtol
 /// [`kstrtoul()`]: https://www.kernel.org/doc/html/latest/core-api/kernel-api.html#c.kstrtoul
@@ -132,6 +132,7 @@ trait ParseInt: Sized {
     fn checked_neg(self) -> Option<Self>;
 
     fn from_str_unsigned(src: &str) -> Result<Self, core::num::ParseIntError> {
+        let src = src.strip_suffix('\n').unwrap_or(src);
         let (radix, digits) = if let Some(n) = src.strip_prefix("0x") {
             (16, n)
         } else if let Some(n) = src.strip_prefix("0X") {

From 875060da7614ef3142b2338f18c3721e3030d498 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 18:03:44 +0900
Subject: [PATCH 0853/1027] rust: Add `name` argument to Module::init()

Co-developed-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Wedson Almeida Filho <wedsonaf@google.com>
Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs    | 2 +-
 rust/macros/module.rs | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index c22a41da6b21e2..f470b7142b5fd9 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -94,7 +94,7 @@ pub trait Module: Sized + Sync + Send {
     /// should do.
     ///
     /// Equivalent to the `module_init` macro in the C API.
-    fn init(module: &'static ThisModule) -> error::Result<Self>;
+    fn init(name: &'static crate::str::CStr, module: &'static ThisModule) -> error::Result<Self>;
 }
 
 /// Equivalent to `THIS_MODULE` in the C API.
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index 7a5b899e47b766..f83cea291740a7 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -319,7 +319,7 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
                     ///
                     /// This function must only be called once.
                     unsafe fn __init() -> core::ffi::c_int {{
-                        match <{type_} as kernel::Module>::init(&super::super::THIS_MODULE) {{
+                        match <{type_} as kernel::Module>::init(kernel::c_str!(\"{name}\"), &THIS_MODULE) {{
                             Ok(m) => {{
                                 // SAFETY: No data race, since `__MOD` can only be accessed by this
                                 // module and there only `__init` and `__exit` access it. These

From 7c4330fc5e51d65e4dc3f73e8c24eb5dc4953023 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 18:04:55 +0900
Subject: [PATCH 0854/1027] *RFL import: kernel::driver

Commit reference: 3dfc5ebff103
---
 rust/kernel/driver.rs | 450 ++++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs    |   3 +
 2 files changed, 453 insertions(+)
 create mode 100644 rust/kernel/driver.rs

diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
new file mode 100644
index 00000000000000..5e81c840359929
--- /dev/null
+++ b/rust/kernel/driver.rs
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Generic support for drivers of different buses (e.g., PCI, Platform, Amba, etc.).
+//!
+//! Each bus/subsystem is expected to implement [`DriverOps`], which allows drivers to register
+//! using the [`Registration`] class.
+
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*},
+    error::code::*,
+    error::Result,
+    str::CStr,
+    sync::Arc,
+    ThisModule,
+};
+use alloc::boxed::Box;
+use core::{cell::UnsafeCell, marker::PhantomData, ops::Deref, pin::Pin};
+
+/// A subsystem (e.g., PCI, Platform, Amba, etc.) that allows drivers to be written for it.
+pub trait DriverOps {
+    /// The type that holds information about the registration. This is typically a struct defined
+    /// by the C portion of the kernel.
+    type RegType: Default;
+
+    /// Registers a driver.
+    ///
+    /// # Safety
+    ///
+    /// `reg` must point to valid, initialised, and writable memory. It may be modified by this
+    /// function to hold registration state.
+    ///
+    /// On success, `reg` must remain pinned and valid until the matching call to
+    /// [`DriverOps::unregister`].
+    unsafe fn register(
+        reg: *mut Self::RegType,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result;
+
+    /// Unregisters a driver previously registered with [`DriverOps::register`].
+    ///
+    /// # Safety
+    ///
+    /// `reg` must point to valid writable memory, initialised by a previous successful call to
+    /// [`DriverOps::register`].
+    unsafe fn unregister(reg: *mut Self::RegType);
+}
+
+/// The registration of a driver.
+pub struct Registration<T: DriverOps> {
+    is_registered: bool,
+    concrete_reg: UnsafeCell<T::RegType>,
+}
+
+// SAFETY: `Registration` has no fields or methods accessible via `&Registration`, so it is safe to
+// share references to it with multiple threads as nothing can be done.
+unsafe impl<T: DriverOps> Sync for Registration<T> {}
+
+impl<T: DriverOps> Registration<T> {
+    /// Creates a new instance of the registration object.
+    pub fn new() -> Self {
+        Self {
+            is_registered: false,
+            concrete_reg: UnsafeCell::new(T::RegType::default()),
+        }
+    }
+
+    /// Allocates a pinned registration object and registers it.
+    ///
+    /// Returns a pinned heap-allocated representation of the registration.
+    pub fn new_pinned(name: &'static CStr, module: &'static ThisModule) -> Result<Pin<Box<Self>>> {
+        let mut reg = Pin::from(Box::new(Self::new(), GFP_KERNEL)?);
+        reg.as_mut().register(name, module)?;
+        Ok(reg)
+    }
+
+    /// Registers a driver with its subsystem.
+    ///
+    /// It must be pinned because the memory block that represents the registration is potentially
+    /// self-referential.
+    pub fn register(
+        self: Pin<&mut Self>,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result {
+        // SAFETY: We never move out of `this`.
+        let this = unsafe { self.get_unchecked_mut() };
+        if this.is_registered {
+            // Already registered.
+            return Err(EINVAL);
+        }
+
+        // SAFETY: `concrete_reg` was initialised via its default constructor. It is only freed
+        // after `Self::drop` is called, which first calls `T::unregister`.
+        unsafe { T::register(this.concrete_reg.get(), name, module) }?;
+
+        this.is_registered = true;
+        Ok(())
+    }
+}
+
+impl<T: DriverOps> Default for Registration<T> {
+    fn default() -> Self {
+        Self::new()
+    }
+}
+
+impl<T: DriverOps> Drop for Registration<T> {
+    fn drop(&mut self) {
+        if self.is_registered {
+            // SAFETY: This path only runs if a previous call to `T::register` completed
+            // successfully.
+            unsafe { T::unregister(self.concrete_reg.get()) };
+        }
+    }
+}
+
+/// Conversion from a device id to a raw device id.
+///
+/// This is meant to be implemented by buses/subsystems so that they can use [`IdTable`] to
+/// guarantee (at compile-time) zero-termination of device id tables provided by drivers.
+///
+/// # Safety
+///
+/// Implementers must ensure that:
+///   - [`RawDeviceId::ZERO`] is actually a zeroed-out version of the raw device id.
+///   - [`RawDeviceId::to_rawid`] stores `offset` in the context/data field of the raw device id so
+///     that buses can recover the pointer to the data.
+#[const_trait]
+pub unsafe trait RawDeviceId {
+    /// The raw type that holds the device id.
+    ///
+    /// Id tables created from [`Self`] are going to hold this type in its zero-terminated array.
+    type RawType: Copy;
+
+    /// A zeroed-out representation of the raw device id.
+    ///
+    /// Id tables created from [`Self`] use [`Self::ZERO`] as the sentinel to indicate the end of
+    /// the table.
+    const ZERO: Self::RawType;
+
+    /// Converts an id into a raw id.
+    ///
+    /// `offset` is the offset from the memory location where the raw device id is stored to the
+    /// location where its associated context information is stored. Implementations must store
+    /// this in the appropriate context/data field of the raw type.
+    fn to_rawid(&self, offset: isize) -> Self::RawType;
+}
+
+/// A zero-terminated device id array, followed by context data.
+#[repr(C)]
+pub struct IdArray<T: RawDeviceId, U, const N: usize> {
+    ids: [T::RawType; N],
+    sentinel: T::RawType,
+    id_infos: [Option<U>; N],
+}
+
+impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
+    /// Creates a new instance of the array.
+    ///
+    /// The contents are derived from the given identifiers and context information.
+    pub const fn new(ids: [T; N], infos: [Option<U>; N]) -> Self
+    where
+        T: ~const RawDeviceId + Copy,
+    {
+        let mut array = Self {
+            ids: [T::ZERO; N],
+            sentinel: T::ZERO,
+            id_infos: infos,
+        };
+        let mut i = 0usize;
+        while i < N {
+            // SAFETY: Both pointers are within `array` (or one byte beyond), consequently they are
+            // derived from the same allocated object. We are using a `u8` pointer, whose size 1,
+            // so the pointers are necessarily 1-byte aligned.
+            let offset = unsafe {
+                (&array.id_infos[i] as *const _ as *const u8)
+                    .offset_from(&array.ids[i] as *const _ as _)
+            };
+            array.ids[i] = ids[i].to_rawid(offset);
+            i += 1;
+        }
+        array
+    }
+
+    /// Returns an `IdTable` backed by `self`.
+    ///
+    /// This is used to essentially erase the array size.
+    pub const fn as_table(&self) -> IdTable<'_, T, U> {
+        IdTable {
+            first: &self.ids[0],
+            _p: PhantomData,
+        }
+    }
+}
+
+/// A device id table.
+///
+/// The table is guaranteed to be zero-terminated and to be followed by an array of context data of
+/// type `Option<U>`.
+pub struct IdTable<'a, T: RawDeviceId, U> {
+    first: &'a T::RawType,
+    _p: PhantomData<&'a U>,
+}
+
+impl<T: RawDeviceId, U> AsRef<T::RawType> for IdTable<'_, T, U> {
+    fn as_ref(&self) -> &T::RawType {
+        self.first
+    }
+}
+
+/// Counts the number of parenthesis-delimited, comma-separated items.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::count_paren_items;
+///
+/// assert_eq!(0, count_paren_items!());
+/// assert_eq!(1, count_paren_items!((A)));
+/// assert_eq!(1, count_paren_items!((A),));
+/// assert_eq!(2, count_paren_items!((A), (B)));
+/// assert_eq!(2, count_paren_items!((A), (B),));
+/// assert_eq!(3, count_paren_items!((A), (B), (C)));
+/// assert_eq!(3, count_paren_items!((A), (B), (C),));
+/// ```
+#[macro_export]
+macro_rules! count_paren_items {
+    (($($item:tt)*), $($remaining:tt)*) => { 1 + $crate::count_paren_items!($($remaining)*) };
+    (($($item:tt)*)) => { 1 };
+    () => { 0 };
+}
+
+/// Converts a comma-separated list of pairs into an array with the first element. That is, it
+/// discards the second element of the pair.
+///
+/// Additionally, it automatically introduces a type if the first element is warpped in curly
+/// braces, for example, if it's `{v: 10}`, it becomes `X { v: 10 }`; this is to avoid repeating
+/// the type.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::first_item;
+///
+/// #[derive(PartialEq, Debug)]
+/// struct X {
+///     v: u32,
+/// }
+///
+/// assert_eq!([] as [X; 0], first_item!(X, ));
+/// assert_eq!([X { v: 10 }], first_item!(X, ({ v: 10 }, Y)));
+/// assert_eq!([X { v: 10 }], first_item!(X, ({ v: 10 }, Y),));
+/// assert_eq!([X { v: 10 }], first_item!(X, (X { v: 10 }, Y)));
+/// assert_eq!([X { v: 10 }], first_item!(X, (X { v: 10 }, Y),));
+/// assert_eq!([X { v: 10 }, X { v: 20 }], first_item!(X, ({ v: 10 }, Y), ({ v: 20 }, Y)));
+/// assert_eq!([X { v: 10 }, X { v: 20 }], first_item!(X, ({ v: 10 }, Y), ({ v: 20 }, Y),));
+/// assert_eq!([X { v: 10 }, X { v: 20 }], first_item!(X, (X { v: 10 }, Y), (X { v: 20 }, Y)));
+/// assert_eq!([X { v: 10 }, X { v: 20 }], first_item!(X, (X { v: 10 }, Y), (X { v: 20 }, Y),));
+/// assert_eq!([X { v: 10 }, X { v: 20 }, X { v: 30 }],
+///            first_item!(X, ({ v: 10 }, Y), ({ v: 20 }, Y), ({v: 30}, Y)));
+/// assert_eq!([X { v: 10 }, X { v: 20 }, X { v: 30 }],
+///            first_item!(X, ({ v: 10 }, Y), ({ v: 20 }, Y), ({v: 30}, Y),));
+/// assert_eq!([X { v: 10 }, X { v: 20 }, X { v: 30 }],
+///            first_item!(X, (X { v: 10 }, Y), (X { v: 20 }, Y), (X {v: 30}, Y)));
+/// assert_eq!([X { v: 10 }, X { v: 20 }, X { v: 30 }],
+///            first_item!(X, (X { v: 10 }, Y), (X { v: 20 }, Y), (X {v: 30}, Y),));
+/// ```
+#[macro_export]
+macro_rules! first_item {
+    ($id_type:ty, $(({$($first:tt)*}, $second:expr)),* $(,)?) => {
+        {
+            type IdType = $id_type;
+            [$(IdType{$($first)*},)*]
+        }
+    };
+    ($id_type:ty, $(($first:expr, $second:expr)),* $(,)?) => { [$($first,)*] };
+}
+
+/// Converts a comma-separated list of pairs into an array with the second element. That is, it
+/// discards the first element of the pair.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::second_item;
+///
+/// assert_eq!([] as [u32; 0], second_item!());
+/// assert_eq!([10u32], second_item!((X, 10u32)));
+/// assert_eq!([10u32], second_item!((X, 10u32),));
+/// assert_eq!([10u32], second_item!(({ X }, 10u32)));
+/// assert_eq!([10u32], second_item!(({ X }, 10u32),));
+/// assert_eq!([10u32, 20], second_item!((X, 10u32), (X, 20)));
+/// assert_eq!([10u32, 20], second_item!((X, 10u32), (X, 20),));
+/// assert_eq!([10u32, 20], second_item!(({ X }, 10u32), ({ X }, 20)));
+/// assert_eq!([10u32, 20], second_item!(({ X }, 10u32), ({ X }, 20),));
+/// assert_eq!([10u32, 20, 30], second_item!((X, 10u32), (X, 20), (X, 30)));
+/// assert_eq!([10u32, 20, 30], second_item!((X, 10u32), (X, 20), (X, 30),));
+/// assert_eq!([10u32, 20, 30], second_item!(({ X }, 10u32), ({ X }, 20), ({ X }, 30)));
+/// assert_eq!([10u32, 20, 30], second_item!(({ X }, 10u32), ({ X }, 20), ({ X }, 30),));
+/// ```
+#[macro_export]
+macro_rules! second_item {
+    ($(({$($first:tt)*}, $second:expr)),* $(,)?) => { [$($second,)*] };
+    ($(($first:expr, $second:expr)),* $(,)?) => { [$($second,)*] };
+}
+
+/// Defines a new constant [`IdArray`] with a concise syntax.
+///
+/// It is meant to be used by buses and subsystems to create a similar macro with their device id
+/// type already specified, i.e., with fewer parameters to the end user.
+///
+/// # Examples
+///
+// TODO: Exported but not usable by kernel modules (requires `const_trait_impl`).
+/// ```ignore
+/// #![feature(const_trait_impl)]
+/// # use kernel::{define_id_array, driver::RawDeviceId};
+///
+/// #[derive(Copy, Clone)]
+/// struct Id(u32);
+///
+/// // SAFETY: `ZERO` is all zeroes and `to_rawid` stores `offset` as the second element of the raw
+/// // device id pair.
+/// unsafe impl const RawDeviceId for Id {
+///     type RawType = (u64, isize);
+///     const ZERO: Self::RawType = (0, 0);
+///     fn to_rawid(&self, offset: isize) -> Self::RawType {
+///         (self.0 as u64 + 1, offset)
+///     }
+/// }
+///
+/// define_id_array!(A1, Id, (), []);
+/// define_id_array!(A2, Id, &'static [u8], [(Id(10), None)]);
+/// define_id_array!(A3, Id, &'static [u8], [(Id(10), Some(b"id1")), ]);
+/// define_id_array!(A4, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2"))]);
+/// define_id_array!(A5, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2")), ]);
+/// define_id_array!(A6, Id, &'static [u8], [(Id(10), None), (Id(20), Some(b"id2")), ]);
+/// define_id_array!(A7, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), None), ]);
+/// define_id_array!(A8, Id, &'static [u8], [(Id(10), None), (Id(20), None), ]);
+/// ```
+#[macro_export]
+macro_rules! define_id_array {
+    ($table_name:ident, $id_type:ty, $data_type:ty, [ $($t:tt)* ]) => {
+        const $table_name:
+            $crate::driver::IdArray<$id_type, $data_type, { $crate::count_paren_items!($($t)*) }> =
+                $crate::driver::IdArray::new(
+                    $crate::first_item!($id_type, $($t)*), $crate::second_item!($($t)*));
+    };
+}
+
+/// Defines a new constant [`IdTable`] with a concise syntax.
+///
+/// It is meant to be used by buses and subsystems to create a similar macro with their device id
+/// type already specified, i.e., with fewer parameters to the end user.
+///
+/// # Examples
+///
+// TODO: Exported but not usable by kernel modules (requires `const_trait_impl`).
+/// ```ignore
+/// #![feature(const_trait_impl)]
+/// # use kernel::{define_id_table, driver::RawDeviceId};
+///
+/// #[derive(Copy, Clone)]
+/// struct Id(u32);
+///
+/// // SAFETY: `ZERO` is all zeroes and `to_rawid` stores `offset` as the second element of the raw
+/// // device id pair.
+/// unsafe impl const RawDeviceId for Id {
+///     type RawType = (u64, isize);
+///     const ZERO: Self::RawType = (0, 0);
+///     fn to_rawid(&self, offset: isize) -> Self::RawType {
+///         (self.0 as u64 + 1, offset)
+///     }
+/// }
+///
+/// define_id_table!(T1, Id, &'static [u8], [(Id(10), None)]);
+/// define_id_table!(T2, Id, &'static [u8], [(Id(10), Some(b"id1")), ]);
+/// define_id_table!(T3, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2"))]);
+/// define_id_table!(T4, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2")), ]);
+/// define_id_table!(T5, Id, &'static [u8], [(Id(10), None), (Id(20), Some(b"id2")), ]);
+/// define_id_table!(T6, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), None), ]);
+/// define_id_table!(T7, Id, &'static [u8], [(Id(10), None), (Id(20), None), ]);
+/// ```
+#[macro_export]
+macro_rules! define_id_table {
+    ($table_name:ident, $id_type:ty, $data_type:ty, [ $($t:tt)* ]) => {
+        const $table_name: Option<$crate::driver::IdTable<'static, $id_type, $data_type>> = {
+            $crate::define_id_array!(ARRAY, $id_type, $data_type, [ $($t)* ]);
+            Some(ARRAY.as_table())
+        };
+    };
+}
+
+/// Custom code within device removal.
+pub trait DeviceRemoval {
+    /// Cleans resources up when the device is removed.
+    ///
+    /// This is called when a device is removed and offers implementers the chance to run some code
+    /// that cleans state up.
+    fn device_remove(&self);
+}
+
+impl DeviceRemoval for () {
+    fn device_remove(&self) {}
+}
+
+impl<T: DeviceRemoval> DeviceRemoval for Arc<T> {
+    fn device_remove(&self) {
+        self.deref().device_remove();
+    }
+}
+
+impl<T: DeviceRemoval> DeviceRemoval for Box<T> {
+    fn device_remove(&self) {
+        self.deref().device_remove();
+    }
+}
+
+/// A kernel module that only registers the given driver on init.
+///
+/// This is a helper struct to make it easier to define single-functionality modules, in this case,
+/// modules that offer a single driver.
+pub struct Module<T: DriverOps> {
+    _driver: Pin<Box<Registration<T>>>,
+}
+
+impl<T: DriverOps> crate::Module for Module<T> {
+    fn init(name: &'static CStr, module: &'static ThisModule) -> Result<Self> {
+        Ok(Self {
+            _driver: Registration::new_pinned(name, module)?,
+        })
+    }
+}
+
+/// Declares a kernel module that exposes a single driver.
+///
+/// It is meant to be used as a helper by other subsystems so they can more easily expose their own
+/// macros.
+#[macro_export]
+macro_rules! module_driver {
+    (<$gen_type:ident>, $driver_ops:ty, { type: $type:ty, $($f:tt)* }) => {
+        type Ops<$gen_type> = $driver_ops;
+        type ModuleType = $crate::driver::Module<Ops<$type>>;
+        $crate::prelude::module! {
+            type: ModuleType,
+            $($f)*
+        }
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index f470b7142b5fd9..929f7c47e4b376 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -14,6 +14,8 @@
 #![no_std]
 #![feature(coerce_unsized)]
 #![feature(const_mut_refs)]
+#![feature(const_refs_to_cell)]
+#![feature(const_trait_impl)]
 #![feature(dispatch_from_dyn)]
 #![feature(new_uninit)]
 #![feature(receiver_trait)]
@@ -33,6 +35,7 @@ pub mod alloc;
 pub mod block;
 mod build_assert;
 pub mod device;
+pub mod driver;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;

From cdd68faa0665067cf7736f31b7a4abc7cb235436 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 18:05:48 +0900
Subject: [PATCH 0855/1027] *RFL import: The rest of kernel::device (minus clk
 stuff)

Commit reference: 3dfc5ebff103

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/device.rs  | 433 +++++++++++++++++++++++++++++++++++++++--
 rust/kernel/prelude.rs |   2 +
 2 files changed, 424 insertions(+), 11 deletions(-)

diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index 0aba52b92839db..1e78694ca851a5 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -5,11 +5,26 @@
 //! C header: [`include/linux/device.h`](srctree/include/linux/device.h)
 
 use crate::{
+    alloc::flags::*,
     bindings,
+    error::Result,
+    macros::pin_data,
+    pin_init, pr_crit,
     str::CStr,
+    sync::{lock::mutex, lock::Guard, LockClassKey, Mutex, UniqueArc},
     types::{ARef, Opaque},
 };
-use core::ptr;
+use core::{
+    fmt,
+    ops::{Deref, DerefMut},
+    pin::Pin,
+    ptr,
+};
+
+use crate::init::InPlaceInit;
+
+#[cfg(CONFIG_PRINTK)]
+use crate::c_str;
 
 /// A raw device.
 ///
@@ -39,6 +54,110 @@ pub unsafe trait RawDevice {
         // by the compiler because of their lifetimes).
         unsafe { CStr::from_char_ptr(name) }
     }
+
+    /// Prints an emergency-level message (level 0) prefixed with device information.
+    ///
+    /// More details are available from [`dev_emerg`].
+    ///
+    /// [`dev_emerg`]: crate::dev_emerg
+    fn pr_emerg(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_EMERG, args) };
+    }
+
+    /// Prints an alert-level message (level 1) prefixed with device information.
+    ///
+    /// More details are available from [`dev_alert`].
+    ///
+    /// [`dev_alert`]: crate::dev_alert
+    fn pr_alert(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_ALERT, args) };
+    }
+
+    /// Prints a critical-level message (level 2) prefixed with device information.
+    ///
+    /// More details are available from [`dev_crit`].
+    ///
+    /// [`dev_crit`]: crate::dev_crit
+    fn pr_crit(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_CRIT, args) };
+    }
+
+    /// Prints an error-level message (level 3) prefixed with device information.
+    ///
+    /// More details are available from [`dev_err`].
+    ///
+    /// [`dev_err`]: crate::dev_err
+    fn pr_err(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_ERR, args) };
+    }
+
+    /// Prints a warning-level message (level 4) prefixed with device information.
+    ///
+    /// More details are available from [`dev_warn`].
+    ///
+    /// [`dev_warn`]: crate::dev_warn
+    fn pr_warn(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_WARNING, args) };
+    }
+
+    /// Prints a notice-level message (level 5) prefixed with device information.
+    ///
+    /// More details are available from [`dev_notice`].
+    ///
+    /// [`dev_notice`]: crate::dev_notice
+    fn pr_notice(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_NOTICE, args) };
+    }
+
+    /// Prints an info-level message (level 6) prefixed with device information.
+    ///
+    /// More details are available from [`dev_info`].
+    ///
+    /// [`dev_info`]: crate::dev_info
+    fn pr_info(&self, args: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+        unsafe { self.printk(bindings::KERN_INFO, args) };
+    }
+
+    /// Prints a debug-level message (level 7) prefixed with device information.
+    ///
+    /// More details are available from [`dev_dbg`].
+    ///
+    /// [`dev_dbg`]: crate::dev_dbg
+    fn pr_dbg(&self, args: fmt::Arguments<'_>) {
+        if cfg!(debug_assertions) {
+            // SAFETY: `klevel` is null-terminated, uses one of the kernel constants.
+            unsafe { self.printk(bindings::KERN_DEBUG, args) };
+        }
+    }
+
+    /// Prints the provided message to the console.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that `klevel` is null-terminated; in particular, one of the
+    /// `KERN_*`constants, for example, `KERN_CRIT`, `KERN_ALERT`, etc.
+    #[cfg_attr(not(CONFIG_PRINTK), allow(unused_variables))]
+    unsafe fn printk(&self, klevel: &[u8], msg: fmt::Arguments<'_>) {
+        // SAFETY: `klevel` is null-terminated and one of the kernel constants. `self.raw_device`
+        // is valid because `self` is valid. The "%pA" format string expects a pointer to
+        // `fmt::Arguments`, which is what we're passing as the last argument.
+        #[cfg(CONFIG_PRINTK)]
+        unsafe {
+            bindings::_dev_printk(
+                klevel as *const _ as *const core::ffi::c_char,
+                self.raw_device(),
+                c_str!("%pA").as_char_ptr(),
+                &msg as *const _ as *const core::ffi::c_void,
+            )
+        };
+    }
 }
 
 /// A reference-counted device.
@@ -136,26 +255,318 @@ unsafe impl crate::types::AlwaysRefCounted for Device {
 }
 
 // SAFETY: The device returned by `raw_device` is the one for which we hold a reference.
-unsafe impl RawDevice for Device {
+unsafe impl RawDevice for ARef<Device> {
     fn raw_device(&self) -> *mut bindings::device {
-        self.ptr
+        self.0.get()
+    }
+}
+
+/// Device data.
+///
+/// When a device is removed (for whatever reason, for example, because the device was unplugged or
+/// because the user decided to unbind the driver), the driver is given a chance to clean its state
+/// up, and all io resources should ideally not be used anymore.
+///
+/// However, the device data is reference-counted because other subsystems hold pointers to it. So
+/// some device state must be freed and not used anymore, while others must remain accessible.
+///
+/// This struct separates the device data into three categories:
+///   1. Registrations: are destroyed when the device is removed, but before the io resources
+///      become inaccessible.
+///   2. Io resources: are available until the device is removed.
+///   3. General data: remain available as long as the ref count is nonzero.
+///
+/// This struct implements the `DeviceRemoval` trait so that it can clean resources up even if not
+/// explicitly called by the device drivers.
+#[pin_data]
+pub struct Data<T, U, V> {
+    #[pin]
+    registrations: Mutex<T>,
+    resources: U,
+    general: V,
+}
+
+/// Safely creates an new reference-counted instance of [`Data`].
+#[doc(hidden)]
+#[macro_export]
+macro_rules! new_device_data {
+    ($reg:expr, $res:expr, $gen:expr, $name:literal) => {{
+        static CLASS1: $crate::sync::LockClassKey = $crate::static_lock_class!();
+        let regs = $reg;
+        let res = $res;
+        let gen = $gen;
+        let name = $crate::c_str!($name);
+        $crate::device::Data::try_new(regs, res, gen, name, CLASS1)
+    }};
+}
+
+impl<T, U, V> Data<T, U, V> {
+    /// Creates a new instance of `Data`.
+    ///
+    /// It is recommended that the [`new_device_data`] macro be used as it automatically creates
+    /// the lock classes.
+    pub fn try_new(
+        registrations: T,
+        resources: U,
+        general: V,
+        name: &'static CStr,
+        key1: LockClassKey,
+    ) -> Result<Pin<UniqueArc<Self>>> {
+        let ret = UniqueArc::pin_init(
+            pin_init!(Self {
+                registrations <- Mutex::new_with_key(registrations, name, key1),
+                resources,
+                general,
+            }),
+            GFP_KERNEL,
+        )?;
+        Ok(ret)
+    }
+
+    /// Returns the resources if they're still available.
+    pub fn resources(&self) -> Option<&U> {
+        Some(&self.resources)
+    }
+
+    /// Returns the locked registrations if they're still available.
+    pub fn registrations(&self) -> Option<Guard<'_, T, mutex::MutexBackend>> {
+        Some(self.registrations.lock())
     }
 }
 
-impl Drop for Device {
-    fn drop(&mut self) {
-        // SAFETY: By the type invariants, we know that `self` owns a reference, so it is safe to
-        // relinquish it now.
-        unsafe { bindings::put_device(self.ptr) };
+impl<T, U, V> crate::driver::DeviceRemoval for Data<T, U, V> {
+    fn device_remove(&self) {
+        pr_crit!("Device removal not properly implemented!\n");
     }
 }
 
-impl Clone for Device {
-    fn clone(&self) -> Self {
-        Device::from_dev(self)
+impl<T, U, V> Deref for Data<T, U, V> {
+    type Target = V;
+
+    fn deref(&self) -> &V {
+        &self.general
     }
 }
 
+impl<T, U, V> DerefMut for Data<T, U, V> {
+    fn deref_mut(&mut self) -> &mut V {
+        &mut self.general
+    }
+}
+
+#[doc(hidden)]
+#[macro_export]
+macro_rules! dev_printk {
+    ($method:ident, $dev:expr, $($f:tt)*) => {
+        {
+            // We have an explicity `use` statement here so that callers of this macro are not
+            // required to explicitly use the `RawDevice` trait to use its functions.
+            use $crate::device::RawDevice;
+            ($dev).$method(core::format_args!($($f)*));
+        }
+    }
+}
+
+/// Prints an emergency-level message (level 0) prefixed with device information.
+///
+/// This level should be used if the system is unusable.
+///
+/// Equivalent to the kernel's `dev_emerg` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_emerg!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_emerg {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_emerg, $($f)*); }
+}
+
+/// Prints an alert-level message (level 1) prefixed with device information.
+///
+/// This level should be used if action must be taken immediately.
+///
+/// Equivalent to the kernel's `dev_alert` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_alert!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_alert {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_alert, $($f)*); }
+}
+
+/// Prints a critical-level message (level 2) prefixed with device information.
+///
+/// This level should be used in critical conditions.
+///
+/// Equivalent to the kernel's `dev_crit` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_crit!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_crit {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_crit, $($f)*); }
+}
+
+/// Prints an error-level message (level 3) prefixed with device information.
+///
+/// This level should be used in error conditions.
+///
+/// Equivalent to the kernel's `dev_err` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_err!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_err {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_err, $($f)*); }
+}
+
+/// Prints a warning-level message (level 4) prefixed with device information.
+///
+/// This level should be used in warning conditions.
+///
+/// Equivalent to the kernel's `dev_warn` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_warn!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_warn {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_warn, $($f)*); }
+}
+
+/// Prints a notice-level message (level 5) prefixed with device information.
+///
+/// This level should be used in normal but significant conditions.
+///
+/// Equivalent to the kernel's `dev_notice` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_notice!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_notice {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_notice, $($f)*); }
+}
+
+/// Prints an info-level message (level 6) prefixed with device information.
+///
+/// This level should be used for informational messages.
+///
+/// Equivalent to the kernel's `dev_info` macro.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_info!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_info {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_info, $($f)*); }
+}
+
+/// Prints a debug-level message (level 7) prefixed with device information.
+///
+/// This level should be used for debug messages.
+///
+/// Equivalent to the kernel's `dev_dbg` macro, except that it doesn't support dynamic debug yet.
+///
+/// Mimics the interface of [`std::print!`]. More information about the syntax is available from
+/// [`core::fmt`] and [`alloc::format!`].
+///
+/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::device::Device;
+///
+/// fn example(dev: &Device) {
+///     dev_dbg!(dev, "hello {}\n", "there");
+/// }
+/// ```
+#[macro_export]
+macro_rules! dev_dbg {
+    ($($f:tt)*) => { $crate::dev_printk!(pr_dbg, $($f)*); }
+}
+
 // SAFETY: As by the type invariant `Device` can be sent to any thread.
 unsafe impl Send for Device {}
 
diff --git a/rust/kernel/prelude.rs b/rust/kernel/prelude.rs
index b37a0b3180fbf3..c5765ab863d6cc 100644
--- a/rust/kernel/prelude.rs
+++ b/rust/kernel/prelude.rs
@@ -27,6 +27,8 @@ pub use super::build_assert;
 // `super::std_vendor` is hidden, which makes the macro inline for some reason.
 #[doc(no_inline)]
 pub use super::dbg;
+pub use super::fmt;
+pub use super::{dev_alert, dev_crit, dev_dbg, dev_emerg, dev_err, dev_info, dev_notice, dev_warn};
 pub use super::{pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
 
 pub use super::{init, pin_init, try_init, try_pin_init};

From 8379df19f9a1d79937efe848ee74e1e15555d51d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 20:20:17 +0900
Subject: [PATCH 0856/1027] *RFL import: kernel::io_mem

Commit reference: 3dfc5ebff103
---
 rust/helpers/helpers.c |   1 +
 rust/helpers/iomem.c   | 119 ++++++++++++++++++
 rust/kernel/io_mem.rs  | 278 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs     |   1 +
 4 files changed, 399 insertions(+)
 create mode 100644 rust/helpers/iomem.c
 create mode 100644 rust/kernel/io_mem.rs

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 074c6994e30eb0..89ebb3a838bdd5 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -13,6 +13,7 @@
 #include "build_bug.c"
 #include "device.c"
 #include "err.c"
+#include "iomem.c"
 #include "kunit.c"
 #include "lockdep.c"
 #include "mutex.c"
diff --git a/rust/helpers/iomem.c b/rust/helpers/iomem.c
new file mode 100644
index 00000000000000..ea9aa2ef513dbb
--- /dev/null
+++ b/rust/helpers/iomem.c
@@ -0,0 +1,119 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <asm/io.h>
+
+void __iomem *rust_helper_ioremap(resource_size_t offset, unsigned long size)
+{
+	return ioremap(offset, size);
+}
+EXPORT_SYMBOL_GPL(rust_helper_ioremap);
+
+u8 rust_helper_readb(const volatile void __iomem *addr)
+{
+	return readb(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readb);
+
+u16 rust_helper_readw(const volatile void __iomem *addr)
+{
+	return readw(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readw);
+
+u32 rust_helper_readl(const volatile void __iomem *addr)
+{
+	return readl(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readl);
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq(const volatile void __iomem *addr)
+{
+	return readq(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readq);
+#endif
+
+void rust_helper_writeb(u8 value, volatile void __iomem *addr)
+{
+	writeb(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeb);
+
+void rust_helper_writew(u16 value, volatile void __iomem *addr)
+{
+	writew(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writew);
+
+void rust_helper_writel(u32 value, volatile void __iomem *addr)
+{
+	writel(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writel);
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq(u64 value, volatile void __iomem *addr)
+{
+	writeq(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeq);
+#endif
+
+u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
+{
+	return readb_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readb_relaxed);
+
+u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
+{
+	return readw_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readw_relaxed);
+
+u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
+{
+	return readl_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readl_relaxed);
+
+#ifdef CONFIG_64BIT
+u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
+{
+	return readq_relaxed(addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_readq_relaxed);
+#endif
+
+void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
+{
+	writeb_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeb_relaxed);
+
+void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
+{
+	writew_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writew_relaxed);
+
+void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
+{
+	writel_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writel_relaxed);
+
+#ifdef CONFIG_64BIT
+void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
+{
+	writeq_relaxed(value, addr);
+}
+EXPORT_SYMBOL_GPL(rust_helper_writeq_relaxed);
+#endif
+
+void rust_helper_memcpy_fromio(void *to, const volatile void __iomem *from, long count)
+{
+	memcpy_fromio(to, from, count);
+}
+EXPORT_SYMBOL_GPL(rust_helper_memcpy_fromio);
diff --git a/rust/kernel/io_mem.rs b/rust/kernel/io_mem.rs
new file mode 100644
index 00000000000000..0c646ddcf7ea09
--- /dev/null
+++ b/rust/kernel/io_mem.rs
@@ -0,0 +1,278 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Memory-mapped IO.
+//!
+//! C header: [`include/asm-generic/io.h`](../../../../include/asm-generic/io.h)
+
+#![allow(dead_code)]
+
+use crate::{bindings, error::code::*, error::Result};
+use core::convert::TryInto;
+
+/// Represents a memory resource.
+pub struct Resource {
+    offset: bindings::resource_size_t,
+    size: bindings::resource_size_t,
+}
+
+impl Resource {
+    pub(crate) fn new(
+        start: bindings::resource_size_t,
+        end: bindings::resource_size_t,
+    ) -> Option<Self> {
+        if start == 0 {
+            return None;
+        }
+        Some(Self {
+            offset: start,
+            size: end.checked_sub(start)?.checked_add(1)?,
+        })
+    }
+}
+
+/// Represents a memory block of at least `SIZE` bytes.
+///
+/// # Invariants
+///
+/// `ptr` is a non-null and valid address of at least `SIZE` bytes and returned by an `ioremap`
+/// variant. `ptr` is also 8-byte aligned.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::prelude::*;
+/// use kernel::io_mem::{IoMem, Resource};
+///
+/// fn test(res: Resource) -> Result {
+///     // Create an io mem block of at least 100 bytes.
+///     // SAFETY: No DMA operations are initiated through `mem`.
+///     let mem = unsafe { IoMem::<100>::try_new(res) }?;
+///
+///     // Read one byte from offset 10.
+///     let v = mem.readb(10);
+///
+///     // Write value to offset 20.
+///     mem.writeb(v, 20);
+///
+///     Ok(())
+/// }
+/// ```
+pub struct IoMem<const SIZE: usize> {
+    ptr: usize,
+}
+
+macro_rules! define_read {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Reads IO data from the given offset known, at compile time.
+        ///
+        /// If the offset is not known at compile time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, offset: usize) -> $type_name {
+            Self::check_offset::<$type_name>(offset);
+            let ptr = self.ptr.wrapping_add(offset);
+            // SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
+            // guarantees that the code won't build if `offset` makes the read go out of bounds
+            // (including the type size).
+            unsafe { bindings::$name(ptr as _) }
+        }
+
+        /// Reads IO data from the given offset.
+        ///
+        /// It fails if/when the offset (plus the type size) is out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, offset: usize) -> Result<$type_name> {
+            if !Self::offset_ok::<$type_name>(offset) {
+                return Err(EINVAL);
+            }
+            let ptr = self.ptr.wrapping_add(offset);
+            // SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
+            // returns an error if `offset` would make the read go out of bounds (including the
+            // type size).
+            Ok(unsafe { bindings::$name(ptr as _) })
+        }
+    };
+}
+
+macro_rules! define_write {
+    ($(#[$attr:meta])* $name:ident, $try_name:ident, $type_name:ty) => {
+        /// Writes IO data to the given offset, known at compile time.
+        ///
+        /// If the offset is not known at compile time, the build will fail.
+        $(#[$attr])*
+        #[inline]
+        pub fn $name(&self, value: $type_name, offset: usize) {
+            Self::check_offset::<$type_name>(offset);
+            let ptr = self.ptr.wrapping_add(offset);
+            // SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
+            // guarantees that the code won't link if `offset` makes the write go out of bounds
+            // (including the type size).
+            unsafe { bindings::$name(value, ptr as _) }
+        }
+
+        /// Writes IO data to the given offset.
+        ///
+        /// It fails if/when the offset (plus the type size) is out of bounds.
+        $(#[$attr])*
+        pub fn $try_name(&self, value: $type_name, offset: usize) -> Result {
+            if !Self::offset_ok::<$type_name>(offset) {
+                return Err(EINVAL);
+            }
+            let ptr = self.ptr.wrapping_add(offset);
+            // SAFETY: The type invariants guarantee that `ptr` is a valid pointer. The check above
+            // returns an error if `offset` would make the write go out of bounds (including the
+            // type size).
+            unsafe { bindings::$name(value, ptr as _) };
+            Ok(())
+        }
+    };
+}
+
+impl<const SIZE: usize> IoMem<SIZE> {
+    /// Tries to create a new instance of a memory block.
+    ///
+    /// The resource described by `res` is mapped into the CPU's address space so that it can be
+    /// accessed directly. It is also consumed by this function so that it can't be mapped again
+    /// to a different address.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA
+    /// operations, or (b) that DMA operations initiated via the returned interface use DMA handles
+    /// allocated through the `dma` module.
+    pub unsafe fn try_new(res: Resource) -> Result<Self> {
+        // Check that the resource has at least `SIZE` bytes in it.
+        if res.size < SIZE.try_into()? {
+            return Err(EINVAL);
+        }
+
+        // To be able to check pointers at compile time based only on offsets, we need to guarantee
+        // that the base pointer is minimally aligned. So we conservatively expect at least 8 bytes.
+        if res.offset % 8 != 0 {
+            crate::pr_err!("Physical address is not 64-bit aligned: {:x}", res.offset);
+            return Err(EDOM);
+        }
+
+        // Try to map the resource.
+        // SAFETY: Just mapping the memory range.
+        let addr = unsafe { bindings::ioremap(res.offset, res.size as _) };
+        if addr.is_null() {
+            Err(ENOMEM)
+        } else {
+            // INVARIANT: `addr` is non-null and was returned by `ioremap`, so it is valid. It is
+            // also 8-byte aligned because we checked it above.
+            Ok(Self { ptr: addr as usize })
+        }
+    }
+
+    #[inline]
+    const fn offset_ok<T>(offset: usize) -> bool {
+        let type_size = core::mem::size_of::<T>();
+        if let Some(end) = offset.checked_add(type_size) {
+            end <= SIZE && offset % type_size == 0
+        } else {
+            false
+        }
+    }
+
+    fn offset_ok_of_val<T: ?Sized>(offset: usize, value: &T) -> bool {
+        let value_size = core::mem::size_of_val(value);
+        let value_alignment = core::mem::align_of_val(value);
+        if let Some(end) = offset.checked_add(value_size) {
+            end <= SIZE && offset % value_alignment == 0
+        } else {
+            false
+        }
+    }
+
+    #[inline]
+    const fn check_offset<T>(offset: usize) {
+        crate::build_assert!(Self::offset_ok::<T>(offset), "IoMem offset overflow");
+    }
+
+    /// Copy memory block from an i/o memory by filling the specified buffer with it.
+    ///
+    /// # Examples
+    /// ```
+    /// use kernel::io_mem::{self, IoMem, Resource};
+    ///
+    /// fn test(res: Resource) -> Result {
+    ///     // Create an i/o memory block of at least 100 bytes.
+    ///     let mem = unsafe { IoMem::<100>::try_new(res) }?;
+    ///
+    ///     let mut buffer: [u8; 32] = [0; 32];
+    ///
+    ///     // Memcpy 16 bytes from an offset 10 of i/o memory block into the buffer.
+    ///     mem.try_memcpy_fromio(&mut buffer[..16], 10)?;
+    ///
+    ///     Ok(())
+    /// }
+    /// ```
+    pub fn try_memcpy_fromio(&self, buffer: &mut [u8], offset: usize) -> Result {
+        if !Self::offset_ok_of_val(offset, buffer) {
+            return Err(EINVAL);
+        }
+
+        let ptr = self.ptr.wrapping_add(offset);
+
+        // SAFETY:
+        //   - The type invariants guarantee that `ptr` is a valid pointer.
+        //   - The bounds of `buffer` are checked with a call to `offset_ok_of_val()`.
+        unsafe {
+            bindings::memcpy_fromio(
+                buffer.as_mut_ptr() as *mut _,
+                ptr as *const _,
+                buffer.len() as _,
+            )
+        };
+        Ok(())
+    }
+
+    define_read!(readb, try_readb, u8);
+    define_read!(readw, try_readw, u16);
+    define_read!(readl, try_readl, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq,
+        try_readq,
+        u64
+    );
+
+    define_read!(readb_relaxed, try_readb_relaxed, u8);
+    define_read!(readw_relaxed, try_readw_relaxed, u16);
+    define_read!(readl_relaxed, try_readl_relaxed, u32);
+    define_read!(
+        #[cfg(CONFIG_64BIT)]
+        readq_relaxed,
+        try_readq_relaxed,
+        u64
+    );
+
+    define_write!(writeb, try_writeb, u8);
+    define_write!(writew, try_writew, u16);
+    define_write!(writel, try_writel, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq,
+        try_writeq,
+        u64
+    );
+
+    define_write!(writeb_relaxed, try_writeb_relaxed, u8);
+    define_write!(writew_relaxed, try_writew_relaxed, u16);
+    define_write!(writel_relaxed, try_writel_relaxed, u32);
+    define_write!(
+        #[cfg(CONFIG_64BIT)]
+        writeq_relaxed,
+        try_writeq_relaxed,
+        u64
+    );
+}
+
+impl<const SIZE: usize> Drop for IoMem<SIZE> {
+    fn drop(&mut self) {
+        // SAFETY: By the type invariant, `self.ptr` is a value returned by a previous successful
+        // call to `ioremap`.
+        unsafe { bindings::iounmap(self.ptr as _) };
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 929f7c47e4b376..8995ff3b4c004a 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -41,6 +41,7 @@ pub mod error;
 pub mod firmware;
 pub mod init;
 pub mod io_buffer;
+pub mod io_mem;
 pub mod io_pgtable;
 pub mod ioctl;
 #[cfg(CONFIG_KUNIT)]

From a460e4b61449ff4c6ba51b78823bd013e8379fa5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 20:23:17 +0900
Subject: [PATCH 0857/1027] *RFL import: kernel::of

Commit reference: 3dfc5ebff103
---
 rust/kernel/lib.rs |  1 +
 rust/kernel/of.rs  | 63 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 64 insertions(+)
 create mode 100644 rust/kernel/of.rs

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 8995ff3b4c004a..fb3caf0374d2ed 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -49,6 +49,7 @@ pub mod kunit;
 pub mod module_param;
 #[cfg(CONFIG_NET)]
 pub mod net;
+pub mod of;
 pub mod page;
 pub mod prelude;
 pub mod print;
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
new file mode 100644
index 00000000000000..cdcd8324433769
--- /dev/null
+++ b/rust/kernel/of.rs
@@ -0,0 +1,63 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Devicetree and Open Firmware abstractions.
+//!
+//! C header: [`include/linux/of_*.h`](../../../../include/linux/of_*.h)
+
+use crate::{bindings, driver, str::BStr};
+
+/// An open firmware device id.
+#[derive(Clone, Copy)]
+pub enum DeviceId {
+    /// An open firmware device id where only a compatible string is specified.
+    Compatible(&'static BStr),
+}
+
+/// Defines a const open firmware device id table that also carries per-entry data/context/info.
+///
+/// The name of the const is `OF_DEVICE_ID_TABLE`, which is what buses are expected to name their
+/// open firmware tables.
+///
+/// # Examples
+///
+/// ```
+/// # use kernel::define_of_id_table;
+/// use kernel::of;
+///
+/// define_of_id_table! {u32, [
+///     (of::DeviceId::Compatible(b"test-device1,test-device2"), Some(0xff)),
+///     (of::DeviceId::Compatible(b"test-device3"), None),
+/// ]};
+/// ```
+#[macro_export]
+macro_rules! define_of_id_table {
+    ($data_type:ty, $($t:tt)*) => {
+        $crate::define_id_table!(OF_DEVICE_ID_TABLE, $crate::of::DeviceId, $data_type, $($t)*);
+    };
+}
+
+// SAFETY: `ZERO` is all zeroed-out and `to_rawid` stores `offset` in `of_device_id::data`.
+unsafe impl const driver::RawDeviceId for DeviceId {
+    type RawType = bindings::of_device_id;
+    const ZERO: Self::RawType = bindings::of_device_id {
+        name: [0; 32],
+        type_: [0; 32],
+        compatible: [0; 128],
+        data: core::ptr::null(),
+    };
+
+    fn to_rawid(&self, offset: isize) -> Self::RawType {
+        let DeviceId::Compatible(compatible) = self;
+        let mut id = Self::ZERO;
+        let mut i = 0;
+        while i < compatible.len() {
+            // If `compatible` does not fit in `id.compatible`, an "index out of bounds" build time
+            // error will be triggered.
+            id.compatible[i] = compatible[i] as _;
+            i += 1;
+        }
+        id.compatible[i] = b'\0' as _;
+        id.data = offset as _;
+        id
+    }
+}

From ef977d6a9aed5d1b93b21c0c0160f45cd1763a6b Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 20:26:46 +0900
Subject: [PATCH 0858/1027] *RFL import: kernel::platform

Commit reference: 3dfc5ebff103
---
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/platform_device.c  |  24 ++++
 rust/kernel/lib.rs              |   2 +
 rust/kernel/platform.rs         | 230 ++++++++++++++++++++++++++++++++
 5 files changed, 258 insertions(+)
 create mode 100644 rust/helpers/platform_device.c
 create mode 100644 rust/kernel/platform.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 6adedd06de6b22..a2ceb28440b0b5 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -20,6 +20,7 @@
 #include <linux/io-pgtable.h>
 #include <linux/ktime.h>
 #include <linux/lockdep.h>
+#include <linux/platform_device.h>
 #include <linux/refcount.h>
 #include <linux/siphash.h>
 #include <linux/sched.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 89ebb3a838bdd5..a7169193c22bff 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -18,6 +18,7 @@
 #include "lockdep.c"
 #include "mutex.c"
 #include "page.c"
+#include "platform_device.c"
 #include "refcount.c"
 #include "signal.c"
 #include "siphash.c"
diff --git a/rust/helpers/platform_device.c b/rust/helpers/platform_device.c
new file mode 100644
index 00000000000000..55419f56dc084f
--- /dev/null
+++ b/rust/helpers/platform_device.c
@@ -0,0 +1,24 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+
+void *
+rust_helper_platform_get_drvdata(const struct platform_device *pdev)
+{
+	return platform_get_drvdata(pdev);
+}
+
+void
+rust_helper_platform_set_drvdata(struct platform_device *pdev,
+                                void *data)
+{
+	platform_set_drvdata(pdev, data);
+}
+
+const struct of_device_id *rust_helper_of_match_device(
+               const struct of_device_id *matches, const struct device *dev)
+{
+	return of_match_device(matches, dev);
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index fb3caf0374d2ed..2d2271104c0649 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -12,6 +12,7 @@
 //! do so first instead of bypassing this crate.
 
 #![no_std]
+#![feature(associated_type_defaults)]
 #![feature(coerce_unsized)]
 #![feature(const_mut_refs)]
 #![feature(const_refs_to_cell)]
@@ -51,6 +52,7 @@ pub mod module_param;
 pub mod net;
 pub mod of;
 pub mod page;
+pub mod platform;
 pub mod prelude;
 pub mod print;
 pub mod siphash;
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
new file mode 100644
index 00000000000000..160e24b56994f9
--- /dev/null
+++ b/rust/kernel/platform.rs
@@ -0,0 +1,230 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Platform devices and drivers.
+//!
+//! Also called `platdev`, `pdev`.
+//!
+//! C header: [`include/linux/platform_device.h`](../../../../include/linux/platform_device.h)
+
+use crate::{
+    bindings,
+    device::{self, RawDevice},
+    driver,
+    error::{from_kernel_result, to_result, Result},
+    of,
+    str::CStr,
+    types::ForeignOwnable,
+    ThisModule,
+};
+
+/// A registration of a platform driver.
+pub type Registration<T> = driver::Registration<Adapter<T>>;
+
+/// An adapter for the registration of platform drivers.
+pub struct Adapter<T: Driver>(T);
+
+impl<T: Driver> driver::DriverOps for Adapter<T> {
+    type RegType = bindings::platform_driver;
+
+    unsafe fn register(
+        reg: *mut bindings::platform_driver,
+        name: &'static CStr,
+        module: &'static ThisModule,
+    ) -> Result {
+        // SAFETY: By the safety requirements of this function (defined in the trait definition),
+        // `reg` is non-null and valid.
+        let pdrv = unsafe { &mut *reg };
+
+        pdrv.driver.name = name.as_char_ptr();
+        pdrv.probe = Some(Self::probe_callback);
+        pdrv.remove = Some(Self::remove_callback);
+        if let Some(t) = T::OF_DEVICE_ID_TABLE {
+            pdrv.driver.of_match_table = t.as_ref();
+        }
+        // SAFETY:
+        //   - `pdrv` lives at least until the call to `platform_driver_unregister()` returns.
+        //   - `name` pointer has static lifetime.
+        //   - `module.0` lives at least as long as the module.
+        //   - `probe()` and `remove()` are static functions.
+        //   - `of_match_table` is either a raw pointer with static lifetime,
+        //      as guaranteed by the [`driver::IdTable`] type, or null.
+        to_result(unsafe { bindings::__platform_driver_register(reg, module.0) })
+    }
+
+    unsafe fn unregister(reg: *mut bindings::platform_driver) {
+        // SAFETY: By the safety requirements of this function (defined in the trait definition),
+        // `reg` was passed (and updated) by a previous successful call to
+        // `platform_driver_register`.
+        unsafe { bindings::platform_driver_unregister(reg) };
+    }
+}
+
+impl<T: Driver> Adapter<T> {
+    fn get_id_info(dev: &Device) -> Option<&'static T::IdInfo> {
+        let table = T::OF_DEVICE_ID_TABLE?;
+
+        // SAFETY: `table` has static lifetime, so it is valid for read. `dev` is guaranteed to be
+        // valid while it's alive, so is the raw device returned by it.
+        let id = unsafe { bindings::of_match_device(table.as_ref(), dev.raw_device()) };
+        if id.is_null() {
+            return None;
+        }
+
+        // SAFETY: `id` is a pointer within the static table, so it's always valid.
+        let offset = unsafe { (*id).data };
+        if offset.is_null() {
+            return None;
+        }
+
+        // SAFETY: The offset comes from a previous call to `offset_from` in `IdArray::new`, which
+        // guarantees that the resulting pointer is within the table.
+        let ptr = unsafe {
+            id.cast::<u8>()
+                .offset(offset as _)
+                .cast::<Option<T::IdInfo>>()
+        };
+
+        // SAFETY: The id table has a static lifetime, so `ptr` is guaranteed to be valid for read.
+        #[allow(clippy::needless_borrow)]
+        unsafe {
+            (&*ptr).as_ref()
+        }
+    }
+
+    extern "C" fn probe_callback(pdev: *mut bindings::platform_device) -> core::ffi::c_int {
+        from_result(|| {
+            // SAFETY: `pdev` is valid by the contract with the C code. `dev` is alive only for the
+            // duration of this call, so it is guaranteed to remain alive for the lifetime of
+            // `pdev`.
+            let mut dev = unsafe { Device::from_ptr(pdev) };
+            let info = Self::get_id_info(&dev);
+            let data = T::probe(&mut dev, info)?;
+            // SAFETY: `pdev` is guaranteed to be a valid, non-null pointer.
+            unsafe { bindings::platform_set_drvdata(pdev, data.into_foreign() as _) };
+            Ok(0)
+        })
+    }
+
+    extern "C" fn remove_callback(pdev: *mut bindings::platform_device) {
+        {
+            // SAFETY: `pdev` is guaranteed to be a valid, non-null pointer.
+            let ptr = unsafe { bindings::platform_get_drvdata(pdev) };
+            // SAFETY:
+            //   - we allocated this pointer using `T::Data::into_foreign`,
+            //     so it is safe to turn back into a `T::Data`.
+            //   - the allocation happened in `probe`, no-one freed the memory,
+            //     `remove` is the canonical kernel location to free driver data. so OK
+            //     to convert the pointer back to a Rust structure here.
+            let data = unsafe { T::Data::from_foreign(ptr) };
+            let _ = T::remove(&data);
+            <T::Data as driver::DeviceRemoval>::device_remove(&data);
+        }
+    }
+}
+
+/// A platform driver.
+pub trait Driver {
+    /// Data stored on device by driver.
+    ///
+    /// Corresponds to the data set or retrieved via the kernel's
+    /// `platform_{set,get}_drvdata()` functions.
+    ///
+    /// Require that `Data` implements `ForeignOwnable`. We guarantee to
+    /// never move the underlying wrapped data structure. This allows
+    type Data: ForeignOwnable + Send + Sync + driver::DeviceRemoval = ();
+
+    /// The type holding information about each device id supported by the driver.
+    type IdInfo: 'static = ();
+
+    /// The table of device ids supported by the driver.
+    const OF_DEVICE_ID_TABLE: Option<driver::IdTable<'static, of::DeviceId, Self::IdInfo>> = None;
+
+    /// Platform driver probe.
+    ///
+    /// Called when a new platform device is added or discovered.
+    /// Implementers should attempt to initialize the device here.
+    fn probe(dev: &mut Device, id_info: Option<&Self::IdInfo>) -> Result<Self::Data>;
+
+    /// Platform driver remove.
+    ///
+    /// Called when a platform device is removed.
+    /// Implementers should prepare the device for complete removal here.
+    fn remove(_data: &Self::Data) -> Result {
+        Ok(())
+    }
+}
+
+/// A platform device.
+///
+/// # Invariants
+///
+/// The field `ptr` is non-null and valid for the lifetime of the object.
+pub struct Device {
+    ptr: *mut bindings::platform_device,
+}
+
+impl Device {
+    /// Creates a new device from the given pointer.
+    ///
+    /// # Safety
+    ///
+    /// `ptr` must be non-null and valid. It must remain valid for the lifetime of the returned
+    /// instance.
+    unsafe fn from_ptr(ptr: *mut bindings::platform_device) -> Self {
+        // INVARIANT: The safety requirements of the function ensure the lifetime invariant.
+        Self { ptr }
+    }
+
+    /// Returns id of the platform device.
+    pub fn id(&self) -> i32 {
+        // SAFETY: By the type invariants, we know that `self.ptr` is non-null and valid.
+        unsafe { (*self.ptr).id }
+    }
+}
+
+impl AsRef<device::Device> for Device {
+    fn as_ref(&self) -> &device::Device {
+        // SAFETY: By the type invariants, we know that `self.ptr` is non-null and valid.
+        unsafe { device::Device::as_ref(&mut (*self.ptr).dev) }
+    }
+}
+
+// SAFETY: The device returned by `raw_device` is the raw platform device.
+unsafe impl device::RawDevice for Device {
+    fn raw_device(&self) -> *mut bindings::device {
+        // SAFETY: By the type invariants, we know that `self.ptr` is non-null and valid.
+        unsafe { &mut (*self.ptr).dev }
+    }
+}
+
+/// Declares a kernel module that exposes a single platform driver.
+///
+/// # Examples
+///
+/// ```ignore
+/// # use kernel::{platform, define_of_id_table, module_platform_driver};
+/// #
+/// struct MyDriver;
+/// impl platform::Driver for MyDriver {
+///     // [...]
+/// #   fn probe(_dev: &mut platform::Device, _id_info: Option<&Self::IdInfo>) -> Result {
+/// #       Ok(())
+/// #   }
+/// #   define_of_id_table! {(), [
+/// #       (of::DeviceId::Compatible(b"brcm,bcm2835-rng"), None),
+/// #   ]}
+/// }
+///
+/// module_platform_driver! {
+///     type: MyDriver,
+///     name: "module_name",
+///     author: "Author name",
+///     license: "GPL",
+/// }
+/// ```
+#[macro_export]
+macro_rules! module_platform_driver {
+    ($($f:tt)*) => {
+        $crate::module_driver!(<T>, $crate::platform::Adapter<T>, { $($f)* });
+    };
+}

From 790910cc236df9462102228015ce9e8f8f6ea999 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 20:29:27 +0900
Subject: [PATCH 0859/1027] *RFL import: kernel::delay

Commit reference: 3dfc5ebff103
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/delay.rs            | 104 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 3 files changed, 107 insertions(+)
 create mode 100644 rust/kernel/delay.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a2ceb28440b0b5..5ca8eebb2f1fdd 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -10,6 +10,7 @@
 #include <linux/blk_types.h>
 #include <linux/blk-mq.h>
 #include <linux/blkdev.h>
+#include <linux/delay.h>
 #include <linux/device.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
diff --git a/rust/kernel/delay.rs b/rust/kernel/delay.rs
new file mode 100644
index 00000000000000..1e987fa659419b
--- /dev/null
+++ b/rust/kernel/delay.rs
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! Delay functions for operations like sleeping.
+//!
+//! C header: [`include/linux/delay.h`](../../../../include/linux/delay.h)
+
+use crate::bindings;
+use core::{cmp::min, time::Duration};
+
+const MILLIS_PER_SEC: u64 = 1_000;
+
+fn coarse_sleep_conversion(duration: Duration) -> core::ffi::c_uint {
+    let milli_as_nanos = Duration::MILLISECOND.subsec_nanos();
+
+    // Rounds the nanosecond component of `duration` up to the nearest millisecond.
+    let nanos_as_millis = duration.subsec_nanos().wrapping_add(milli_as_nanos - 1) / milli_as_nanos;
+
+    // Saturates the second component of `duration` to `c_uint::MAX`.
+    let seconds_as_millis = min(
+        duration.as_secs().saturating_mul(MILLIS_PER_SEC),
+        u64::from(core::ffi::c_uint::MAX),
+    ) as core::ffi::c_uint;
+
+    seconds_as_millis.saturating_add(nanos_as_millis)
+}
+
+/// Sleeps safely even with waitqueue interruptions.
+///
+/// This function forwards the call to the C side `msleep` function. As a result,
+/// `duration` will be rounded up to the nearest millisecond if granularity less
+/// than a millisecond is provided. Any [`Duration`] that exceeds
+/// [`c_uint::MAX`][core::ffi::c_uint::MAX] in milliseconds is saturated.
+///
+/// # Examples
+///
+// Keep these in sync with `test_coarse_sleep_examples`.
+/// ```
+/// # use core::time::Duration;
+/// # use kernel::delay::coarse_sleep;
+/// coarse_sleep(Duration::ZERO);                   // Equivalent to `msleep(0)`.
+/// coarse_sleep(Duration::from_nanos(1));          // Equivalent to `msleep(1)`.
+///
+/// coarse_sleep(Duration::from_nanos(1_000_000));  // Equivalent to `msleep(1)`.
+/// coarse_sleep(Duration::from_nanos(1_000_001));  // Equivalent to `msleep(2)`.
+/// coarse_sleep(Duration::from_nanos(1_999_999));  // Equivalent to `msleep(2)`.
+///
+/// coarse_sleep(Duration::from_millis(1));         // Equivalent to `msleep(1)`.
+/// coarse_sleep(Duration::from_millis(2));         // Equivalent to `msleep(2)`.
+///
+/// coarse_sleep(Duration::from_secs(1));           // Equivalent to `msleep(1000)`.
+/// coarse_sleep(Duration::new(1, 1));              // Equivalent to `msleep(1001)`.
+/// coarse_sleep(Duration::new(1, 2));              // Equivalent to `msleep(1001)`.
+/// ```
+pub fn coarse_sleep(duration: Duration) {
+    // SAFETY: `msleep` is safe for all values of its argument.
+    unsafe { bindings::msleep(coarse_sleep_conversion(duration)) }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::{coarse_sleep_conversion, MILLIS_PER_SEC};
+    use core::time::Duration;
+
+    #[test]
+    fn test_coarse_sleep_examples() {
+        // Keep these in sync with `coarse_sleep`'s `# Examples` section.
+
+        assert_eq!(coarse_sleep_conversion(Duration::ZERO), 0);
+        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1)), 1);
+
+        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_000_000)), 1);
+        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_000_001)), 2);
+        assert_eq!(coarse_sleep_conversion(Duration::from_nanos(1_999_999)), 2);
+
+        assert_eq!(coarse_sleep_conversion(Duration::from_millis(1)), 1);
+        assert_eq!(coarse_sleep_conversion(Duration::from_millis(2)), 2);
+
+        assert_eq!(coarse_sleep_conversion(Duration::from_secs(1)), 1000);
+        assert_eq!(coarse_sleep_conversion(Duration::new(1, 1)), 1001);
+        assert_eq!(coarse_sleep_conversion(Duration::new(1, 2)), 1001);
+    }
+
+    #[test]
+    fn test_coarse_sleep_saturation() {
+        assert!(
+            coarse_sleep_conversion(Duration::new(
+                core::ffi::c_uint::MAX as u64 / MILLIS_PER_SEC,
+                0
+            )) < core::ffi::c_uint::MAX
+        );
+        assert_eq!(
+            coarse_sleep_conversion(Duration::new(
+                core::ffi::c_uint::MAX as u64 / MILLIS_PER_SEC,
+                999_999_999
+            )),
+            core::ffi::c_uint::MAX
+        );
+
+        assert_eq!(
+            coarse_sleep_conversion(Duration::MAX),
+            core::ffi::c_uint::MAX
+        );
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 2d2271104c0649..b9f4d52e0a2fd9 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -18,6 +18,7 @@
 #![feature(const_refs_to_cell)]
 #![feature(const_trait_impl)]
 #![feature(dispatch_from_dyn)]
+#![feature(duration_constants)]
 #![feature(new_uninit)]
 #![feature(receiver_trait)]
 #![feature(type_alias_impl_trait)]
@@ -35,6 +36,7 @@ pub mod alloc;
 #[cfg(CONFIG_BLOCK)]
 pub mod block;
 mod build_assert;
+pub mod delay;
 pub mod device;
 pub mod driver;
 pub mod error;

From b2d51c477548af67a7057b35bbda6736f65aa675 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Oct 2022 00:10:30 +0900
Subject: [PATCH 0860/1027] rust: of: Add OF node abstraction

This abstraction enables Rust drivers to walk Device Tree nodes and
query their properties.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   3 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/of.c               |  17 ++
 rust/kernel/device.rs           |  10 +-
 rust/kernel/of.rs               | 463 +++++++++++++++++++++++++++++++-
 5 files changed, 492 insertions(+), 2 deletions(-)
 create mode 100644 rust/helpers/of.c

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 5ca8eebb2f1fdd..0012144bca4059 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -21,6 +21,9 @@
 #include <linux/io-pgtable.h>
 #include <linux/ktime.h>
 #include <linux/lockdep.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/of_device.h>
 #include <linux/platform_device.h>
 #include <linux/refcount.h>
 #include <linux/siphash.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a7169193c22bff..5fb11348a08374 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -17,6 +17,7 @@
 #include "kunit.c"
 #include "lockdep.c"
 #include "mutex.c"
+#include "of.c"
 #include "page.c"
 #include "platform_device.c"
 #include "refcount.c"
diff --git a/rust/helpers/of.c b/rust/helpers/of.c
new file mode 100644
index 00000000000000..6a47595ed6a717
--- /dev/null
+++ b/rust/helpers/of.c
@@ -0,0 +1,17 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/of.h>
+
+bool rust_helper_of_node_is_root(const struct device_node *np)
+{
+	return of_node_is_root(np);
+}
+EXPORT_SYMBOL_GPL(rust_helper_of_node_is_root);
+
+struct device_node *rust_helper_of_parse_phandle(const struct device_node *np,
+               const char *phandle_name,
+               int index)
+{
+	return of_parse_phandle(np, phandle_name, index);
+}
+EXPORT_SYMBOL_GPL(rust_helper_of_parse_phandle);
diff --git a/rust/kernel/device.rs b/rust/kernel/device.rs
index 1e78694ca851a5..f9ecaf8be9d780 100644
--- a/rust/kernel/device.rs
+++ b/rust/kernel/device.rs
@@ -9,7 +9,7 @@ use crate::{
     bindings,
     error::Result,
     macros::pin_data,
-    pin_init, pr_crit,
+    of, pin_init, pr_crit,
     str::CStr,
     sync::{lock::mutex, lock::Guard, LockClassKey, Mutex, UniqueArc},
     types::{ARef, Opaque},
@@ -55,6 +55,14 @@ pub unsafe trait RawDevice {
         unsafe { CStr::from_char_ptr(name) }
     }
 
+    /// Gets the OpenFirmware node attached to this device
+    fn of_node(&self) -> Option<of::Node> {
+        let ptr = self.raw_device();
+
+        // SAFETY: This is safe as long as of_node is NULL or valid.
+        unsafe { of::Node::get_from_raw((*ptr).of_node) }
+    }
+
     /// Prints an emergency-level message (level 0) prefixed with device information.
     ///
     /// More details are available from [`dev_emerg`].
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index cdcd8324433769..6ec2278a549b5b 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -4,7 +4,20 @@
 //!
 //! C header: [`include/linux/of_*.h`](../../../../include/linux/of_*.h)
 
-use crate::{bindings, driver, str::BStr};
+// Note: Most OF functions turn into inline dummies with CONFIG_OF(_*) disabled.
+// We have to either add config conditionals to helpers.c or here; let's do it
+// here for now. In the future, once bindgen can auto-generate static inline
+// helpers, this can go away if desired.
+
+use core::marker::PhantomData;
+use core::num::NonZeroU32;
+
+use crate::{
+    alloc::flags::*,
+    bindings, driver,
+    prelude::*,
+    str::{BStr, CStr},
+};
 
 /// An open firmware device id.
 #[derive(Clone, Copy)]
@@ -61,3 +74,451 @@ unsafe impl const driver::RawDeviceId for DeviceId {
         id
     }
 }
+
+/// Type alias for an OF phandle
+pub type PHandle = bindings::phandle;
+
+/// An OF device tree node.
+///
+/// # Invariants
+///
+/// `raw_node` points to a valid OF node, and we hold a reference to it.
+pub struct Node {
+    raw_node: *mut bindings::device_node,
+}
+
+#[allow(dead_code)]
+impl Node {
+    /// Creates a `Node` from a raw C pointer. The pointer must be owned (the caller
+    /// gives up its reference). If the pointer is NULL, returns None.
+    pub(crate) unsafe fn from_raw(raw_node: *mut bindings::device_node) -> Option<Node> {
+        if raw_node.is_null() {
+            None
+        } else {
+            // INVARIANT: `raw_node` is valid per the above contract, and non-null per the
+            // above check.
+            Some(Node { raw_node })
+        }
+    }
+
+    /// Creates a `Node` from a raw C pointer. The pointer must be borrowed (the caller
+    /// retains its reference, which must be valid for the duration of the call). If the
+    /// pointer is NULL, returns None.
+    pub(crate) unsafe fn get_from_raw(raw_node: *mut bindings::device_node) -> Option<Node> {
+        // SAFETY: `raw_node` is valid or NULL per the above contract. `of_node_get` can handle
+        // NULL.
+        unsafe {
+            #[cfg(CONFIG_OF_DYNAMIC)]
+            bindings::of_node_get(raw_node);
+            Node::from_raw(raw_node)
+        }
+    }
+
+    /// Returns a reference to the underlying C `device_node` structure.
+    fn node(&self) -> &bindings::device_node {
+        // SAFETY: `raw_node` is valid per the type invariant.
+        unsafe { &*self.raw_node }
+    }
+
+    /// Returns the name of the node.
+    pub fn name(&self) -> &CStr {
+        // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`.
+        unsafe { CStr::from_char_ptr(self.node().name) }
+    }
+
+    /// Returns the phandle for this node.
+    pub fn phandle(&self) -> PHandle {
+        self.node().phandle
+    }
+
+    /// Returns the full name (with address) for this node.
+    pub fn full_name(&self) -> &CStr {
+        // SAFETY: The lifetime of the `CStr` is the same as the lifetime of this `Node`.
+        unsafe { CStr::from_char_ptr(self.node().full_name) }
+    }
+
+    /// Returns `true` if the node is the root node.
+    pub fn is_root(&self) -> bool {
+        unsafe { bindings::of_node_is_root(self.raw_node) }
+    }
+
+    /// Returns the parent node, if any.
+    pub fn parent(&self) -> Option<Node> {
+        #[cfg(not(CONFIG_OF))]
+        {
+            None
+        }
+        #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant, and `of_get_parent()` takes a
+        // new reference to the parent (or returns NULL).
+        unsafe {
+            Node::from_raw(bindings::of_get_parent(self.raw_node))
+        }
+    }
+
+    /// Returns an iterator over the node's children.
+    // TODO: use type alias for return type once type_alias_impl_trait is stable
+    pub fn children(
+        &self,
+    ) -> NodeIterator<'_, impl Fn(*mut bindings::device_node) -> *mut bindings::device_node + '_>
+    {
+        #[cfg(not(CONFIG_OF))]
+        {
+            NodeIterator::new(|_prev| core::ptr::null_mut())
+        }
+        #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant, and the lifetime of the `NodeIterator`
+        // does not exceed the lifetime of the `Node` so it can borrow its reference.
+        NodeIterator::new(|prev| unsafe { bindings::of_get_next_child(self.raw_node, prev) })
+    }
+
+    /// Find a child by its name and return it, or None if not found.
+    #[allow(unused_variables)]
+    pub fn get_child_by_name(&self, name: &CStr) -> Option<Node> {
+        #[cfg(not(CONFIG_OF))]
+        {
+            None
+        }
+        #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant.
+        unsafe {
+            Node::from_raw(bindings::of_get_child_by_name(
+                self.raw_node,
+                name.as_char_ptr(),
+            ))
+        }
+    }
+
+    /// Checks whether the node is compatible with the given compatible string.
+    ///
+    /// Returns `None` if there is no match, or `Some<NonZeroU32>` if there is, with the value
+    /// representing as match score (higher values for more specific compatible matches).
+    #[allow(unused_variables)]
+    pub fn is_compatible(&self, compatible: &CStr) -> Option<NonZeroU32> {
+        #[cfg(not(CONFIG_OF))]
+        let ret = 0;
+        #[cfg(CONFIG_OF)]
+        let ret =
+            // SAFETY: `raw_node` is valid per the type invariant.
+            unsafe { bindings::of_device_is_compatible(self.raw_node, compatible.as_char_ptr()) };
+
+        NonZeroU32::new(ret.try_into().ok()?)
+    }
+
+    /// Parse a phandle property and return the Node referenced at a given index, if any.
+    ///
+    /// Used only for phandle properties with no arguments.
+    #[allow(unused_variables)]
+    pub fn parse_phandle(&self, name: &CStr, index: usize) -> Option<Node> {
+        #[cfg(not(CONFIG_OF))]
+        {
+            None
+        }
+        #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant. `of_parse_phandle` returns an
+        // owned reference.
+        unsafe {
+            Node::from_raw(bindings::of_parse_phandle(
+                self.raw_node,
+                name.as_char_ptr(),
+                index.try_into().ok()?,
+            ))
+        }
+    }
+
+    #[allow(unused_variables)]
+    /// Look up a node property by name, returning a `Property` object if found.
+    pub fn find_property(&self, propname: &CStr) -> Option<Property<'_>> {
+        #[cfg(not(CONFIG_OF))]
+        {
+            None
+        }
+        #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant. The property structure
+        // returned borrows the reference to the owning node, and so has the same
+        // lifetime.
+        unsafe {
+            Property::from_raw(bindings::of_find_property(
+                self.raw_node,
+                propname.as_char_ptr(),
+                core::ptr::null_mut(),
+            ))
+        }
+    }
+
+    /// Look up a mandatory node property by name, and decode it into a value type.
+    ///
+    /// Returns `Err(ENOENT)` if the property is not found.
+    ///
+    /// The type `T` must implement `TryFrom<Property<'_>>`.
+    pub fn get_property<'a, T: TryFrom<Property<'a>>>(&'a self, propname: &CStr) -> Result<T>
+    where
+        crate::error::Error: From<<T as TryFrom<Property<'a>>>::Error>,
+    {
+        Ok(self.find_property(propname).ok_or(ENOENT)?.try_into()?)
+    }
+
+    /// Look up an optional node property by name, and decode it into a value type.
+    ///
+    /// Returns `Ok(None)` if the property is not found.
+    ///
+    /// The type `T` must implement `TryFrom<Property<'_>>`.
+    pub fn get_opt_property<'a, T: TryFrom<Property<'a>>>(
+        &'a self,
+        propname: &CStr,
+    ) -> Result<Option<T>>
+    where
+        crate::error::Error: From<<T as TryFrom<Property<'a>>>::Error>,
+    {
+        self.find_property(propname)
+            .map_or(Ok(None), |p| Ok(Some(p.try_into()?)))
+    }
+}
+
+/// A property attached to a device tree `Node`.
+///
+/// # Invariants
+///
+/// `raw` must be valid and point to a property that outlives the lifetime of this object.
+#[derive(Copy, Clone)]
+pub struct Property<'a> {
+    raw: *mut bindings::property,
+    _p: PhantomData<&'a Node>,
+}
+
+impl<'a> Property<'a> {
+    #[cfg(CONFIG_OF)]
+    /// Create a `Property` object from a raw C pointer. Returns `None` if NULL.
+    ///
+    /// The passed pointer must be valid and outlive the lifetime argument, or NULL.
+    unsafe fn from_raw(raw: *mut bindings::property) -> Option<Property<'a>> {
+        if raw.is_null() {
+            None
+        } else {
+            Some(Property {
+                raw,
+                _p: PhantomData,
+            })
+        }
+    }
+
+    /// Returns the name of the property as a `CStr`.
+    pub fn name(&self) -> &CStr {
+        // SAFETY: `raw` is valid per the type invariant, and the lifetime of the `CStr` does not
+        // outlive it.
+        unsafe { CStr::from_char_ptr((*self.raw).name) }
+    }
+
+    /// Returns the name of the property as a `&[u8]`.
+    pub fn value(&self) -> &[u8] {
+        // SAFETY: `raw` is valid per the type invariant, and the lifetime of the slice does not
+        // outlive it.
+        unsafe { core::slice::from_raw_parts((*self.raw).value as *const u8, self.len()) }
+    }
+
+    /// Returns the length of the property in bytes.
+    pub fn len(&self) -> usize {
+        // SAFETY: `raw` is valid per the type invariant.
+        unsafe { (*self.raw).length.try_into().unwrap() }
+    }
+
+    /// Returns true if the property is empty (zero-length), which typically represents boolean true.
+    pub fn is_empty(&self) -> bool {
+        self.len() == 0
+    }
+}
+
+/// A trait that represents a value decodable from a property with a fixed unit size.
+///
+/// This allows us to auto-derive property decode implementations for `Vec<T: PropertyUnit>`.
+pub trait PropertyUnit: Sized {
+    /// The size in bytes of a single data unit.
+    const UNIT_SIZE: usize;
+
+    /// Decode this data unit from a byte slice. The passed slice will have a length of `UNIT_SIZE`.
+    fn from_bytes(data: &[u8]) -> Result<Self>;
+}
+
+// This doesn't work...
+// impl<'a, T: PropertyUnit> TryFrom<Property<'a>> for T {
+//     type Error = Error;
+//
+//     fn try_from(p: Property<'_>) -> core::result::Result<T, Self::Error> {
+//         if p.value().len() != T::UNIT_SIZE {
+//             Err(EINVAL)
+//         } else {
+//             Ok(T::from_bytes(p.value())?)
+//         }
+//     }
+// }
+
+impl<'a, T: PropertyUnit> TryFrom<Property<'a>> for Vec<T> {
+    type Error = Error;
+
+    fn try_from(p: Property<'_>) -> core::result::Result<Vec<T>, Self::Error> {
+        if p.len() % T::UNIT_SIZE != 0 {
+            return Err(EINVAL);
+        }
+
+        let mut v = Vec::new();
+        let val = p.value();
+        for off in (0..p.len()).step_by(T::UNIT_SIZE) {
+            v.push(T::from_bytes(&val[off..off + T::UNIT_SIZE])?, GFP_KERNEL)?;
+        }
+        Ok(v)
+    }
+}
+
+macro_rules! prop_int_type (
+    ($type:ty) => {
+        impl<'a> TryFrom<Property<'a>> for $type {
+            type Error = Error;
+
+            fn try_from(p: Property<'_>) -> core::result::Result<$type, Self::Error> {
+                Ok(<$type>::from_be_bytes(p.value().try_into().or(Err(EINVAL))?))
+            }
+        }
+
+        impl PropertyUnit for $type {
+            const UNIT_SIZE: usize = <$type>::BITS as usize / 8;
+
+            fn from_bytes(data: &[u8]) -> Result<Self> {
+                Ok(<$type>::from_be_bytes(data.try_into().or(Err(EINVAL))?))
+            }
+        }
+    }
+);
+
+prop_int_type!(u8);
+prop_int_type!(u16);
+prop_int_type!(u32);
+prop_int_type!(u64);
+prop_int_type!(i8);
+prop_int_type!(i16);
+prop_int_type!(i32);
+prop_int_type!(i64);
+
+/// An iterator across a collection of Node objects.
+///
+/// # Invariants
+///
+/// `cur` must be NULL or a valid node owned reference. If NULL, it represents either the first
+/// or last position of the iterator.
+///
+/// If `done` is true, `cur` must be NULL.
+///
+/// fn_next must be a callback that iterates from one node to the next, and it must not capture
+/// values that exceed the lifetime of the iterator. It must return owned references and also
+/// take owned references.
+pub struct NodeIterator<'a, T>
+where
+    T: Fn(*mut bindings::device_node) -> *mut bindings::device_node,
+{
+    cur: *mut bindings::device_node,
+    done: bool,
+    fn_next: T,
+    _p: PhantomData<&'a T>,
+}
+
+impl<'a, T> NodeIterator<'a, T>
+where
+    T: Fn(*mut bindings::device_node) -> *mut bindings::device_node,
+{
+    fn new(next: T) -> NodeIterator<'a, T> {
+        // INVARIANT: `cur` is initialized to NULL to represent the initial state.
+        NodeIterator {
+            cur: core::ptr::null_mut(),
+            done: false,
+            fn_next: next,
+            _p: PhantomData,
+        }
+    }
+}
+
+impl<'a, T> Iterator for NodeIterator<'a, T>
+where
+    T: Fn(*mut bindings::device_node) -> *mut bindings::device_node,
+{
+    type Item = Node;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.done {
+            None
+        } else {
+            // INVARIANT: if the new `cur` is NULL, then the iterator has reached its end and we
+            // set `done` to `true`.
+            self.cur = (self.fn_next)(self.cur);
+            self.done = self.cur.is_null();
+            // SAFETY: `fn_next` must return an owned reference per the iterator contract.
+            // The iterator itself is considered to own this reference, so we take another one.
+            unsafe { Node::get_from_raw(self.cur) }
+        }
+    }
+}
+
+// Drop impl to ensure we drop the current node being iterated on, if any.
+impl<'a, T> Drop for NodeIterator<'a, T>
+where
+    T: Fn(*mut bindings::device_node) -> *mut bindings::device_node,
+{
+    fn drop(&mut self) {
+        // SAFETY: `cur` is valid or NULL, and `of_node_put()` can handle NULL.
+        #[cfg(CONFIG_OF_DYNAMIC)]
+        unsafe {
+            bindings::of_node_put(self.cur)
+        };
+    }
+}
+
+/// Returns the root node of the OF device tree (if any).
+pub fn root() -> Option<Node> {
+    unsafe { Node::get_from_raw(bindings::of_root) }
+}
+
+/// Returns the /chosen node of the OF device tree (if any).
+pub fn chosen() -> Option<Node> {
+    unsafe { Node::get_from_raw(bindings::of_chosen) }
+}
+
+/// Returns the /aliases node of the OF device tree (if any).
+pub fn aliases() -> Option<Node> {
+    unsafe { Node::get_from_raw(bindings::of_aliases) }
+}
+
+/// Returns the system stdout node of the OF device tree (if any).
+pub fn stdout() -> Option<Node> {
+    unsafe { Node::get_from_raw(bindings::of_stdout) }
+}
+
+#[allow(unused_variables)]
+/// Looks up a node in the device tree by phandle.
+pub fn find_node_by_phandle(handle: PHandle) -> Option<Node> {
+    #[cfg(not(CONFIG_OF))]
+    {
+        None
+    }
+    #[cfg(CONFIG_OF)]
+    // SAFETY: bindings::of_find_node_by_phandle always returns a valid pointer or NULL
+    unsafe {
+        #[allow(dead_code)]
+        Node::from_raw(bindings::of_find_node_by_phandle(handle))
+    }
+}
+
+impl Clone for Node {
+    fn clone(&self) -> Node {
+        // SAFETY: `raw_node` is valid and non-NULL per the type invariant,
+        // so this can never return None.
+        unsafe { Node::get_from_raw(self.raw_node).unwrap() }
+    }
+}
+
+impl Drop for Node {
+    fn drop(&mut self) {
+        #[cfg(CONFIG_OF_DYNAMIC)]
+        // SAFETY: `raw_node` is valid per the type invariant.
+        unsafe {
+            bindings::of_node_put(self.raw_node)
+        };
+    }
+}

From ba82921836eff5b01400d8bdb5fb9d63c5f90260 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 8 Dec 2022 15:57:50 +0900
Subject: [PATCH 0861/1027] rust: driver,of: Support passing ID tables to
 modpost for alias generation

In order for modpost to work and correctly generate module aliases from
device ID tables, it needs those tables to exist as global symbols with
a specific name. Additionally, modpost checks the size of the symbol, so
it cannot contain trailing data.

To support this, split IdArrayIds out of IdArray. The former contains
just the IDs. Then split out the device table definition macro from the
macro that defines the device table for a given bus driver, and add
another macro to declare a device table as a module device table.
Drivers can now define their ID table once, and then specify that it
should be used for both the driver and the module:

// Generic OF Device ID table.
kernel::define_of_id_table! {ASAHI_ID_TABLE, &'static hw::HwConfig, [
    (of::DeviceId::Compatible(b"apple,agx-t8103"), Some(&hw::t8103::HWCONFIG)),
    (of::DeviceId::Compatible(b"apple,agx-t8112"), Some(&hw::t8112::HWCONFIG)),
    // ...
]}

/// Platform Driver implementation for `AsahiDriver`.
impl platform::Driver for AsahiDriver {
    /// Data associated with each hardware ID.
    type IdInfo = &'static hw::HwConfig;

    // Assign the above OF ID table to this driver.
    kernel::driver_of_id_table!(ASAHI_ID_TABLE);

    // ...
}

// Export the OF ID table as a module ID table, to make modpost/autoloading work.
kernel::module_of_id_table!(MOD_TABLE, ASAHI_ID_TABLE);

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/driver.rs | 99 ++++++++++++++++++++++++++++---------------
 rust/kernel/of.rs     | 40 +++++++++++++----
 2 files changed, 98 insertions(+), 41 deletions(-)

diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index 5e81c840359929..acb9adf806b0f0 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -147,11 +147,21 @@ pub unsafe trait RawDeviceId {
     fn to_rawid(&self, offset: isize) -> Self::RawType;
 }
 
-/// A zero-terminated device id array, followed by context data.
+/// A zero-terminated device id array.
+#[derive(Copy, Clone)]
 #[repr(C)]
-pub struct IdArray<T: RawDeviceId, U, const N: usize> {
+pub struct IdArrayIds<T: RawDeviceId, const N: usize> {
     ids: [T::RawType; N],
     sentinel: T::RawType,
+}
+
+// SAFETY: All ID types used in the kernel crate are inherently Sync
+unsafe impl<T: RawDeviceId, const N: usize> Sync for IdArrayIds<T, N> {}
+
+/// A zero-terminated device id array, followed by context data.
+#[repr(C)]
+pub struct IdArray<T: RawDeviceId, U, const N: usize> {
+    ids: IdArrayIds<T, N>,
     id_infos: [Option<U>; N],
 }
 
@@ -162,10 +172,13 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
     pub const fn new(ids: [T; N], infos: [Option<U>; N]) -> Self
     where
         T: ~const RawDeviceId + Copy,
+        T::RawType: Copy + Clone,
     {
         let mut array = Self {
-            ids: [T::ZERO; N],
-            sentinel: T::ZERO,
+            ids: IdArrayIds {
+                ids: [T::ZERO; N],
+                sentinel: T::ZERO,
+            },
             id_infos: infos,
         };
         let mut i = 0usize;
@@ -175,9 +188,9 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
             // so the pointers are necessarily 1-byte aligned.
             let offset = unsafe {
                 (&array.id_infos[i] as *const _ as *const u8)
-                    .offset_from(&array.ids[i] as *const _ as _)
+                    .offset_from(&array.ids.ids[i] as *const _ as _)
             };
-            array.ids[i] = ids[i].to_rawid(offset);
+            array.ids.ids[i] = ids[i].to_rawid(offset);
             i += 1;
         }
         array
@@ -188,10 +201,23 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
     /// This is used to essentially erase the array size.
     pub const fn as_table(&self) -> IdTable<'_, T, U> {
         IdTable {
-            first: &self.ids[0],
+            first: &self.ids.ids[0],
             _p: PhantomData,
         }
     }
+
+    /// Returns the number of items in the ID table.
+    pub const fn count(&self) -> usize {
+        self.ids.ids.len()
+    }
+
+    /// Returns the inner IdArrayIds array, without the context data.
+    pub const fn as_ids(&self) -> IdArrayIds<T, N>
+    where
+        T: ~const RawDeviceId + Copy,
+    {
+        self.ids
+    }
 }
 
 /// A device id table.
@@ -338,6 +364,11 @@ macro_rules! second_item {
 /// define_id_array!(A6, Id, &'static [u8], [(Id(10), None), (Id(20), Some(b"id2")), ]);
 /// define_id_array!(A7, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), None), ]);
 /// define_id_array!(A8, Id, &'static [u8], [(Id(10), None), (Id(20), None), ]);
+///
+/// // Within a bus driver:
+/// driver_id_table!(BUS_ID_TABLE, Id, &'static [u8], A1);
+/// // At the top level:
+/// module_id_table!(MODULE_ID_TABLE, "mybus", Id, A1);
 /// ```
 #[macro_export]
 macro_rules! define_id_array {
@@ -349,7 +380,7 @@ macro_rules! define_id_array {
     };
 }
 
-/// Defines a new constant [`IdTable`] with a concise syntax.
+/// Declares an [`IdArray`] as an [`IdTable`] for a bus driver with a concise syntax.
 ///
 /// It is meant to be used by buses and subsystems to create a similar macro with their device id
 /// type already specified, i.e., with fewer parameters to the end user.
@@ -359,36 +390,38 @@ macro_rules! define_id_array {
 // TODO: Exported but not usable by kernel modules (requires `const_trait_impl`).
 /// ```ignore
 /// #![feature(const_trait_impl)]
-/// # use kernel::{define_id_table, driver::RawDeviceId};
+/// # use kernel::{driver_id_table};
+
+/// driver_id_table!(BUS_ID_TABLE, Id, &'static [u8], MY_ID_ARRAY);
+/// ```
+#[macro_export]
+macro_rules! driver_id_table {
+    ($table_name:ident, $id_type:ty, $data_type:ty, $target:expr) => {
+        const $table_name: Option<$crate::driver::IdTable<'static, $id_type, $data_type>> =
+            Some($target.as_table());
+    };
+}
+
+/// Declares an [`IdArray`] as a module-level ID tablewith a concise syntax.
 ///
-/// #[derive(Copy, Clone)]
-/// struct Id(u32);
+/// It is meant to be used by buses and subsystems to create a similar macro with their device id
+/// type already specified, i.e., with fewer parameters to the end user.
 ///
-/// // SAFETY: `ZERO` is all zeroes and `to_rawid` stores `offset` as the second element of the raw
-/// // device id pair.
-/// unsafe impl const RawDeviceId for Id {
-///     type RawType = (u64, isize);
-///     const ZERO: Self::RawType = (0, 0);
-///     fn to_rawid(&self, offset: isize) -> Self::RawType {
-///         (self.0 as u64 + 1, offset)
-///     }
-/// }
+/// # Examples
 ///
-/// define_id_table!(T1, Id, &'static [u8], [(Id(10), None)]);
-/// define_id_table!(T2, Id, &'static [u8], [(Id(10), Some(b"id1")), ]);
-/// define_id_table!(T3, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2"))]);
-/// define_id_table!(T4, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), Some(b"id2")), ]);
-/// define_id_table!(T5, Id, &'static [u8], [(Id(10), None), (Id(20), Some(b"id2")), ]);
-/// define_id_table!(T6, Id, &'static [u8], [(Id(10), Some(b"id1")), (Id(20), None), ]);
-/// define_id_table!(T7, Id, &'static [u8], [(Id(10), None), (Id(20), None), ]);
+// TODO: Exported but not usable by kernel modules (requires `const_trait_impl`).
+/// ```ignore
+/// #![feature(const_trait_impl)]
+/// # use kernel::{driver_id_table};
+
+/// driver_id_table!(BUS_ID_TABLE, Id, &'static [u8], MY_ID_ARRAY);
 /// ```
 #[macro_export]
-macro_rules! define_id_table {
-    ($table_name:ident, $id_type:ty, $data_type:ty, [ $($t:tt)* ]) => {
-        const $table_name: Option<$crate::driver::IdTable<'static, $id_type, $data_type>> = {
-            $crate::define_id_array!(ARRAY, $id_type, $data_type, [ $($t)* ]);
-            Some(ARRAY.as_table())
-        };
+macro_rules! module_id_table {
+    ($item_name:ident, $table_type:literal, $id_type:ty, $table_name:ident) => {
+        #[export_name = concat!("__mod_", $table_type, "__", stringify!($table_name), "_device_table")]
+        static $item_name: $crate::driver::IdArrayIds<$id_type, { $table_name.count() }> =
+            $table_name.as_ids();
     };
 }
 
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index 6ec2278a549b5b..e6089f93739346 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -28,24 +28,48 @@ pub enum DeviceId {
 
 /// Defines a const open firmware device id table that also carries per-entry data/context/info.
 ///
-/// The name of the const is `OF_DEVICE_ID_TABLE`, which is what buses are expected to name their
-/// open firmware tables.
-///
-/// # Examples
+/// # Example
 ///
 /// ```
-/// # use kernel::define_of_id_table;
+/// # use kernel::{define_of_id_table, module_of_id_table, driver_of_id_table};
 /// use kernel::of;
 ///
-/// define_of_id_table! {u32, [
+/// define_of_id_table! {MY_ID_TABLE, u32, [
 ///     (of::DeviceId::Compatible(b"test-device1,test-device2"), Some(0xff)),
 ///     (of::DeviceId::Compatible(b"test-device3"), None),
 /// ]};
+///
+/// module_of_id_table!(MOD_TABLE, ASAHI_ID_TABLE);
+///
+/// // Within the `Driver` implementation:
+/// driver_of_id_table!(MY_ID_TABLE);
 /// ```
 #[macro_export]
 macro_rules! define_of_id_table {
-    ($data_type:ty, $($t:tt)*) => {
-        $crate::define_id_table!(OF_DEVICE_ID_TABLE, $crate::of::DeviceId, $data_type, $($t)*);
+    ($name:ident, $data_type:ty, $($t:tt)*) => {
+        $crate::define_id_array!($name, $crate::of::DeviceId, $data_type, $($t)*);
+    };
+}
+
+/// Convenience macro to declare which device ID table to use for a bus driver.
+#[macro_export]
+macro_rules! driver_of_id_table {
+    ($name:expr) => {
+        $crate::driver_id_table!(
+            OF_DEVICE_ID_TABLE,
+            $crate::of::DeviceId,
+            Self::IdInfo,
+            $name
+        );
+    };
+}
+
+/// Declare a device ID table as a module-level table. This creates the necessary module alias
+/// entries to enable module autoloading.
+#[macro_export]
+macro_rules! module_of_id_table {
+    ($item_name:ident, $table_name:ident) => {
+        $crate::module_id_table!($item_name, "of", $crate::of::DeviceId, $table_name);
     };
 }
 

From 07109849e0bf73c188a4e8f8e5b80e4f48ea95a8 Mon Sep 17 00:00:00 2001
From: Maciej Falkowski <m.falkowski@samsung.com>
Date: Tue, 8 Feb 2022 16:31:13 +0100
Subject: [PATCH 0862/1027] rust: platform: add `ioremap_resource` and
 `get_resource` methods

This patch adds a logic similar to `devm_platform_ioremap_resource`
function adding:
  - `IoResource` enumerated type that groups the `IORESOURCE_*` macros.
  - `get_resource()` method that is a binding of `platform_get_resource`
  - `ioremap_resource` that is newly written method similar to
    `devm_platform_ioremap_resource`.

Lina: Removed `bit` dependency and rebased

Co-developed-by: Asahi Lina <lina@asahilina.net>
Signed-off-by: Maciej Falkowski <m.falkowski@samsung.com>
---
 rust/kernel/io_mem.rs   |  6 +++++
 rust/kernel/platform.rs | 60 +++++++++++++++++++++++++++++++++++++++--
 2 files changed, 64 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/io_mem.rs b/rust/kernel/io_mem.rs
index 0c646ddcf7ea09..1083e6f4aec08f 100644
--- a/rust/kernel/io_mem.rs
+++ b/rust/kernel/io_mem.rs
@@ -9,6 +9,12 @@
 use crate::{bindings, error::code::*, error::Result};
 use core::convert::TryInto;
 
+/// The type of `Resource`.
+pub enum IoResource {
+    /// i/o memory
+    Mem = bindings::IORESOURCE_MEM as _,
+}
+
 /// Represents a memory resource.
 pub struct Resource {
     offset: bindings::resource_size_t,
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 160e24b56994f9..78110367fac2d3 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -10,7 +10,8 @@ use crate::{
     bindings,
     device::{self, RawDevice},
     driver,
-    error::{from_kernel_result, to_result, Result},
+    error::{code::*, from_result, to_result, Result},
+    io_mem::{IoMem, IoResource, Resource},
     of,
     str::CStr,
     types::ForeignOwnable,
@@ -161,6 +162,7 @@ pub trait Driver {
 /// The field `ptr` is non-null and valid for the lifetime of the object.
 pub struct Device {
     ptr: *mut bindings::platform_device,
+    used_resource: u64,
 }
 
 impl Device {
@@ -172,7 +174,10 @@ impl Device {
     /// instance.
     unsafe fn from_ptr(ptr: *mut bindings::platform_device) -> Self {
         // INVARIANT: The safety requirements of the function ensure the lifetime invariant.
-        Self { ptr }
+        Self {
+            ptr,
+            used_resource: 0,
+        }
     }
 
     /// Returns id of the platform device.
@@ -180,6 +185,57 @@ impl Device {
         // SAFETY: By the type invariants, we know that `self.ptr` is non-null and valid.
         unsafe { (*self.ptr).id }
     }
+
+    /// Gets a system resources of a platform device.
+    pub fn get_resource(&mut self, rtype: IoResource, num: usize) -> Result<Resource> {
+        // SAFETY: `self.ptr` is valid by the type invariant.
+        let res = unsafe { bindings::platform_get_resource(self.ptr, rtype as _, num as _) };
+        if res.is_null() {
+            return Err(EINVAL);
+        }
+
+        // Get the position of the found resource in the array.
+        // SAFETY:
+        //   - `self.ptr` is valid by the type invariant.
+        //   - `res` is a displaced pointer to one of the array's elements,
+        //     and `resource` is its base pointer.
+        let index = unsafe { res.offset_from((*self.ptr).resource) } as usize;
+
+        // Make sure that the index does not exceed the 64-bit mask.
+        assert!(index < 64);
+
+        if self.used_resource >> index & 1 == 1 {
+            return Err(EBUSY);
+        }
+        self.used_resource |= 1 << index;
+
+        // SAFETY: The pointer `res` is returned from `bindings::platform_get_resource`
+        // above and checked if it is not a NULL.
+        unsafe { Resource::new((*res).start, (*res).end) }.ok_or(EINVAL)
+    }
+
+    /// Ioremaps resources of a platform device.
+    ///
+    /// # Safety
+    ///
+    /// Callers must ensure that either (a) the resulting interface cannot be used to initiate DMA
+    /// operations, or (b) that DMA operations initiated via the returned interface use DMA handles
+    /// allocated through the `dma` module.
+    pub unsafe fn ioremap_resource<const SIZE: usize>(
+        &mut self,
+        index: usize,
+    ) -> Result<IoMem<SIZE>> {
+        let mask = self.used_resource;
+        let res = self.get_resource(IoResource::Mem, index)?;
+
+        // SAFETY: Valid by the safety contract.
+        let iomem = unsafe { IoMem::<SIZE>::try_new(res) };
+        // If remapping fails, the given resource won't be used, so restore the old mask.
+        if iomem.is_err() {
+            self.used_resource = mask;
+        }
+        iomem
+    }
 }
 
 impl AsRef<device::Device> for Device {

From e06a4f3df91371cd5a6d09ca60b318308eee7e49 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 7 Sep 2022 17:25:18 +0900
Subject: [PATCH 0863/1027] rust: kernel: platform: Add Device.set_dma_masks()

Allows drivers to configure the DMA masks for a device. Implemented
here, not in device, because it requires a mutable platform device
reference this way (device::Device is a safely clonable reference).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h | 1 +
 rust/helpers/device.c           | 9 ++++++---
 rust/kernel/platform.rs         | 6 ++++++
 3 files changed, 13 insertions(+), 3 deletions(-)

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 0012144bca4059..00e1f6bd5cf184 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -12,6 +12,7 @@
 #include <linux/blkdev.h>
 #include <linux/delay.h>
 #include <linux/device.h>
+#include <linux/dma-mapping.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
 #include <linux/firmware.h>
diff --git a/rust/helpers/device.c b/rust/helpers/device.c
index 70a7cd2135858b..452818592e63e1 100644
--- a/rust/helpers/device.c
+++ b/rust/helpers/device.c
@@ -1,16 +1,19 @@
 // SPDX-License-Identifier: GPL-2.0
 
 #include <linux/device.h>
-#include <linux/export.h>
+#include <linux/dma-mapping.h>
 
 void *rust_helper_dev_get_drvdata(struct device *dev)
 {
 	return dev_get_drvdata(dev);
 }
-EXPORT_SYMBOL_GPL(rust_helper_dev_get_drvdata);
 
 const char *rust_helper_dev_name(const struct device *dev)
 {
 	return dev_name(dev);
 }
-EXPORT_SYMBOL_GPL(rust_helper_dev_name);
+
+int rust_helper_dma_set_mask_and_coherent(struct device *dev, u64 mask)
+{
+	return dma_set_mask_and_coherent(dev, mask);
+}
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 78110367fac2d3..db2cbae0701628 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -186,6 +186,12 @@ impl Device {
         unsafe { (*self.ptr).id }
     }
 
+    /// Sets the DMA masks (normal and coherent) for a platform device.
+    pub fn set_dma_masks(&mut self, mask: u64) -> Result {
+        // SAFETY: `self.ptr` is valid by the type invariant.
+        to_result(unsafe { bindings::dma_set_mask_and_coherent(&mut (*self.ptr).dev, mask) })
+    }
+
     /// Gets a system resources of a platform device.
     pub fn get_resource(&mut self, rtype: IoResource, num: usize) -> Result<Resource> {
         // SAFETY: `self.ptr` is valid by the type invariant.

From 96de2289a6151497acf7e5519674da91eadc3f5c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 21 Sep 2022 14:50:56 +0900
Subject: [PATCH 0864/1027] rust: Add ioremap_np support to io_mem & friends

Apple SoCs require non-posted mappings for MMIO, and this is
automatically handled by devm_ioremap_resource() and friends via a
resource flag. Implement the same logic in kernel::io_mem, so it can
work the same way.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/helpers/iomem.c    | 23 +++++------------------
 rust/kernel/io_mem.rs   | 10 +++++++++-
 rust/kernel/platform.rs |  2 +-
 3 files changed, 15 insertions(+), 20 deletions(-)

diff --git a/rust/helpers/iomem.c b/rust/helpers/iomem.c
index ea9aa2ef513dbb..7e6f50f1fff537 100644
--- a/rust/helpers/iomem.c
+++ b/rust/helpers/iomem.c
@@ -6,114 +6,101 @@ void __iomem *rust_helper_ioremap(resource_size_t offset, unsigned long size)
 {
 	return ioremap(offset, size);
 }
-EXPORT_SYMBOL_GPL(rust_helper_ioremap);
+
+void __iomem *rust_helper_ioremap_np(resource_size_t offset, unsigned long size)
+{
+	return ioremap_np(offset, size);
+}
 
 u8 rust_helper_readb(const volatile void __iomem *addr)
 {
 	return readb(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readb);
 
 u16 rust_helper_readw(const volatile void __iomem *addr)
 {
 	return readw(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readw);
 
 u32 rust_helper_readl(const volatile void __iomem *addr)
 {
 	return readl(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readl);
 
 #ifdef CONFIG_64BIT
 u64 rust_helper_readq(const volatile void __iomem *addr)
 {
 	return readq(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readq);
 #endif
 
 void rust_helper_writeb(u8 value, volatile void __iomem *addr)
 {
 	writeb(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writeb);
 
 void rust_helper_writew(u16 value, volatile void __iomem *addr)
 {
 	writew(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writew);
 
 void rust_helper_writel(u32 value, volatile void __iomem *addr)
 {
 	writel(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writel);
 
 #ifdef CONFIG_64BIT
 void rust_helper_writeq(u64 value, volatile void __iomem *addr)
 {
 	writeq(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writeq);
 #endif
 
 u8 rust_helper_readb_relaxed(const volatile void __iomem *addr)
 {
 	return readb_relaxed(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readb_relaxed);
 
 u16 rust_helper_readw_relaxed(const volatile void __iomem *addr)
 {
 	return readw_relaxed(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readw_relaxed);
 
 u32 rust_helper_readl_relaxed(const volatile void __iomem *addr)
 {
 	return readl_relaxed(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readl_relaxed);
 
 #ifdef CONFIG_64BIT
 u64 rust_helper_readq_relaxed(const volatile void __iomem *addr)
 {
 	return readq_relaxed(addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_readq_relaxed);
 #endif
 
 void rust_helper_writeb_relaxed(u8 value, volatile void __iomem *addr)
 {
 	writeb_relaxed(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writeb_relaxed);
 
 void rust_helper_writew_relaxed(u16 value, volatile void __iomem *addr)
 {
 	writew_relaxed(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writew_relaxed);
 
 void rust_helper_writel_relaxed(u32 value, volatile void __iomem *addr)
 {
 	writel_relaxed(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writel_relaxed);
 
 #ifdef CONFIG_64BIT
 void rust_helper_writeq_relaxed(u64 value, volatile void __iomem *addr)
 {
 	writeq_relaxed(value, addr);
 }
-EXPORT_SYMBOL_GPL(rust_helper_writeq_relaxed);
 #endif
 
 void rust_helper_memcpy_fromio(void *to, const volatile void __iomem *from, long count)
 {
 	memcpy_fromio(to, from, count);
 }
-EXPORT_SYMBOL_GPL(rust_helper_memcpy_fromio);
diff --git a/rust/kernel/io_mem.rs b/rust/kernel/io_mem.rs
index 1083e6f4aec08f..5bb8800b04f59b 100644
--- a/rust/kernel/io_mem.rs
+++ b/rust/kernel/io_mem.rs
@@ -19,12 +19,14 @@ pub enum IoResource {
 pub struct Resource {
     offset: bindings::resource_size_t,
     size: bindings::resource_size_t,
+    flags: core::ffi::c_ulong,
 }
 
 impl Resource {
     pub(crate) fn new(
         start: bindings::resource_size_t,
         end: bindings::resource_size_t,
+        flags: core::ffi::c_ulong,
     ) -> Option<Self> {
         if start == 0 {
             return None;
@@ -32,6 +34,7 @@ impl Resource {
         Some(Self {
             offset: start,
             size: end.checked_sub(start)?.checked_add(1)?,
+            flags,
         })
     }
 }
@@ -161,7 +164,12 @@ impl<const SIZE: usize> IoMem<SIZE> {
 
         // Try to map the resource.
         // SAFETY: Just mapping the memory range.
-        let addr = unsafe { bindings::ioremap(res.offset, res.size as _) };
+        let addr = if res.flags & (bindings::IORESOURCE_MEM_NONPOSTED as core::ffi::c_ulong) != 0 {
+            unsafe { bindings::ioremap_np(res.offset, res.size as _) }
+        } else {
+            unsafe { bindings::ioremap(res.offset, res.size as _) }
+        };
+
         if addr.is_null() {
             Err(ENOMEM)
         } else {
diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index db2cbae0701628..43865dc71fd6f4 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -217,7 +217,7 @@ impl Device {
 
         // SAFETY: The pointer `res` is returned from `bindings::platform_get_resource`
         // above and checked if it is not a NULL.
-        unsafe { Resource::new((*res).start, (*res).end) }.ok_or(EINVAL)
+        unsafe { Resource::new((*res).start, (*res).end, (*res).flags) }.ok_or(EINVAL)
     }
 
     /// Ioremaps resources of a platform device.

From 237386bb77abe74747b68d41b56a91d32b9835e9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 21:28:58 +0900
Subject: [PATCH 0865/1027] *RFL import: macros::module params functionality &
 deps

Commit reference: 3dfc5ebff103
---
 rust/macros/helpers.rs |  20 +++
 rust/macros/module.rs  | 335 ++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 354 insertions(+), 1 deletion(-)

diff --git a/rust/macros/helpers.rs b/rust/macros/helpers.rs
index 563dcd2b7ace5e..8c0e97b6bb49fb 100644
--- a/rust/macros/helpers.rs
+++ b/rust/macros/helpers.rs
@@ -56,6 +56,10 @@ pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
     string
 }
 
+pub(crate) fn expect_literal(it: &mut token_stream::IntoIter) -> String {
+    try_literal(it).expect("Expected Literal")
+}
+
 pub(crate) fn expect_group(it: &mut token_stream::IntoIter) -> Group {
     if let TokenTree::Group(group) = it.next().expect("Reached end of token stream for Group") {
         group
@@ -215,3 +219,19 @@ pub(crate) fn parse_generics(input: TokenStream) -> (Generics, Vec<TokenTree>) {
         rest,
     )
 }
+
+pub(crate) fn get_literal(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
+    assert_eq!(expect_ident(it), expected_name);
+    assert_eq!(expect_punct(it), ':');
+    let literal = expect_literal(it);
+    assert_eq!(expect_punct(it), ',');
+    literal
+}
+
+pub(crate) fn get_string(it: &mut token_stream::IntoIter, expected_name: &str) -> String {
+    assert_eq!(expect_ident(it), expected_name);
+    assert_eq!(expect_punct(it), ':');
+    let string = expect_string(it);
+    assert_eq!(expect_punct(it), ',');
+    string
+}
diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index f83cea291740a7..f5b9d6d8a430fe 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -1,9 +1,41 @@
 // SPDX-License-Identifier: GPL-2.0
 
 use crate::helpers::*;
-use proc_macro::{token_stream, Delimiter, Literal, TokenStream, TokenTree};
+use proc_macro::{token_stream, Delimiter, Group, Literal, TokenStream, TokenTree};
 use std::fmt::Write;
 
+#[derive(Clone, PartialEq)]
+enum ParamType {
+    Ident(String),
+    Array { vals: String, max_length: usize },
+}
+
+fn expect_array_fields(it: &mut token_stream::IntoIter) -> ParamType {
+    assert_eq!(expect_punct(it), '<');
+    let vals = expect_ident(it);
+    assert_eq!(expect_punct(it), ',');
+    let max_length_str = expect_literal(it);
+    let max_length = max_length_str
+        .parse::<usize>()
+        .expect("Expected usize length");
+    assert_eq!(expect_punct(it), '>');
+    ParamType::Array { vals, max_length }
+}
+
+fn expect_type(it: &mut token_stream::IntoIter) -> ParamType {
+    if let TokenTree::Ident(ident) = it
+        .next()
+        .expect("Reached end of token stream for param type")
+    {
+        match ident.to_string().as_ref() {
+            "ArrayParam" => expect_array_fields(it),
+            _ => ParamType::Ident(ident.to_string()),
+        }
+    } else {
+        panic!("Expected Param Type")
+    }
+}
+
 fn expect_string_array(it: &mut token_stream::IntoIter) -> Vec<String> {
     let group = expect_group(it);
     assert_eq!(group.delimiter(), Delimiter::Bracket);
@@ -87,6 +119,113 @@ impl<'a> ModInfoBuilder<'a> {
         self.emit_only_builtin(field, content);
         self.emit_only_loadable(field, content);
     }
+
+    fn emit_param(&mut self, field: &str, param: &str, content: &str) {
+        let content = format!("{param}:{content}", param = param, content = content);
+        self.emit(field, &content);
+    }
+}
+
+fn permissions_are_readonly(perms: &str) -> bool {
+    let (radix, digits) = if let Some(n) = perms.strip_prefix("0x") {
+        (16, n)
+    } else if let Some(n) = perms.strip_prefix("0o") {
+        (8, n)
+    } else if let Some(n) = perms.strip_prefix("0b") {
+        (2, n)
+    } else {
+        (10, perms)
+    };
+    match u32::from_str_radix(digits, radix) {
+        Ok(perms) => perms & 0o222 == 0,
+        Err(_) => false,
+    }
+}
+
+fn param_ops_path(param_type: &str) -> &'static str {
+    match param_type {
+        "bool" => "kernel::module_param::PARAM_OPS_BOOL",
+        "i8" => "kernel::module_param::PARAM_OPS_I8",
+        "u8" => "kernel::module_param::PARAM_OPS_U8",
+        "i16" => "kernel::module_param::PARAM_OPS_I16",
+        "u16" => "kernel::module_param::PARAM_OPS_U16",
+        "i32" => "kernel::module_param::PARAM_OPS_I32",
+        "u32" => "kernel::module_param::PARAM_OPS_U32",
+        "i64" => "kernel::module_param::PARAM_OPS_I64",
+        "u64" => "kernel::module_param::PARAM_OPS_U64",
+        "isize" => "kernel::module_param::PARAM_OPS_ISIZE",
+        "usize" => "kernel::module_param::PARAM_OPS_USIZE",
+        "str" => "kernel::module_param::PARAM_OPS_STR",
+        t => panic!("Unrecognized type {}", t),
+    }
+}
+
+#[allow(clippy::type_complexity)]
+fn try_simple_param_val(
+    param_type: &str,
+) -> Box<dyn Fn(&mut token_stream::IntoIter) -> Option<String>> {
+    match param_type {
+        "bool" => Box::new(try_ident),
+        "str" => Box::new(|param_it| {
+            try_string(param_it)
+                .map(|s| format!("kernel::module_param::StringParam::Ref(b\"{}\")", s))
+        }),
+        _ => Box::new(try_literal),
+    }
+}
+
+fn get_default(param_type: &ParamType, param_it: &mut token_stream::IntoIter) -> String {
+    let try_param_val = match param_type {
+        ParamType::Ident(ref param_type)
+        | ParamType::Array {
+            vals: ref param_type,
+            max_length: _,
+        } => try_simple_param_val(param_type),
+    };
+    assert_eq!(expect_ident(param_it), "default");
+    assert_eq!(expect_punct(param_it), ':');
+    let default = match param_type {
+        ParamType::Ident(_) => try_param_val(param_it).expect("Expected default param value"),
+        ParamType::Array {
+            vals: _,
+            max_length: _,
+        } => {
+            let group = expect_group(param_it);
+            assert_eq!(group.delimiter(), Delimiter::Bracket);
+            let mut default_vals = Vec::new();
+            let mut it = group.stream().into_iter();
+
+            while let Some(default_val) = try_param_val(&mut it) {
+                default_vals.push(default_val);
+                match it.next() {
+                    Some(TokenTree::Punct(punct)) => assert_eq!(punct.as_char(), ','),
+                    None => break,
+                    _ => panic!("Expected ',' or end of array default values"),
+                }
+            }
+
+            let mut default_array = "kernel::module_param::ArrayParam::create(&[".to_string();
+            default_array.push_str(
+                &default_vals
+                    .iter()
+                    .map(|val| val.to_string())
+                    .collect::<Vec<String>>()
+                    .join(","),
+            );
+            default_array.push_str("])");
+            default_array
+        }
+    };
+    assert_eq!(expect_punct(param_it), ',');
+    default
+}
+
+fn generated_array_ops_name(vals: &str, max_length: usize) -> String {
+    format!(
+        "__generated_array_ops_{vals}_{max_length}",
+        vals = vals,
+        max_length = max_length
+    )
 }
 
 #[derive(Debug, Default)]
@@ -98,6 +237,7 @@ struct ModuleInfo {
     description: Option<String>,
     alias: Option<Vec<String>>,
     firmware: Option<Vec<String>>,
+    params: Option<Group>,
 }
 
 impl ModuleInfo {
@@ -112,6 +252,7 @@ impl ModuleInfo {
             "license",
             "alias",
             "firmware",
+            "params",
         ];
         const REQUIRED_KEYS: &[&str] = &["type", "name", "license"];
         let mut seen_keys = Vec::new();
@@ -140,6 +281,7 @@ impl ModuleInfo {
                 "license" => info.license = expect_string_ascii(it),
                 "alias" => info.alias = Some(expect_string_array(it)),
                 "firmware" => info.firmware = Some(expect_string_array(it)),
+                "params" => info.params = Some(expect_group(it)),
                 _ => panic!(
                     "Unknown key \"{}\". Valid keys are: {:?}.",
                     key, EXPECTED_KEYS
@@ -206,6 +348,195 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
         std::env::var("RUST_MODFILE").expect("Unable to fetch RUST_MODFILE environmental variable");
     modinfo.emit_only_builtin("file", &file);
 
+    let mut array_types_to_generate = Vec::new();
+    if let Some(params) = info.params {
+        assert_eq!(params.delimiter(), Delimiter::Brace);
+
+        let mut it = params.stream().into_iter();
+
+        loop {
+            let param_name = match it.next() {
+                Some(TokenTree::Ident(ident)) => ident.to_string(),
+                Some(_) => panic!("Expected Ident or end"),
+                None => break,
+            };
+
+            assert_eq!(expect_punct(&mut it), ':');
+            let param_type = expect_type(&mut it);
+            let group = expect_group(&mut it);
+            assert_eq!(expect_punct(&mut it), ',');
+
+            assert_eq!(group.delimiter(), Delimiter::Brace);
+
+            let mut param_it = group.stream().into_iter();
+            let param_default = get_default(&param_type, &mut param_it);
+            let param_permissions = get_literal(&mut param_it, "permissions");
+            let param_description = get_string(&mut param_it, "description");
+            expect_end(&mut param_it);
+
+            // TODO: More primitive types.
+            // TODO: Other kinds: unsafes, etc.
+            let (param_kernel_type, ops): (String, _) = match param_type {
+                ParamType::Ident(ref param_type) => (
+                    param_type.to_string(),
+                    param_ops_path(param_type).to_string(),
+                ),
+                ParamType::Array {
+                    ref vals,
+                    max_length,
+                } => {
+                    array_types_to_generate.push((vals.clone(), max_length));
+                    (
+                        format!("__rust_array_param_{}_{}", vals, max_length),
+                        generated_array_ops_name(vals, max_length),
+                    )
+                }
+            };
+
+            modinfo.emit_param("parmtype", &param_name, &param_kernel_type);
+            modinfo.emit_param("parm", &param_name, &param_description);
+            let param_type_internal = match param_type {
+                ParamType::Ident(ref param_type) => match param_type.as_ref() {
+                    "str" => "kernel::module_param::StringParam".to_string(),
+                    other => other.to_string(),
+                },
+                ParamType::Array {
+                    ref vals,
+                    max_length,
+                } => format!(
+                    "kernel::module_param::ArrayParam<{vals}, {max_length}>",
+                    vals = vals,
+                    max_length = max_length
+                ),
+            };
+            let read_func = if permissions_are_readonly(&param_permissions) {
+                format!(
+                    "
+                        fn read(&self)
+                            -> &<{param_type_internal} as kernel::module_param::ModuleParam>::Value {{
+                            // SAFETY: Parameters do not need to be locked because they are
+                            // read only or sysfs is not enabled.
+                            unsafe {{
+                                <{param_type_internal} as kernel::module_param::ModuleParam>::value(
+                                    &__{name}_{param_name}_value
+                                )
+                            }}
+                        }}
+                    ",
+                    name = info.name,
+                    param_name = param_name,
+                    param_type_internal = param_type_internal,
+                )
+            } else {
+                format!(
+                    "
+                        fn read<'lck>(&self, lock: &'lck kernel::KParamGuard)
+                            -> &'lck <{param_type_internal} as kernel::module_param::ModuleParam>::Value {{
+                            // SAFETY: Parameters are locked by `KParamGuard`.
+                            unsafe {{
+                                <{param_type_internal} as kernel::module_param::ModuleParam>::value(
+                                    &__{name}_{param_name}_value
+                                )
+                            }}
+                        }}
+                    ",
+                    name = info.name,
+                    param_name = param_name,
+                    param_type_internal = param_type_internal,
+                )
+            };
+            let kparam = format!(
+                "
+                    kernel::bindings::kernel_param__bindgen_ty_1 {{
+                        arg: unsafe {{ &__{name}_{param_name}_value }}
+                            as *const _ as *mut core::ffi::c_void,
+                    }},
+                ",
+                name = info.name,
+                param_name = param_name,
+            );
+            write!(
+                modinfo.buffer,
+                "
+                static mut __{name}_{param_name}_value: {param_type_internal} = {param_default};
+
+                struct __{name}_{param_name};
+
+                impl __{name}_{param_name} {{ {read_func} }}
+
+                const {param_name}: __{name}_{param_name} = __{name}_{param_name};
+
+                // Note: the C macro that generates the static structs for the `__param` section
+                // asks for them to be `aligned(sizeof(void *))`. However, that was put in place
+                // in 2003 in commit 38d5b085d2a0 (\"[PATCH] Fix over-alignment problem on x86-64\")
+                // to undo GCC over-alignment of static structs of >32 bytes. It seems that is
+                // not the case anymore, so we simplify to a transparent representation here
+                // in the expectation that it is not needed anymore.
+                // TODO: Revisit this to confirm the above comment and remove it if it happened.
+                #[repr(transparent)]
+                struct __{name}_{param_name}_RacyKernelParam(kernel::bindings::kernel_param);
+
+                unsafe impl Sync for __{name}_{param_name}_RacyKernelParam {{
+                }}
+
+                #[cfg(not(MODULE))]
+                const __{name}_{param_name}_name: *const core::ffi::c_char =
+                    b\"{name}.{param_name}\\0\" as *const _ as *const core::ffi::c_char;
+
+                #[cfg(MODULE)]
+                const __{name}_{param_name}_name: *const core::ffi::c_char =
+                    b\"{param_name}\\0\" as *const _ as *const core::ffi::c_char;
+
+                #[link_section = \"__param\"]
+                #[used]
+                static __{name}_{param_name}_struct: __{name}_{param_name}_RacyKernelParam =
+                    __{name}_{param_name}_RacyKernelParam(kernel::bindings::kernel_param {{
+                        name: __{name}_{param_name}_name,
+                        // SAFETY: `__this_module` is constructed by the kernel at load time
+                        // and will not be freed until the module is unloaded.
+                        #[cfg(MODULE)]
+                        mod_: unsafe {{ &kernel::bindings::__this_module as *const _ as *mut _ }},
+                        #[cfg(not(MODULE))]
+                        mod_: core::ptr::null_mut(),
+                        ops: unsafe {{ &{ops} }} as *const kernel::bindings::kernel_param_ops,
+                        perm: {permissions},
+                        level: -1,
+                        flags: 0,
+                        __bindgen_anon_1: {kparam}
+                    }});
+                ",
+                name = info.name,
+                param_type_internal = param_type_internal,
+                read_func = read_func,
+                param_default = param_default,
+                param_name = param_name,
+                ops = ops,
+                permissions = param_permissions,
+                kparam = kparam,
+            )
+            .unwrap();
+        }
+    }
+
+    let mut generated_array_types = String::new();
+
+    for (vals, max_length) in array_types_to_generate {
+        let ops_name = generated_array_ops_name(&vals, max_length);
+        write!(
+            generated_array_types,
+            "
+                kernel::make_param_ops!(
+                    {ops_name},
+                    kernel::module_param::ArrayParam<{vals}, {{ {max_length} }}>
+                );
+            ",
+            ops_name = ops_name,
+            vals = vals,
+            max_length = max_length,
+        )
+        .unwrap();
+    }
+
     format!(
         "
             /// The module name.
@@ -352,12 +683,14 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
                     }}
 
                     {modinfo}
+                    {generated_array_types}
                 }}
             }}
         ",
         type_ = info.type_,
         name = info.name,
         modinfo = modinfo.buffer,
+        generated_array_types = generated_array_types,
         initcall_section = ".initcall6.init"
     )
     .parse()

From f8b48f189cb2cbf4a6cd86624c921adabf1f1e04 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 21:34:36 +0900
Subject: [PATCH 0866/1027] *RFL import: Rest of kernel::error::code::*

Commit reference: 3dfc5ebff103
---
 rust/kernel/error.rs | 108 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 108 insertions(+)

diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 145f5c3970096f..8dec422b5e2feb 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -58,6 +58,114 @@ pub mod code {
     declare_err!(EPIPE, "Broken pipe.");
     declare_err!(EDOM, "Math argument out of domain of func.");
     declare_err!(ERANGE, "Math result not representable.");
+    declare_err!(EDEADLK, "Resource deadlock would occur");
+    declare_err!(ENAMETOOLONG, "File name too long");
+    declare_err!(ENOLCK, "No record locks available");
+    declare_err!(
+        ENOSYS,
+        "Invalid system call number.",
+        "",
+        "This error code is special: arch syscall entry code will return",
+        "[`ENOSYS`] if users try to call a syscall that doesn't exist.",
+        "To keep failures of syscalls that really do exist distinguishable from",
+        "failures due to attempts to use a nonexistent syscall, syscall",
+        "implementations should refrain from returning [`ENOSYS`]."
+    );
+    declare_err!(ENOTEMPTY, "Directory not empty.");
+    declare_err!(ELOOP, "Too many symbolic links encountered.");
+    declare_err!(EWOULDBLOCK, "Operation would block.");
+    declare_err!(ENOMSG, "No message of desired type.");
+    declare_err!(EIDRM, "Identifier removed.");
+    declare_err!(ECHRNG, "Channel number out of range.");
+    declare_err!(EL2NSYNC, "Level 2 not synchronized.");
+    declare_err!(EL3HLT, "Level 3 halted.");
+    declare_err!(EL3RST, "Level 3 reset.");
+    declare_err!(ELNRNG, "Link number out of range.");
+    declare_err!(EUNATCH, "Protocol driver not attached.");
+    declare_err!(ENOCSI, "No CSI structure available.");
+    declare_err!(EL2HLT, "Level 2 halted.");
+    declare_err!(EBADE, "Invalid exchange.");
+    declare_err!(EBADR, "Invalid request descriptor.");
+    declare_err!(EXFULL, "Exchange full.");
+    declare_err!(ENOANO, "No anode.");
+    declare_err!(EBADRQC, "Invalid request code.");
+    declare_err!(EBADSLT, "Invalid slot.");
+    declare_err!(EDEADLOCK, "Resource deadlock would occur.");
+    declare_err!(EBFONT, "Bad font file format.");
+    declare_err!(ENOSTR, "Device not a stream.");
+    declare_err!(ENODATA, "No data available.");
+    declare_err!(ETIME, "Timer expired.");
+    declare_err!(ENOSR, "Out of streams resources.");
+    declare_err!(ENONET, "Machine is not on the network.");
+    declare_err!(ENOPKG, "Package not installed.");
+    declare_err!(EREMOTE, "Object is remote.");
+    declare_err!(ENOLINK, "Link has been severed.");
+    declare_err!(EADV, "Advertise error.");
+    declare_err!(ESRMNT, "Srmount error.");
+    declare_err!(ECOMM, "Communication error on send.");
+    declare_err!(EPROTO, "Protocol error.");
+    declare_err!(EMULTIHOP, "Multihop attempted.");
+    declare_err!(EDOTDOT, "RFS specific error.");
+    declare_err!(EBADMSG, "Not a data message.");
+    declare_err!(EOVERFLOW, "Value too large for defined data type.");
+    declare_err!(ENOTUNIQ, "Name not unique on network.");
+    declare_err!(EBADFD, "File descriptor in bad state.");
+    declare_err!(EREMCHG, "Remote address changed.");
+    declare_err!(ELIBACC, "Can not access a needed shared library.");
+    declare_err!(ELIBBAD, "Accessing a corrupted shared library.");
+    declare_err!(ELIBSCN, ".lib section in a.out corrupted.");
+    declare_err!(ELIBMAX, "Attempting to link in too many shared libraries.");
+    declare_err!(ELIBEXEC, "Cannot exec a shared library directly.");
+    declare_err!(EILSEQ, "Illegal byte sequence.");
+    declare_err!(ERESTART, "Interrupted system call should be restarted.");
+    declare_err!(ESTRPIPE, "Streams pipe error.");
+    declare_err!(EUSERS, "Too many users.");
+    declare_err!(ENOTSOCK, "Socket operation on non-socket.");
+    declare_err!(EDESTADDRREQ, "Destination address required.");
+    declare_err!(EMSGSIZE, "Message too long.");
+    declare_err!(EPROTOTYPE, "Protocol wrong type for socket.");
+    declare_err!(ENOPROTOOPT, "Protocol not available.");
+    declare_err!(EPROTONOSUPPORT, "Protocol not supported.");
+    declare_err!(ESOCKTNOSUPPORT, "Socket type not supported.");
+    declare_err!(EOPNOTSUPP, "Operation not supported on transport endpoint.");
+    declare_err!(EPFNOSUPPORT, "Protocol family not supported.");
+    declare_err!(EAFNOSUPPORT, "Address family not supported by protocol.");
+    declare_err!(EADDRINUSE, "Address already in use.");
+    declare_err!(EADDRNOTAVAIL, "Cannot assign requested address.");
+    declare_err!(ENETDOWN, "Network is down.");
+    declare_err!(ENETUNREACH, "Network is unreachable.");
+    declare_err!(ENETRESET, "Network dropped connection because of reset.");
+    declare_err!(ECONNABORTED, "Software caused connection abort.");
+    declare_err!(ECONNRESET, "Connection reset by peer.");
+    declare_err!(ENOBUFS, "No buffer space available.");
+    declare_err!(EISCONN, "Transport endpoint is already connected.");
+    declare_err!(ENOTCONN, "Transport endpoint is not connected.");
+    declare_err!(ESHUTDOWN, "Cannot send after transport endpoint shutdown.");
+    declare_err!(ETOOMANYREFS, "Too many references: cannot splice.");
+    declare_err!(ETIMEDOUT, "Connection timed out.");
+    declare_err!(ECONNREFUSED, "Connection refused.");
+    declare_err!(EHOSTDOWN, "Host is down.");
+    declare_err!(EHOSTUNREACH, "No route to host.");
+    declare_err!(EALREADY, "Operation already in progress.");
+    declare_err!(EINPROGRESS, "Operation now in progress.");
+    declare_err!(ESTALE, "Stale file handle.");
+    declare_err!(EUCLEAN, "Structure needs cleaning.");
+    declare_err!(ENOTNAM, "Not a XENIX named type file.");
+    declare_err!(ENAVAIL, "No XENIX semaphores available.");
+    declare_err!(EISNAM, "Is a named type file.");
+    declare_err!(EREMOTEIO, "Remote I/O error.");
+    declare_err!(EDQUOT, "Quota exceeded.");
+    declare_err!(ENOMEDIUM, "No medium found.");
+    declare_err!(EMEDIUMTYPE, "Wrong medium type.");
+    declare_err!(ECANCELED, "Operation Canceled.");
+    declare_err!(ENOKEY, "Required key not available.");
+    declare_err!(EKEYEXPIRED, "Key has expired.");
+    declare_err!(EKEYREVOKED, "Key has been revoked.");
+    declare_err!(EKEYREJECTED, "Key was rejected by service.");
+    declare_err!(EOWNERDEAD, "Owner died.", "", "For robust mutexes.");
+    declare_err!(ENOTRECOVERABLE, "State not recoverable.");
+    declare_err!(ERFKILL, "Operation not possible due to RF-kill.");
+    declare_err!(EHWPOISON, "Memory page has hardware error.");
     declare_err!(ERESTARTSYS, "Restart the system call.");
     declare_err!(ERESTARTNOINTR, "System call was interrupted by a signal and will be restarted.");
     declare_err!(ERESTARTNOHAND, "Restart if no handler.");

From 119d74f8c6365c207c47197d026e1163cc63cb75 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 7 Sep 2022 17:41:06 +0900
Subject: [PATCH 0867/1027] rust: bindings: Add resource_size wrapper

TODO: This isn't abstracted properly yet

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/helpers/helpers.c | 1 +
 rust/helpers/ioport.c  | 8 ++++++++
 2 files changed, 9 insertions(+)
 create mode 100644 rust/helpers/ioport.c

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 5fb11348a08374..905688bd8c00c9 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -14,6 +14,7 @@
 #include "device.c"
 #include "err.c"
 #include "iomem.c"
+#include "ioport.c"
 #include "kunit.c"
 #include "lockdep.c"
 #include "mutex.c"
diff --git a/rust/helpers/ioport.c b/rust/helpers/ioport.c
new file mode 100644
index 00000000000000..59e9ede9b83344
--- /dev/null
+++ b/rust/helpers/ioport.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/ioport.h>
+
+resource_size_t rust_helper_resource_size(const struct resource *res)
+{
+	return resource_size(res);
+}

From f0816d4bf4fd2ff8a006df4eabe549be129b61f8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 21:45:19 +0900
Subject: [PATCH 0868/1027] rust: Allow feature allocator_api

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 scripts/Makefile.build | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index ccf1fd65cb48ea..8b5efdb3f60d46 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -263,7 +263,7 @@ $(obj)/%.lst: $(obj)/%.c FORCE
 # Compile Rust sources (.rs)
 # ---------------------------------------------------------------------------
 
-rust_allowed_features := new_uninit,type_alias_impl_trait
+rust_allowed_features := allocator_api,new_uninit,type_alias_impl_trait
 
 # `--out-dir` is required to avoid temporaries being created by `rustc` in the
 # current working directory, which may be not accessible in the out-of-tree

From 9d77708ec187fb7beabcfbf4046626b1a842576a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 16 Feb 2023 21:47:25 +0900
Subject: [PATCH 0869/1027] *RFL import: kernel::KParamGuard & friends

Commit reference: 3dfc5ebff103
---
 rust/kernel/lib.rs | 37 +++++++++++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index b9f4d52e0a2fd9..286475437abf55 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -130,6 +130,43 @@ impl ThisModule {
     pub const fn as_ptr(&self) -> *mut bindings::module {
         self.0
     }
+
+    /// Locks the module parameters to access them.
+    ///
+    /// Returns a [`KParamGuard`] that will release the lock when dropped.
+    pub fn kernel_param_lock(&self) -> KParamGuard<'_> {
+        // SAFETY: `kernel_param_lock` will check if the pointer is null and
+        // use the built-in mutex in that case.
+        #[cfg(CONFIG_SYSFS)]
+        unsafe {
+            bindings::kernel_param_lock(self.0)
+        }
+
+        KParamGuard {
+            #[cfg(CONFIG_SYSFS)]
+            this_module: self,
+            phantom: core::marker::PhantomData,
+        }
+    }
+}
+
+/// Scoped lock on the kernel parameters of [`ThisModule`].
+///
+/// Lock will be released when this struct is dropped.
+pub struct KParamGuard<'a> {
+    #[cfg(CONFIG_SYSFS)]
+    this_module: &'a ThisModule,
+    phantom: core::marker::PhantomData<&'a ()>,
+}
+
+#[cfg(CONFIG_SYSFS)]
+impl<'a> Drop for KParamGuard<'a> {
+    fn drop(&mut self) {
+        // SAFETY: `kernel_param_lock` will check if the pointer is null and
+        // use the built-in mutex in that case. The existence of `self`
+        // guarantees that the lock is held.
+        unsafe { bindings::kernel_param_unlock(self.this_module.0) }
+    }
 }
 
 #[cfg(not(any(testlib, test)))]

From 9acb544c697556dbb1dcd6e83033cddd88fad406 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 18 Aug 2022 02:07:35 +0900
Subject: [PATCH 0870/1027] iommu/io-pgtable: Add Apple UAT variant format

Apple Silicon SoCs (M1, M2, etc.) have a GPU with an ARM64 firmware
coprocessor. The firmware and the GPU share page tables in the standard
ARM64 format (the firmware literally sets the base as its TTBR0/1
registers). TTBR0 covers the low half of the address space and is
intended to be per-GPU-VM (GPU user mappings and kernel-managed
buffers), while TTBR1 covers the upper half and is global (firmware
code, data, management structures shared with the AP, and a few
GPU-accessible data structures).

In typical Apple fashion, the permissions are interpreted differently
from traditional ARM PTEs. By default, firmware mappings use Apple SPRR
permission remapping. The firmware only uses that for its own
code/data/MMIO mappings, and those pages are not accessible by the GPU
hardware. We never need to touch/manage these mappings, so this patch
does not support them.

When a specific bit is set in the PTEs, permissions switch to a
different scheme which supports various combinations of firmware/GPU
access. This is the mode intended to be used by AP GPU drivers, and what
we implement here.

The prot bits are interpreted as follows:

- IOMMU_READ and IOMMU_WRITE have the usual meaning.

- IOMMU_PRIV creates firmware-only mappings (no GPU access)
- IOMMU_NOEXEC creates GPU-only structures (no FW access)
- Otherwise structures are accessible by both GPU and FW

- IOMMU_MMIO creates Device mappings for firmware
- IOMMU_CACHE creates Normal-NC mappings for firmware (cache-coherent
  from the point of view of the AP, but slower)
- Otherwise creates Normal mappings for firmware (this requires manual
  cache management on the firmware side, as it is not coherent with the
  SoC fabric)

GPU-only mappings (textures/etc) are expected to use IOMMU_CACHE and are
seemingly coherent with the CPU (or otherwise the firmware/GPU already
issue the required cache management operations when correctly
configured).

There is a GPU-RO/FW-RW mode, but it is not currently implemented (it
doesn't seem to be very useful for the driver). There seems to be no
real noexec control (i.e. for shaders) on the GPU side. All of these
mappings are implicitly noexec for the firmware.

Drivers are expected to fully manage per-user (TTBR0) page tables, but
ownership of shared kernel (TTBR1) page tables is shared between the
firmware and the AP OS. We handle this by simply using a smaller IAS to
drop down one level of page tables, so the driver can install a PTE in
the top-level (firmware-initialized) page table directly and just add an
offset to the VAs passed into the io_pgtable code. This avoids having to
have any special handling for this here. The firmware-relevant data
structures are small, so we do not expect to ever require more VA space
than one top-level PTE covers (IAS=36 for the next level, 64 GiB).

Only 16K page mode is supported. The coprocessor MMU supports huge pages
as usual for ARM64, but the GPU MMU does not, so we do not enable them.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/iommu/io-pgtable-arm.c | 101 +++++++++++++++++++++++++++++++--
 drivers/iommu/io-pgtable.c     |   1 +
 include/linux/io-pgtable.h     |   6 ++
 3 files changed, 104 insertions(+), 4 deletions(-)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index ff4149ae1751d4..21643c2a98bff0 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -136,6 +136,15 @@
 #define ARM_MALI_LPAE_MEMATTR_IMP_DEF	0x88ULL
 #define ARM_MALI_LPAE_MEMATTR_WRITE_ALLOC 0x8DULL
 
+#define APPLE_UAT_MEMATTR_PRIV		(((arm_lpae_iopte)0x0) << 2)
+#define APPLE_UAT_MEMATTR_DEV		(((arm_lpae_iopte)0x1) << 2)
+#define APPLE_UAT_MEMATTR_SHARED	(((arm_lpae_iopte)0x2) << 2)
+#define APPLE_UAT_GPU_ACCESS			(((arm_lpae_iopte)1) << 55)
+#define APPLE_UAT_UXN				(((arm_lpae_iopte)1) << 54)
+#define APPLE_UAT_PXN				(((arm_lpae_iopte)1) << 53)
+#define APPLE_UAT_AP1				(((arm_lpae_iopte)1) << 7)
+#define APPLE_UAT_AP0				(((arm_lpae_iopte)1) << 6)
+
 /* IOPTE accessors */
 #define iopte_deref(pte,d) __va(iopte_to_paddr(pte, d))
 
@@ -435,7 +444,42 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
 {
 	arm_lpae_iopte pte;
 
-	if (data->iop.fmt == ARM_64_LPAE_S1 ||
+	if (data->iop.fmt == APPLE_UAT) {
+		/*
+		 * This bit enables GPU access and the particular permission
+		 * rules that follow. Without it, access is firmware-only and
+		 * permissions follow the firmware's Apple SPRR configuration.
+		 */
+		pte = APPLE_UAT_GPU_ACCESS;
+		if (prot & IOMMU_PRIV) {
+			/* Firmware structures */
+			pte |= APPLE_UAT_AP0;
+			if (prot & IOMMU_WRITE) {
+				/* Firmware RW */
+				pte |= APPLE_UAT_UXN;
+			} else if (!(prot & IOMMU_READ)) {
+				/* No access */
+				pte |= APPLE_UAT_PXN;
+			}
+		} else if (prot & IOMMU_NOEXEC) {
+			/* GPU structures (no FW access) */
+			pte |= APPLE_UAT_AP1 | ARM_LPAE_PTE_nG;
+			if (!(prot & IOMMU_READ)) {
+				pte |= APPLE_UAT_PXN;
+				if (!(prot & IOMMU_WRITE))
+					pte |= APPLE_UAT_UXN;
+			} else if (prot & IOMMU_WRITE) {
+				pte |= APPLE_UAT_UXN;
+			}
+		} else {
+			pte |= ARM_LPAE_PTE_nG;
+			/* GPU structures (also FW accessible) */
+			if (prot & IOMMU_WRITE)
+				pte |= APPLE_UAT_UXN;
+			if (prot & IOMMU_READ)
+				pte |= APPLE_UAT_PXN;
+		}
+	} else if (data->iop.fmt == ARM_64_LPAE_S1 ||
 	    data->iop.fmt == ARM_32_LPAE_S1) {
 		pte = ARM_LPAE_PTE_nG;
 		if (!(prot & IOMMU_WRITE) && (prot & IOMMU_READ))
@@ -456,7 +500,14 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
 	 * Note that this logic is structured to accommodate Mali LPAE
 	 * having stage-1-like attributes but stage-2-like permissions.
 	 */
-	if (data->iop.fmt == ARM_64_LPAE_S2 ||
+	if (data->iop.fmt == APPLE_UAT) {
+		if (prot & IOMMU_MMIO)
+			pte |= APPLE_UAT_MEMATTR_DEV;
+		else if (prot & IOMMU_CACHE)
+			pte |= APPLE_UAT_MEMATTR_SHARED;
+		else
+			pte |= APPLE_UAT_MEMATTR_PRIV;
+	} else if (data->iop.fmt == ARM_64_LPAE_S2 ||
 	    data->iop.fmt == ARM_32_LPAE_S2) {
 		if (prot & IOMMU_MMIO)
 			pte |= ARM_LPAE_PTE_MEMATTR_DEV;
@@ -479,12 +530,14 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
 	 * "outside the GPU" (i.e. either the Inner or System domain in CPU
 	 * terms, depending on coherency).
 	 */
-	if (prot & IOMMU_CACHE && data->iop.fmt != ARM_MALI_LPAE)
+	if (data->iop.fmt == APPLE_UAT)
+		pte |= ARM_LPAE_PTE_SH_NS;
+	else if (prot & IOMMU_CACHE && data->iop.fmt != ARM_MALI_LPAE)
 		pte |= ARM_LPAE_PTE_SH_IS;
 	else
 		pte |= ARM_LPAE_PTE_SH_OS;
 
-	if (prot & IOMMU_NOEXEC)
+	if (prot & IOMMU_NOEXEC && data->iop.fmt != APPLE_UAT)
 		pte |= ARM_LPAE_PTE_XN;
 
 	if (data->iop.cfg.quirks & IO_PGTABLE_QUIRK_ARM_NS)
@@ -1206,6 +1259,41 @@ arm_mali_lpae_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
 	return NULL;
 }
 
+static struct io_pgtable *
+apple_uat_alloc_pgtable(struct io_pgtable_cfg *cfg, void *cookie)
+{
+	struct arm_lpae_io_pgtable *data;
+
+	/* No quirks for UAT (hopefully) */
+	if (cfg->quirks)
+		return NULL;
+
+	if (cfg->ias > 48 || cfg->oas > 42)
+		return NULL;
+
+	cfg->pgsize_bitmap &= SZ_16K;
+
+	data = arm_lpae_alloc_pgtable(cfg);
+	if (!data)
+		return NULL;
+
+	/* UAT needs full 16K aligned pages for the pgd */
+	data->pgd = __arm_lpae_alloc_pages(SZ_16K, GFP_KERNEL, cfg, cookie);
+	if (!data->pgd)
+		goto out_free_data;
+
+	/* Ensure the empty pgd is visible before the TTBAT can be written */
+	wmb();
+
+	cfg->apple_uat_cfg.ttbr = virt_to_phys(data->pgd);
+
+	return &data->iop;
+
+out_free_data:
+	kfree(data);
+	return NULL;
+}
+
 struct io_pgtable_init_fns io_pgtable_arm_64_lpae_s1_init_fns = {
 	.caps	= IO_PGTABLE_CAP_CUSTOM_ALLOCATOR,
 	.alloc	= arm_64_lpae_alloc_pgtable_s1,
@@ -1236,6 +1324,11 @@ struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns = {
 	.free	= arm_lpae_free_pgtable,
 };
 
+struct io_pgtable_init_fns io_pgtable_apple_uat_init_fns = {
+	.alloc	= apple_uat_alloc_pgtable,
+	.free	= arm_lpae_free_pgtable,
+};
+
 #ifdef CONFIG_IOMMU_IO_PGTABLE_LPAE_SELFTEST
 
 static struct io_pgtable_cfg *cfg_cookie __initdata;
diff --git a/drivers/iommu/io-pgtable.c b/drivers/iommu/io-pgtable.c
index 8841c1487f0048..50e610a9055e1a 100644
--- a/drivers/iommu/io-pgtable.c
+++ b/drivers/iommu/io-pgtable.c
@@ -20,6 +20,7 @@ io_pgtable_init_table[IO_PGTABLE_NUM_FMTS] = {
 	[ARM_64_LPAE_S1] = &io_pgtable_arm_64_lpae_s1_init_fns,
 	[ARM_64_LPAE_S2] = &io_pgtable_arm_64_lpae_s2_init_fns,
 	[ARM_MALI_LPAE] = &io_pgtable_arm_mali_lpae_init_fns,
+	[APPLE_UAT] = &io_pgtable_apple_uat_init_fns,
 #endif
 #ifdef CONFIG_IOMMU_IO_PGTABLE_DART
 	[APPLE_DART] = &io_pgtable_apple_dart_init_fns,
diff --git a/include/linux/io-pgtable.h b/include/linux/io-pgtable.h
index f9a81761bfceda..eadfc82875d3e2 100644
--- a/include/linux/io-pgtable.h
+++ b/include/linux/io-pgtable.h
@@ -19,6 +19,7 @@ enum io_pgtable_fmt {
 	AMD_IOMMU_V2,
 	APPLE_DART,
 	APPLE_DART2,
+	APPLE_UAT,
 	IO_PGTABLE_NUM_FMTS,
 };
 
@@ -171,6 +172,10 @@ struct io_pgtable_cfg {
 			u64 ttbr[4];
 			u32 n_ttbrs;
 		} apple_dart_cfg;
+
+		struct {
+			u64	ttbr;
+		} apple_uat_cfg;
 	};
 };
 
@@ -299,5 +304,6 @@ extern struct io_pgtable_init_fns io_pgtable_arm_mali_lpae_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_amd_iommu_v1_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_amd_iommu_v2_init_fns;
 extern struct io_pgtable_init_fns io_pgtable_apple_dart_init_fns;
+extern struct io_pgtable_init_fns io_pgtable_apple_uat_init_fns;
 
 #endif /* __IO_PGTABLE_H */

From 83bfbb38c31701ce230d65bee4e3b33a6c1d8897 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 20 Apr 2023 00:04:02 +0900
Subject: [PATCH 0871/1027] iommu/io-pgtable: Hack in FW-RW/GPU-RO mode into
 UAT io_pgtable

This is ugly, we need a better way of expressing this.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/iommu/io-pgtable-arm.c | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/drivers/iommu/io-pgtable-arm.c b/drivers/iommu/io-pgtable-arm.c
index 21643c2a98bff0..b786ef3d33e0d3 100644
--- a/drivers/iommu/io-pgtable-arm.c
+++ b/drivers/iommu/io-pgtable-arm.c
@@ -457,6 +457,9 @@ static arm_lpae_iopte arm_lpae_prot_to_pte(struct arm_lpae_io_pgtable *data,
 			if (prot & IOMMU_WRITE) {
 				/* Firmware RW */
 				pte |= APPLE_UAT_UXN;
+				if (!(prot & IOMMU_READ))
+					/* Hack: Firmware RW, GPU RO */
+					pte |= APPLE_UAT_PXN;
 			} else if (!(prot & IOMMU_READ)) {
 				/* No access */
 				pte |= APPLE_UAT_PXN;

From fc974da2bb25a463f80eacd2e6e74e75d31ccaef Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Feb 2023 00:21:35 +0900
Subject: [PATCH 0872/1027] rust: io_pgtable: Add the Apple UAT format
 abstraction

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/io_pgtable.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/rust/kernel/io_pgtable.rs b/rust/kernel/io_pgtable.rs
index c967ea90cc00e5..e7367368a20e18 100644
--- a/rust/kernel/io_pgtable.rs
+++ b/rust/kernel/io_pgtable.rs
@@ -339,6 +339,11 @@ iopt_cfg!(
     apple_dart_cfg,
     io_pgtable_cfg__bindgen_ty_1__bindgen_ty_5
 );
+iopt_cfg!(
+    AppleUATCfg,
+    apple_uat_cfg,
+    io_pgtable_cfg__bindgen_ty_1__bindgen_ty_6
+);
 
 iopt_type!(ARM32LPAES1, ARMLPAES1Cfg, io_pgtable_fmt_ARM_32_LPAE_S1);
 iopt_type!(ARM32LPAES2, ARMLPAES2Cfg, io_pgtable_fmt_ARM_32_LPAE_S2);
@@ -349,3 +354,4 @@ iopt_type!(ARMMaliLPAE, ARMMaliLPAECfg, io_pgtable_fmt_ARM_MALI_LPAE);
 iopt_type!(AMDIOMMUV1, (), io_pgtable_fmt_AMD_IOMMU_V1);
 iopt_type!(AppleDART, AppleDARTCfg, io_pgtable_fmt_APPLE_DART);
 iopt_type!(AppleDART2, AppleDARTCfg, io_pgtable_fmt_APPLE_DART2);
+iopt_type!(AppleUAT, AppleUATCfg, io_pgtable_fmt_APPLE_UAT);

From d2d65addce4fc328849248937a14d3e45ba5f557 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 5 Apr 2023 17:44:13 +0900
Subject: [PATCH 0873/1027] drm/scheduler: Fix UAF in
 drm_sched_fence_get_timeline_name

A signaled scheduler fence can outlive its scheduler, since fences are
independencly reference counted. Therefore, we can't reference the
scheduler in the get_timeline_name() implementation.

Fixes oopses on `cat /sys/kernel/debug/dma_buf/bufinfo` when shared
dma-bufs reference fences from GPU schedulers that no longer exist.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/scheduler/sched_entity.c | 7 ++++++-
 drivers/gpu/drm/scheduler/sched_fence.c  | 4 +++-
 include/drm/gpu_scheduler.h              | 5 +++++
 3 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/scheduler/sched_entity.c b/drivers/gpu/drm/scheduler/sched_entity.c
index a75eede8bf8dab..44b9e18613cf68 100644
--- a/drivers/gpu/drm/scheduler/sched_entity.c
+++ b/drivers/gpu/drm/scheduler/sched_entity.c
@@ -429,7 +429,12 @@ static bool drm_sched_entity_add_dependency_cb(struct drm_sched_entity *entity)
 
 		/*
 		 * Fence is from the same scheduler, only need to wait for
-		 * it to be scheduled
+		 * it to be scheduled.
+		 *
+		 * Note: s_fence->sched could have been freed and reallocated
+		 * as another scheduler. This false positive case is okay, as if
+		 * the old scheduler was freed all of its jobs must have
+		 * signaled their completion fences.
 		 */
 		fence = dma_fence_get(&s_fence->scheduled);
 		dma_fence_put(entity->dependency);
diff --git a/drivers/gpu/drm/scheduler/sched_fence.c b/drivers/gpu/drm/scheduler/sched_fence.c
index 0f35f009b9d373..a12fef84a19d87 100644
--- a/drivers/gpu/drm/scheduler/sched_fence.c
+++ b/drivers/gpu/drm/scheduler/sched_fence.c
@@ -90,7 +90,7 @@ static const char *drm_sched_fence_get_driver_name(struct dma_fence *fence)
 static const char *drm_sched_fence_get_timeline_name(struct dma_fence *f)
 {
 	struct drm_sched_fence *fence = to_drm_sched_fence(f);
-	return (const char *)fence->sched->name;
+	return (const char *)fence->sched_name;
 }
 
 static void drm_sched_fence_free_rcu(struct rcu_head *rcu)
@@ -224,6 +224,8 @@ void drm_sched_fence_init(struct drm_sched_fence *fence,
 	unsigned seq;
 
 	fence->sched = entity->rq->sched;
+	strscpy(fence->sched_name, entity->rq->sched->name,
+		sizeof(fence->sched_name));
 	seq = atomic_inc_return(&entity->fence_seq);
 	dma_fence_init(&fence->scheduled, &drm_sched_fence_ops_scheduled,
 		       &fence->lock, entity->fence_context, seq);
diff --git a/include/drm/gpu_scheduler.h b/include/drm/gpu_scheduler.h
index e28bc649b5c9b7..805f53c29a8357 100644
--- a/include/drm/gpu_scheduler.h
+++ b/include/drm/gpu_scheduler.h
@@ -304,6 +304,11 @@ struct drm_sched_fence {
          * @lock: the lock used by the scheduled and the finished fences.
          */
 	spinlock_t			lock;
+        /**
+         * @sched_name: the name of the scheduler that owns this fence. We
+	 * keep a copy here since fences can outlive their scheduler.
+         */
+	char sched_name[16];
         /**
          * @owner: job owner for debugging
          */

From 34a4274426e97f1f768d4c5b42f36377072044b9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 16:56:21 +0900
Subject: [PATCH 0874/1027] rust: dma_fence: Add DMA Fence abstraction

DMA fences are the internal synchronization primitive used for DMA
operations like GPU rendering, video en/decoding, etc. Add an
abstraction to allow Rust drivers to interact with this subsystem.

Note: This uses a raw spinlock living next to the fence, since we do
not interact with it other than for initialization.
TODO: Expose this to the user at some point with a safe abstraction.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   2 +
 rust/helpers/dma-fence.c        |  33 ++
 rust/helpers/helpers.c          |   1 +
 rust/kernel/dma_fence.rs        | 546 ++++++++++++++++++++++++++++++++
 rust/kernel/lib.rs              |   2 +
 5 files changed, 584 insertions(+)
 create mode 100644 rust/helpers/dma-fence.c
 create mode 100644 rust/kernel/dma_fence.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 00e1f6bd5cf184..d4c3cc8f80f4ac 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -12,6 +12,8 @@
 #include <linux/blkdev.h>
 #include <linux/delay.h>
 #include <linux/device.h>
+#include <linux/dma-fence.h>
+#include <linux/dma-fence-chain.h>
 #include <linux/dma-mapping.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
diff --git a/rust/helpers/dma-fence.c b/rust/helpers/dma-fence.c
new file mode 100644
index 00000000000000..6491016262934b
--- /dev/null
+++ b/rust/helpers/dma-fence.c
@@ -0,0 +1,33 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dma-fence.h>
+#include <linux/dma-fence-chain.h>
+
+#ifdef CONFIG_DMA_SHARED_BUFFER
+
+void rust_helper_dma_fence_get(struct dma_fence *fence)
+{
+	dma_fence_get(fence);
+}
+
+void rust_helper_dma_fence_put(struct dma_fence *fence)
+{
+	dma_fence_put(fence);
+}
+
+struct dma_fence_chain *rust_helper_dma_fence_chain_alloc(void)
+{
+	return dma_fence_chain_alloc();
+}
+
+void rust_helper_dma_fence_chain_free(struct dma_fence_chain *chain)
+{
+	dma_fence_chain_free(chain);
+}
+
+void rust_helper_dma_fence_set_error(struct dma_fence *fence, int error)
+{
+	dma_fence_set_error(fence, error);
+}
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 905688bd8c00c9..f892bdef0fc36f 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -12,6 +12,7 @@
 #include "build_assert.c"
 #include "build_bug.c"
 #include "device.c"
+#include "dma-fence.c"
 #include "err.c"
 #include "iomem.c"
 #include "ioport.c"
diff --git a/rust/kernel/dma_fence.rs b/rust/kernel/dma_fence.rs
new file mode 100644
index 00000000000000..b35ef8053358e0
--- /dev/null
+++ b/rust/kernel/dma_fence.rs
@@ -0,0 +1,546 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! DMA fence abstraction.
+//!
+//! C header: [`include/linux/dma_fence.h`](../../include/linux/dma_fence.h)
+
+use crate::{
+    alloc::{flags::*, vec_ext::VecExt},
+    bindings,
+    error::{to_result, Result},
+    prelude::*,
+    sync::LockClassKey,
+    types::Opaque,
+};
+use core::fmt::Write;
+use core::ops::{Deref, DerefMut};
+use core::ptr::addr_of_mut;
+use core::sync::atomic::{AtomicU64, Ordering};
+
+/// Any kind of DMA Fence Object
+///
+/// # Invariants
+/// raw() returns a valid pointer to a dma_fence and we own a reference to it.
+pub trait RawDmaFence: crate::private::Sealed {
+    /// Returns the raw `struct dma_fence` pointer.
+    fn raw(&self) -> *mut bindings::dma_fence;
+
+    /// Returns the raw `struct dma_fence` pointer and consumes the object.
+    ///
+    /// The caller is responsible for dropping the reference.
+    fn into_raw(self) -> *mut bindings::dma_fence
+    where
+        Self: Sized,
+    {
+        let ptr = self.raw();
+        core::mem::forget(self);
+        ptr
+    }
+
+    /// Advances this fence to the chain node which will signal this sequence number.
+    /// If no sequence number is provided, this returns `self` again.
+    /// If the seqno has already been signaled, returns None.
+    fn chain_find_seqno(self, seqno: u64) -> Result<Option<Fence>>
+    where
+        Self: Sized,
+    {
+        let mut ptr = self.into_raw();
+
+        // SAFETY: This will safely fail if this DmaFence is not a chain.
+        // `ptr` is valid per the type invariant.
+        let ret = unsafe { bindings::dma_fence_chain_find_seqno(&mut ptr, seqno) };
+
+        if ret != 0 {
+            // SAFETY: This is either an owned reference or NULL, dma_fence_put can handle both.
+            unsafe { bindings::dma_fence_put(ptr) };
+            Err(Error::from_errno(ret))
+        } else if ptr.is_null() {
+            Ok(None)
+        } else {
+            // SAFETY: ptr is valid and non-NULL as checked above.
+            Ok(Some(unsafe { Fence::from_raw(ptr) }))
+        }
+    }
+
+    /// Signal completion of this fence
+    fn signal(&self) -> Result {
+        // SAFETY: Safe to call on any valid dma_fence object
+        to_result(unsafe { bindings::dma_fence_signal(self.raw()) })
+    }
+
+    /// Set the error flag on this fence
+    fn set_error(&self, err: Error) {
+        // SAFETY: Safe to call on any valid dma_fence object
+        unsafe { bindings::dma_fence_set_error(self.raw(), err.to_errno()) };
+    }
+}
+
+/// A generic DMA Fence Object
+///
+/// # Invariants
+/// ptr is a valid pointer to a dma_fence and we own a reference to it.
+pub struct Fence {
+    ptr: *mut bindings::dma_fence,
+}
+
+impl Fence {
+    /// Create a new Fence object from a raw pointer to a dma_fence.
+    ///
+    /// # Safety
+    /// The caller must own a reference to the dma_fence, which is transferred to the new object.
+    pub(crate) unsafe fn from_raw(ptr: *mut bindings::dma_fence) -> Fence {
+        Fence { ptr }
+    }
+
+    /// Create a new Fence object from a raw pointer to a dma_fence.
+    ///
+    /// # Safety
+    /// Takes a borrowed reference to the dma_fence, and increments the reference count.
+    pub(crate) unsafe fn get_raw(ptr: *mut bindings::dma_fence) -> Fence {
+        // SAFETY: Pointer is valid per the safety contract
+        unsafe { bindings::dma_fence_get(ptr) };
+        Fence { ptr }
+    }
+
+    /// Create a new Fence object from a RawDmaFence.
+    pub fn from_fence(fence: &dyn RawDmaFence) -> Fence {
+        // SAFETY: Pointer is valid per the RawDmaFence contract
+        unsafe { Self::get_raw(fence.raw()) }
+    }
+}
+
+impl crate::private::Sealed for Fence {}
+
+impl RawDmaFence for Fence {
+    fn raw(&self) -> *mut bindings::dma_fence {
+        self.ptr
+    }
+}
+
+impl Drop for Fence {
+    fn drop(&mut self) {
+        // SAFETY: We own a reference to this syncobj.
+        unsafe { bindings::dma_fence_put(self.ptr) };
+    }
+}
+
+impl Clone for Fence {
+    fn clone(&self) -> Self {
+        // SAFETY: `ptr` is valid per the type invariant and we own a reference to it.
+        unsafe {
+            bindings::dma_fence_get(self.ptr);
+            Self::from_raw(self.ptr)
+        }
+    }
+}
+
+// SAFETY: The API for these objects is thread safe
+unsafe impl Sync for Fence {}
+// SAFETY: The API for these objects is thread safe
+unsafe impl Send for Fence {}
+
+/// Trait which must be implemented by driver-specific fence objects.
+#[vtable]
+pub trait FenceOps: Sized + Send + Sync {
+    /// True if this dma_fence implementation uses 64bit seqno, false otherwise.
+    const USE_64BIT_SEQNO: bool;
+
+    /// Returns the driver name. This is a callback to allow drivers to compute the name at
+    /// runtime, without having it to store permanently for each fence, or build a cache of
+    /// some sort.
+    fn get_driver_name<'a>(self: &'a FenceObject<Self>) -> &'a CStr;
+
+    /// Return the name of the context this fence belongs to. This is a callback to allow drivers
+    /// to compute the name at runtime, without having it to store permanently for each fence, or
+    /// build a cache of some sort.
+    fn get_timeline_name<'a>(self: &'a FenceObject<Self>) -> &'a CStr;
+
+    /// Enable software signaling of fence.
+    fn enable_signaling(self: &FenceObject<Self>) -> bool {
+        false
+    }
+
+    /// Peek whether the fence is signaled, as a fastpath optimization for e.g. dma_fence_wait() or
+    /// dma_fence_add_callback().
+    fn signaled(self: &FenceObject<Self>) -> bool {
+        false
+    }
+
+    /// Callback to fill in free-form debug info specific to this fence, like the sequence number.
+    fn fence_value_str(self: &FenceObject<Self>, _output: &mut dyn Write) {}
+
+    /// Fills in the current value of the timeline as a string, like the sequence number. Note that
+    /// the specific fence passed to this function should not matter, drivers should only use it to
+    /// look up the corresponding timeline structures.
+    fn timeline_value_str(self: &FenceObject<Self>, _output: &mut dyn Write) {}
+}
+
+unsafe extern "C" fn get_driver_name_cb<T: FenceOps>(
+    fence: *mut bindings::dma_fence,
+) -> *const core::ffi::c_char {
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::get_driver_name(unsafe { &mut *p }).as_char_ptr()
+}
+
+unsafe extern "C" fn get_timeline_name_cb<T: FenceOps>(
+    fence: *mut bindings::dma_fence,
+) -> *const core::ffi::c_char {
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::get_timeline_name(unsafe { &mut *p }).as_char_ptr()
+}
+
+unsafe extern "C" fn enable_signaling_cb<T: FenceOps>(fence: *mut bindings::dma_fence) -> bool {
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::enable_signaling(unsafe { &mut *p })
+}
+
+unsafe extern "C" fn signaled_cb<T: FenceOps>(fence: *mut bindings::dma_fence) -> bool {
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::signaled(unsafe { &mut *p })
+}
+
+unsafe extern "C" fn release_cb<T: FenceOps>(fence: *mut bindings::dma_fence) {
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: p is never used after this
+    unsafe {
+        core::ptr::drop_in_place(&mut (*p).inner);
+    }
+
+    // SAFETY: All of our fences are allocated using kmalloc, so this is safe.
+    unsafe { bindings::dma_fence_free(fence) };
+}
+
+unsafe extern "C" fn fence_value_str_cb<T: FenceOps>(
+    fence: *mut bindings::dma_fence,
+    string: *mut core::ffi::c_char,
+    size: core::ffi::c_int,
+) {
+    let size: usize = size.try_into().unwrap_or(0);
+
+    if size == 0 {
+        return;
+    }
+
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for the validity of string/size
+    let mut f = unsafe { crate::str::Formatter::from_buffer(string as *mut _, size) };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::fence_value_str(unsafe { &mut *p }, &mut f);
+    let _ = f.write_str("\0");
+
+    // SAFETY: `size` is at least 1 per the check above
+    unsafe { *string.add(size - 1) = 0 };
+}
+
+unsafe extern "C" fn timeline_value_str_cb<T: FenceOps>(
+    fence: *mut bindings::dma_fence,
+    string: *mut core::ffi::c_char,
+    size: core::ffi::c_int,
+) {
+    let size: usize = size.try_into().unwrap_or(0);
+
+    if size == 0 {
+        return;
+    }
+
+    // SAFETY: All of our fences are FenceObject<T>.
+    let p = unsafe { crate::container_of!(fence, FenceObject<T>, fence) as *mut FenceObject<T> };
+
+    // SAFETY: The caller is responsible for the validity of string/size
+    let mut f = unsafe { crate::str::Formatter::from_buffer(string as *mut _, size) };
+
+    // SAFETY: The caller is responsible for passing a valid dma_fence subtype
+    T::timeline_value_str(unsafe { &mut *p }, &mut f);
+    let _ = f.write_str("\0");
+
+    // SAFETY: `size` is at least 1 per the check above
+    unsafe { *string.add(size - 1) = 0 };
+}
+
+// Allow FenceObject<Self> to be used as a self argument, for ergonomics
+impl<T: FenceOps> core::ops::Receiver for FenceObject<T> {}
+
+/// A driver-specific DMA Fence Object
+///
+/// # Invariants
+/// ptr is a valid pointer to a dma_fence and we own a reference to it.
+#[repr(C)]
+pub struct FenceObject<T: FenceOps> {
+    fence: bindings::dma_fence,
+    lock: Opaque<bindings::spinlock>,
+    inner: T,
+}
+
+impl<T: FenceOps> FenceObject<T> {
+    const SIZE: usize = core::mem::size_of::<Self>();
+
+    const VTABLE: bindings::dma_fence_ops = bindings::dma_fence_ops {
+        use_64bit_seqno: T::USE_64BIT_SEQNO,
+        get_driver_name: Some(get_driver_name_cb::<T>),
+        get_timeline_name: Some(get_timeline_name_cb::<T>),
+        enable_signaling: if T::HAS_ENABLE_SIGNALING {
+            Some(enable_signaling_cb::<T>)
+        } else {
+            None
+        },
+        signaled: if T::HAS_SIGNALED {
+            Some(signaled_cb::<T>)
+        } else {
+            None
+        },
+        wait: None, // Deprecated
+        release: Some(release_cb::<T>),
+        fence_value_str: if T::HAS_FENCE_VALUE_STR {
+            Some(fence_value_str_cb::<T>)
+        } else {
+            None
+        },
+        timeline_value_str: if T::HAS_TIMELINE_VALUE_STR {
+            Some(timeline_value_str_cb::<T>)
+        } else {
+            None
+        },
+        set_deadline: None,
+    };
+}
+
+impl<T: FenceOps> Deref for FenceObject<T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        &self.inner
+    }
+}
+
+impl<T: FenceOps> DerefMut for FenceObject<T> {
+    fn deref_mut(&mut self) -> &mut T {
+        &mut self.inner
+    }
+}
+
+impl<T: FenceOps> crate::private::Sealed for FenceObject<T> {}
+impl<T: FenceOps> RawDmaFence for FenceObject<T> {
+    fn raw(&self) -> *mut bindings::dma_fence {
+        &self.fence as *const _ as *mut _
+    }
+}
+
+/// A unique reference to a driver-specific fence object
+pub struct UniqueFence<T: FenceOps>(*mut FenceObject<T>);
+
+impl<T: FenceOps> Deref for UniqueFence<T> {
+    type Target = FenceObject<T>;
+
+    fn deref(&self) -> &FenceObject<T> {
+        // SAFETY: The pointer is always valid for UniqueFence objects
+        unsafe { &*self.0 }
+    }
+}
+
+impl<T: FenceOps> DerefMut for UniqueFence<T> {
+    fn deref_mut(&mut self) -> &mut FenceObject<T> {
+        // SAFETY: The pointer is always valid for UniqueFence objects
+        unsafe { &mut *self.0 }
+    }
+}
+
+impl<T: FenceOps> crate::private::Sealed for UniqueFence<T> {}
+impl<T: FenceOps> RawDmaFence for UniqueFence<T> {
+    fn raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: The pointer is always valid for UniqueFence objects
+        unsafe { addr_of_mut!((*self.0).fence) }
+    }
+}
+
+impl<T: FenceOps> From<UniqueFence<T>> for UserFence<T> {
+    fn from(value: UniqueFence<T>) -> Self {
+        let ptr = value.0;
+        core::mem::forget(value);
+
+        UserFence(ptr)
+    }
+}
+
+impl<T: FenceOps> Drop for UniqueFence<T> {
+    fn drop(&mut self) {
+        // SAFETY: We own a reference to this fence.
+        unsafe { bindings::dma_fence_put(self.raw()) };
+    }
+}
+
+// SAFETY: The API for these objects is thread safe
+unsafe impl<T: FenceOps> Sync for UniqueFence<T> {}
+// SAFETY: The API for these objects is thread safe
+unsafe impl<T: FenceOps> Send for UniqueFence<T> {}
+
+/// A shared reference to a driver-specific fence object
+pub struct UserFence<T: FenceOps>(*mut FenceObject<T>);
+
+impl<T: FenceOps> Deref for UserFence<T> {
+    type Target = FenceObject<T>;
+
+    fn deref(&self) -> &FenceObject<T> {
+        // SAFETY: The pointer is always valid for UserFence objects
+        unsafe { &*self.0 }
+    }
+}
+
+impl<T: FenceOps> Clone for UserFence<T> {
+    fn clone(&self) -> Self {
+        // SAFETY: `ptr` is valid per the type invariant and we own a reference to it.
+        unsafe {
+            bindings::dma_fence_get(self.raw());
+            Self(self.0)
+        }
+    }
+}
+
+impl<T: FenceOps> crate::private::Sealed for UserFence<T> {}
+impl<T: FenceOps> RawDmaFence for UserFence<T> {
+    fn raw(&self) -> *mut bindings::dma_fence {
+        // SAFETY: The pointer is always valid for UserFence objects
+        unsafe { addr_of_mut!((*self.0).fence) }
+    }
+}
+
+impl<T: FenceOps> Drop for UserFence<T> {
+    fn drop(&mut self) {
+        // SAFETY: We own a reference to this fence.
+        unsafe { bindings::dma_fence_put(self.raw()) };
+    }
+}
+
+// SAFETY: The API for these objects is thread safe
+unsafe impl<T: FenceOps> Sync for UserFence<T> {}
+// SAFETY: The API for these objects is thread safe
+unsafe impl<T: FenceOps> Send for UserFence<T> {}
+
+/// An array of fence contexts, out of which fences can be created.
+pub struct FenceContexts {
+    start: u64,
+    count: u32,
+    seqnos: Vec<AtomicU64>,
+    lock_name: &'static CStr,
+    lock_key: LockClassKey,
+}
+
+impl FenceContexts {
+    /// Create a new set of fence contexts.
+    pub fn new(count: u32, name: &'static CStr, key: LockClassKey) -> Result<FenceContexts> {
+        let mut seqnos: Vec<AtomicU64> = Vec::new();
+
+        seqnos.reserve(count as usize, GFP_KERNEL)?;
+
+        for _ in 0..count {
+            seqnos.push(Default::default(), GFP_KERNEL)?;
+        }
+
+        // SAFETY: This is always safe to call
+        let start = unsafe { bindings::dma_fence_context_alloc(count as core::ffi::c_uint) };
+
+        Ok(FenceContexts {
+            start,
+            count,
+            seqnos,
+            lock_name: name,
+            lock_key: key,
+        })
+    }
+
+    /// Create a new fence in a given context index.
+    pub fn new_fence<T: FenceOps>(&self, context: u32, inner: T) -> Result<UniqueFence<T>> {
+        if context > self.count {
+            return Err(EINVAL);
+        }
+
+        // SAFETY: krealloc is always safe to call like this
+        let p = unsafe {
+            bindings::krealloc(
+                core::ptr::null_mut(),
+                FenceObject::<T>::SIZE,
+                bindings::GFP_KERNEL | bindings::__GFP_ZERO,
+            ) as *mut FenceObject<T>
+        };
+
+        if p.is_null() {
+            return Err(ENOMEM);
+        }
+
+        let seqno = self.seqnos[context as usize].fetch_add(1, Ordering::Relaxed);
+
+        // SAFETY: The pointer is valid, so pointers to members are too.
+        // After this, all fields are initialized.
+        unsafe {
+            addr_of_mut!((*p).inner).write(inner);
+            bindings::__spin_lock_init(
+                addr_of_mut!((*p).lock) as *mut _,
+                self.lock_name.as_char_ptr(),
+                self.lock_key.as_ptr(),
+            );
+            bindings::dma_fence_init(
+                addr_of_mut!((*p).fence),
+                &FenceObject::<T>::VTABLE,
+                addr_of_mut!((*p).lock) as *mut _,
+                self.start + context as u64,
+                seqno,
+            );
+        };
+
+        Ok(UniqueFence(p))
+    }
+}
+
+/// A DMA Fence Chain Object
+///
+/// # Invariants
+/// ptr is a valid pointer to a dma_fence_chain which we own.
+pub struct FenceChain {
+    ptr: *mut bindings::dma_fence_chain,
+}
+
+impl FenceChain {
+    /// Create a new DmaFenceChain object.
+    pub fn new() -> Result<Self> {
+        // SAFETY: This function is safe to call and takes no arguments.
+        let ptr = unsafe { bindings::dma_fence_chain_alloc() };
+
+        if ptr.is_null() {
+            Err(ENOMEM)
+        } else {
+            Ok(FenceChain { ptr })
+        }
+    }
+
+    /// Convert the DmaFenceChain into the underlying raw pointer.
+    ///
+    /// This assumes the caller will take ownership of the object.
+    pub(crate) fn into_raw(self) -> *mut bindings::dma_fence_chain {
+        let ptr = self.ptr;
+        core::mem::forget(self);
+        ptr
+    }
+}
+
+impl Drop for FenceChain {
+    fn drop(&mut self) {
+        // SAFETY: We own this dma_fence_chain.
+        unsafe { bindings::dma_fence_chain_free(self.ptr) };
+    }
+}
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 286475437abf55..1c0df72b9d5876 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -38,6 +38,8 @@ pub mod block;
 mod build_assert;
 pub mod delay;
 pub mod device;
+#[cfg(CONFIG_DMA_SHARED_BUFFER)]
+pub mod dma_fence;
 pub mod driver;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]

From 2b2d58d8ed4f592fc27165c8d1d59a9f1c7e4676 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 14:12:08 +0900
Subject: [PATCH 0875/1027] rust: helpers: Add bindings/wrappers for dma_resv

This is just for basic usage in the DRM shmem abstractions for implied
locking, not intended as a full DMA Reservation abstraction yet.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/helpers/dma-resv.c         | 13 +++++++++++++
 rust/helpers/helpers.c          |  1 +
 3 files changed, 15 insertions(+)
 create mode 100644 rust/helpers/dma-resv.c

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index d4c3cc8f80f4ac..d950d50a3dbe48 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -15,6 +15,7 @@
 #include <linux/dma-fence.h>
 #include <linux/dma-fence-chain.h>
 #include <linux/dma-mapping.h>
+#include <linux/dma-resv.h>
 #include <linux/errname.h>
 #include <linux/ethtool.h>
 #include <linux/firmware.h>
diff --git a/rust/helpers/dma-resv.c b/rust/helpers/dma-resv.c
new file mode 100644
index 00000000000000..05501cb814513b
--- /dev/null
+++ b/rust/helpers/dma-resv.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/dma-resv.h>
+
+int rust_helper_dma_resv_lock(struct dma_resv *obj, struct ww_acquire_ctx *ctx)
+{
+	return dma_resv_lock(obj, ctx);
+}
+
+void rust_helper_dma_resv_unlock(struct dma_resv *obj)
+{
+	dma_resv_unlock(obj);
+}
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index f892bdef0fc36f..ee9a63168fd3e3 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -13,6 +13,7 @@
 #include "build_bug.c"
 #include "device.c"
 #include "dma-fence.c"
+#include "dma-resv.c"
 #include "err.c"
 #include "iomem.c"
 #include "ioport.c"

From ba640dde0999991a63aeb9a37b04b57888d581e9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 20:42:15 +0900
Subject: [PATCH 0876/1027] rust: drm: ioctl: Add DRM ioctl abstraction

DRM drivers need to be able to declare which driver-specific ioctls they
support. This abstraction adds the required types and a helper macro to
generate the ioctl definition inside the DRM driver.

Note that this macro is not usable until further bits of the
abstraction are in place (but it will not fail to compile on its own, if
not called).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/drm/ioctl.rs        | 153 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   5 ++
 rust/kernel/lib.rs              |   2 +
 rust/uapi/uapi_helper.h         |   1 +
 5 files changed, 162 insertions(+)
 create mode 100644 rust/kernel/drm/ioctl.rs
 create mode 100644 rust/kernel/drm/mod.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index d950d50a3dbe48..08c8378e542c8b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -6,6 +6,7 @@
  * Sorted alphabetically.
  */
 
+#include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
 #include <linux/blk-mq.h>
diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs
new file mode 100644
index 00000000000000..6df59b34567af8
--- /dev/null
+++ b/rust/kernel/drm/ioctl.rs
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+#![allow(non_snake_case)]
+
+//! DRM IOCTL definitions.
+//!
+//! C header: [`include/drm/drm_ioctl.h`](../../../../include/drm/drm_ioctl.h)
+
+use crate::ioctl;
+
+const BASE: u32 = bindings::DRM_IOCTL_BASE as u32;
+
+/// Construct a DRM ioctl number with no argument.
+#[inline(always)]
+pub const fn IO(nr: u32) -> u32 {
+    ioctl::_IO(BASE, nr)
+}
+
+/// Construct a DRM ioctl number with a read-only argument.
+#[inline(always)]
+pub const fn IOR<T>(nr: u32) -> u32 {
+    ioctl::_IOR::<T>(BASE, nr)
+}
+
+/// Construct a DRM ioctl number with a write-only argument.
+#[inline(always)]
+pub const fn IOW<T>(nr: u32) -> u32 {
+    ioctl::_IOW::<T>(BASE, nr)
+}
+
+/// Construct a DRM ioctl number with a read-write argument.
+#[inline(always)]
+pub const fn IOWR<T>(nr: u32) -> u32 {
+    ioctl::_IOWR::<T>(BASE, nr)
+}
+
+/// Descriptor type for DRM ioctls. Use the `declare_drm_ioctls!{}` macro to construct them.
+pub type DrmIoctlDescriptor = bindings::drm_ioctl_desc;
+
+/// This is for ioctl which are used for rendering, and require that the file descriptor is either
+/// for a render node, or if it’s a legacy/primary node, then it must be authenticated.
+pub const AUTH: u32 = bindings::drm_ioctl_flags_DRM_AUTH;
+
+/// This must be set for any ioctl which can change the modeset or display state. Userspace must
+/// call the ioctl through a primary node, while it is the active master.
+///
+/// Note that read-only modeset ioctl can also be called by unauthenticated clients, or when a
+/// master is not the currently active one.
+pub const MASTER: u32 = bindings::drm_ioctl_flags_DRM_MASTER;
+
+/// Anything that could potentially wreak a master file descriptor needs to have this flag set.
+///
+/// Current that’s only for the SETMASTER and DROPMASTER ioctl, which e.g. logind can call to force
+/// a non-behaving master (display compositor) into compliance.
+///
+/// This is equivalent to callers with the SYSADMIN capability.
+pub const ROOT_ONLY: u32 = bindings::drm_ioctl_flags_DRM_ROOT_ONLY;
+
+/// This is used for all ioctl needed for rendering only, for drivers which support render nodes.
+/// This should be all new render drivers, and hence it should be always set for any ioctl with
+/// `AUTH` set. Note though that read-only query ioctl might have this set, but have not set
+/// DRM_AUTH because they do not require authentication.
+pub const RENDER_ALLOW: u32 = bindings::drm_ioctl_flags_DRM_RENDER_ALLOW;
+
+/// Internal structures used by the [`declare_drm_ioctls!{}`] macro. Do not use directly.
+#[doc(hidden)]
+pub mod internal {
+    pub use bindings::drm_device;
+    pub use bindings::drm_file;
+    pub use bindings::drm_ioctl_desc;
+}
+
+/// Declare the DRM ioctls for a driver.
+///
+/// Each entry in the list should have the form:
+///
+/// `(ioctl_number, argument_type, flags, user_callback),`
+///
+/// `argument_type` is the type name within the `bindings` crate.
+/// `user_callback` should have the following prototype:
+///
+/// ```
+/// fn foo(device: &kernel::drm::device::Device<Self>,
+///        data: &mut bindings::argument_type,
+///        file: &kernel::drm::file::File<Self::File>,
+/// )
+/// ```
+/// where `Self` is the drm::drv::Driver implementation these ioctls are being declared within.
+///
+/// # Examples
+///
+/// ```
+/// kernel::declare_drm_ioctls! {
+///     (FOO_GET_PARAM, drm_foo_get_param, ioctl::RENDER_ALLOW, my_get_param_handler),
+/// }
+/// ```
+///
+#[macro_export]
+macro_rules! declare_drm_ioctls {
+    ( $(($cmd:ident, $struct:ident, $flags:expr, $func:expr)),* $(,)? ) => {
+        const IOCTLS: &'static [$crate::drm::ioctl::DrmIoctlDescriptor] = {
+            use $crate::uapi::*;
+            const _:() = {
+                let i: u32 = $crate::uapi::DRM_COMMAND_BASE;
+                // Assert that all the IOCTLs are in the right order and there are no gaps,
+                // and that the sizeof of the specified type is correct.
+                $(
+                    let cmd: u32 = $crate::macros::concat_idents!(DRM_IOCTL_, $cmd);
+                    ::core::assert!(i == $crate::ioctl::_IOC_NR(cmd));
+                    ::core::assert!(core::mem::size_of::<$crate::uapi::$struct>() == $crate::ioctl::_IOC_SIZE(cmd));
+                    let i: u32 = i + 1;
+                )*
+            };
+
+            let ioctls = &[$(
+                $crate::drm::ioctl::internal::drm_ioctl_desc {
+                    cmd: $crate::macros::concat_idents!(DRM_IOCTL_, $cmd) as u32,
+                    func: {
+                        #[allow(non_snake_case)]
+                        unsafe extern "C" fn $cmd(
+                                raw_dev: *mut $crate::drm::ioctl::internal::drm_device,
+                                raw_data: *mut ::core::ffi::c_void,
+                                raw_file_priv: *mut $crate::drm::ioctl::internal::drm_file,
+                        ) -> core::ffi::c_int {
+                            // SAFETY: We never drop this, and the DRM core ensures the device lives
+                            // while callbacks are being called.
+                            //
+                            // FIXME: Currently there is nothing enforcing that the types of the
+                            // dev/file match the current driver these ioctls are being declared
+                            // for, and it's not clear how to enforce this within the type system.
+                            let dev = ::core::mem::ManuallyDrop::new(unsafe {
+                                $crate::drm::device::Device::from_raw(raw_dev)
+                            });
+                            // SAFETY: This is just the ioctl argument, which hopefully has the right type
+                            // (we've done our best checking the size).
+                            let data = unsafe { &mut *(raw_data as *mut $crate::uapi::$struct) };
+                            // SAFETY: This is just the DRM file structure
+                            let file = unsafe { $crate::drm::file::File::from_raw(raw_file_priv) };
+
+                            match $func(&*dev, data, &file) {
+                                Err(e) => e.to_errno(),
+                                Ok(i) => i.try_into().unwrap_or(code::ERANGE.to_errno()),
+                            }
+                        }
+                        Some($cmd)
+                    },
+                    flags: $flags,
+                    name: $crate::c_str!(::core::stringify!($cmd)).as_char_ptr(),
+                }
+            ),*];
+            ioctls
+        };
+    };
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
new file mode 100644
index 00000000000000..9ec6d7cbcaf331
--- /dev/null
+++ b/rust/kernel/drm/mod.rs
@@ -0,0 +1,5 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM subsystem abstractions.
+
+pub mod ioctl;
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 1c0df72b9d5876..5453e8b2591300 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -41,6 +41,8 @@ pub mod device;
 #[cfg(CONFIG_DMA_SHARED_BUFFER)]
 pub mod dma_fence;
 pub mod driver;
+#[cfg(CONFIG_DRM = "y")]
+pub mod drm;
 pub mod error;
 #[cfg(CONFIG_RUST_FW_LOADER_ABSTRACTIONS)]
 pub mod firmware;
diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h
index 08f5e9334c9e83..ed42a456da2efa 100644
--- a/rust/uapi/uapi_helper.h
+++ b/rust/uapi/uapi_helper.h
@@ -7,5 +7,6 @@
  */
 
 #include <uapi/asm-generic/ioctl.h>
+#include <uapi/drm/drm.h>
 #include <uapi/linux/mii.h>
 #include <uapi/linux/ethtool.h>

From ef200d9fa7d60c24830ac49b4da7548d8accac23 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 20:53:47 +0900
Subject: [PATCH 0877/1027] rust: drm: Add Device and Driver abstractions

Add the initial abstractions for DRM drivers and devices. These go
together in one commit since they are fairly tightly coupled types.

A few things have been stubbed out, to be implemented as further bits of
the DRM subsystem are introduced.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   3 +
 rust/kernel/drm/device.rs       |  73 +++++++
 rust/kernel/drm/drv.rs          | 328 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/ioctl.rs        |  10 +-
 rust/kernel/drm/mod.rs          |   2 +
 5 files changed, 410 insertions(+), 6 deletions(-)
 create mode 100644 rust/kernel/drm/device.rs
 create mode 100644 rust/kernel/drm/drv.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 08c8378e542c8b..a0428512223659 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -6,6 +6,8 @@
  * Sorted alphabetically.
  */
 
+#include <drm/drm_device.h>
+#include <drm/drm_drv.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
@@ -23,6 +25,7 @@
 #include <linux/jiffies.h>
 #include <linux/mdio.h>
 #include <linux/phy.h>
+#include <linux/fs.h>
 #include <linux/io-pgtable.h>
 #include <linux/ktime.h>
 #include <linux/lockdep.h>
diff --git a/rust/kernel/drm/device.rs b/rust/kernel/drm/device.rs
new file mode 100644
index 00000000000000..90e0ff6d591593
--- /dev/null
+++ b/rust/kernel/drm/device.rs
@@ -0,0 +1,73 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM device.
+//!
+//! C header: [`include/drm/drm_device.h`](../../../../include/drm/drm_device.h)
+
+use crate::{
+    bindings, device, drm,
+    types::{AlwaysRefCounted, ForeignOwnable},
+};
+use core::cell::UnsafeCell;
+use core::marker::PhantomData;
+use core::ptr::NonNull;
+
+/// A typed DRM device with a specific driver. The device is always reference-counted.
+#[repr(transparent)]
+pub struct Device<T: drm::drv::Driver> {
+    pub(super) drm: UnsafeCell<bindings::drm_device>,
+    _p: PhantomData<T>,
+}
+
+impl<T: drm::drv::Driver> Device<T> {
+    #[allow(dead_code, clippy::mut_from_ref)]
+    pub(crate) unsafe fn raw_mut(&self) -> &mut bindings::drm_device {
+        // SAFETY: Depends on safe usage by the caller
+        unsafe { &mut *self.drm.get() }
+    }
+
+    // Not intended to be called externally, except via declare_drm_ioctls!()
+    #[doc(hidden)]
+    pub unsafe fn borrow<'a>(raw: *const bindings::drm_device) -> &'a Self {
+        // SAFETY: Hidden helper, depends on safe usage by the caller
+        unsafe { &*(raw as *const Self) }
+    }
+
+    /// Returns a borrowed reference to the user data associated with this Device.
+    pub fn data(&self) -> <T::Data as ForeignOwnable>::Borrowed<'_> {
+        // SAFETY: dev_private is guaranteed to be initialized for all
+        // Device objects exposed to users.
+        unsafe { T::Data::borrow((*self.drm.get()).dev_private) }
+    }
+}
+
+// SAFETY: DRM device objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: drm::drv::Driver> AlwaysRefCounted for Device<T> {
+    fn inc_ref(&self) {
+        // SAFETY: We already have a reference per the contract.
+        unsafe { bindings::drm_dev_get(&self.drm as *const _ as *mut _) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The Device<T> type has the same layout as drm_device,
+        // so we can just cast.
+        unsafe { bindings::drm_dev_put(obj.as_ptr() as *mut _) };
+    }
+}
+
+// SAFETY: `Device` only holds a pointer to a C device, which is safe to be used from any thread.
+unsafe impl<T: drm::drv::Driver> Send for Device<T> {}
+
+// SAFETY: `Device` only holds a pointer to a C device, references to which are safe to be used
+// from any thread.
+unsafe impl<T: drm::drv::Driver> Sync for Device<T> {}
+
+// Make drm::Device work for dev_info!() and friends
+// SAFETY: dev is initialized by C for all Device objects
+unsafe impl<T: drm::drv::Driver> device::RawDevice for Device<T> {
+    fn raw_device(&self) -> *mut bindings::device {
+        // SAFETY: dev is initialized by C for all Device objects
+        unsafe { (*self.drm.get()).dev }
+    }
+}
diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs
new file mode 100644
index 00000000000000..d67c953a3accd4
--- /dev/null
+++ b/rust/kernel/drm/drv.rs
@@ -0,0 +1,328 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM driver core.
+//!
+//! C header: [`include/drm/drm_drv.h`](../../../../include/drm/drm_drv.h)
+
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*},
+    bindings, device, drm,
+    error::code::*,
+    error::from_err_ptr,
+    error::{Error, Result},
+    prelude::*,
+    private::Sealed,
+    str::CStr,
+    types::{ARef, ForeignOwnable},
+    ThisModule,
+};
+use core::{
+    marker::{PhantomData, PhantomPinned},
+    pin::Pin,
+    ptr::NonNull,
+};
+use macros::vtable;
+
+/// Driver use the GEM memory manager. This should be set for all modern drivers.
+pub const FEAT_GEM: u32 = bindings::drm_driver_feature_DRIVER_GEM;
+/// Driver supports mode setting interfaces (KMS).
+pub const FEAT_MODESET: u32 = bindings::drm_driver_feature_DRIVER_MODESET;
+/// Driver supports dedicated render nodes.
+pub const FEAT_RENDER: u32 = bindings::drm_driver_feature_DRIVER_RENDER;
+/// Driver supports the full atomic modesetting userspace API.
+///
+/// Drivers which only use atomic internally, but do not support the full userspace API (e.g. not
+/// all properties converted to atomic, or multi-plane updates are not guaranteed to be tear-free)
+/// should not set this flag.
+pub const FEAT_ATOMIC: u32 = bindings::drm_driver_feature_DRIVER_ATOMIC;
+/// Driver supports DRM sync objects for explicit synchronization of command submission.
+pub const FEAT_SYNCOBJ: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ;
+/// Driver supports the timeline flavor of DRM sync objects for explicit synchronization of command
+/// submission.
+pub const FEAT_SYNCOBJ_TIMELINE: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ_TIMELINE;
+
+/// Information data for a DRM Driver.
+pub struct DriverInfo {
+    /// Driver major version.
+    pub major: i32,
+    /// Driver minor version.
+    pub minor: i32,
+    /// Driver patchlevel version.
+    pub patchlevel: i32,
+    /// Driver name.
+    pub name: &'static CStr,
+    /// Driver description.
+    pub desc: &'static CStr,
+    /// Driver date.
+    pub date: &'static CStr,
+}
+
+/// Internal memory management operation set, normally created by memory managers (e.g. GEM).
+///
+/// See `kernel::drm::gem` and `kernel::drm::gem::shmem`.
+pub struct AllocOps {
+    pub(crate) gem_create_object: Option<
+        unsafe extern "C" fn(
+            dev: *mut bindings::drm_device,
+            size: usize,
+        ) -> *mut bindings::drm_gem_object,
+    >,
+    pub(crate) prime_handle_to_fd: Option<
+        unsafe extern "C" fn(
+            dev: *mut bindings::drm_device,
+            file_priv: *mut bindings::drm_file,
+            handle: u32,
+            flags: u32,
+            prime_fd: *mut core::ffi::c_int,
+        ) -> core::ffi::c_int,
+    >,
+    pub(crate) prime_fd_to_handle: Option<
+        unsafe extern "C" fn(
+            dev: *mut bindings::drm_device,
+            file_priv: *mut bindings::drm_file,
+            prime_fd: core::ffi::c_int,
+            handle: *mut u32,
+        ) -> core::ffi::c_int,
+    >,
+    pub(crate) gem_prime_import: Option<
+        unsafe extern "C" fn(
+            dev: *mut bindings::drm_device,
+            dma_buf: *mut bindings::dma_buf,
+        ) -> *mut bindings::drm_gem_object,
+    >,
+    pub(crate) gem_prime_import_sg_table: Option<
+        unsafe extern "C" fn(
+            dev: *mut bindings::drm_device,
+            attach: *mut bindings::dma_buf_attachment,
+            sgt: *mut bindings::sg_table,
+        ) -> *mut bindings::drm_gem_object,
+    >,
+    pub(crate) dumb_create: Option<
+        unsafe extern "C" fn(
+            file_priv: *mut bindings::drm_file,
+            dev: *mut bindings::drm_device,
+            args: *mut bindings::drm_mode_create_dumb,
+        ) -> core::ffi::c_int,
+    >,
+    pub(crate) dumb_map_offset: Option<
+        unsafe extern "C" fn(
+            file_priv: *mut bindings::drm_file,
+            dev: *mut bindings::drm_device,
+            handle: u32,
+            offset: *mut u64,
+        ) -> core::ffi::c_int,
+    >,
+}
+
+/// Trait for memory manager implementations. Implemented internally.
+pub trait AllocImpl: Sealed {
+    /// The C callback operations for this memory manager.
+    const ALLOC_OPS: AllocOps;
+}
+
+/// A DRM driver implementation.
+#[vtable]
+pub trait Driver {
+    /// Context data associated with the DRM driver
+    ///
+    /// Determines the type of the context data passed to each of the methods of the trait.
+    type Data: ForeignOwnable + Sync + Send;
+
+    /// The type used to manage memory for this driver.
+    ///
+    /// Should be either `drm::gem::Object<T>` or `drm::gem::shmem::Object<T>`.
+    type Object: AllocImpl;
+
+    /// Driver metadata
+    const INFO: DriverInfo;
+
+    /// Feature flags
+    const FEATURES: u32;
+
+    /// IOCTL list. See `kernel::drm::ioctl::declare_drm_ioctls!{}`.
+    const IOCTLS: &'static [drm::ioctl::DrmIoctlDescriptor];
+}
+
+/// A registration of a DRM device
+///
+/// # Invariants:
+///
+/// drm is always a valid pointer to an allocated drm_device
+pub struct Registration<T: Driver> {
+    drm: ARef<drm::device::Device<T>>,
+    registered: bool,
+    fops: bindings::file_operations,
+    vtable: Pin<Box<bindings::drm_driver>>,
+    _p: PhantomData<T>,
+    _pin: PhantomPinned,
+}
+
+#[cfg(CONFIG_DRM_LEGACY)]
+macro_rules! drm_legacy_fields {
+    ( $($field:ident: $val:expr),* $(,)? ) => {
+        bindings::drm_driver {
+            $( $field: $val ),*,
+            firstopen: None,
+            preclose: None,
+            dma_ioctl: None,
+            dma_quiescent: None,
+            context_dtor: None,
+            irq_handler: None,
+            irq_preinstall: None,
+            irq_postinstall: None,
+            irq_uninstall: None,
+            get_vblank_counter: None,
+            enable_vblank: None,
+            disable_vblank: None,
+            dev_priv_size: 0,
+        }
+    }
+}
+
+#[cfg(not(CONFIG_DRM_LEGACY))]
+macro_rules! drm_legacy_fields {
+    ( $($field:ident: $val:expr),* $(,)? ) => {
+        bindings::drm_driver {
+            $( $field: $val ),*
+        }
+    }
+}
+
+/// Registers a DRM device with the rest of the kernel.
+///
+/// It automatically picks up THIS_MODULE.
+#[allow(clippy::crate_in_macro_def)]
+#[macro_export]
+macro_rules! drm_device_register {
+    ($reg:expr, $data:expr, $flags:expr $(,)?) => {{
+        $crate::drm::drv::Registration::register($reg, $data, $flags, &crate::THIS_MODULE)
+    }};
+}
+
+impl<T: Driver> Registration<T> {
+    const VTABLE: bindings::drm_driver = drm_legacy_fields! {
+        load: None,
+        open: None, // TODO: File abstraction
+        postclose: None, // TODO: File abstraction
+        lastclose: None,
+        unload: None,
+        release: None,
+        master_set: None,
+        master_drop: None,
+        debugfs_init: None,
+        gem_create_object: T::Object::ALLOC_OPS.gem_create_object,
+        prime_handle_to_fd: T::Object::ALLOC_OPS.prime_handle_to_fd,
+        prime_fd_to_handle: T::Object::ALLOC_OPS.prime_fd_to_handle,
+        gem_prime_import: T::Object::ALLOC_OPS.gem_prime_import,
+        gem_prime_import_sg_table: T::Object::ALLOC_OPS.gem_prime_import_sg_table,
+        dumb_create: T::Object::ALLOC_OPS.dumb_create,
+        dumb_map_offset: T::Object::ALLOC_OPS.dumb_map_offset,
+        show_fdinfo: None,
+
+        major: T::INFO.major,
+        minor: T::INFO.minor,
+        patchlevel: T::INFO.patchlevel,
+        name: T::INFO.name.as_char_ptr() as *mut _,
+        desc: T::INFO.desc.as_char_ptr() as *mut _,
+        date: T::INFO.date.as_char_ptr() as *mut _,
+
+        driver_features: T::FEATURES,
+        ioctls: T::IOCTLS.as_ptr(),
+        num_ioctls: T::IOCTLS.len() as i32,
+        fops: core::ptr::null_mut(),
+    };
+
+    /// Creates a new [`Registration`] but does not register it yet.
+    ///
+    /// It is allowed to move.
+    pub fn new(parent: &dyn device::RawDevice) -> Result<Self> {
+        let vtable = Pin::new(Box::new(Self::VTABLE, GFP_KERNEL)?);
+        // SAFETY: Safe to call at any time (with valid args)
+        let raw_drm = unsafe { bindings::drm_dev_alloc(&*vtable, parent.raw_device()) };
+        let raw_drm = NonNull::new(from_err_ptr(raw_drm)? as *mut _).ok_or(ENOMEM)?;
+
+        // SAFETY: The reference count is one, and now we take ownership of that reference as a
+        // drm::device::Device.
+        let drm = unsafe { ARef::from_raw(raw_drm) };
+
+        Ok(Self {
+            drm,
+            registered: false,
+            vtable,
+            fops: Default::default(), // TODO: GEM abstraction
+            _pin: PhantomPinned,
+            _p: PhantomData,
+        })
+    }
+
+    /// Registers a DRM device with the rest of the kernel.
+    ///
+    /// Users are encouraged to use the [`drm_device_register!()`] macro because it automatically
+    /// picks up the current module.
+    pub fn register(
+        self: Pin<&mut Self>,
+        data: T::Data,
+        flags: usize,
+        module: &'static ThisModule,
+    ) -> Result {
+        if self.registered {
+            // Already registered.
+            return Err(EINVAL);
+        }
+
+        // SAFETY: We never move out of `this`.
+        let this = unsafe { self.get_unchecked_mut() };
+        let data_pointer = <T::Data as ForeignOwnable>::into_foreign(data);
+        // SAFETY: This is the only code touching dev_private, so it is safe to upgrade to a
+        // mutable reference.
+        unsafe { this.drm.raw_mut() }.dev_private = data_pointer as *mut _;
+
+        this.fops.owner = module.0;
+        this.vtable.fops = &this.fops;
+
+        // SAFETY: The device is now initialized and ready to be registered.
+        let ret = unsafe { bindings::drm_dev_register(this.drm.raw_mut(), flags as u64) };
+        if ret < 0 {
+            // SAFETY: `data_pointer` was returned by `into_foreign` above.
+            unsafe { T::Data::from_foreign(data_pointer) };
+            return Err(Error::from_errno(ret));
+        }
+
+        this.registered = true;
+        Ok(())
+    }
+
+    /// Returns a reference to the `Device` instance for this registration.
+    pub fn device(&self) -> &drm::device::Device<T> {
+        // TODO: rework this, ensure this only works after registration
+        &self.drm
+    }
+}
+
+// SAFETY: `Registration` doesn't offer any methods or access to fields when shared between threads
+// or CPUs, so it is safe to share it.
+unsafe impl<T: Driver> Sync for Registration<T> {}
+
+#[allow(clippy::non_send_fields_in_send_ty)]
+// SAFETY: Registration with and unregistration from the drm subsystem can happen from any thread.
+// Additionally, `T::Data` (which is dropped during unregistration) is `Send`, so it is ok to move
+// `Registration` to different threads.
+unsafe impl<T: Driver> Send for Registration<T> {}
+
+impl<T: Driver> Drop for Registration<T> {
+    /// Removes the registration from the kernel if it has completed successfully before.
+    fn drop(&mut self) {
+        if self.registered {
+            // Get a pointer to the data stored in device before destroying it.
+            // SAFETY: `drm` is valid per the type invariant
+            let data_pointer = unsafe { self.drm.raw_mut().dev_private };
+
+            // SAFETY: Since `registered` is true, `self.drm` is both valid and registered.
+            unsafe { bindings::drm_dev_unregister(self.drm.raw_mut()) };
+
+            // Free data as well.
+            // SAFETY: `data_pointer` was returned by `into_foreign` during registration.
+            unsafe { <T::Data as ForeignOwnable>::from_foreign(data_pointer) };
+        }
+    }
+}
diff --git a/rust/kernel/drm/ioctl.rs b/rust/kernel/drm/ioctl.rs
index 6df59b34567af8..39846cd35da2b8 100644
--- a/rust/kernel/drm/ioctl.rs
+++ b/rust/kernel/drm/ioctl.rs
@@ -121,22 +121,20 @@ macro_rules! declare_drm_ioctls {
                                 raw_data: *mut ::core::ffi::c_void,
                                 raw_file_priv: *mut $crate::drm::ioctl::internal::drm_file,
                         ) -> core::ffi::c_int {
-                            // SAFETY: We never drop this, and the DRM core ensures the device lives
-                            // while callbacks are being called.
+                            // SAFETY: The DRM core ensures the device lives while callbacks are
+                            // being called.
                             //
                             // FIXME: Currently there is nothing enforcing that the types of the
                             // dev/file match the current driver these ioctls are being declared
                             // for, and it's not clear how to enforce this within the type system.
-                            let dev = ::core::mem::ManuallyDrop::new(unsafe {
-                                $crate::drm::device::Device::from_raw(raw_dev)
-                            });
+                            let dev = $crate::drm::device::Device::borrow(raw_dev);
                             // SAFETY: This is just the ioctl argument, which hopefully has the right type
                             // (we've done our best checking the size).
                             let data = unsafe { &mut *(raw_data as *mut $crate::uapi::$struct) };
                             // SAFETY: This is just the DRM file structure
                             let file = unsafe { $crate::drm::file::File::from_raw(raw_file_priv) };
 
-                            match $func(&*dev, data, &file) {
+                            match $func(dev, data, &file) {
                                 Err(e) => e.to_errno(),
                                 Ok(i) => i.try_into().unwrap_or(code::ERANGE.to_errno()),
                             }
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 9ec6d7cbcaf331..69376b3c6db99f 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -2,4 +2,6 @@
 
 //! DRM subsystem abstractions.
 
+pub mod device;
+pub mod drv;
 pub mod ioctl;

From 96e5043f6b7f6da1cbaefc40c09ab9c315a90f7e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 20:58:29 +0900
Subject: [PATCH 0878/1027] rust: drm: file: Add File abstraction

A DRM File is the DRM counterpart to a kernel file structure,
representing an open DRM file descriptor. Add a Rust abstraction to
allow drivers to implement their own File types that implement the
DriverFile trait.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/kernel/drm/drv.rs          |   7 +-
 rust/kernel/drm/file.rs         | 117 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   1 +
 4 files changed, 124 insertions(+), 2 deletions(-)
 create mode 100644 rust/kernel/drm/file.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a0428512223659..3b4e5eedeabc9d 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -8,6 +8,7 @@
 
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_file.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs
index d67c953a3accd4..747af55c15f11a 100644
--- a/rust/kernel/drm/drv.rs
+++ b/rust/kernel/drm/drv.rs
@@ -133,6 +133,9 @@ pub trait Driver {
     /// Should be either `drm::gem::Object<T>` or `drm::gem::shmem::Object<T>`.
     type Object: AllocImpl;
 
+    /// The type used to represent a DRM File (client)
+    type File: drm::file::DriverFile;
+
     /// Driver metadata
     const INFO: DriverInfo;
 
@@ -202,8 +205,8 @@ macro_rules! drm_device_register {
 impl<T: Driver> Registration<T> {
     const VTABLE: bindings::drm_driver = drm_legacy_fields! {
         load: None,
-        open: None, // TODO: File abstraction
-        postclose: None, // TODO: File abstraction
+        open: Some(drm::file::open_callback::<T::File>),
+        postclose: Some(drm::file::postclose_callback::<T::File>),
         lastclose: None,
         unload: None,
         release: None,
diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs
new file mode 100644
index 00000000000000..60bcbe3fa9fadf
--- /dev/null
+++ b/rust/kernel/drm/file.rs
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM File objects.
+//!
+//! C header: [`include/drm/drm_file.h`](../../../../include/drm/drm_file.h)
+
+use crate::{bindings, drm, error::Result};
+use alloc::boxed::Box;
+use core::marker::PhantomData;
+use core::pin::Pin;
+
+/// Trait that must be implemented by DRM drivers to represent a DRM File (a client instance).
+pub trait DriverFile {
+    /// The parent `Driver` implementation for this `DriverFile`.
+    type Driver: drm::drv::Driver;
+
+    /// Open a new file (called when a client opens the DRM device).
+    fn open(device: &drm::device::Device<Self::Driver>) -> Result<Pin<Box<Self>>>;
+}
+
+/// An open DRM File.
+///
+/// # Invariants
+/// `raw` is a valid pointer to a `drm_file` struct.
+#[repr(transparent)]
+pub struct File<T: DriverFile> {
+    raw: *mut bindings::drm_file,
+    _p: PhantomData<T>,
+}
+
+pub(super) unsafe extern "C" fn open_callback<T: DriverFile>(
+    raw_dev: *mut bindings::drm_device,
+    raw_file: *mut bindings::drm_file,
+) -> core::ffi::c_int {
+    // SAFETY: The raw_drm arg is always a valid borrowed reference
+    let drm = core::mem::ManuallyDrop::new(unsafe { drm::device::Device::from_raw(raw_dev) });
+    // SAFETY: This reference won't escape this function
+    let file = unsafe { &mut *raw_file };
+
+    let inner = match T::open(&drm) {
+        Err(e) => {
+            return e.to_errno();
+        }
+        Ok(i) => i,
+    };
+
+    // SAFETY: This pointer is treated as pinned, and the Drop guarantee is upheld below.
+    file.driver_priv = Box::into_raw(unsafe { Pin::into_inner_unchecked(inner) }) as *mut _;
+
+    0
+}
+
+pub(super) unsafe extern "C" fn postclose_callback<T: DriverFile>(
+    _dev: *mut bindings::drm_device,
+    raw_file: *mut bindings::drm_file,
+) {
+    // SAFETY: This reference won't escape this function
+    let file = unsafe { &*raw_file };
+
+    // Drop the DriverFile
+    // SAFETY: file.driver_priv is always a Box<T> pointer
+    unsafe { drop(Box::from_raw(file.driver_priv as *mut T)) };
+}
+
+impl<T: DriverFile> File<T> {
+    // Not intended to be called externally, except via declare_drm_ioctls!()
+    #[doc(hidden)]
+    pub unsafe fn from_raw(raw_file: *mut bindings::drm_file) -> File<T> {
+        File {
+            raw: raw_file,
+            _p: PhantomData,
+        }
+    }
+
+    #[allow(dead_code)]
+    /// Return the raw pointer to the underlying `drm_file`.
+    pub(super) fn raw(&self) -> *const bindings::drm_file {
+        self.raw
+    }
+
+    /// Return an immutable reference to the raw `drm_file` structure.
+    pub(super) fn file(&self) -> &bindings::drm_file {
+        // SAFETY: The raw pointer is always valid per usage in declare_drm_ioctls!()
+        unsafe { &*self.raw }
+    }
+
+    /// Return a pinned reference to the driver file structure.
+    pub fn inner(&self) -> Pin<&T> {
+        // SAFETY: The driver_priv pointer is always a pinned reference to the driver
+        // file structure.
+        unsafe { Pin::new_unchecked(&*(self.file().driver_priv as *const T)) }
+    }
+}
+
+impl<T: DriverFile> crate::private::Sealed for File<T> {}
+
+/// Generic trait to allow users that don't care about driver specifics to accept any File<T>.
+///
+/// # Safety
+/// Must only be implemented for File<T> and return the pointer, following the normal invariants
+/// of that type.
+pub unsafe trait GenericFile: crate::private::Sealed {
+    /// Returns the raw const pointer to the `struct drm_file`
+    fn raw(&self) -> *const bindings::drm_file;
+    /// Returns the raw mut pointer to the `struct drm_file`
+    fn raw_mut(&mut self) -> *mut bindings::drm_file;
+}
+
+// SAFETY: Follows the invariants of the File<T>.
+unsafe impl<T: DriverFile> GenericFile for File<T> {
+    fn raw(&self) -> *const bindings::drm_file {
+        self.raw
+    }
+    fn raw_mut(&mut self) -> *mut bindings::drm_file {
+        self.raw
+    }
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 69376b3c6db99f..a767942d0b5220 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -4,4 +4,5 @@
 
 pub mod device;
 pub mod drv;
+pub mod file;
 pub mod ioctl;

From ec9896ae2f251200396bee432d876786379b405e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 19 May 2023 22:21:10 +0900
Subject: [PATCH 0879/1027] rust: drm: device: Convert Device to
 AlwaysRefCounted

Switch from being a refcount wrapper itself to a transparent wrapper
around `bindings::drm_device`. The refcounted type then becomes
ARef<Device<T>>.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/file.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/drm/file.rs b/rust/kernel/drm/file.rs
index 60bcbe3fa9fadf..a20ab4b8769b3a 100644
--- a/rust/kernel/drm/file.rs
+++ b/rust/kernel/drm/file.rs
@@ -33,11 +33,11 @@ pub(super) unsafe extern "C" fn open_callback<T: DriverFile>(
     raw_file: *mut bindings::drm_file,
 ) -> core::ffi::c_int {
     // SAFETY: The raw_drm arg is always a valid borrowed reference
-    let drm = core::mem::ManuallyDrop::new(unsafe { drm::device::Device::from_raw(raw_dev) });
+    let drm = unsafe { drm::device::Device::borrow(raw_dev) };
     // SAFETY: This reference won't escape this function
     let file = unsafe { &mut *raw_file };
 
-    let inner = match T::open(&drm) {
+    let inner = match T::open(drm) {
         Err(e) => {
             return e.to_errno();
         }

From 17b3e35baebc8dad02fc864729b5ed2b9f97be91 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 21:12:31 +0900
Subject: [PATCH 0880/1027] rust: drm: gem: Add GEM object abstraction

The DRM GEM subsystem is the DRM memory management subsystem used by
most modern drivers. Add a Rust abstraction to allow Rust DRM driver
implementations to use it.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/drm_gem.c          |  26 +++
 rust/helpers/helpers.c          |   1 +
 rust/kernel/drm/drv.rs          |   4 +-
 rust/kernel/drm/gem/mod.rs      | 385 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   1 +
 6 files changed, 416 insertions(+), 2 deletions(-)
 create mode 100644 rust/helpers/drm_gem.c
 create mode 100644 rust/kernel/drm/gem/mod.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 3b4e5eedeabc9d..7b2814a3e7a3e6 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -9,6 +9,7 @@
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
+#include <drm/drm_gem.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
diff --git a/rust/helpers/drm_gem.c b/rust/helpers/drm_gem.c
new file mode 100644
index 00000000000000..99d3230f0addcd
--- /dev/null
+++ b/rust/helpers/drm_gem.c
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/export.h>
+#include <linux/drm_gem.h>
+
+#ifdef CONFIG_DRM
+
+void rust_helper_drm_gem_object_get(struct drm_gem_object *obj)
+{
+	drm_gem_object_get(obj);
+}
+EXPORT_SYMBOL_GPL(rust_helper_drm_gem_object_get);
+
+void rust_helper_drm_gem_object_put(struct drm_gem_object *obj)
+{
+	drm_gem_object_put(obj);
+}
+EXPORT_SYMBOL_GPL(rust_helper_drm_gem_object_put);
+
+__u64 rust_helper_drm_vma_node_offset_addr(struct drm_vma_offset_node *node)
+{
+	return drm_vma_node_offset_addr(node);
+}
+EXPORT_SYMBOL_GPL(rust_helper_drm_vma_node_offset_addr);
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index ee9a63168fd3e3..ce3ddc0f7c5b2a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -14,6 +14,7 @@
 #include "device.c"
 #include "dma-fence.c"
 #include "dma-resv.c"
+#include "drm_gem.c"
 #include "err.c"
 #include "iomem.c"
 #include "ioport.c"
diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs
index 747af55c15f11a..a7a8393ec604e8 100644
--- a/rust/kernel/drm/drv.rs
+++ b/rust/kernel/drm/drv.rs
@@ -115,7 +115,7 @@ pub struct AllocOps {
 }
 
 /// Trait for memory manager implementations. Implemented internally.
-pub trait AllocImpl: Sealed {
+pub trait AllocImpl: Sealed + drm::gem::IntoGEMObject {
     /// The C callback operations for this memory manager.
     const ALLOC_OPS: AllocOps;
 }
@@ -252,7 +252,7 @@ impl<T: Driver> Registration<T> {
             drm,
             registered: false,
             vtable,
-            fops: Default::default(), // TODO: GEM abstraction
+            fops: drm::gem::create_fops(),
             _pin: PhantomPinned,
             _p: PhantomData,
         })
diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
new file mode 100644
index 00000000000000..f9110c69d18349
--- /dev/null
+++ b/rust/kernel/drm/gem/mod.rs
@@ -0,0 +1,385 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM GEM API
+//!
+//! C header: [`include/linux/drm/drm_gem.h`](../../../../include/linux/drm/drm_gem.h)
+
+use alloc::boxed::Box;
+
+use crate::{
+    alloc::flags::*,
+    bindings,
+    drm::{device, drv, file},
+    error::{to_result, Result},
+    prelude::*,
+};
+use core::{mem, mem::ManuallyDrop, ops::Deref, ops::DerefMut};
+
+/// GEM object functions, which must be implemented by drivers.
+pub trait BaseDriverObject<T: BaseObject>: Sync + Send + Sized {
+    /// Create a new driver data object for a GEM object of a given size.
+    fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<Self>;
+
+    /// Open a new handle to an existing object, associated with a File.
+    fn open(
+        _obj: &<<T as IntoGEMObject>::Driver as drv::Driver>::Object,
+        _file: &file::File<<<T as IntoGEMObject>::Driver as drv::Driver>::File>,
+    ) -> Result {
+        Ok(())
+    }
+
+    /// Close a handle to an existing object, associated with a File.
+    fn close(
+        _obj: &<<T as IntoGEMObject>::Driver as drv::Driver>::Object,
+        _file: &file::File<<<T as IntoGEMObject>::Driver as drv::Driver>::File>,
+    ) {
+    }
+}
+
+/// Trait that represents a GEM object subtype
+pub trait IntoGEMObject: Sized + crate::private::Sealed {
+    /// Owning driver for this type
+    type Driver: drv::Driver;
+
+    /// Returns a reference to the raw `drm_gem_object` structure, which must be valid as long as
+    /// this owning object is valid.
+    fn gem_obj(&self) -> &bindings::drm_gem_object;
+
+    /// Converts a pointer to a `drm_gem_object` into a pointer to this type.
+    ///
+    /// # Safety
+    ///
+    /// The argument must an object owned by this Driver.
+    unsafe fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Self;
+}
+
+/// Trait which must be implemented by drivers using base GEM objects.
+pub trait DriverObject: BaseDriverObject<Object<Self>> {
+    /// Parent `Driver` for this object.
+    type Driver: drv::Driver;
+}
+
+unsafe extern "C" fn free_callback<T: DriverObject>(obj: *mut bindings::drm_gem_object) {
+    // SAFETY: All of our objects are Object<T>.
+    let this = unsafe { crate::container_of!(obj, Object<T>, obj) as *mut Object<T> };
+
+    // SAFETY: The pointer we got has to be valid
+    unsafe { bindings::drm_gem_object_release(obj) };
+
+    // SAFETY: All of our objects are allocated via Box<>, and we're in the
+    // free callback which guarantees this object has zero remaining references,
+    // so we can drop it
+    unsafe { drop(Box::from_raw(this)) };
+}
+
+unsafe extern "C" fn open_callback<T: BaseDriverObject<U>, U: BaseObject>(
+    raw_obj: *mut bindings::drm_gem_object,
+    raw_file: *mut bindings::drm_file,
+) -> core::ffi::c_int {
+    // SAFETY: The file pointer is valid when called from the C side.
+    let file = unsafe {
+        file::File::<<<U as IntoGEMObject>::Driver as drv::Driver>::File>::from_raw(raw_file)
+    };
+    // SAFETY: The object pointer is valid and owned by us when called from the C side.
+    let obj = unsafe {
+        <<<U as IntoGEMObject>::Driver as drv::Driver>::Object as IntoGEMObject>::from_gem_obj(
+            raw_obj,
+        )
+    };
+
+    // SAFETY: from_gem_obj() returns a valid pointer as long as the type is
+    // correct and the raw_obj we got is valid.
+    match T::open(unsafe { &*obj }, &file) {
+        Err(e) => e.to_errno(),
+        Ok(()) => 0,
+    }
+}
+
+unsafe extern "C" fn close_callback<T: BaseDriverObject<U>, U: BaseObject>(
+    raw_obj: *mut bindings::drm_gem_object,
+    raw_file: *mut bindings::drm_file,
+) {
+    // SAFETY: The pointer we got has to be valid.
+    let file = unsafe {
+        file::File::<<<U as IntoGEMObject>::Driver as drv::Driver>::File>::from_raw(raw_file)
+    };
+    // SAFETY: The object pointer is valid and owned by us when called from the C side.
+    let obj = unsafe {
+        <<<U as IntoGEMObject>::Driver as drv::Driver>::Object as IntoGEMObject>::from_gem_obj(
+            raw_obj,
+        )
+    };
+
+    // SAFETY: from_gem_obj() returns a valid pointer as long as the type is
+    // correct and the raw_obj we got is valid.
+    T::close(unsafe { &*obj }, &file);
+}
+
+impl<T: DriverObject> IntoGEMObject for Object<T> {
+    type Driver = T::Driver;
+
+    fn gem_obj(&self) -> &bindings::drm_gem_object {
+        &self.obj
+    }
+
+    unsafe fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Object<T> {
+        // SAFETY: Safe as long as the safety invariants of this trait method hold.
+        unsafe { crate::container_of!(obj, Object<T>, obj) as *mut Object<T> }
+    }
+}
+
+/// Base operations shared by all GEM object classes
+pub trait BaseObject: IntoGEMObject {
+    /// Returns the size of the object in bytes.
+    fn size(&self) -> usize {
+        self.gem_obj().size
+    }
+
+    /// Creates a new reference to the object.
+    fn reference(&self) -> ObjectRef<Self> {
+        // SAFETY: Having a reference to an Object implies holding a GEM reference
+        unsafe {
+            bindings::drm_gem_object_get(self.gem_obj() as *const _ as *mut _);
+        }
+        ObjectRef {
+            ptr: self as *const _,
+        }
+    }
+
+    /// Creates a new handle for the object associated with a given `File`
+    /// (or returns an existing one).
+    fn create_handle(
+        &self,
+        file: &file::File<<<Self as IntoGEMObject>::Driver as drv::Driver>::File>,
+    ) -> Result<u32> {
+        let mut handle: u32 = 0;
+        // SAFETY: The arguments are all valid per the type invariants.
+        to_result(unsafe {
+            bindings::drm_gem_handle_create(
+                file.raw() as *mut _,
+                self.gem_obj() as *const _ as *mut _,
+                &mut handle,
+            )
+        })?;
+        Ok(handle)
+    }
+
+    /// Looks up an object by its handle for a given `File`.
+    fn lookup_handle(
+        file: &file::File<<<Self as IntoGEMObject>::Driver as drv::Driver>::File>,
+        handle: u32,
+    ) -> Result<ObjectRef<Self>> {
+        // SAFETY: The arguments are all valid per the type invariants.
+        let ptr = unsafe { bindings::drm_gem_object_lookup(file.raw() as *mut _, handle) };
+
+        if ptr.is_null() {
+            Err(ENOENT)
+        } else {
+            Ok(ObjectRef {
+                ptr: ptr as *const _,
+            })
+        }
+    }
+
+    /// Creates an mmap offset to map the object from userspace.
+    fn create_mmap_offset(&self) -> Result<u64> {
+        // SAFETY: The arguments are valid per the type invariant.
+        to_result(unsafe {
+            // TODO: is this threadsafe?
+            bindings::drm_gem_create_mmap_offset(self.gem_obj() as *const _ as *mut _)
+        })?;
+        // SAFETY: Safe to call on vma_node (which is guaranteed to be valid after the above)
+        Ok(unsafe {
+            bindings::drm_vma_node_offset_addr(&self.gem_obj().vma_node as *const _ as *mut _)
+        })
+    }
+}
+
+impl<T: IntoGEMObject> BaseObject for T {}
+
+/// A base GEM object.
+#[repr(C)]
+pub struct Object<T: DriverObject> {
+    obj: bindings::drm_gem_object,
+    // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
+    // manage the reference count here.
+    dev: ManuallyDrop<device::Device<T::Driver>>,
+    inner: T,
+}
+
+impl<T: DriverObject> Object<T> {
+    /// The size of this object's structure.
+    pub const SIZE: usize = mem::size_of::<Self>();
+
+    const OBJECT_FUNCS: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
+        free: Some(free_callback::<T>),
+        open: Some(open_callback::<T, Object<T>>),
+        close: Some(close_callback::<T, Object<T>>),
+        print_info: None,
+        export: None,
+        pin: None,
+        unpin: None,
+        get_sg_table: None,
+        vmap: None,
+        vunmap: None,
+        mmap: None,
+        status: None,
+        rss: None,
+        vm_ops: core::ptr::null_mut(),
+        evict: None,
+    };
+
+    /// Create a new GEM object.
+    pub fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<UniqueObjectRef<Self>> {
+        let mut obj: Box<Self> = Box::try_new(Self {
+            // SAFETY: This struct is expected to be zero-initialized
+            obj: unsafe { mem::zeroed() },
+            // SAFETY: The drm subsystem guarantees that the drm_device will live as long as
+            // the GEM object lives, so we can conjure a reference out of thin air.
+            dev: ManuallyDrop::new(unsafe { device::Device::from_raw(dev.ptr) }),
+            inner: T::new(dev, size)?,
+        })?;
+
+        obj.obj.funcs = &Self::OBJECT_FUNCS;
+        // SAFETY: Safe to call as long as the pointer is a properly allocated GEM object
+        to_result(unsafe {
+            bindings::drm_gem_object_init(dev.raw() as *mut _, &mut obj.obj, size)
+        })?;
+
+        let obj_ref = UniqueObjectRef {
+            ptr: Box::leak(obj),
+        };
+
+        Ok(obj_ref)
+    }
+
+    /// Returns the `Device` that owns this GEM object.
+    pub fn dev(&self) -> &device::Device<T::Driver> {
+        &self.dev
+    }
+}
+
+impl<T: DriverObject> crate::private::Sealed for Object<T> {}
+
+impl<T: DriverObject> Deref for Object<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T: DriverObject> DerefMut for Object<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
+impl<T: DriverObject> drv::AllocImpl for Object<T> {
+    const ALLOC_OPS: drv::AllocOps = drv::AllocOps {
+        gem_create_object: None,
+        prime_handle_to_fd: None,
+        prime_fd_to_handle: None,
+        gem_prime_import: None,
+        gem_prime_import_sg_table: None,
+        dumb_create: None,
+        dumb_map_offset: None,
+    };
+}
+
+/// A reference-counted shared reference to a base GEM object.
+pub struct ObjectRef<T: IntoGEMObject> {
+    // Invariant: the pointer is valid and initialized, and this ObjectRef owns a reference to it.
+    ptr: *const T,
+}
+
+/// SAFETY: GEM object references are safe to share between threads.
+unsafe impl<T: IntoGEMObject> Send for ObjectRef<T> {}
+/// SAFETY: GEM object references are safe to share between threads.
+unsafe impl<T: IntoGEMObject> Sync for ObjectRef<T> {}
+
+impl<T: IntoGEMObject> Clone for ObjectRef<T> {
+    fn clone(&self) -> Self {
+        self.reference()
+    }
+}
+
+impl<T: IntoGEMObject> Drop for ObjectRef<T> {
+    fn drop(&mut self) {
+        // SAFETY: Having an ObjectRef implies holding a GEM reference.
+        // The free callback will take care of deallocation.
+        unsafe {
+            bindings::drm_gem_object_put((*self.ptr).gem_obj() as *const _ as *mut _);
+        }
+    }
+}
+
+impl<T: IntoGEMObject> Deref for ObjectRef<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: The pointer is valid per the invariant
+        unsafe { &*self.ptr }
+    }
+}
+
+/// A unique reference to a base GEM object.
+pub struct UniqueObjectRef<T: IntoGEMObject> {
+    // Invariant: the pointer is valid and initialized, and this ObjectRef owns the only reference
+    // to it.
+    ptr: *mut T,
+}
+
+impl<T: IntoGEMObject> UniqueObjectRef<T> {
+    /// Downgrade this reference to a shared reference.
+    pub fn into_ref(self) -> ObjectRef<T> {
+        let ptr = self.ptr as *const _;
+        core::mem::forget(self);
+
+        ObjectRef { ptr }
+    }
+}
+
+impl<T: IntoGEMObject> Drop for UniqueObjectRef<T> {
+    fn drop(&mut self) {
+        // SAFETY: Having a UniqueObjectRef implies holding a GEM
+        // reference. The free callback will take care of deallocation.
+        unsafe {
+            bindings::drm_gem_object_put((*self.ptr).gem_obj() as *const _ as *mut _);
+        }
+    }
+}
+
+impl<T: IntoGEMObject> Deref for UniqueObjectRef<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        // SAFETY: The pointer is valid per the invariant
+        unsafe { &*self.ptr }
+    }
+}
+
+impl<T: IntoGEMObject> DerefMut for UniqueObjectRef<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        // SAFETY: The pointer is valid per the invariant
+        unsafe { &mut *self.ptr }
+    }
+}
+
+pub(super) fn create_fops() -> bindings::file_operations {
+    bindings::file_operations {
+        owner: core::ptr::null_mut(),
+        open: Some(bindings::drm_open),
+        release: Some(bindings::drm_release),
+        unlocked_ioctl: Some(bindings::drm_ioctl),
+        #[cfg(CONFIG_COMPAT)]
+        compat_ioctl: Some(bindings::drm_compat_ioctl),
+        #[cfg(not(CONFIG_COMPAT))]
+        compat_ioctl: None,
+        poll: Some(bindings::drm_poll),
+        read: Some(bindings::drm_read),
+        llseek: Some(bindings::noop_llseek),
+        mmap: Some(bindings::drm_gem_mmap),
+        ..Default::default()
+    }
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index a767942d0b5220..c44760a1332fa1 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -5,4 +5,5 @@
 pub mod device;
 pub mod drv;
 pub mod file;
+pub mod gem;
 pub mod ioctl;

From 13db11c952eb5950b22a00b2f92e20d01789d29c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 19 May 2023 22:21:10 +0900
Subject: [PATCH 0881/1027] rust: drm: device: Convert Device to
 AlwaysRefCounted

Switch from being a refcount wrapper itself to a transparent wrapper
around `bindings::drm_device`. The refcounted type then becomes
ARef<Device<T>>.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/gem/mod.rs | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index f9110c69d18349..b2fb774f203f83 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -13,7 +13,7 @@ use crate::{
     error::{to_result, Result},
     prelude::*,
 };
-use core::{mem, mem::ManuallyDrop, ops::Deref, ops::DerefMut};
+use core::{mem, ops::Deref, ops::DerefMut};
 
 /// GEM object functions, which must be implemented by drivers.
 pub trait BaseDriverObject<T: BaseObject>: Sync + Send + Sized {
@@ -203,7 +203,7 @@ pub struct Object<T: DriverObject> {
     obj: bindings::drm_gem_object,
     // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
     // manage the reference count here.
-    dev: ManuallyDrop<device::Device<T::Driver>>,
+    dev: *const bindings::drm_device,
     inner: T,
 }
 
@@ -236,7 +236,7 @@ impl<T: DriverObject> Object<T> {
             obj: unsafe { mem::zeroed() },
             // SAFETY: The drm subsystem guarantees that the drm_device will live as long as
             // the GEM object lives, so we can conjure a reference out of thin air.
-            dev: ManuallyDrop::new(unsafe { device::Device::from_raw(dev.ptr) }),
+            dev: dev.drm.get(),
             inner: T::new(dev, size)?,
         })?;
 
@@ -255,7 +255,9 @@ impl<T: DriverObject> Object<T> {
 
     /// Returns the `Device` that owns this GEM object.
     pub fn dev(&self) -> &device::Device<T::Driver> {
-        &self.dev
+        // SAFETY: The drm subsystem guarantees that the drm_device will live as long as
+        // the GEM object lives, so we can just borrow from the raw pointer.
+        unsafe { device::Device::borrow(self.dev) }
     }
 }
 

From f61a12a7025990680730cd256052dc06ffa87f5c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 28 Apr 2023 20:14:32 +0900
Subject: [PATCH 0882/1027] rust: drm: gem: Allow pinning GEM object driver
 data

This requires type_alias_impl_trait.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/gem/mod.rs | 49 ++++++++++++++++++++++++++++----------
 1 file changed, 36 insertions(+), 13 deletions(-)

diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index b2fb774f203f83..4cfa3d3fb3bb3e 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -13,12 +13,16 @@ use crate::{
     error::{to_result, Result},
     prelude::*,
 };
-use core::{mem, ops::Deref, ops::DerefMut};
+use core::{marker::PhantomPinned, mem, ops::Deref, ops::DerefMut};
 
 /// GEM object functions, which must be implemented by drivers.
 pub trait BaseDriverObject<T: BaseObject>: Sync + Send + Sized {
+    /// The return type of the new() function. Should be `impl PinInit<Self, Error>`.
+    /// TODO: Remove this when return_position_impl_trait_in_trait is stable.
+    type Initializer: PinInit<Self, Error>;
+
     /// Create a new driver data object for a GEM object of a given size.
-    fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<Self>;
+    fn new(dev: &device::Device<T::Driver>, size: usize) -> Self::Initializer;
 
     /// Open a new handle to an existing object, associated with a File.
     fn open(
@@ -199,14 +203,21 @@ impl<T: IntoGEMObject> BaseObject for T {}
 
 /// A base GEM object.
 #[repr(C)]
+#[pin_data]
 pub struct Object<T: DriverObject> {
     obj: bindings::drm_gem_object,
     // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
     // manage the reference count here.
     dev: *const bindings::drm_device,
+    #[pin]
     inner: T,
+    #[pin]
+    _p: PhantomPinned,
 }
 
+// SAFETY: This struct is safe to zero-initialize
+unsafe impl init::Zeroable for bindings::drm_gem_object {}
+
 impl<T: DriverObject> Object<T> {
     /// The size of this object's structure.
     pub const SIZE: usize = mem::size_of::<Self>();
@@ -230,24 +241,35 @@ impl<T: DriverObject> Object<T> {
     };
 
     /// Create a new GEM object.
-    pub fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<UniqueObjectRef<Self>> {
-        let mut obj: Box<Self> = Box::try_new(Self {
-            // SAFETY: This struct is expected to be zero-initialized
-            obj: unsafe { mem::zeroed() },
+    pub fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<Pin<UniqueObjectRef<Self>>> {
+        let obj: Pin<Box<Self>> = Box::try_pin_init(
+            try_pin_init!(Self {
+                // SAFETY: This struct is expected to be zero-initialized
+                obj: bindings::drm_gem_object {
+                    funcs: &Self::OBJECT_FUNCS,
+                    ..Default::default()
+                },
+                inner <- T::new(dev, size),
             // SAFETY: The drm subsystem guarantees that the drm_device will live as long as
             // the GEM object lives, so we can conjure a reference out of thin air.
-            dev: dev.drm.get(),
-            inner: T::new(dev, size)?,
-        })?;
+                dev: dev.drm.get(),
+                _p: PhantomPinned
+            }),
+            GFP_KERNEL,
+        )?;
 
-        obj.obj.funcs = &Self::OBJECT_FUNCS;
         // SAFETY: Safe to call as long as the pointer is a properly allocated GEM object
         to_result(unsafe {
-            bindings::drm_gem_object_init(dev.raw() as *mut _, &mut obj.obj, size)
+            bindings::drm_gem_object_init(dev.raw() as *mut _, &obj.obj as *const _ as *mut _, size)
         })?;
 
-        let obj_ref = UniqueObjectRef {
-            ptr: Box::leak(obj),
+        // SAFETY: We never move out of self
+        let obj_ref = unsafe {
+            Pin::new_unchecked(UniqueObjectRef {
+                // SAFETY: We never move out of the Box
+                ptr: Box::leak(Pin::into_inner_unchecked(obj)),
+                _p: PhantomPinned,
+            })
         };
 
         Ok(obj_ref)
@@ -330,6 +352,7 @@ pub struct UniqueObjectRef<T: IntoGEMObject> {
     // Invariant: the pointer is valid and initialized, and this ObjectRef owns the only reference
     // to it.
     ptr: *mut T,
+    _p: PhantomPinned,
 }
 
 impl<T: IntoGEMObject> UniqueObjectRef<T> {

From fbe9c0a945646a1e73bb3f660cbaab2ccf464246 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 7 Sep 2022 17:34:35 +0900
Subject: [PATCH 0883/1027] drm/gem-shmem: Export VM ops functions

There doesn't seem to be a way for the Rust bindings to get a
compile-time constant reference to drm_gem_shmem_vm_ops, so we need to
duplicate that structure in Rust... this isn't nice...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/drm_gem_shmem_helper.c | 9 ++++++---
 include/drm/drm_gem_shmem_helper.h     | 3 +++
 2 files changed, 9 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index 53c003983ad183..34bf55b4559ccf 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -509,7 +509,7 @@ int drm_gem_shmem_dumb_create(struct drm_file *file, struct drm_device *dev,
 }
 EXPORT_SYMBOL_GPL(drm_gem_shmem_dumb_create);
 
-static vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf)
+vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf)
 {
 	struct vm_area_struct *vma = vmf->vma;
 	struct drm_gem_object *obj = vma->vm_private_data;
@@ -538,8 +538,9 @@ static vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf)
 
 	return ret;
 }
+EXPORT_SYMBOL_GPL(drm_gem_shmem_fault);
 
-static void drm_gem_shmem_vm_open(struct vm_area_struct *vma)
+void drm_gem_shmem_vm_open(struct vm_area_struct *vma)
 {
 	struct drm_gem_object *obj = vma->vm_private_data;
 	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
@@ -560,8 +561,9 @@ static void drm_gem_shmem_vm_open(struct vm_area_struct *vma)
 
 	drm_gem_vm_open(vma);
 }
+EXPORT_SYMBOL_GPL(drm_gem_shmem_vm_open);
 
-static void drm_gem_shmem_vm_close(struct vm_area_struct *vma)
+void drm_gem_shmem_vm_close(struct vm_area_struct *vma)
 {
 	struct drm_gem_object *obj = vma->vm_private_data;
 	struct drm_gem_shmem_object *shmem = to_drm_gem_shmem_obj(obj);
@@ -572,6 +574,7 @@ static void drm_gem_shmem_vm_close(struct vm_area_struct *vma)
 
 	drm_gem_vm_close(vma);
 }
+EXPORT_SYMBOL_GPL(drm_gem_shmem_vm_close);
 
 const struct vm_operations_struct drm_gem_shmem_vm_ops = {
 	.fault = drm_gem_shmem_fault,
diff --git a/include/drm/drm_gem_shmem_helper.h b/include/drm/drm_gem_shmem_helper.h
index efbc9f27312b53..4d319bd2f02670 100644
--- a/include/drm/drm_gem_shmem_helper.h
+++ b/include/drm/drm_gem_shmem_helper.h
@@ -129,6 +129,9 @@ void drm_gem_shmem_print_info(const struct drm_gem_shmem_object *shmem,
 			      struct drm_printer *p, unsigned int indent);
 
 extern const struct vm_operations_struct drm_gem_shmem_vm_ops;
+vm_fault_t drm_gem_shmem_fault(struct vm_fault *vmf);
+void drm_gem_shmem_vm_open(struct vm_area_struct *vma);
+void drm_gem_shmem_vm_close(struct vm_area_struct *vma);
 
 /*
  * GEM object functions

From 09420c592d2e4912639c0208082d5e01484d4845 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 5 Feb 2023 21:18:25 +0900
Subject: [PATCH 0884/1027] rust: drm: gem: shmem: Add DRM shmem helper
 abstraction

The DRM shmem helper includes common code useful for drivers which
allocate GEM objects as anonymous shmem. Add a Rust abstraction for
this. Drivers can choose the raw GEM implementation or the shmem layer,
depending on their needs.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   2 +
 rust/helpers/drm_gem.c          |  53 +++-
 rust/helpers/helpers.c          |   1 +
 rust/helpers/scatterlist.c      |  13 +
 rust/kernel/drm/gem/mod.rs      |   9 +-
 rust/kernel/drm/gem/shmem.rs    | 412 ++++++++++++++++++++++++++++++++
 6 files changed, 480 insertions(+), 10 deletions(-)
 create mode 100644 rust/helpers/scatterlist.c
 create mode 100644 rust/kernel/drm/gem/shmem.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 7b2814a3e7a3e6..a49f4a22650852 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -10,6 +10,7 @@
 #include <drm/drm_drv.h>
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
+#include <drm/drm_gem_shmem_helper.h>
 #include <drm/drm_ioctl.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
@@ -29,6 +30,7 @@
 #include <linux/phy.h>
 #include <linux/fs.h>
 #include <linux/io-pgtable.h>
+#include <linux/iosys-map.h>
 #include <linux/ktime.h>
 #include <linux/lockdep.h>
 #include <linux/of.h>
diff --git a/rust/helpers/drm_gem.c b/rust/helpers/drm_gem.c
index 99d3230f0addcd..ecf4be1e0c264a 100644
--- a/rust/helpers/drm_gem.c
+++ b/rust/helpers/drm_gem.c
@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0
 
-#include <linux/export.h>
-#include <linux/drm_gem.h>
+#include <drm/drm_gem.h>
+#include <drm/drm_gem_shmem_helper.h>
 
 #ifdef CONFIG_DRM
 
@@ -9,18 +9,61 @@ void rust_helper_drm_gem_object_get(struct drm_gem_object *obj)
 {
 	drm_gem_object_get(obj);
 }
-EXPORT_SYMBOL_GPL(rust_helper_drm_gem_object_get);
 
 void rust_helper_drm_gem_object_put(struct drm_gem_object *obj)
 {
 	drm_gem_object_put(obj);
 }
-EXPORT_SYMBOL_GPL(rust_helper_drm_gem_object_put);
 
 __u64 rust_helper_drm_vma_node_offset_addr(struct drm_vma_offset_node *node)
 {
 	return drm_vma_node_offset_addr(node);
 }
-EXPORT_SYMBOL_GPL(rust_helper_drm_vma_node_offset_addr);
 
+#ifdef CONFIG_DRM_GEM_SHMEM_HELPER
+
+void rust_helper_drm_gem_shmem_object_free(struct drm_gem_object *obj)
+{
+	return drm_gem_shmem_object_free(obj);
+}
+
+void rust_helper_drm_gem_shmem_object_print_info(struct drm_printer *p, unsigned int indent,
+                                                  const struct drm_gem_object *obj)
+{
+	drm_gem_shmem_object_print_info(p, indent, obj);
+}
+
+int rust_helper_drm_gem_shmem_object_pin(struct drm_gem_object *obj)
+{
+	return drm_gem_shmem_object_pin(obj);
+}
+
+void rust_helper_drm_gem_shmem_object_unpin(struct drm_gem_object *obj)
+{
+	drm_gem_shmem_object_unpin(obj);
+}
+
+struct sg_table *rust_helper_drm_gem_shmem_object_get_sg_table(struct drm_gem_object *obj)
+{
+	return drm_gem_shmem_object_get_sg_table(obj);
+}
+
+int rust_helper_drm_gem_shmem_object_vmap(struct drm_gem_object *obj,
+                                           struct iosys_map *map)
+{
+	return drm_gem_shmem_object_vmap(obj, map);
+}
+
+void rust_helper_drm_gem_shmem_object_vunmap(struct drm_gem_object *obj,
+                                              struct iosys_map *map)
+{
+	drm_gem_shmem_object_vunmap(obj, map);
+}
+
+int rust_helper_drm_gem_shmem_object_mmap(struct drm_gem_object *obj, struct vm_area_struct *vma)
+{
+	return drm_gem_shmem_object_mmap(obj, vma);
+}
+
+#endif
 #endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index ce3ddc0f7c5b2a..2a4ec7533fd141 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -25,6 +25,7 @@
 #include "page.c"
 #include "platform_device.c"
 #include "refcount.c"
+#include "scatterlist.c"
 #include "signal.c"
 #include "siphash.c"
 #include "slab.c"
diff --git a/rust/helpers/scatterlist.c b/rust/helpers/scatterlist.c
new file mode 100644
index 00000000000000..cc5553b76c25f0
--- /dev/null
+++ b/rust/helpers/scatterlist.c
@@ -0,0 +1,13 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/scatterlist.h>
+
+dma_addr_t rust_helper_sg_dma_address(const struct scatterlist *sg)
+{
+	return sg_dma_address(sg);
+}
+
+int rust_helper_sg_dma_len(const struct scatterlist *sg)
+{
+	return sg_dma_len(sg);
+}
diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index 4cfa3d3fb3bb3e..f8011c4393b0d8 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -4,6 +4,9 @@
 //!
 //! C header: [`include/linux/drm/drm_gem.h`](../../../../include/linux/drm/drm_gem.h)
 
+#[cfg(CONFIG_DRM_GEM_SHMEM_HELPER = "y")]
+pub mod shmem;
+
 use alloc::boxed::Box;
 
 use crate::{
@@ -206,8 +209,6 @@ impl<T: IntoGEMObject> BaseObject for T {}
 #[pin_data]
 pub struct Object<T: DriverObject> {
     obj: bindings::drm_gem_object,
-    // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
-    // manage the reference count here.
     dev: *const bindings::drm_device,
     #[pin]
     inner: T,
@@ -250,8 +251,6 @@ impl<T: DriverObject> Object<T> {
                     ..Default::default()
                 },
                 inner <- T::new(dev, size),
-            // SAFETY: The drm subsystem guarantees that the drm_device will live as long as
-            // the GEM object lives, so we can conjure a reference out of thin air.
                 dev: dev.drm.get(),
                 _p: PhantomPinned
             }),
@@ -260,7 +259,7 @@ impl<T: DriverObject> Object<T> {
 
         // SAFETY: Safe to call as long as the pointer is a properly allocated GEM object
         to_result(unsafe {
-            bindings::drm_gem_object_init(dev.raw() as *mut _, &obj.obj as *const _ as *mut _, size)
+            bindings::drm_gem_object_init(dev.raw_mut(), &obj.obj as *const _ as *mut _, size)
         })?;
 
         // SAFETY: We never move out of self
diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
new file mode 100644
index 00000000000000..e8d4ae75cc2f5c
--- /dev/null
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -0,0 +1,412 @@
+// SPDX-License-Identifier: GPL-2.0
+
+//! DRM GEM shmem helper objects
+//!
+//! C header: [`include/linux/drm/drm_gem_shmem_helper.h`](../../../../include/linux/drm/drm_gem_shmem_helper.h)
+
+use crate::drm::{device, drv, gem};
+use crate::{
+    error::{from_err_ptr, to_result},
+    prelude::*,
+};
+use core::{
+    marker::PhantomData,
+    mem,
+    mem::{ManuallyDrop, MaybeUninit},
+    ops::{Deref, DerefMut},
+    ptr::addr_of_mut,
+    slice,
+};
+
+use gem::BaseObject;
+
+/// Trait which must be implemented by drivers using shmem-backed GEM objects.
+pub trait DriverObject: gem::BaseDriverObject<Object<Self>> {
+    /// Parent `Driver` for this object.
+    type Driver: drv::Driver;
+}
+
+// FIXME: This is terrible and I don't know how to avoid it
+#[cfg(CONFIG_NUMA)]
+macro_rules! vm_numa_fields {
+    ( $($field:ident: $val:expr),* $(,)? ) => {
+        bindings::vm_operations_struct {
+            $( $field: $val ),*,
+            set_policy: None,
+            get_policy: None,
+        }
+    }
+}
+
+#[cfg(not(CONFIG_NUMA))]
+macro_rules! vm_numa_fields {
+    ( $($field:ident: $val:expr),* $(,)? ) => {
+        bindings::vm_operations_struct {
+            $( $field: $val ),*
+        }
+    }
+}
+
+const SHMEM_VM_OPS: bindings::vm_operations_struct = vm_numa_fields! {
+    open: Some(bindings::drm_gem_shmem_vm_open),
+    close: Some(bindings::drm_gem_shmem_vm_close),
+    may_split: None,
+    mremap: None,
+    mprotect: None,
+    fault: Some(bindings::drm_gem_shmem_fault),
+    huge_fault: None,
+    map_pages: None,
+    pagesize: None,
+    page_mkwrite: None,
+    pfn_mkwrite: None,
+    access: None,
+    name: None,
+    find_special_page: None,
+};
+
+/// A shmem-backed GEM object.
+#[repr(C)]
+pub struct Object<T: DriverObject> {
+    obj: bindings::drm_gem_shmem_object,
+    // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
+    // manage the reference count here.
+    dev: ManuallyDrop<device::Device<T::Driver>>,
+    inner: T,
+}
+
+unsafe extern "C" fn gem_create_object<T: DriverObject>(
+    raw_dev: *mut bindings::drm_device,
+    size: usize,
+) -> *mut bindings::drm_gem_object {
+    // SAFETY: GEM ensures the device lives as long as its objects live,
+    // so we can conjure up a reference from thin air and never drop it.
+    let dev = ManuallyDrop::new(unsafe { device::Device::from_raw(raw_dev) });
+
+    let inner = match T::new(&*dev, size) {
+        Ok(v) => v,
+        Err(e) => return e.to_ptr(),
+    };
+
+    // SAFETY: krealloc is always safe to call like this
+    let p = unsafe {
+        bindings::krealloc(
+            core::ptr::null(),
+            Object::<T>::SIZE,
+            bindings::GFP_KERNEL | bindings::__GFP_ZERO,
+        ) as *mut Object<T>
+    };
+
+    if p.is_null() {
+        return ENOMEM.to_ptr();
+    }
+
+    // SAFETY: p is valid as long as the alloc succeeded
+    unsafe {
+        addr_of_mut!((*p).dev).write(dev);
+        addr_of_mut!((*p).inner).write(inner);
+    }
+
+    // SAFETY: drm_gem_shmem_object is safe to zero-init, and
+    // the rest of Object has been initialized
+    let new: &mut Object<T> = unsafe { &mut *(p as *mut _) };
+
+    new.obj.base.funcs = &Object::<T>::VTABLE;
+    &mut new.obj.base
+}
+
+unsafe extern "C" fn free_callback<T: DriverObject>(obj: *mut bindings::drm_gem_object) {
+    // SAFETY: All of our objects are Object<T>.
+    let shmem = unsafe {
+        crate::container_of!(obj, bindings::drm_gem_shmem_object, base)
+            as *mut bindings::drm_gem_shmem_object
+    };
+    // SAFETY: All of our objects are Object<T>.
+    let p = unsafe { crate::container_of!(shmem, Object<T>, obj) as *mut Object<T> };
+
+    // SAFETY: p is never used after this
+    unsafe {
+        core::ptr::drop_in_place(&mut (*p).inner);
+    }
+
+    // SAFETY: This pointer has to be valid, since p is valid
+    unsafe {
+        bindings::drm_gem_shmem_free(&mut (*p).obj);
+    }
+}
+
+impl<T: DriverObject> Object<T> {
+    /// The size of this object's structure.
+    const SIZE: usize = mem::size_of::<Self>();
+
+    /// `drm_gem_object_funcs` vtable suitable for GEM shmem objects.
+    const VTABLE: bindings::drm_gem_object_funcs = bindings::drm_gem_object_funcs {
+        free: Some(free_callback::<T>),
+        open: Some(super::open_callback::<T, Object<T>>),
+        close: Some(super::close_callback::<T, Object<T>>),
+        print_info: Some(bindings::drm_gem_shmem_object_print_info),
+        export: None,
+        pin: Some(bindings::drm_gem_shmem_object_pin),
+        unpin: Some(bindings::drm_gem_shmem_object_unpin),
+        get_sg_table: Some(bindings::drm_gem_shmem_object_get_sg_table),
+        vmap: Some(bindings::drm_gem_shmem_object_vmap),
+        vunmap: Some(bindings::drm_gem_shmem_object_vunmap),
+        mmap: Some(bindings::drm_gem_shmem_object_mmap),
+        status: None,
+        rss: None,
+        vm_ops: &SHMEM_VM_OPS,
+        evict: None,
+    };
+
+    // SAFETY: Must only be used with DRM functions that are thread-safe
+    unsafe fn mut_shmem(&self) -> *mut bindings::drm_gem_shmem_object {
+        &self.obj as *const _ as *mut _
+    }
+
+    /// Create a new shmem-backed DRM object of the given size.
+    pub fn new(dev: &device::Device<T::Driver>, size: usize) -> Result<gem::UniqueObjectRef<Self>> {
+        // SAFETY: This function can be called as long as the ALLOC_OPS are set properly
+        // for this driver, and the gem_create_object is called.
+        let p = unsafe {
+            let p = bindings::drm_gem_shmem_create(dev.raw() as *mut _, size);
+            crate::container_of!(p, Object<T>, obj) as *mut _
+        };
+
+        // SAFETY: The gem_create_object callback ensures this is a valid Object<T>,
+        // so we can take a unique reference to it.
+        let obj_ref = gem::UniqueObjectRef { ptr: p };
+
+        Ok(obj_ref)
+    }
+
+    /// Returns the `Device` that owns this GEM object.
+    pub fn dev(&self) -> &device::Device<T::Driver> {
+        &self.dev
+    }
+
+    /// Creates (if necessary) and returns a scatter-gather table of DMA pages for this object.
+    ///
+    /// This will pin the object in memory.
+    pub fn sg_table(&self) -> Result<SGTable<T>> {
+        // SAFETY: drm_gem_shmem_get_pages_sgt is thread-safe.
+        let sgt = from_err_ptr(unsafe { bindings::drm_gem_shmem_get_pages_sgt(self.mut_shmem()) })?;
+
+        Ok(SGTable {
+            sgt,
+            _owner: self.reference(),
+        })
+    }
+
+    /// Creates and returns a virtual kernel memory mapping for this object.
+    pub fn vmap(&self) -> Result<VMap<T>> {
+        let mut map: MaybeUninit<bindings::iosys_map> = MaybeUninit::uninit();
+
+        // SAFETY: drm_gem_shmem_vmap can be called with the DMA reservation lock held
+        to_result(unsafe {
+            let resv = self.obj.base.resv as *const _ as *mut _;
+            bindings::dma_resv_lock(resv, core::ptr::null_mut());
+            let ret = bindings::drm_gem_shmem_vmap(self.mut_shmem(), map.as_mut_ptr());
+            bindings::dma_resv_unlock(resv);
+            ret
+        })?;
+
+        // SAFETY: if drm_gem_shmem_vmap did not fail, map is initialized now
+        let map = unsafe { map.assume_init() };
+
+        Ok(VMap {
+            map,
+            owner: self.reference(),
+        })
+    }
+
+    /// Set the write-combine flag for this object.
+    ///
+    /// Should be called before any mappings are made.
+    pub fn set_wc(&mut self, map_wc: bool) {
+        // SAFETY: mut_shmem always returns a valid pointer
+        (unsafe { *self.mut_shmem() }).set_map_wc(map_wc);
+    }
+}
+
+impl<T: DriverObject> Deref for Object<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T: DriverObject> DerefMut for Object<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
+impl<T: DriverObject> crate::private::Sealed for Object<T> {}
+
+impl<T: DriverObject> gem::IntoGEMObject for Object<T> {
+    type Driver = T::Driver;
+
+    fn gem_obj(&self) -> &bindings::drm_gem_object {
+        &self.obj.base
+    }
+
+    // Safety: the passed GEM object must be owned by this driver (and be a shmem object).
+    unsafe fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Object<T> {
+        // SAFETY: The invariant guarantees this is correct.
+        unsafe {
+            let shmem = crate::container_of!(obj, bindings::drm_gem_shmem_object, base)
+                as *mut bindings::drm_gem_shmem_object;
+            crate::container_of!(shmem, Object<T>, obj) as *mut Object<T>
+        }
+    }
+}
+
+impl<T: DriverObject> drv::AllocImpl for Object<T> {
+    const ALLOC_OPS: drv::AllocOps = drv::AllocOps {
+        gem_create_object: Some(gem_create_object::<T>),
+        prime_handle_to_fd: None,
+        prime_fd_to_handle: None,
+        gem_prime_import: None,
+        gem_prime_import_sg_table: Some(bindings::drm_gem_shmem_prime_import_sg_table),
+        dumb_create: Some(bindings::drm_gem_shmem_dumb_create),
+        dumb_map_offset: None,
+    };
+}
+
+/// A virtual mapping for a shmem-backed GEM object in kernel address space.
+pub struct VMap<T: DriverObject> {
+    map: bindings::iosys_map,
+    owner: gem::ObjectRef<Object<T>>,
+}
+
+impl<T: DriverObject> VMap<T> {
+    /// Returns a const raw pointer to the start of the mapping.
+    pub fn as_ptr(&self) -> *const core::ffi::c_void {
+        // SAFETY: The shmem helpers always return non-iomem maps
+        unsafe { self.map.__bindgen_anon_1.vaddr }
+    }
+
+    /// Returns a mutable raw pointer to the start of the mapping.
+    pub fn as_mut_ptr(&mut self) -> *mut core::ffi::c_void {
+        // SAFETY: The shmem helpers always return non-iomem maps
+        unsafe { self.map.__bindgen_anon_1.vaddr }
+    }
+
+    /// Returns a byte slice view of the mapping.
+    pub fn as_slice(&self) -> &[u8] {
+        // SAFETY: The vmap maps valid memory up to the owner size
+        unsafe { slice::from_raw_parts(self.as_ptr() as *const u8, self.owner.size()) }
+    }
+
+    /// Returns mutable a byte slice view of the mapping.
+    pub fn as_mut_slice(&mut self) -> &mut [u8] {
+        // SAFETY: The vmap maps valid memory up to the owner size
+        unsafe { slice::from_raw_parts_mut(self.as_mut_ptr() as *mut u8, self.owner.size()) }
+    }
+
+    /// Borrows a reference to the object that owns this virtual mapping.
+    pub fn owner(&self) -> &gem::ObjectRef<Object<T>> {
+        &self.owner
+    }
+}
+
+impl<T: DriverObject> Drop for VMap<T> {
+    fn drop(&mut self) {
+        // SAFETY: This function is safe to call with the DMA reservation lock held
+        unsafe {
+            let resv = self.owner.obj.base.resv as *const _ as *mut _;
+            bindings::dma_resv_lock(resv, core::ptr::null_mut());
+            bindings::drm_gem_shmem_vunmap(self.owner.mut_shmem(), &mut self.map);
+            bindings::dma_resv_unlock(resv);
+        }
+    }
+}
+
+/// SAFETY: `iosys_map` objects are safe to send across threads.
+unsafe impl<T: DriverObject> Send for VMap<T> {}
+/// SAFETY: `iosys_map` objects are safe to send across threads.
+unsafe impl<T: DriverObject> Sync for VMap<T> {}
+
+/// A single scatter-gather entry, representing a span of pages in the device's DMA address space.
+///
+/// For devices not behind a standalone IOMMU, this corresponds to physical addresses.
+#[repr(transparent)]
+pub struct SGEntry(bindings::scatterlist);
+
+impl SGEntry {
+    /// Returns the starting DMA address of this span
+    pub fn dma_address(&self) -> usize {
+        // SAFETY: Always safe to call on scatterlist objects
+        (unsafe { bindings::sg_dma_address(&self.0) }) as usize
+    }
+
+    /// Returns the length of this span in bytes
+    pub fn dma_len(&self) -> usize {
+        // SAFETY: Always safe to call on scatterlist objects
+        (unsafe { bindings::sg_dma_len(&self.0) }) as usize
+    }
+}
+
+/// A scatter-gather table of DMA address spans for a GEM shmem object.
+///
+/// # Invariants
+/// `sgt` must be a valid pointer to the `sg_table`, which must correspond to the owned
+/// object in `_owner` (which ensures it remains valid).
+pub struct SGTable<T: DriverObject> {
+    sgt: *const bindings::sg_table,
+    _owner: gem::ObjectRef<Object<T>>,
+}
+
+impl<T: DriverObject> SGTable<T> {
+    /// Returns an iterator through the SGTable's entries
+    pub fn iter(&'_ self) -> SGTableIter<'_> {
+        SGTableIter {
+            // SAFETY: sgt is always a valid pointer
+            left: unsafe { (*self.sgt).nents } as usize,
+            // SAFETY: sgt is always a valid pointer
+            sg: unsafe { (*self.sgt).sgl },
+            _p: PhantomData,
+        }
+    }
+}
+
+impl<'a, T: DriverObject> IntoIterator for &'a SGTable<T> {
+    type Item = &'a SGEntry;
+    type IntoIter = SGTableIter<'a>;
+
+    fn into_iter(self) -> Self::IntoIter {
+        self.iter()
+    }
+}
+
+/// SAFETY: `sg_table` objects are safe to send across threads.
+unsafe impl<T: DriverObject> Send for SGTable<T> {}
+/// SAFETY: `sg_table` objects are safe to send across threads.
+unsafe impl<T: DriverObject> Sync for SGTable<T> {}
+
+/// An iterator through `SGTable` entries.
+///
+/// # Invariants
+/// `sg` must be a valid pointer to the scatterlist, which must outlive our lifetime.
+pub struct SGTableIter<'a> {
+    sg: *mut bindings::scatterlist,
+    left: usize,
+    _p: PhantomData<&'a ()>,
+}
+
+impl<'a> Iterator for SGTableIter<'a> {
+    type Item = &'a SGEntry;
+
+    fn next(&mut self) -> Option<Self::Item> {
+        if self.left == 0 {
+            None
+        } else {
+            let sg = self.sg;
+            // SAFETY: `self.sg` is always a valid pointer
+            self.sg = unsafe { bindings::sg_next(self.sg) };
+            self.left -= 1;
+            // SAFETY: `self.sg` is always a valid pointer
+            Some(unsafe { &(*(sg as *const SGEntry)) })
+        }
+    }
+}

From 3fa173819bd4c00d8b1332a30e8df1eb47e9f022 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 19 May 2023 22:21:10 +0900
Subject: [PATCH 0885/1027] rust: drm: device: Convert Device to
 AlwaysRefCounted

Switch from being a refcount wrapper itself to a transparent wrapper
around `bindings::drm_device`. The refcounted type then becomes
ARef<Device<T>>.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/gem/shmem.rs | 39 +++++++++++++++++++-----------------
 1 file changed, 21 insertions(+), 18 deletions(-)

diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index e8d4ae75cc2f5c..0ae037036d52b7 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -12,7 +12,7 @@ use crate::{
 use core::{
     marker::PhantomData,
     mem,
-    mem::{ManuallyDrop, MaybeUninit},
+    mem::MaybeUninit,
     ops::{Deref, DerefMut},
     ptr::addr_of_mut,
     slice,
@@ -70,23 +70,15 @@ pub struct Object<T: DriverObject> {
     obj: bindings::drm_gem_shmem_object,
     // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
     // manage the reference count here.
-    dev: ManuallyDrop<device::Device<T::Driver>>,
+    dev: *const bindings::drm_device,
+    #[pin]
     inner: T,
 }
 
 unsafe extern "C" fn gem_create_object<T: DriverObject>(
-    raw_dev: *mut bindings::drm_device,
+    dev: *mut bindings::drm_device,
     size: usize,
 ) -> *mut bindings::drm_gem_object {
-    // SAFETY: GEM ensures the device lives as long as its objects live,
-    // so we can conjure up a reference from thin air and never drop it.
-    let dev = ManuallyDrop::new(unsafe { device::Device::from_raw(raw_dev) });
-
-    let inner = match T::new(&*dev, size) {
-        Ok(v) => v,
-        Err(e) => return e.to_ptr(),
-    };
-
     // SAFETY: krealloc is always safe to call like this
     let p = unsafe {
         bindings::krealloc(
@@ -100,10 +92,19 @@ unsafe extern "C" fn gem_create_object<T: DriverObject>(
         return ENOMEM.to_ptr();
     }
 
-    // SAFETY: p is valid as long as the alloc succeeded
-    unsafe {
-        addr_of_mut!((*p).dev).write(dev);
-        addr_of_mut!((*p).inner).write(inner);
+    let init = try_pin_init!(Object {
+        obj <- init::zeroed(),
+        // SAFETY: GEM ensures the device lives as long as its objects live
+        inner <- T::new(unsafe { device::Device::borrow(dev)}, size),
+        dev,
+    });
+
+    // SAFETY: p is a valid pointer to an uninitialized Object<T>.
+    if let Err(e) = unsafe { init.__pinned_init(p) } {
+        // SAFETY: p is a valid pointer from `krealloc` and __pinned_init guarantees we can dealloc it.
+        unsafe { bindings::kfree(p as *mut _) };
+
+        return e.to_ptr();
     }
 
     // SAFETY: drm_gem_shmem_object is safe to zero-init, and
@@ -167,7 +168,7 @@ impl<T: DriverObject> Object<T> {
         // SAFETY: This function can be called as long as the ALLOC_OPS are set properly
         // for this driver, and the gem_create_object is called.
         let p = unsafe {
-            let p = bindings::drm_gem_shmem_create(dev.raw() as *mut _, size);
+            let p = bindings::drm_gem_shmem_create(dev.raw_mut(), size);
             crate::container_of!(p, Object<T>, obj) as *mut _
         };
 
@@ -180,7 +181,9 @@ impl<T: DriverObject> Object<T> {
 
     /// Returns the `Device` that owns this GEM object.
     pub fn dev(&self) -> &device::Device<T::Driver> {
-        &self.dev
+        // SAFETY: GEM ensures that the device outlives its objects, so we can
+        // just borrow here.
+        unsafe { device::Device::borrow(self.dev) }
     }
 
     /// Creates (if necessary) and returns a scatter-gather table of DMA pages for this object.

From 708fcbc2f44283e96aee4f181d17599bdc9abca2 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 28 Apr 2023 20:14:32 +0900
Subject: [PATCH 0886/1027] rust: drm: gem: Allow pinning GEM object driver
 data

This requires type_alias_impl_trait.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/gem/shmem.rs | 23 +++++++++++++----------
 1 file changed, 13 insertions(+), 10 deletions(-)

diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index 0ae037036d52b7..ce765d5bf96be1 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -10,11 +10,10 @@ use crate::{
     prelude::*,
 };
 use core::{
-    marker::PhantomData,
+    marker::{PhantomData, PhantomPinned},
     mem,
     mem::MaybeUninit,
     ops::{Deref, DerefMut},
-    ptr::addr_of_mut,
     slice,
 };
 
@@ -66,7 +65,9 @@ const SHMEM_VM_OPS: bindings::vm_operations_struct = vm_numa_fields! {
 
 /// A shmem-backed GEM object.
 #[repr(C)]
+#[pin_data]
 pub struct Object<T: DriverObject> {
+    #[pin]
     obj: bindings::drm_gem_shmem_object,
     // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
     // manage the reference count here.
@@ -75,17 +76,17 @@ pub struct Object<T: DriverObject> {
     inner: T,
 }
 
+// SAFETY: drm_gem_shmem_object is safe to zero-initialize
+unsafe impl init::Zeroable for bindings::drm_gem_shmem_object {}
+
 unsafe extern "C" fn gem_create_object<T: DriverObject>(
     dev: *mut bindings::drm_device,
     size: usize,
 ) -> *mut bindings::drm_gem_object {
     // SAFETY: krealloc is always safe to call like this
     let p = unsafe {
-        bindings::krealloc(
-            core::ptr::null(),
-            Object::<T>::SIZE,
-            bindings::GFP_KERNEL | bindings::__GFP_ZERO,
-        ) as *mut Object<T>
+        bindings::krealloc(core::ptr::null(), Object::<T>::SIZE, bindings::GFP_KERNEL)
+            as *mut Object<T>
     };
 
     if p.is_null() {
@@ -107,8 +108,7 @@ unsafe extern "C" fn gem_create_object<T: DriverObject>(
         return e.to_ptr();
     }
 
-    // SAFETY: drm_gem_shmem_object is safe to zero-init, and
-    // the rest of Object has been initialized
+    // SAFETY: __pinned_init() guarantees the object has been initialized
     let new: &mut Object<T> = unsafe { &mut *(p as *mut _) };
 
     new.obj.base.funcs = &Object::<T>::VTABLE;
@@ -174,7 +174,10 @@ impl<T: DriverObject> Object<T> {
 
         // SAFETY: The gem_create_object callback ensures this is a valid Object<T>,
         // so we can take a unique reference to it.
-        let obj_ref = gem::UniqueObjectRef { ptr: p };
+        let obj_ref = gem::UniqueObjectRef {
+            ptr: p,
+            _p: PhantomPinned,
+        };
 
         Ok(obj_ref)
     }

From 8028f6caf8cd58ee63ab381555deb705944b331b Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 16:50:51 +0900
Subject: [PATCH 0887/1027] rust: drm: mm: Add DRM MM Range Allocator
 abstraction

drm_mm provides a simple range allocator, useful for managing virtual
address ranges. Add a Rust abstraction to expose this module to Rust
drivers.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/mm.rs  | 310 +++++++++++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs |   1 +
 2 files changed, 311 insertions(+)
 create mode 100644 rust/kernel/drm/mm.rs

diff --git a/rust/kernel/drm/mm.rs b/rust/kernel/drm/mm.rs
new file mode 100644
index 00000000000000..682658bede631e
--- /dev/null
+++ b/rust/kernel/drm/mm.rs
@@ -0,0 +1,310 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM MM range allocator
+//!
+//! C header: [`include/drm/drm_mm.h`](../../../../include/drm/drm_mm.h)
+
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*},
+    bindings,
+    error::{to_result, Result},
+    sync::{Arc, Mutex, UniqueArc},
+    types::Opaque,
+};
+
+use crate::init::InPlaceInit;
+use alloc::boxed::Box;
+
+use core::{
+    marker::{PhantomData, PhantomPinned},
+    ops::Deref,
+    pin::Pin,
+};
+
+/// Type alias representing a DRM MM node.
+pub type Node<A, T> = Pin<Box<NodeData<A, T>>>;
+
+/// Trait which must be implemented by the inner allocator state type provided by the user.
+pub trait AllocInner<T> {
+    /// Notification that a node was dropped from the allocator.
+    fn drop_object(&mut self, _start: u64, _size: u64, _color: usize, _object: &mut T) {}
+}
+
+impl<T> AllocInner<T> for () {}
+
+/// Wrapper type for a `struct drm_mm` plus user AllocInner object.
+///
+/// # Invariants
+/// The `drm_mm` struct is valid and initialized.
+struct MmInner<A: AllocInner<T>, T>(Opaque<bindings::drm_mm>, A, PhantomData<T>);
+
+/// Represents a single allocated node in the MM allocator
+pub struct NodeData<A: AllocInner<T>, T> {
+    node: bindings::drm_mm_node,
+    mm: Arc<Mutex<MmInner<A, T>>>,
+    valid: bool,
+    /// A drm_mm_node needs to be pinned because nodes reference each other in a linked list.
+    _pin: PhantomPinned,
+    inner: T,
+}
+
+// SAFETY: Allocator ops take the mutex, and there are no mutable actions on the node.
+unsafe impl<A: Send + AllocInner<T>, T: Send> Send for NodeData<A, T> {}
+// SAFETY: Allocator ops take the mutex, and there are no mutable actions on the node.
+unsafe impl<A: Send + AllocInner<T>, T: Sync> Sync for NodeData<A, T> {}
+
+/// Available MM node insertion modes
+#[repr(u32)]
+pub enum InsertMode {
+    /// Search for the smallest hole (within the search range) that fits the desired node.
+    ///
+    /// Allocates the node from the bottom of the found hole.
+    Best = bindings::drm_mm_insert_mode_DRM_MM_INSERT_BEST,
+
+    /// Search for the lowest hole (address closest to 0, within the search range) that fits the
+    /// desired node.
+    ///
+    /// Allocates the node from the bottom of the found hole.
+    Low = bindings::drm_mm_insert_mode_DRM_MM_INSERT_LOW,
+
+    /// Search for the highest hole (address closest to U64_MAX, within the search range) that fits
+    /// the desired node.
+    ///
+    /// Allocates the node from the top of the found hole. The specified alignment for the node is
+    /// applied to the base of the node (`Node.start()`).
+    High = bindings::drm_mm_insert_mode_DRM_MM_INSERT_HIGH,
+
+    /// Search for the most recently evicted hole (within the search range) that fits the desired
+    /// node. This is appropriate for use immediately after performing an eviction scan and removing
+    /// the selected nodes to form a hole.
+    ///
+    /// Allocates the node from the bottom of the found hole.
+    Evict = bindings::drm_mm_insert_mode_DRM_MM_INSERT_EVICT,
+}
+
+/// A clonable, interlocked reference to the allocator state.
+///
+/// This is useful to perform actions on the user-supplied `AllocInner<T>` type given just a Node,
+/// without immediately taking the lock.
+#[derive(Clone)]
+pub struct InnerRef<A: AllocInner<T>, T>(Arc<Mutex<MmInner<A, T>>>);
+
+impl<A: AllocInner<T>, T> InnerRef<A, T> {
+    /// Operate on the user `AllocInner<T>` implementation, taking the lock.
+    pub fn with<RetVal>(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal {
+        let mut l = self.0.lock();
+        cb(&mut l.1)
+    }
+}
+
+impl<A: AllocInner<T>, T> NodeData<A, T> {
+    /// Returns the color of the node (an opaque value)
+    pub fn color(&self) -> usize {
+        self.node.color as usize
+    }
+
+    /// Returns the start address of the node
+    pub fn start(&self) -> u64 {
+        self.node.start
+    }
+
+    /// Returns the size of the node in bytes
+    pub fn size(&self) -> u64 {
+        self.node.size
+    }
+
+    /// Operate on the user `AllocInner<T>` implementation associated with this node's allocator.
+    pub fn with_inner<RetVal>(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal {
+        let mut l = self.mm.lock();
+        cb(&mut l.1)
+    }
+
+    /// Return a clonable, detached reference to the allocator inner data.
+    pub fn alloc_ref(&self) -> InnerRef<A, T> {
+        InnerRef(self.mm.clone())
+    }
+
+    /// Return a mutable reference to the inner data.
+    pub fn inner_mut(self: Pin<&mut Self>) -> &mut T {
+        // SAFETY: This is okay because inner is not structural
+        unsafe { &mut self.get_unchecked_mut().inner }
+    }
+}
+
+impl<A: AllocInner<T>, T> Deref for NodeData<A, T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<A: AllocInner<T>, T> Drop for NodeData<A, T> {
+    fn drop(&mut self) {
+        if self.valid {
+            let mut guard = self.mm.lock();
+
+            // Inform the user allocator that a node is being dropped.
+            guard
+                .1
+                .drop_object(self.start(), self.size(), self.color(), &mut self.inner);
+            // SAFETY: The MM lock is still taken, so we can safely remove the node.
+            unsafe { bindings::drm_mm_remove_node(&mut self.node) };
+        }
+    }
+}
+
+/// An instance of a DRM MM range allocator.
+pub struct Allocator<A: AllocInner<T>, T> {
+    mm: Arc<Mutex<MmInner<A, T>>>,
+    _p: PhantomData<T>,
+}
+
+impl<A: AllocInner<T>, T> Allocator<A, T> {
+    /// Create a new range allocator for the given start and size range of addresses.
+    ///
+    /// The user may optionally provide an inner object representing allocator state, which will
+    /// be protected by the same lock. If not required, `()` can be used.
+    #[track_caller]
+    pub fn new(start: u64, size: u64, inner: A) -> Result<Allocator<A, T>> {
+        // SAFETY: We call `Mutex::init_lock` below.
+        let mm = UniqueArc::pin_init(
+            Mutex::new(MmInner(Opaque::uninit(), inner, PhantomData)),
+            GFP_KERNEL,
+        )?;
+
+        // SAFETY: The Opaque instance provides a valid pointer, and it is initialized after
+        // this call.
+        unsafe {
+            bindings::drm_mm_init(mm.lock().0.get(), start, size);
+        }
+
+        Ok(Allocator {
+            mm: mm.into(),
+            _p: PhantomData,
+        })
+    }
+
+    /// Insert a new node into the allocator of a given size.
+    ///
+    /// `node` is the user `T` type data to store into the node.
+    pub fn insert_node(&mut self, node: T, size: u64) -> Result<Node<A, T>> {
+        self.insert_node_generic(node, size, 0, 0, InsertMode::Best)
+    }
+
+    /// Insert a new node into the allocator of a given size, with configurable alignment,
+    /// color, and insertion mode.
+    ///
+    /// `node` is the user `T` type data to store into the node.
+    pub fn insert_node_generic(
+        &mut self,
+        node: T,
+        size: u64,
+        alignment: u64,
+        color: usize,
+        mode: InsertMode,
+    ) -> Result<Node<A, T>> {
+        self.insert_node_in_range(node, size, alignment, color, 0, u64::MAX, mode)
+    }
+
+    /// Insert a new node into the allocator of a given size, with configurable alignment,
+    /// color, insertion mode, and sub-range to allocate from.
+    ///
+    /// `node` is the user `T` type data to store into the node.
+    #[allow(clippy::too_many_arguments)]
+    pub fn insert_node_in_range(
+        &mut self,
+        node: T,
+        size: u64,
+        alignment: u64,
+        color: usize,
+        start: u64,
+        end: u64,
+        mode: InsertMode,
+    ) -> Result<Node<A, T>> {
+        let mut mm_node = Box::new(
+            NodeData {
+                // SAFETY: This C struct should be zero-initialized.
+                node: unsafe { core::mem::zeroed() },
+                valid: false,
+                inner: node,
+                mm: self.mm.clone(),
+                _pin: PhantomPinned,
+            },
+            GFP_KERNEL,
+        )?;
+
+        let guard = self.mm.lock();
+        // SAFETY: We hold the lock and all pointers are valid.
+        to_result(unsafe {
+            bindings::drm_mm_insert_node_in_range(
+                guard.0.get(),
+                &mut mm_node.node,
+                size,
+                alignment,
+                color as core::ffi::c_ulong,
+                start,
+                end,
+                mode as u32,
+            )
+        })?;
+
+        mm_node.valid = true;
+
+        Ok(Pin::from(mm_node))
+    }
+
+    /// Insert a node into the allocator at a fixed start address.
+    ///
+    /// `node` is the user `T` type data to store into the node.
+    pub fn reserve_node(
+        &mut self,
+        node: T,
+        start: u64,
+        size: u64,
+        color: usize,
+    ) -> Result<Node<A, T>> {
+        let mut mm_node = Box::new(
+            NodeData {
+                // SAFETY: This C struct should be zero-initialized.
+                node: unsafe { core::mem::zeroed() },
+                valid: false,
+                inner: node,
+                mm: self.mm.clone(),
+                _pin: PhantomPinned,
+            },
+            GFP_KERNEL,
+        )?;
+
+        mm_node.node.start = start;
+        mm_node.node.size = size;
+        mm_node.node.color = color as core::ffi::c_ulong;
+
+        let guard = self.mm.lock();
+        // SAFETY: We hold the lock and all pointers are valid.
+        to_result(unsafe { bindings::drm_mm_reserve_node(guard.0.get(), &mut mm_node.node) })?;
+
+        mm_node.valid = true;
+
+        Ok(Pin::from(mm_node))
+    }
+
+    /// Operate on the inner user type `A`, taking the allocator lock
+    pub fn with_inner<RetVal>(&self, cb: impl FnOnce(&mut A) -> RetVal) -> RetVal {
+        let mut guard = self.mm.lock();
+        cb(&mut guard.1)
+    }
+}
+
+impl<A: AllocInner<T>, T> Drop for MmInner<A, T> {
+    fn drop(&mut self) {
+        // SAFETY: If the MmInner is dropped then all nodes are gone (since they hold references),
+        // so it is safe to tear down the allocator.
+        unsafe {
+            bindings::drm_mm_takedown(self.0.get());
+        }
+    }
+}
+
+// SAFETY: MmInner is safely Send if the AllocInner user type is Send.
+unsafe impl<A: Send + AllocInner<T>, T> Send for MmInner<A, T> {}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index c44760a1332fa1..73fab2dee3af95 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -7,3 +7,4 @@ pub mod drv;
 pub mod file;
 pub mod gem;
 pub mod ioctl;
+pub mod mm;

From f66ae0b86b93c69d135683171fe06809f424071b Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 16:59:20 +0900
Subject: [PATCH 0888/1027] rust: drm: syncobj: Add DRM Sync Object abstraction

DRM Sync Objects are a container for a DMA fence, and can be waited on
signaled, exported, and imported from userspace. Add a Rust abstraction
so Rust DRM drivers can support this functionality.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |  1 +
 rust/helpers/drm_syncobj.c      | 22 +++++++++
 rust/helpers/helpers.c          |  1 +
 rust/kernel/drm/mod.rs          |  1 +
 rust/kernel/drm/syncobj.rs      | 80 +++++++++++++++++++++++++++++++++
 5 files changed, 105 insertions(+)
 create mode 100644 rust/helpers/drm_syncobj.c
 create mode 100644 rust/kernel/drm/syncobj.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index a49f4a22650852..37588d226c239b 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -12,6 +12,7 @@
 #include <drm/drm_gem.h>
 #include <drm/drm_gem_shmem_helper.h>
 #include <drm/drm_ioctl.h>
+#include <drm/drm_syncobj.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
 #include <linux/blk-mq.h>
diff --git a/rust/helpers/drm_syncobj.c b/rust/helpers/drm_syncobj.c
new file mode 100644
index 00000000000000..9e14c989edfd72
--- /dev/null
+++ b/rust/helpers/drm_syncobj.c
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_syncobj.h>
+
+#ifdef CONFIG_DRM
+
+void rust_helper_drm_syncobj_get(struct drm_syncobj *obj)
+{
+	drm_syncobj_get(obj);
+}
+
+void rust_helper_drm_syncobj_put(struct drm_syncobj *obj)
+{
+	drm_syncobj_put(obj);
+}
+
+struct dma_fence *rust_helper_drm_syncobj_fence_get(struct drm_syncobj *syncobj)
+{
+	return drm_syncobj_fence_get(syncobj);
+}
+
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 2a4ec7533fd141..c6549f7f63b477 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -15,6 +15,7 @@
 #include "dma-fence.c"
 #include "dma-resv.c"
 #include "drm_gem.c"
+#include "drm_syncobj.c"
 #include "err.c"
 #include "iomem.c"
 #include "ioport.c"
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index 73fab2dee3af95..dae98826edfd1d 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -8,3 +8,4 @@ pub mod file;
 pub mod gem;
 pub mod ioctl;
 pub mod mm;
+pub mod syncobj;
diff --git a/rust/kernel/drm/syncobj.rs b/rust/kernel/drm/syncobj.rs
new file mode 100644
index 00000000000000..e2d82c0ceb1e0b
--- /dev/null
+++ b/rust/kernel/drm/syncobj.rs
@@ -0,0 +1,80 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM Sync Objects
+//!
+//! C header: [`include/drm/drm_syncobj.h`](../../../../include/drm/drm_syncobj.h)
+
+use crate::{bindings, dma_fence::*, drm, error::Result, prelude::*};
+
+/// A DRM Sync Object
+///
+/// # Invariants
+/// ptr is a valid pointer to a drm_syncobj and we own a reference to it.
+pub struct SyncObj {
+    ptr: *mut bindings::drm_syncobj,
+}
+
+impl SyncObj {
+    /// Looks up a sync object by its handle for a given `File`.
+    pub fn lookup_handle(file: &impl drm::file::GenericFile, handle: u32) -> Result<SyncObj> {
+        // SAFETY: The arguments are all valid per the type invariants.
+        let ptr = unsafe { bindings::drm_syncobj_find(file.raw() as *mut _, handle) };
+
+        if ptr.is_null() {
+            Err(ENOENT)
+        } else {
+            Ok(SyncObj { ptr })
+        }
+    }
+
+    /// Returns the DMA fence associated with this sync object, if any.
+    pub fn fence_get(&self) -> Option<Fence> {
+        // SAFETY: self.ptr is always valid
+        let fence = unsafe { bindings::drm_syncobj_fence_get(self.ptr) };
+        if fence.is_null() {
+            None
+        } else {
+            // SAFETY: The pointer is non-NULL and drm_syncobj_fence_get acquired an
+            // additional reference.
+            Some(unsafe { Fence::from_raw(fence) })
+        }
+    }
+
+    /// Replaces the DMA fence with a new one, or removes it if fence is None.
+    pub fn replace_fence(&self, fence: Option<&Fence>) {
+        // SAFETY: All arguments should be valid per the respective type invariants.
+        unsafe {
+            bindings::drm_syncobj_replace_fence(
+                self.ptr,
+                fence.map_or(core::ptr::null_mut(), |a| a.raw()),
+            )
+        };
+    }
+
+    /// Adds a new timeline point to the syncobj.
+    pub fn add_point(&self, chain: FenceChain, fence: &Fence, point: u64) {
+        // SAFETY: All arguments should be valid per the respective type invariants.
+        // This takes over the FenceChain ownership.
+        unsafe { bindings::drm_syncobj_add_point(self.ptr, chain.into_raw(), fence.raw(), point) };
+    }
+}
+
+impl Drop for SyncObj {
+    fn drop(&mut self) {
+        // SAFETY: We own a reference to this syncobj.
+        unsafe { bindings::drm_syncobj_put(self.ptr) };
+    }
+}
+
+impl Clone for SyncObj {
+    fn clone(&self) -> Self {
+        // SAFETY: `ptr` is valid per the type invariant and we own a reference to it.
+        unsafe { bindings::drm_syncobj_get(self.ptr) };
+        SyncObj { ptr: self.ptr }
+    }
+}
+
+// SAFETY: drm_syncobj operations are internally locked.
+unsafe impl Sync for SyncObj {}
+// SAFETY: drm_syncobj operations are internally locked.
+unsafe impl Send for SyncObj {}

From 2a63057fcd7524da55fd0101743acce7a296efed Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 17:08:36 +0900
Subject: [PATCH 0889/1027] rust: drm: sched: Add GPU scheduler abstraction

The GPU scheduler manages scheduling GPU jobs and dependencies between
them. This Rust abstraction allows Rust DRM drivers to use this
functionality.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindings/bindings_helper.h |   1 +
 rust/helpers/helpers.c          |   1 +
 rust/helpers/jiffies.c          |   8 +
 rust/kernel/drm/mod.rs          |   1 +
 rust/kernel/drm/sched.rs        | 369 ++++++++++++++++++++++++++++++++
 5 files changed, 380 insertions(+)
 create mode 100644 rust/helpers/jiffies.c
 create mode 100644 rust/kernel/drm/sched.rs

diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 37588d226c239b..1891cd0de0b791 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -13,6 +13,7 @@
 #include <drm/drm_gem_shmem_helper.h>
 #include <drm/drm_ioctl.h>
 #include <drm/drm_syncobj.h>
+#include <drm/gpu_scheduler.h>
 #include <kunit/test.h>
 #include <linux/blk_types.h>
 #include <linux/blk-mq.h>
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index c6549f7f63b477..7d087e8191923c 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -19,6 +19,7 @@
 #include "err.c"
 #include "iomem.c"
 #include "ioport.c"
+#include "jiffies.c"
 #include "kunit.c"
 #include "lockdep.c"
 #include "mutex.c"
diff --git a/rust/helpers/jiffies.c b/rust/helpers/jiffies.c
new file mode 100644
index 00000000000000..c046d82951d882
--- /dev/null
+++ b/rust/helpers/jiffies.c
@@ -0,0 +1,8 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/jiffies.h>
+
+unsigned long rust_helper_msecs_to_jiffies(const unsigned int m)
+{
+	return msecs_to_jiffies(m);
+}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index dae98826edfd1d..b1f182453ec1dc 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -8,4 +8,5 @@ pub mod file;
 pub mod gem;
 pub mod ioctl;
 pub mod mm;
+pub mod sched;
 pub mod syncobj;
diff --git a/rust/kernel/drm/sched.rs b/rust/kernel/drm/sched.rs
new file mode 100644
index 00000000000000..53ca7ff953120d
--- /dev/null
+++ b/rust/kernel/drm/sched.rs
@@ -0,0 +1,369 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM Scheduler
+//!
+//! C header: [`include/drm/gpu_scheduler.h`](../../../../include/drm/gpu_scheduler.h)
+
+use crate::{
+    alloc::{box_ext::BoxExt, flags::*},
+    bindings, device,
+    dma_fence::*,
+    error::{to_result, Result},
+    prelude::*,
+    sync::{Arc, UniqueArc},
+};
+use alloc::boxed::Box;
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+use core::ops::{Deref, DerefMut};
+use core::ptr::addr_of_mut;
+
+/// Scheduler status after timeout recovery
+#[repr(u32)]
+pub enum Status {
+    /// Device recovered from the timeout and can execute jobs again
+    Nominal = bindings::drm_gpu_sched_stat_DRM_GPU_SCHED_STAT_NOMINAL,
+    /// Device is no longer available
+    NoDevice = bindings::drm_gpu_sched_stat_DRM_GPU_SCHED_STAT_ENODEV,
+}
+
+/// Scheduler priorities
+#[repr(u32)]
+pub enum Priority {
+    /// Low userspace priority
+    Low = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_LOW,
+    /// Normal userspace priority
+    Normal = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_NORMAL,
+    /// High userspace priority
+    High = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_HIGH,
+    /// Kernel priority (highest)
+    Kernel = bindings::drm_sched_priority_DRM_SCHED_PRIORITY_KERNEL,
+}
+
+/// Trait to be implemented by driver job objects.
+pub trait JobImpl: Sized {
+    /// Called when the scheduler is considering scheduling this job next, to get another Fence
+    /// for this job to block on. Once it returns None, run() may be called.
+    fn prepare(_job: &mut Job<Self>) -> Option<Fence> {
+        None // Equivalent to NULL function pointer
+    }
+
+    /// Called to execute the job once all of the dependencies have been resolved. This may be
+    /// called multiple times, if timed_out() has happened and drm_sched_job_recovery() decides
+    /// to try it again.
+    fn run(job: &mut Job<Self>) -> Result<Option<Fence>>;
+
+    /// Called when a job has taken too long to execute, to trigger GPU recovery.
+    ///
+    /// This method is called in a workqueue context.
+    fn timed_out(job: &mut Job<Self>) -> Status;
+}
+
+unsafe extern "C" fn prepare_job_cb<T: JobImpl>(
+    sched_job: *mut bindings::drm_sched_job,
+    _s_entity: *mut bindings::drm_sched_entity,
+) -> *mut bindings::dma_fence {
+    // SAFETY: All of our jobs are Job<T>.
+    let p = unsafe { crate::container_of!(sched_job, Job<T>, job) as *mut Job<T> };
+
+    // SAFETY: All of our jobs are Job<T>.
+    match T::prepare(unsafe { &mut *p }) {
+        None => core::ptr::null_mut(),
+        Some(fence) => fence.into_raw(),
+    }
+}
+
+unsafe extern "C" fn run_job_cb<T: JobImpl>(
+    sched_job: *mut bindings::drm_sched_job,
+) -> *mut bindings::dma_fence {
+    // SAFETY: All of our jobs are Job<T>.
+    let p = unsafe { crate::container_of!(sched_job, Job<T>, job) as *mut Job<T> };
+
+    // SAFETY: All of our jobs are Job<T>.
+    match T::run(unsafe { &mut *p }) {
+        Err(e) => e.to_ptr(),
+        Ok(None) => core::ptr::null_mut(),
+        Ok(Some(fence)) => fence.into_raw(),
+    }
+}
+
+unsafe extern "C" fn timedout_job_cb<T: JobImpl>(
+    sched_job: *mut bindings::drm_sched_job,
+) -> bindings::drm_gpu_sched_stat {
+    // SAFETY: All of our jobs are Job<T>.
+    let p = unsafe { crate::container_of!(sched_job, Job<T>, job) as *mut Job<T> };
+
+    // SAFETY: All of our jobs are Job<T>.
+    T::timed_out(unsafe { &mut *p }) as bindings::drm_gpu_sched_stat
+}
+
+unsafe extern "C" fn free_job_cb<T: JobImpl>(sched_job: *mut bindings::drm_sched_job) {
+    // SAFETY: All of our jobs are Job<T>.
+    let p = unsafe { crate::container_of!(sched_job, Job<T>, job) as *mut Job<T> };
+
+    // Convert the job back to a Box and drop it
+    // SAFETY: All of our Job<T>s are created inside a box.
+    unsafe { drop(Box::from_raw(p)) };
+}
+
+/// A DRM scheduler job.
+pub struct Job<T: JobImpl> {
+    job: bindings::drm_sched_job,
+    inner: T,
+}
+
+impl<T: JobImpl> Deref for Job<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T: JobImpl> DerefMut for Job<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
+impl<T: JobImpl> Drop for Job<T> {
+    fn drop(&mut self) {
+        // SAFETY: At this point the job has either been submitted and this is being called from
+        // `free_job_cb` above, or it hasn't and it is safe to call `drm_sched_job_cleanup`.
+        unsafe { bindings::drm_sched_job_cleanup(&mut self.job) };
+    }
+}
+
+/// A pending DRM scheduler job (not yet armed)
+pub struct PendingJob<'a, T: JobImpl>(Box<Job<T>>, PhantomData<&'a T>);
+
+impl<'a, T: JobImpl> PendingJob<'a, T> {
+    /// Add a fence as a dependency to the job
+    pub fn add_dependency(&mut self, fence: Fence) -> Result {
+        // SAFETY: C call with correct arguments
+        to_result(unsafe {
+            bindings::drm_sched_job_add_dependency(&mut self.0.job, fence.into_raw())
+        })
+    }
+
+    /// Arm the job to make it ready for execution
+    pub fn arm(mut self) -> ArmedJob<'a, T> {
+        // SAFETY: C call with correct arguments
+        unsafe { bindings::drm_sched_job_arm(&mut self.0.job) };
+        ArmedJob(self.0, PhantomData)
+    }
+}
+
+impl<'a, T: JobImpl> Deref for PendingJob<'a, T> {
+    type Target = Job<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<'a, T: JobImpl> DerefMut for PendingJob<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// An armed DRM scheduler job (not yet submitted)
+pub struct ArmedJob<'a, T: JobImpl>(Box<Job<T>>, PhantomData<&'a T>);
+
+impl<'a, T: JobImpl> ArmedJob<'a, T> {
+    /// Returns the job fences
+    pub fn fences(&self) -> JobFences<'_> {
+        // SAFETY: s_fence is always a valid drm_sched_fence pointer
+        JobFences(unsafe { &mut *self.0.job.s_fence })
+    }
+
+    /// Push the job for execution into the scheduler
+    pub fn push(self) {
+        // After this point, the job is submitted and owned by the scheduler
+        let ptr = match self {
+            ArmedJob(job, _) => Box::<Job<T>>::into_raw(job),
+        };
+
+        // SAFETY: We are passing in ownership of a valid Box raw pointer.
+        unsafe { bindings::drm_sched_entity_push_job(addr_of_mut!((*ptr).job)) };
+    }
+}
+impl<'a, T: JobImpl> Deref for ArmedJob<'a, T> {
+    type Target = Job<T>;
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<'a, T: JobImpl> DerefMut for ArmedJob<'a, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+/// Reference to the bundle of fences attached to a DRM scheduler job
+pub struct JobFences<'a>(&'a mut bindings::drm_sched_fence);
+
+impl<'a> JobFences<'a> {
+    /// Returns a new reference to the job scheduled fence.
+    pub fn scheduled(&mut self) -> Fence {
+        // SAFETY: self.0.scheduled is always a valid fence
+        unsafe { Fence::get_raw(&mut self.0.scheduled) }
+    }
+
+    /// Returns a new reference to the job finished fence.
+    pub fn finished(&mut self) -> Fence {
+        // SAFETY: self.0.finished is always a valid fence
+        unsafe { Fence::get_raw(&mut self.0.finished) }
+    }
+}
+
+struct EntityInner<T: JobImpl> {
+    entity: bindings::drm_sched_entity,
+    // TODO: Allow users to share guilty flag between entities
+    sched: Arc<SchedulerInner<T>>,
+    guilty: bindings::atomic_t,
+    _p: PhantomData<T>,
+}
+
+impl<T: JobImpl> Drop for EntityInner<T> {
+    fn drop(&mut self) {
+        // SAFETY: The EntityInner is initialized. This will cancel/free all jobs.
+        unsafe { bindings::drm_sched_entity_destroy(&mut self.entity) };
+    }
+}
+
+// SAFETY: TODO
+unsafe impl<T: JobImpl> Sync for EntityInner<T> {}
+// SAFETY: TODO
+unsafe impl<T: JobImpl> Send for EntityInner<T> {}
+
+/// A DRM scheduler entity.
+pub struct Entity<T: JobImpl>(Pin<Box<EntityInner<T>>>);
+
+impl<T: JobImpl> Entity<T> {
+    /// Create a new scheduler entity.
+    pub fn new(sched: &Scheduler<T>, priority: Priority) -> Result<Self> {
+        let mut entity: Box<MaybeUninit<EntityInner<T>>> =
+            Box::new_uninit(GFP_KERNEL | __GFP_ZERO)?;
+
+        let mut sched_ptr = &sched.0.sched as *const _ as *mut _;
+
+        // SAFETY: The Box is allocated above and valid.
+        unsafe {
+            bindings::drm_sched_entity_init(
+                addr_of_mut!((*entity.as_mut_ptr()).entity),
+                priority as _,
+                &mut sched_ptr,
+                1,
+                addr_of_mut!((*entity.as_mut_ptr()).guilty),
+            )
+        };
+
+        // SAFETY: The Box is allocated above and valid.
+        unsafe { addr_of_mut!((*entity.as_mut_ptr()).sched).write(sched.0.clone()) };
+
+        // SAFETY: entity is now initialized.
+        Ok(Self(Pin::from(unsafe { entity.assume_init() })))
+    }
+
+    /// Create a new job on this entity.
+    ///
+    /// The entity must outlive the pending job until it transitions into the submitted state,
+    /// after which the scheduler owns it. Since jobs must be submitted in creation order,
+    /// this requires a mutable reference to the entity, ensuring that only one new job can be
+    /// in flight at once.
+    pub fn new_job(&mut self, credits: u32, inner: T) -> Result<PendingJob<'_, T>> {
+        let mut job: Box<MaybeUninit<Job<T>>> = Box::new_uninit(GFP_KERNEL | __GFP_ZERO)?;
+
+        // SAFETY: We hold a reference to the entity (which is a valid pointer),
+        // and the job object was just allocated above.
+        to_result(unsafe {
+            bindings::drm_sched_job_init(
+                addr_of_mut!((*job.as_mut_ptr()).job),
+                &self.0.as_ref().get_ref().entity as *const _ as *mut _,
+                credits,
+                core::ptr::null_mut(),
+            )
+        })?;
+
+        // SAFETY: The Box pointer is valid, and this initializes the inner member.
+        unsafe { addr_of_mut!((*job.as_mut_ptr()).inner).write(inner) };
+
+        // SAFETY: All fields of the Job<T> are now initialized.
+        Ok(PendingJob(unsafe { job.assume_init() }, PhantomData))
+    }
+}
+
+/// DRM scheduler inner data
+pub struct SchedulerInner<T: JobImpl> {
+    sched: bindings::drm_gpu_scheduler,
+    _p: PhantomData<T>,
+}
+
+impl<T: JobImpl> Drop for SchedulerInner<T> {
+    fn drop(&mut self) {
+        // SAFETY: The scheduler is valid. This assumes drm_sched_fini() will take care of
+        // freeing all in-progress jobs.
+        unsafe { bindings::drm_sched_fini(&mut self.sched) };
+    }
+}
+
+// SAFETY: TODO
+unsafe impl<T: JobImpl> Sync for SchedulerInner<T> {}
+// SAFETY: TODO
+unsafe impl<T: JobImpl> Send for SchedulerInner<T> {}
+
+/// A DRM Scheduler
+pub struct Scheduler<T: JobImpl>(Arc<SchedulerInner<T>>);
+
+impl<T: JobImpl> Scheduler<T> {
+    const OPS: bindings::drm_sched_backend_ops = bindings::drm_sched_backend_ops {
+        prepare_job: Some(prepare_job_cb::<T>),
+        run_job: Some(run_job_cb::<T>),
+        timedout_job: Some(timedout_job_cb::<T>),
+        free_job: Some(free_job_cb::<T>),
+        update_job_credits: None,
+    };
+    /// Creates a new DRM Scheduler object
+    // TODO: Shared timeout workqueues & scores
+    pub fn new(
+        device: &impl device::RawDevice,
+        num_rqs: u32,
+        credit_limit: u32,
+        hang_limit: u32,
+        timeout_ms: usize,
+        name: &'static CStr,
+    ) -> Result<Scheduler<T>> {
+        let mut sched: UniqueArc<MaybeUninit<SchedulerInner<T>>> =
+            UniqueArc::new_uninit(GFP_KERNEL)?;
+
+        // SAFETY: zero sched->sched_rq as drm_sched_init() uses it to exit early withoput initialisation
+        // TODO: allocate sched zzeroed instead
+        unsafe {
+            (*sched.as_mut_ptr()).sched.sched_rq = core::ptr::null_mut();
+        };
+
+        // SAFETY: The drm_sched pointer is valid and pinned as it was just allocated above.
+        to_result(unsafe {
+            bindings::drm_sched_init(
+                addr_of_mut!((*sched.as_mut_ptr()).sched),
+                &Self::OPS,
+                core::ptr::null_mut(),
+                num_rqs,
+                credit_limit,
+                hang_limit,
+                bindings::msecs_to_jiffies(timeout_ms.try_into()?).try_into()?,
+                core::ptr::null_mut(),
+                core::ptr::null_mut(),
+                name.as_char_ptr(),
+                device.raw_device(),
+            )
+        })?;
+
+        // SAFETY: All fields of SchedulerInner are now initialized.
+        Ok(Scheduler(unsafe { sched.assume_init() }.into()))
+    }
+}

From 3415616b212fa06078eca4aa68efb96ab7e2148c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 Feb 2023 15:31:42 +0900
Subject: [PATCH 0890/1027] drm/gem: Add a flag to control whether objects can
 be exported

Drivers may want to support driver-private objects, which cannot be
shared. This allows them to share a single lock and enables other
optimizations.

Add an `exportable` field to drm_gem_object, which blocks PRIME export
if set to false. It is initialized to true in
drm_gem_private_object_init.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/drm_gem.c   | 1 +
 drivers/gpu/drm/drm_prime.c | 5 +++++
 include/drm/drm_gem.h       | 8 ++++++++
 3 files changed, 14 insertions(+)

diff --git a/drivers/gpu/drm/drm_gem.c b/drivers/gpu/drm/drm_gem.c
index d4bbc5d109c8bc..90e4fb34aa53df 100644
--- a/drivers/gpu/drm/drm_gem.c
+++ b/drivers/gpu/drm/drm_gem.c
@@ -169,6 +169,7 @@ void drm_gem_private_object_init(struct drm_device *dev,
 
 	drm_vma_node_reset(&obj->vma_node);
 	INIT_LIST_HEAD(&obj->lru_node);
+	obj->exportable = true;
 }
 EXPORT_SYMBOL(drm_gem_private_object_init);
 
diff --git a/drivers/gpu/drm/drm_prime.c b/drivers/gpu/drm/drm_prime.c
index 03bd3c7bd0dc2c..7ba879e78a7302 100644
--- a/drivers/gpu/drm/drm_prime.c
+++ b/drivers/gpu/drm/drm_prime.c
@@ -387,6 +387,11 @@ static struct dma_buf *export_and_register_object(struct drm_device *dev,
 		return dmabuf;
 	}
 
+	if (!obj->exportable) {
+		dmabuf = ERR_PTR(-EINVAL);
+		return dmabuf;
+	}
+
 	if (obj->funcs && obj->funcs->export)
 		dmabuf = obj->funcs->export(obj, flags);
 	else
diff --git a/include/drm/drm_gem.h b/include/drm/drm_gem.h
index bae4865b2101ae..1d3bddd9c7eba1 100644
--- a/include/drm/drm_gem.h
+++ b/include/drm/drm_gem.h
@@ -430,6 +430,14 @@ struct drm_gem_object {
 	 * The current LRU list that the GEM object is on.
 	 */
 	struct drm_gem_lru *lru;
+
+	/**
+	 * @exportable:
+	 *
+	 * Whether this GEM object can be exported via the drm_gem_object_funcs->export
+	 * callback. Defaults to true.
+	 */
+	bool exportable;
 };
 
 /**

From 7118322bd81d8691f46baaf008b1d69fb59dfd33 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 11 Feb 2023 16:03:02 +0900
Subject: [PATCH 0891/1027] rust: drm: gem: Add set_exportable() method

This allows drivers to control whether a given GEM object is allowed to
be exported via PRIME to other drivers.
---
 rust/kernel/drm/gem/mod.rs   | 13 +++++++++++++
 rust/kernel/drm/gem/shmem.rs |  4 ++++
 2 files changed, 17 insertions(+)

diff --git a/rust/kernel/drm/gem/mod.rs b/rust/kernel/drm/gem/mod.rs
index f8011c4393b0d8..fdcd415e585fdd 100644
--- a/rust/kernel/drm/gem/mod.rs
+++ b/rust/kernel/drm/gem/mod.rs
@@ -52,6 +52,10 @@ pub trait IntoGEMObject: Sized + crate::private::Sealed {
     /// this owning object is valid.
     fn gem_obj(&self) -> &bindings::drm_gem_object;
 
+    /// Returns a reference to the raw `drm_gem_object` structure, which must be valid as long as
+    /// this owning object is valid.
+    fn mut_gem_obj(&mut self) -> &mut bindings::drm_gem_object;
+
     /// Converts a pointer to a `drm_gem_object` into a pointer to this type.
     ///
     /// # Safety
@@ -129,6 +133,10 @@ impl<T: DriverObject> IntoGEMObject for Object<T> {
         &self.obj
     }
 
+    fn mut_gem_obj(&mut self) -> &mut bindings::drm_gem_object {
+        &mut self.obj
+    }
+
     unsafe fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Object<T> {
         // SAFETY: Safe as long as the safety invariants of this trait method hold.
         unsafe { crate::container_of!(obj, Object<T>, obj) as *mut Object<T> }
@@ -142,6 +150,11 @@ pub trait BaseObject: IntoGEMObject {
         self.gem_obj().size
     }
 
+    /// Sets the exportable flag, which controls whether the object can be exported via PRIME.
+    fn set_exportable(&mut self, exportable: bool) {
+        self.mut_gem_obj().exportable = exportable;
+    }
+
     /// Creates a new reference to the object.
     fn reference(&self) -> ObjectRef<Self> {
         // SAFETY: Having a reference to an Object implies holding a GEM reference
diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index ce765d5bf96be1..93f47a871c8720 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -256,6 +256,10 @@ impl<T: DriverObject> gem::IntoGEMObject for Object<T> {
         &self.obj.base
     }
 
+    fn mut_gem_obj(&mut self) -> &mut bindings::drm_gem_object {
+        &mut self.obj.base
+    }
+
     // Safety: the passed GEM object must be owned by this driver (and be a shmem object).
     unsafe fn from_gem_obj(obj: *mut bindings::drm_gem_object) -> *mut Object<T> {
         // SAFETY: The invariant guarantees this is correct.

From 628473e965bfa805237f67e54a2ea24a39250694 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Feb 2023 00:20:39 +0900
Subject: [PATCH 0892/1027] drm/asahi: Add the Asahi driver UAPI

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 include/uapi/drm/asahi_drm.h | 607 +++++++++++++++++++++++++++++++++++
 1 file changed, 607 insertions(+)
 create mode 100644 include/uapi/drm/asahi_drm.h

diff --git a/include/uapi/drm/asahi_drm.h b/include/uapi/drm/asahi_drm.h
new file mode 100644
index 00000000000000..6ea316f87ed451
--- /dev/null
+++ b/include/uapi/drm/asahi_drm.h
@@ -0,0 +1,607 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) The Asahi Linux Contributors
+ *
+ * Based on asahi_drm.h which is
+ *
+ * Copyright © 2014-2018 Broadcom
+ * Copyright © 2019 Collabora ltd.
+ */
+#ifndef _ASAHI_DRM_H_
+#define _ASAHI_DRM_H_
+
+#include "drm.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#define DRM_ASAHI_UNSTABLE_UABI_VERSION		10011
+
+#define DRM_ASAHI_GET_PARAMS			0x00
+#define DRM_ASAHI_VM_CREATE			0x01
+#define DRM_ASAHI_VM_DESTROY			0x02
+#define DRM_ASAHI_GEM_CREATE			0x03
+#define DRM_ASAHI_GEM_MMAP_OFFSET		0x04
+#define DRM_ASAHI_GEM_BIND			0x05
+#define DRM_ASAHI_QUEUE_CREATE			0x06
+#define DRM_ASAHI_QUEUE_DESTROY			0x07
+#define DRM_ASAHI_SUBMIT			0x08
+#define DRM_ASAHI_GET_TIME			0x09
+
+#define DRM_ASAHI_MAX_CLUSTERS	32
+
+struct drm_asahi_params_global {
+	__u32 unstable_uabi_version;
+	__u32 pad0;
+
+	__u64 feat_compat;
+	__u64 feat_incompat;
+
+	__u32 gpu_generation;
+	__u32 gpu_variant;
+	__u32 gpu_revision;
+	__u32 chip_id;
+
+	__u32 num_dies;
+	__u32 num_clusters_total;
+	__u32 num_cores_per_cluster;
+	__u32 num_frags_per_cluster;
+	__u32 num_gps_per_cluster;
+	__u32 num_cores_total_active;
+	__u64 core_masks[DRM_ASAHI_MAX_CLUSTERS];
+
+	__u32 vm_page_size;
+	__u32 pad1;
+	__u64 vm_user_start;
+	__u64 vm_user_end;
+	__u64 vm_usc_start;
+	__u64 vm_usc_end;
+	__u64 vm_kernel_min_size;
+
+	__u32 max_syncs_per_submission;
+	__u32 max_commands_per_submission;
+	__u32 max_commands_in_flight;
+	__u32 max_attachments;
+
+	__u32 timer_frequency_hz;
+	__u32 min_frequency_khz;
+	__u32 max_frequency_khz;
+	__u32 max_power_mw;
+
+	__u32 result_render_size;
+	__u32 result_compute_size;
+
+	__u32 firmware_version[4];
+};
+
+enum drm_asahi_feat_compat {
+	DRM_ASAHI_FEAT_SOFT_FAULTS = (1UL) << 0,
+};
+
+enum drm_asahi_feat_incompat {
+	DRM_ASAHI_FEAT_MANDATORY_ZS_COMPRESSION = (1UL) << 0,
+};
+
+struct drm_asahi_get_params {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @param: Parameter group to fetch (MBZ) */
+	__u32 param_group;
+
+	/** @pad: MBZ */
+	__u32 pad;
+
+	/** @value: User pointer to write parameter struct */
+	__u64 pointer;
+
+	/** @value: Size of user buffer, max size supported on return */
+	__u64 size;
+};
+
+struct drm_asahi_vm_create {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @kernel_start: Start of the kernel-reserved address range */
+	__u64 kernel_start;
+
+	/** @kernel_end: End of the kernel-reserved address range */
+	__u64 kernel_end;
+
+	/** @value: Returned VM ID */
+	__u32 vm_id;
+
+	/** @pad: MBZ */
+	__u32 pad;
+};
+
+struct drm_asahi_vm_destroy {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @value: VM ID to be destroyed */
+	__u32 vm_id;
+
+	/** @pad: MBZ */
+	__u32 pad;
+};
+
+#define ASAHI_GEM_WRITEBACK	(1L << 0)
+#define ASAHI_GEM_VM_PRIVATE	(1L << 1)
+
+struct drm_asahi_gem_create {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @size: Size of the BO */
+	__u64 size;
+
+	/** @flags: BO creation flags */
+	__u32 flags;
+
+	/** @handle: VM ID to assign to the BO, if ASAHI_GEM_VM_PRIVATE is set. */
+	__u32 vm_id;
+
+	/** @handle: Returned GEM handle for the BO */
+	__u32 handle;
+};
+
+struct drm_asahi_gem_mmap_offset {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @handle: Handle for the object being mapped. */
+	__u32 handle;
+
+	/** @flags: Must be zero */
+	__u32 flags;
+
+	/** @offset: The fake offset to use for subsequent mmap call */
+	__u64 offset;
+};
+
+enum drm_asahi_bind_op {
+	ASAHI_BIND_OP_BIND = 0,
+	ASAHI_BIND_OP_UNBIND = 1,
+	ASAHI_BIND_OP_UNBIND_ALL = 2,
+};
+
+#define ASAHI_BIND_READ		(1L << 0)
+#define ASAHI_BIND_WRITE	(1L << 1)
+
+struct drm_asahi_gem_bind {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @obj: Bind operation */
+	__u32 op;
+
+	/** @flags: One or more of ASAHI_BIND_* */
+	__u32 flags;
+
+	/** @obj: GEM object to bind */
+	__u32 handle;
+
+	/** @vm_id: The ID of the VM to bind to */
+	__u32 vm_id;
+
+	/** @offset: Offset into the object */
+	__u64 offset;
+
+	/** @range: Number of bytes from the object to bind to addr */
+	__u64 range;
+
+	/** @addr: Address to bind to */
+	__u64 addr;
+};
+
+enum drm_asahi_cmd_type {
+	DRM_ASAHI_CMD_RENDER = 0,
+	DRM_ASAHI_CMD_BLIT = 1,
+	DRM_ASAHI_CMD_COMPUTE = 2,
+};
+
+/* Note: this is an enum so that it can be resolved by Rust bindgen. */
+enum drm_asahi_queue_cap {
+	DRM_ASAHI_QUEUE_CAP_RENDER	= (1UL << DRM_ASAHI_CMD_RENDER),
+	DRM_ASAHI_QUEUE_CAP_BLIT	= (1UL << DRM_ASAHI_CMD_BLIT),
+	DRM_ASAHI_QUEUE_CAP_COMPUTE	= (1UL << DRM_ASAHI_CMD_COMPUTE),
+};
+
+struct drm_asahi_queue_create {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @flags: MBZ */
+	__u32 flags;
+
+	/** @vm_id: The ID of the VM this queue is bound to */
+	__u32 vm_id;
+
+	/** @type: Bitmask of DRM_ASAHI_QUEUE_CAP_* */
+	__u32 queue_caps;
+
+	/** @priority: Queue priority, 0-3 */
+	__u32 priority;
+
+	/** @queue_id: The returned queue ID */
+	__u32 queue_id;
+};
+
+struct drm_asahi_queue_destroy {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @queue_id: The queue ID to be destroyed */
+	__u32 queue_id;
+};
+
+enum drm_asahi_sync_type {
+	DRM_ASAHI_SYNC_SYNCOBJ = 0,
+	DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ = 1,
+};
+
+struct drm_asahi_sync {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @sync_type: One of drm_asahi_sync_type */
+	__u32 sync_type;
+
+	/** @handle: The sync object handle */
+	__u32 handle;
+
+	/** @timeline_value: Timeline value for timeline sync objects */
+	__u64 timeline_value;
+};
+
+enum drm_asahi_subqueue {
+	DRM_ASAHI_SUBQUEUE_RENDER = 0, /* Also blit */
+	DRM_ASAHI_SUBQUEUE_COMPUTE = 1,
+	DRM_ASAHI_SUBQUEUE_COUNT = 2,
+};
+
+#define DRM_ASAHI_BARRIER_NONE ~(0U)
+
+struct drm_asahi_command {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @type: One of drm_asahi_cmd_type */
+	__u32 cmd_type;
+
+	/** @flags: Flags for command submission */
+	__u32 flags;
+
+	/** @cmdbuf: Pointer to the appropriate command buffer structure */
+	__u64 cmd_buffer;
+
+	/** @cmdbuf: Size of the command buffer structure */
+	__u64 cmd_buffer_size;
+
+	/** @cmdbuf: Offset into the result BO to return information about this command */
+	__u64 result_offset;
+
+	/** @cmdbuf: Size of the result data structure */
+	__u64 result_size;
+
+	/** @barriers: Array of command indices per subqueue to wait on */
+	__u32 barriers[DRM_ASAHI_SUBQUEUE_COUNT];
+};
+
+struct drm_asahi_submit {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @in_syncs: An optional array of drm_asahi_sync to wait on before starting this job. */
+	__u64 in_syncs;
+
+	/** @in_syncs: An optional array of drm_asahi_sync objects to signal upon completion. */
+	__u64 out_syncs;
+
+	/** @commands: Pointer to the drm_asahi_command array of commands to submit. */
+	__u64 commands;
+
+	/** @flags: Flags for command submission (MBZ) */
+	__u32 flags;
+
+	/** @queue_id: The queue ID to be submitted to */
+	__u32 queue_id;
+
+	/** @result_handle: An optional BO handle to place result data in */
+	__u32 result_handle;
+
+	/** @in_sync_count: Number of sync objects to wait on before starting this job. */
+	__u32 in_sync_count;
+
+	/** @in_sync_count: Number of sync objects to signal upon completion of this job. */
+	__u32 out_sync_count;
+
+	/** @pad: Number of commands to be submitted */
+	__u32 command_count;
+};
+
+struct drm_asahi_attachment {
+	/** @pointer: Base address of the attachment */
+	__u64 pointer;
+	/** @size: Size of the attachment in bytes */
+	__u64 size;
+	/** @order: Power of 2 exponent related to attachment size (?) */
+	__u32 order;
+	/** @flags: MBZ */
+	__u32 flags;
+};
+
+#define ASAHI_RENDER_NO_CLEAR_PIPELINE_TEXTURES (1UL << 0)
+#define ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S (1UL << 1)
+#define ASAHI_RENDER_VERTEX_SPILLS (1UL << 2)
+#define ASAHI_RENDER_PROCESS_EMPTY_TILES (1UL << 3)
+#define ASAHI_RENDER_NO_VERTEX_CLUSTERING (1UL << 4)
+#define ASAHI_RENDER_MSAA_ZS (1UL << 5)
+/* XXX check */
+#define ASAHI_RENDER_NO_PREEMPTION (1UL << 6)
+
+struct drm_asahi_cmd_render {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	__u64 flags;
+
+	__u64 encoder_ptr;
+	__u64 vertex_usc_base;
+	__u64 fragment_usc_base;
+
+	__u64 vertex_attachments;
+	__u64 fragment_attachments;
+	__u32 vertex_attachment_count;
+	__u32 fragment_attachment_count;
+
+	__u32 vertex_helper_program;
+	__u32 fragment_helper_program;
+	__u32 vertex_helper_cfg;
+	__u32 fragment_helper_cfg;
+	__u64 vertex_helper_arg;
+	__u64 fragment_helper_arg;
+
+	__u64 depth_buffer_load;
+	__u64 depth_buffer_load_stride;
+	__u64 depth_buffer_store;
+	__u64 depth_buffer_store_stride;
+	__u64 depth_buffer_partial;
+	__u64 depth_buffer_partial_stride;
+	__u64 depth_meta_buffer_load;
+	__u64 depth_meta_buffer_load_stride;
+	__u64 depth_meta_buffer_store;
+	__u64 depth_meta_buffer_store_stride;
+	__u64 depth_meta_buffer_partial;
+	__u64 depth_meta_buffer_partial_stride;
+
+	__u64 stencil_buffer_load;
+	__u64 stencil_buffer_load_stride;
+	__u64 stencil_buffer_store;
+	__u64 stencil_buffer_store_stride;
+	__u64 stencil_buffer_partial;
+	__u64 stencil_buffer_partial_stride;
+	__u64 stencil_meta_buffer_load;
+	__u64 stencil_meta_buffer_load_stride;
+	__u64 stencil_meta_buffer_store;
+	__u64 stencil_meta_buffer_store_stride;
+	__u64 stencil_meta_buffer_partial;
+	__u64 stencil_meta_buffer_partial_stride;
+
+	__u64 scissor_array;
+	__u64 depth_bias_array;
+	__u64 visibility_result_buffer;
+
+	__u64 vertex_sampler_array;
+	__u32 vertex_sampler_count;
+	__u32 vertex_sampler_max;
+
+	__u64 fragment_sampler_array;
+	__u32 fragment_sampler_count;
+	__u32 fragment_sampler_max;
+
+	__u64 zls_ctrl;
+	__u64 ppp_multisamplectl;
+	__u32 ppp_ctrl;
+
+	__u32 fb_width;
+	__u32 fb_height;
+
+	__u32 utile_width;
+	__u32 utile_height;
+
+	__u32 samples;
+	__u32 layers;
+
+	__u32 encoder_id;
+	__u32 cmd_ta_id;
+	__u32 cmd_3d_id;
+
+	__u32 sample_size;
+	__u32 tib_blocks;
+	__u32 iogpu_unk_214;
+
+	__u32 merge_upper_x;
+	__u32 merge_upper_y;
+
+	__u32 load_pipeline;
+	__u32 load_pipeline_bind;
+
+	__u32 store_pipeline;
+	__u32 store_pipeline_bind;
+
+	__u32 partial_reload_pipeline;
+	__u32 partial_reload_pipeline_bind;
+
+	__u32 partial_store_pipeline;
+	__u32 partial_store_pipeline_bind;
+
+	__u32 depth_dimensions;
+	__u32 isp_bgobjdepth;
+	__u32 isp_bgobjvals;
+};
+
+/* XXX check */
+#define ASAHI_COMPUTE_NO_PREEMPTION (1UL << 0)
+
+struct drm_asahi_cmd_compute {
+	__u64 flags;
+
+	__u64 encoder_ptr;
+	__u64 encoder_end;
+	__u64 usc_base;
+
+	__u64 attachments;
+	__u32 attachment_count;
+	__u32 pad;
+
+	__u32 helper_program;
+	__u32 helper_cfg;
+	__u64 helper_arg;
+
+	__u32 encoder_id;
+	__u32 cmd_id;
+
+	__u64 sampler_array;
+	__u32 sampler_count;
+	__u32 sampler_max;
+
+	__u32 iogpu_unk_40;
+	__u32 unk_mask;
+};
+
+enum drm_asahi_status {
+	DRM_ASAHI_STATUS_PENDING = 0,
+	DRM_ASAHI_STATUS_COMPLETE,
+	DRM_ASAHI_STATUS_UNKNOWN_ERROR,
+	DRM_ASAHI_STATUS_TIMEOUT,
+	DRM_ASAHI_STATUS_FAULT,
+	DRM_ASAHI_STATUS_KILLED,
+	DRM_ASAHI_STATUS_NO_DEVICE,
+};
+
+enum drm_asahi_fault {
+	DRM_ASAHI_FAULT_NONE = 0,
+	DRM_ASAHI_FAULT_UNKNOWN,
+	DRM_ASAHI_FAULT_UNMAPPED,
+	DRM_ASAHI_FAULT_AF_FAULT,
+	DRM_ASAHI_FAULT_WRITE_ONLY,
+	DRM_ASAHI_FAULT_READ_ONLY,
+	DRM_ASAHI_FAULT_NO_ACCESS,
+};
+
+struct drm_asahi_result_info {
+	/** @status: One of enum drm_asahi_status */
+	__u32 status;
+
+	/** @reason: One of drm_asahi_fault_type */
+	__u32 fault_type;
+
+	/** @unit: Unit number, hardware dependent */
+	__u32 unit;
+
+	/** @sideband: Sideband information, hardware dependent */
+	__u32 sideband;
+
+	/** @level: Page table level at which the fault occurred, hardware dependent */
+	__u8 level;
+
+	/** @read: Fault was a read */
+	__u8 is_read;
+
+	/** @pad: MBZ */
+	__u16 pad;
+
+	/** @unk_5: Extra bits, hardware dependent */
+	__u32 extra;
+
+	/** @address: Fault address, cache line aligned */
+	__u64 address;
+};
+
+#define DRM_ASAHI_RESULT_RENDER_TVB_GROW_OVF (1UL << 0)
+#define DRM_ASAHI_RESULT_RENDER_TVB_GROW_MIN (1UL << 1)
+#define DRM_ASAHI_RESULT_RENDER_TVB_OVERFLOWED (1UL << 2)
+
+struct drm_asahi_result_render {
+	/** @address: Common result information */
+	struct drm_asahi_result_info info;
+
+	/** @flags: Zero or more of of DRM_ASAHI_RESULT_RENDER_* */
+	__u64 flags;
+
+	/** @vertex_ts_start: Timestamp of the start of vertex processing */
+	__u64 vertex_ts_start;
+
+	/** @vertex_ts_end: Timestamp of the end of vertex processing */
+	__u64 vertex_ts_end;
+
+	/** @fragment_ts_start: Timestamp of the start of fragment processing */
+	__u64 fragment_ts_start;
+
+	/** @fragment_ts_end: Timestamp of the end of fragment processing */
+	__u64 fragment_ts_end;
+
+	/** @tvb_size_bytes: TVB size at the start of this render */
+	__u64 tvb_size_bytes;
+
+	/** @tvb_usage_bytes: Total TVB usage in bytes for this render */
+	__u64 tvb_usage_bytes;
+
+	/** @num_tvb_overflows: Number of TVB overflows that occurred for this render */
+	__u32 num_tvb_overflows;
+};
+
+struct drm_asahi_result_compute {
+	/** @address: Common result information */
+	struct drm_asahi_result_info info;
+
+	/** @flags: Zero or more of of DRM_ASAHI_RESULT_COMPUTE_* */
+	__u64 flags;
+
+	/** @ts_start: Timestamp of the start of this compute command */
+	__u64 ts_start;
+
+	/** @vertex_ts_end: Timestamp of the end of this compute command */
+	__u64 ts_end;
+};
+
+struct drm_asahi_get_time {
+	/** @extensions: Pointer to the first extension struct, if any */
+	__u64 extensions;
+
+	/** @flags: MBZ. */
+	__u64 flags;
+
+	/** @tv_sec: On return, seconds part of a point in time */
+	__s64 tv_sec;
+
+	/** @tv_nsec: On return, nanoseconds part of a point in time */
+	__s64 tv_nsec;
+
+	/** @gpu_timestamp: On return, the GPU timestamp at that point in time */
+	__u64 gpu_timestamp;
+};
+
+/* Note: this is an enum so that it can be resolved by Rust bindgen. */
+enum {
+   DRM_IOCTL_ASAHI_GET_PARAMS       = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_GET_PARAMS, struct drm_asahi_get_params),
+   DRM_IOCTL_ASAHI_VM_CREATE        = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_VM_CREATE, struct drm_asahi_vm_create),
+   DRM_IOCTL_ASAHI_VM_DESTROY       = DRM_IOW(DRM_COMMAND_BASE + DRM_ASAHI_VM_DESTROY, struct drm_asahi_vm_destroy),
+   DRM_IOCTL_ASAHI_GEM_CREATE       = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_GEM_CREATE, struct drm_asahi_gem_create),
+   DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET  = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_GEM_MMAP_OFFSET, struct drm_asahi_gem_mmap_offset),
+   DRM_IOCTL_ASAHI_GEM_BIND         = DRM_IOW(DRM_COMMAND_BASE + DRM_ASAHI_GEM_BIND, struct drm_asahi_gem_bind),
+   DRM_IOCTL_ASAHI_QUEUE_CREATE     = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_QUEUE_CREATE, struct drm_asahi_queue_create),
+   DRM_IOCTL_ASAHI_QUEUE_DESTROY    = DRM_IOW(DRM_COMMAND_BASE + DRM_ASAHI_QUEUE_DESTROY, struct drm_asahi_queue_destroy),
+   DRM_IOCTL_ASAHI_SUBMIT           = DRM_IOW(DRM_COMMAND_BASE + DRM_ASAHI_SUBMIT, struct drm_asahi_submit),
+   DRM_IOCTL_ASAHI_GET_TIME         = DRM_IOWR(DRM_COMMAND_BASE + DRM_ASAHI_GET_TIME, struct drm_asahi_get_time),
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* _ASAHI_DRM_H_ */

From 074007764acaa47af3b7c7b981eefdaa676befe0 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Feb 2023 00:20:55 +0900
Subject: [PATCH 0893/1027] rust: bindings: Bind the Asahi DRM UAPI

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/uapi/uapi_helper.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rust/uapi/uapi_helper.h b/rust/uapi/uapi_helper.h
index ed42a456da2efa..c6e996d4796438 100644
--- a/rust/uapi/uapi_helper.h
+++ b/rust/uapi/uapi_helper.h
@@ -7,6 +7,7 @@
  */
 
 #include <uapi/asm-generic/ioctl.h>
+#include <uapi/drm/asahi_drm.h>
 #include <uapi/drm/drm.h>
 #include <uapi/linux/mii.h>
 #include <uapi/linux/ethtool.h>

From 192b6020f92bdcbe7199566d75abc72262bfe7d3 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Feb 2023 00:28:27 +0900
Subject: [PATCH 0894/1027] rust: macros: Add versions macro

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/macros/lib.rs      |   7 +
 rust/macros/versions.rs | 341 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 348 insertions(+)
 create mode 100644 rust/macros/versions.rs

diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 5be0cb9db3ee49..99ac924f2af795 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -10,6 +10,7 @@ mod module;
 mod paste;
 mod pin_data;
 mod pinned_drop;
+mod versions;
 mod vtable;
 mod zeroable;
 
@@ -100,6 +101,12 @@ pub fn module(ts: TokenStream) -> TokenStream {
     module::module(ts)
 }
 
+/// Declares multiple variants of a structure or impl code
+#[proc_macro_attribute]
+pub fn versions(attr: TokenStream, item: TokenStream) -> TokenStream {
+    versions::versions(attr, item)
+}
+
 /// Declares or implements a vtable trait.
 ///
 /// Linux's use of pure vtables is very close to Rust traits, but they differ
diff --git a/rust/macros/versions.rs b/rust/macros/versions.rs
new file mode 100644
index 00000000000000..89d43096d35808
--- /dev/null
+++ b/rust/macros/versions.rs
@@ -0,0 +1,341 @@
+use proc_macro::{Delimiter, Group, Ident, Punct, Spacing, Span, TokenStream, TokenTree};
+
+//use crate::helpers::expect_punct;
+
+fn expect_group(it: &mut impl Iterator<Item = TokenTree>) -> Group {
+    if let Some(TokenTree::Group(group)) = it.next() {
+        group
+    } else {
+        panic!("Expected Group")
+    }
+}
+
+fn expect_punct(it: &mut impl Iterator<Item = TokenTree>) -> String {
+    if let Some(TokenTree::Punct(punct)) = it.next() {
+        punct.to_string()
+    } else {
+        panic!("Expected Group")
+    }
+}
+
+fn drop_until_punct(it: &mut impl Iterator<Item = TokenTree>, delimiter: &str, is_struct: bool) {
+    let mut depth: isize = 0;
+    let mut colons: isize = 0;
+    for token in it.by_ref() {
+        if let TokenTree::Punct(punct) = token {
+            match punct.as_char() {
+                ':' => {
+                    colons += 1;
+                }
+                '<' => {
+                    if depth > 0 || colons == 2 || is_struct {
+                        depth += 1;
+                    }
+                    colons = 0;
+                }
+                '>' => {
+                    if depth > 0 {
+                        depth -= 1;
+                    }
+                    colons = 0;
+                }
+                _ => {
+                    colons = 0;
+                    if depth == 0 && delimiter.contains(&punct.to_string()) {
+                        break;
+                    }
+                }
+            }
+        }
+    }
+}
+
+fn drop_until_braces(it: &mut impl Iterator<Item = TokenTree>) {
+    let mut depth: isize = 0;
+    let mut colons: isize = 0;
+    for token in it.by_ref() {
+        match token {
+            TokenTree::Punct(punct) => match punct.as_char() {
+                ':' => {
+                    colons += 1;
+                }
+                '<' => {
+                    if depth > 0 || colons == 2 {
+                        depth += 1;
+                    }
+                    colons = 0;
+                }
+                '>' => {
+                    if depth > 0 {
+                        depth -= 1;
+                    }
+                    colons = 0;
+                }
+                _ => colons = 0,
+            },
+            TokenTree::Group(group) if group.delimiter() == Delimiter::Brace => {
+                if depth == 0 {
+                    break;
+                }
+            }
+            _ => (),
+        }
+    }
+}
+
+struct VersionConfig {
+    fields: &'static [&'static str],
+    enums: &'static [&'static [&'static str]],
+    versions: &'static [&'static [&'static str]],
+}
+
+static AGX_VERSIONS: VersionConfig = VersionConfig {
+    fields: &["G", "V"],
+    enums: &[
+        &["G13", "G14", "G14X"],
+        &["V12_3", "V12_4", "V13_0B4", "V13_2", "V13_3"],
+    ],
+    versions: &[
+        &["G13", "V12_3"],
+        &["G14", "V12_4"],
+        &["G13", "V13_3"],
+        &["G14", "V13_3"],
+        &["G14X", "V13_3"],
+    ],
+};
+
+fn check_version(
+    config: &VersionConfig,
+    ver: &[usize],
+    it: &mut impl Iterator<Item = TokenTree>,
+) -> bool {
+    let first = it.next().unwrap();
+    let val: bool = match &first {
+        TokenTree::Group(group) => check_version(config, ver, &mut group.stream().into_iter()),
+        TokenTree::Ident(ident) => {
+            let key = config
+                .fields
+                .iter()
+                .position(|&r| r == ident.to_string())
+                .unwrap_or_else(|| panic!("Unknown field {}", ident));
+            let mut operator = expect_punct(it);
+            let mut rhs_token = it.next().unwrap();
+            if let TokenTree::Punct(punct) = &rhs_token {
+                operator.extend(std::iter::once(punct.as_char()));
+                rhs_token = it.next().unwrap();
+            }
+            let rhs_name = if let TokenTree::Ident(ident) = &rhs_token {
+                ident.to_string()
+            } else {
+                panic!("Unexpected token {}", ident)
+            };
+
+            let rhs = config.enums[key]
+                .iter()
+                .position(|&r| r == rhs_name)
+                .unwrap_or_else(|| panic!("Unknown value for {}:{}", ident, rhs_name));
+            let lhs = ver[key];
+
+            match operator.as_str() {
+                "==" => lhs == rhs,
+                "!=" => lhs != rhs,
+                ">" => lhs > rhs,
+                ">=" => lhs >= rhs,
+                "<" => lhs < rhs,
+                "<=" => lhs <= rhs,
+                _ => panic!("Unknown operator {}", operator),
+            }
+        }
+        _ => {
+            panic!("Unknown token {}", first)
+        }
+    };
+
+    let boolop = it.next();
+    match boolop {
+        Some(TokenTree::Punct(punct)) => {
+            let right = expect_punct(it);
+            if right != punct.to_string() {
+                panic!("Unexpected op {}{}", punct, right);
+            }
+            match punct.as_char() {
+                '&' => val && check_version(config, ver, it),
+                '|' => val || check_version(config, ver, it),
+                _ => panic!("Unexpected op {}{}", right, right),
+            }
+        }
+        Some(a) => panic!("Unexpected op {}", a),
+        None => val,
+    }
+}
+
+fn filter_versions(
+    config: &VersionConfig,
+    tag: &str,
+    ver: &[usize],
+    tree: impl IntoIterator<Item = TokenTree>,
+    is_struct: bool,
+) -> Vec<TokenTree> {
+    let mut out = Vec::<TokenTree>::new();
+    let mut it = tree.into_iter();
+
+    while let Some(token) = it.next() {
+        let mut tail: Option<TokenTree> = None;
+        match &token {
+            TokenTree::Punct(punct) if punct.to_string() == "#" => {
+                let group = expect_group(&mut it);
+                let mut grp_it = group.stream().into_iter();
+                let attr = grp_it.next().unwrap();
+                match attr {
+                    TokenTree::Ident(ident) if ident.to_string() == "ver" => {
+                        if check_version(config, ver, &mut grp_it) {
+                        } else if is_struct {
+                            drop_until_punct(&mut it, ",", true);
+                        } else {
+                            let first = it.next().unwrap();
+                            match &first {
+                                TokenTree::Ident(ident)
+                                    if ["while", "for", "loop", "if", "match", "unsafe", "fn"]
+                                        .contains(&ident.to_string().as_str()) =>
+                                {
+                                    drop_until_braces(&mut it);
+                                }
+                                TokenTree::Group(_) => (),
+                                _ => {
+                                    drop_until_punct(&mut it, ",;", false);
+                                }
+                            }
+                        }
+                    }
+                    _ => {
+                        out.push(token.clone());
+                        out.push(TokenTree::Group(group.clone()));
+                    }
+                }
+                continue;
+            }
+            TokenTree::Punct(punct) if punct.to_string() == ":" => {
+                let next = it.next();
+                match next {
+                    Some(TokenTree::Punct(punct)) if punct.to_string() == ":" => {
+                        let next = it.next();
+                        match next {
+                            Some(TokenTree::Ident(idtag)) if idtag.to_string() == "ver" => {
+                                let ident = match out.pop() {
+                                    Some(TokenTree::Ident(ident)) => ident,
+                                    a => panic!("$ver not following ident: {:?}", a),
+                                };
+                                let name = ident.to_string() + tag;
+                                let new_ident = Ident::new(name.as_str(), ident.span());
+                                out.push(TokenTree::Ident(new_ident));
+                                continue;
+                            }
+                            Some(a) => {
+                                out.push(token.clone());
+                                out.push(token.clone());
+                                tail = Some(a);
+                            }
+                            None => {
+                                out.push(token.clone());
+                                out.push(token.clone());
+                            }
+                        }
+                    }
+                    Some(a) => {
+                        out.push(token.clone());
+                        tail = Some(a);
+                    }
+                    None => {
+                        out.push(token.clone());
+                        continue;
+                    }
+                }
+            }
+            _ => {
+                tail = Some(token);
+            }
+        }
+        match &tail {
+            Some(TokenTree::Group(group)) => {
+                let new_body =
+                    filter_versions(config, tag, ver, &mut group.stream().into_iter(), is_struct);
+                let mut stream = TokenStream::new();
+                stream.extend(new_body);
+                let mut filtered_group = Group::new(group.delimiter(), stream);
+                filtered_group.set_span(group.span());
+                out.push(TokenTree::Group(filtered_group));
+            }
+            Some(token) => {
+                out.push(token.clone());
+            }
+            None => {}
+        }
+    }
+
+    out
+}
+
+pub(crate) fn versions(attr: TokenStream, item: TokenStream) -> TokenStream {
+    let config = match attr.to_string().as_str() {
+        "AGX" => &AGX_VERSIONS,
+        _ => panic!("Unknown version group {}", attr),
+    };
+
+    let mut it = item.into_iter();
+    let mut out = TokenStream::new();
+    let mut body: Vec<TokenTree> = Vec::new();
+    let mut is_struct = false;
+
+    while let Some(token) = it.next() {
+        match token {
+            TokenTree::Punct(punct) if punct.to_string() == "#" => {
+                body.push(TokenTree::Punct(punct));
+                body.push(it.next().unwrap());
+            }
+            TokenTree::Ident(ident)
+                if ["struct", "enum", "union", "const", "type"]
+                    .contains(&ident.to_string().as_str()) =>
+            {
+                is_struct = ident.to_string() != "const";
+                body.push(TokenTree::Ident(ident));
+                body.push(it.next().unwrap());
+                // This isn't valid syntax in a struct definition, so add it for the user
+                body.push(TokenTree::Punct(Punct::new(':', Spacing::Joint)));
+                body.push(TokenTree::Punct(Punct::new(':', Spacing::Alone)));
+                body.push(TokenTree::Ident(Ident::new("ver", Span::call_site())));
+                break;
+            }
+            TokenTree::Ident(ident) if ident.to_string() == "impl" => {
+                body.push(TokenTree::Ident(ident));
+                break;
+            }
+            TokenTree::Ident(ident) if ident.to_string() == "fn" => {
+                body.push(TokenTree::Ident(ident));
+                break;
+            }
+            _ => {
+                body.push(token);
+            }
+        }
+    }
+
+    body.extend(it);
+
+    for ver in config.versions {
+        let tag = ver.join("");
+        let mut ver_num = Vec::<usize>::new();
+        for (i, comp) in ver.iter().enumerate() {
+            let idx = config.enums[i].iter().position(|&r| r == *comp).unwrap();
+            ver_num.push(idx);
+        }
+        out.extend(filter_versions(
+            config,
+            &tag,
+            &ver_num,
+            body.clone(),
+            is_struct,
+        ));
+    }
+
+    out
+}

From 1092d7b9ceb90c0ea780621e268a33edc042402a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Feb 2023 00:31:51 +0900
Subject: [PATCH 0895/1027] drm/asahi: Add the Asahi driver for Apple AGX GPUs

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/Kconfig                |    2 +
 drivers/gpu/drm/Makefile               |    1 +
 drivers/gpu/drm/asahi/Kconfig          |   40 +
 drivers/gpu/drm/asahi/Makefile         |    3 +
 drivers/gpu/drm/asahi/alloc.rs         | 1058 ++++++++++++++++++
 drivers/gpu/drm/asahi/asahi.rs         |   52 +
 drivers/gpu/drm/asahi/buffer.rs        |  775 +++++++++++++
 drivers/gpu/drm/asahi/channel.rs       |  567 ++++++++++
 drivers/gpu/drm/asahi/debug.rs         |  131 +++
 drivers/gpu/drm/asahi/driver.rs        |  176 +++
 drivers/gpu/drm/asahi/event.rs         |  233 ++++
 drivers/gpu/drm/asahi/file.rs          |  763 +++++++++++++
 drivers/gpu/drm/asahi/float.rs         |  383 +++++++
 drivers/gpu/drm/asahi/fw/buffer.rs     |  180 +++
 drivers/gpu/drm/asahi/fw/channels.rs   |  406 +++++++
 drivers/gpu/drm/asahi/fw/compute.rs    |  114 ++
 drivers/gpu/drm/asahi/fw/event.rs      |  100 ++
 drivers/gpu/drm/asahi/fw/fragment.rs   |  285 +++++
 drivers/gpu/drm/asahi/fw/initdata.rs   | 1325 ++++++++++++++++++++++
 drivers/gpu/drm/asahi/fw/job.rs        |  114 ++
 drivers/gpu/drm/asahi/fw/microseq.rs   |  399 +++++++
 drivers/gpu/drm/asahi/fw/mod.rs        |   15 +
 drivers/gpu/drm/asahi/fw/types.rs      |  212 ++++
 drivers/gpu/drm/asahi/fw/vertex.rs     |  183 +++
 drivers/gpu/drm/asahi/fw/workqueue.rs  |  176 +++
 drivers/gpu/drm/asahi/gem.rs           |  274 +++++
 drivers/gpu/drm/asahi/gpu.rs           | 1385 +++++++++++++++++++++++
 drivers/gpu/drm/asahi/hw/mod.rs        |  664 +++++++++++
 drivers/gpu/drm/asahi/hw/t600x.rs      |  160 +++
 drivers/gpu/drm/asahi/hw/t602x.rs      |  167 +++
 drivers/gpu/drm/asahi/hw/t8103.rs      |   93 ++
 drivers/gpu/drm/asahi/hw/t8112.rs      |  106 ++
 drivers/gpu/drm/asahi/initdata.rs      |  901 +++++++++++++++
 drivers/gpu/drm/asahi/mem.rs           |  138 +++
 drivers/gpu/drm/asahi/microseq.rs      |   61 +
 drivers/gpu/drm/asahi/mmu.rs           | 1262 +++++++++++++++++++++
 drivers/gpu/drm/asahi/object.rs        |  711 ++++++++++++
 drivers/gpu/drm/asahi/queue/common.rs  |   58 +
 drivers/gpu/drm/asahi/queue/compute.rs |  413 +++++++
 drivers/gpu/drm/asahi/queue/mod.rs     |  775 +++++++++++++
 drivers/gpu/drm/asahi/queue/render.rs  | 1424 ++++++++++++++++++++++++
 drivers/gpu/drm/asahi/regs.rs          |  475 ++++++++
 drivers/gpu/drm/asahi/slotalloc.rs     |  295 +++++
 drivers/gpu/drm/asahi/util.rs          |   44 +
 drivers/gpu/drm/asahi/workqueue.rs     |  905 +++++++++++++++
 rust/kernel/sync/lock.rs               |    3 +-
 rust/macros/versions.rs                |    8 +-
 47 files changed, 18010 insertions(+), 5 deletions(-)
 create mode 100644 drivers/gpu/drm/asahi/Kconfig
 create mode 100644 drivers/gpu/drm/asahi/Makefile
 create mode 100644 drivers/gpu/drm/asahi/alloc.rs
 create mode 100644 drivers/gpu/drm/asahi/asahi.rs
 create mode 100644 drivers/gpu/drm/asahi/buffer.rs
 create mode 100644 drivers/gpu/drm/asahi/channel.rs
 create mode 100644 drivers/gpu/drm/asahi/debug.rs
 create mode 100644 drivers/gpu/drm/asahi/driver.rs
 create mode 100644 drivers/gpu/drm/asahi/event.rs
 create mode 100644 drivers/gpu/drm/asahi/file.rs
 create mode 100644 drivers/gpu/drm/asahi/float.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/buffer.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/channels.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/compute.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/event.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/fragment.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/initdata.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/job.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/microseq.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/mod.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/types.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/vertex.rs
 create mode 100644 drivers/gpu/drm/asahi/fw/workqueue.rs
 create mode 100644 drivers/gpu/drm/asahi/gem.rs
 create mode 100644 drivers/gpu/drm/asahi/gpu.rs
 create mode 100644 drivers/gpu/drm/asahi/hw/mod.rs
 create mode 100644 drivers/gpu/drm/asahi/hw/t600x.rs
 create mode 100644 drivers/gpu/drm/asahi/hw/t602x.rs
 create mode 100644 drivers/gpu/drm/asahi/hw/t8103.rs
 create mode 100644 drivers/gpu/drm/asahi/hw/t8112.rs
 create mode 100644 drivers/gpu/drm/asahi/initdata.rs
 create mode 100644 drivers/gpu/drm/asahi/mem.rs
 create mode 100644 drivers/gpu/drm/asahi/microseq.rs
 create mode 100644 drivers/gpu/drm/asahi/mmu.rs
 create mode 100644 drivers/gpu/drm/asahi/object.rs
 create mode 100644 drivers/gpu/drm/asahi/queue/common.rs
 create mode 100644 drivers/gpu/drm/asahi/queue/compute.rs
 create mode 100644 drivers/gpu/drm/asahi/queue/mod.rs
 create mode 100644 drivers/gpu/drm/asahi/queue/render.rs
 create mode 100644 drivers/gpu/drm/asahi/regs.rs
 create mode 100644 drivers/gpu/drm/asahi/slotalloc.rs
 create mode 100644 drivers/gpu/drm/asahi/util.rs
 create mode 100644 drivers/gpu/drm/asahi/workqueue.rs

diff --git a/drivers/gpu/drm/Kconfig b/drivers/gpu/drm/Kconfig
index 6b2c6b91f96250..fb5b347b86eef2 100644
--- a/drivers/gpu/drm/Kconfig
+++ b/drivers/gpu/drm/Kconfig
@@ -340,6 +340,8 @@ config DRM_VGEM
 
 source "drivers/gpu/drm/vkms/Kconfig"
 
+source "drivers/gpu/drm/asahi/Kconfig"
+
 source "drivers/gpu/drm/exynos/Kconfig"
 
 source "drivers/gpu/drm/rockchip/Kconfig"
diff --git a/drivers/gpu/drm/Makefile b/drivers/gpu/drm/Makefile
index fa432a1ac9e2b7..54d1f97c92c073 100644
--- a/drivers/gpu/drm/Makefile
+++ b/drivers/gpu/drm/Makefile
@@ -218,3 +218,4 @@ obj-y			+= solomon/
 obj-$(CONFIG_DRM_SPRD) += sprd/
 obj-$(CONFIG_DRM_LOONGSON) += loongson/
 obj-$(CONFIG_DRM_POWERVR) += imagination/
+obj-$(CONFIG_DRM_ASAHI) += asahi/
diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig
new file mode 100644
index 00000000000000..4ce4a773db5d43
--- /dev/null
+++ b/drivers/gpu/drm/asahi/Kconfig
@@ -0,0 +1,40 @@
+# SPDX-License-Identifier: GPL-2.0
+
+config RUST_DRM_SCHED
+	bool
+	select DRM_SCHED
+
+config RUST_DRM_GEM_SHMEM_HELPER
+	bool
+	select DRM_GEM_SHMEM_HELPER
+
+config RUST_APPLE_RTKIT
+	bool
+	select APPLE_RTKIT
+	select APPLE_MBOX
+
+config DRM_ASAHI
+	tristate "Asahi (DRM support for Apple AGX GPUs)"
+	depends on RUST
+	depends on DRM
+	depends on (ARM64 && ARCH_APPLE) || (COMPILE_TEST && !GENERIC_ATOMIC64)
+	depends on MMU
+	select RUST_DRM_SCHED
+	select IOMMU_SUPPORT
+	select IOMMU_IO_PGTABLE_LPAE
+	select RUST_DRM_GEM_SHMEM_HELPER
+	select RUST_APPLE_RTKIT
+	help
+	  DRM driver for Apple AGX GPUs (G13x, found in the M1 SoC family)
+
+config DRM_ASAHI_DEBUG_ALLOCATOR
+	bool "Use debug allocator"
+	depends on DRM_ASAHI
+	help
+	  Use an alternate, simpler allocator which significantly reduces
+	  performance, but can help find firmware- or GPU-side memory safety
+	  issues. However, it can also trigger firmware bugs more easily,
+	  so expect GPU crashes.
+
+	  Say N unless you are debugging firmware structures or porting to a
+	  new firmware version.
diff --git a/drivers/gpu/drm/asahi/Makefile b/drivers/gpu/drm/asahi/Makefile
new file mode 100644
index 00000000000000..e6724866798760
--- /dev/null
+++ b/drivers/gpu/drm/asahi/Makefile
@@ -0,0 +1,3 @@
+# SPDX-License-Identifier: GPL-2.0
+
+obj-$(CONFIG_DRM_ASAHI) += asahi.o
diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
new file mode 100644
index 00000000000000..f042cd7ebcbee0
--- /dev/null
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -0,0 +1,1058 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU kernel object allocator.
+//!
+//! This kernel driver needs to manage a large number of GPU objects, in both firmware/kernel
+//! address space and user address space. This module implements a simple grow-only heap allocator
+//! based on the DRM MM range allocator, and a debug allocator that allocates each object as a
+//! separate GEM object.
+//!
+//! Allocations may optionally have debugging enabled, which adds preambles that store metadata
+//! about the allocation. This is useful for live debugging using the hypervisor or postmortem
+//! debugging with a GPU memory snapshot, since it makes it easier to identify use-after-free and
+//! caching issues.
+
+use kernel::{drm::mm, error::Result, prelude::*, str::CString};
+
+use crate::debug::*;
+use crate::driver::{AsahiDevRef, AsahiDevice};
+use crate::fw::types::Zeroable;
+use crate::mmu;
+use crate::object::{GpuArray, GpuObject, GpuOnlyArray, GpuStruct, GpuWeakPointer};
+
+use core::cmp::Ordering;
+use core::fmt;
+use core::fmt::{Debug, Formatter};
+use core::marker::PhantomData;
+use core::mem;
+use core::mem::MaybeUninit;
+use core::ptr::NonNull;
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Alloc;
+
+#[cfg(not(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR))]
+/// The driver-global allocator type
+pub(crate) type DefaultAllocator = HeapAllocator;
+
+#[cfg(not(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR))]
+/// The driver-global allocation type
+pub(crate) type DefaultAllocation = HeapAllocation;
+
+#[cfg(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR)]
+/// The driver-global allocator type
+pub(crate) type DefaultAllocator = SimpleAllocator;
+
+#[cfg(CONFIG_DRM_ASAHI_DEBUG_ALLOCATOR)]
+/// The driver-global allocation type
+pub(crate) type DefaultAllocation = SimpleAllocation;
+
+/// Represents a raw allocation (without any type information).
+pub(crate) trait RawAllocation {
+    /// Returns the CPU-side pointer (if CPU mapping is enabled) as a byte non-null pointer.
+    fn ptr(&self) -> Option<NonNull<u8>>;
+    /// Returns the GPU VA pointer as a u64.
+    fn gpu_ptr(&self) -> u64;
+    /// Returns the size of the allocation in bytes.
+    fn size(&self) -> usize;
+    /// Returns the AsahiDevice that owns this allocation.
+    fn device(&self) -> &AsahiDevice;
+}
+
+/// Represents a typed allocation.
+pub(crate) trait Allocation<T>: Debug {
+    /// Returns the typed CPU-side pointer (if CPU mapping is enabled).
+    fn ptr(&self) -> Option<NonNull<T>>;
+    /// Returns the GPU VA pointer as a u64.
+    fn gpu_ptr(&self) -> u64;
+    /// Returns the size of the allocation in bytes.
+    fn size(&self) -> usize;
+    /// Returns the AsahiDevice that owns this allocation.
+    fn device(&self) -> &AsahiDevice;
+}
+
+/// A generic typed allocation wrapping a RawAllocation.
+///
+/// This is currently the only Allocation implementation, since it is shared by all allocators.
+pub(crate) struct GenericAlloc<T, U: RawAllocation> {
+    alloc: U,
+    alloc_size: usize,
+    debug_offset: usize,
+    padding: usize,
+    _p: PhantomData<T>,
+}
+
+impl<T, U: RawAllocation> Allocation<T> for GenericAlloc<T, U> {
+    fn ptr(&self) -> Option<NonNull<T>> {
+        self.alloc
+            .ptr()
+            .map(|p| unsafe { NonNull::new_unchecked(p.as_ptr().add(self.debug_offset) as *mut T) })
+    }
+    fn gpu_ptr(&self) -> u64 {
+        self.alloc.gpu_ptr() + self.debug_offset as u64
+    }
+    fn size(&self) -> usize {
+        self.alloc_size
+    }
+    fn device(&self) -> &AsahiDevice {
+        self.alloc.device()
+    }
+}
+
+impl<T, U: RawAllocation> Debug for GenericAlloc<T, U> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct(core::any::type_name::<GenericAlloc<T, U>>())
+            .field("ptr", &format_args!("{:?}", self.ptr()))
+            .field("gpu_ptr", &format_args!("{:#X?}", self.gpu_ptr()))
+            .field("size", &format_args!("{:#X?}", self.size()))
+            .finish()
+    }
+}
+
+/// Debugging data associated with an allocation, when debugging is enabled.
+#[repr(C)]
+struct AllocDebugData {
+    state: u32,
+    _pad: u32,
+    size: u64,
+    base_gpuva: u64,
+    obj_gpuva: u64,
+    name: [u8; 0x20],
+}
+
+/// Magic flag indicating a live allocation.
+const STATE_LIVE: u32 = 0x4556494c;
+/// Magic flag indicating a freed allocation.
+const STATE_DEAD: u32 = 0x44414544;
+
+/// Marker byte to identify when firmware/GPU write beyond the end of an allocation.
+const GUARD_MARKER: u8 = 0x93;
+
+impl<T, U: RawAllocation> Drop for GenericAlloc<T, U> {
+    fn drop(&mut self) {
+        let debug_len = mem::size_of::<AllocDebugData>();
+        if self.debug_offset >= debug_len {
+            if let Some(p) = self.alloc.ptr() {
+                unsafe {
+                    let p = p.as_ptr().add(self.debug_offset - debug_len);
+                    (p as *mut u32).write(STATE_DEAD);
+                }
+            }
+        }
+        if debug_enabled(DebugFlags::FillAllocations) {
+            if let Some(p) = self.ptr() {
+                unsafe { (p.as_ptr() as *mut u8).write_bytes(0xde, self.size()) };
+            }
+        }
+        if self.padding != 0 {
+            if let Some(p) = self.ptr() {
+                let guard = unsafe {
+                    core::slice::from_raw_parts(
+                        (p.as_ptr() as *mut u8 as *const u8).add(self.size()),
+                        self.padding,
+                    )
+                };
+                if let Some(first_err) = guard.iter().position(|&r| r != GUARD_MARKER) {
+                    let last_err = guard
+                        .iter()
+                        .rev()
+                        .position(|&r| r != GUARD_MARKER)
+                        .unwrap_or(0);
+                    dev_warn!(
+                        self.device(),
+                        "Allocator: Corruption after object of type {} at {:#x}:{:#x} + {:#x}..={:#x}\n",
+                        core::any::type_name::<T>(),
+                        self.gpu_ptr(),
+                        self.size(),
+                        first_err,
+                        self.padding - last_err - 1
+                    );
+                }
+            }
+        }
+    }
+}
+
+static_assert!(mem::size_of::<AllocDebugData>() == 0x40);
+
+/// A trait representing an allocator.
+pub(crate) trait Allocator {
+    /// The raw allocation type used by this allocator.
+    type Raw: RawAllocation;
+    // TODO: Needs associated_type_defaults
+    // type Allocation<T> = GenericAlloc<T, Self::Raw>;
+
+    /// Returns the `AsahiDevice` associated with this allocator.
+    fn device(&self) -> &AsahiDevice;
+    /// Returns whether CPU-side mapping is enabled.
+    fn cpu_maps(&self) -> bool;
+    /// Returns the minimum alignment for allocations.
+    fn min_align(&self) -> usize;
+    /// Allocate an object of the given size in bytes with the given alignment.
+    fn alloc(&mut self, size: usize, align: usize) -> Result<Self::Raw>;
+
+    /// Returns a tuple of (count, size) of how much garbage (freed but not yet reusable objects)
+    /// exists in this allocator. Optional.
+    fn garbage(&self) -> (usize, usize) {
+        (0, 0)
+    }
+    /// Collect garbage for this allocator, up to the given object count. Optional.
+    fn collect_garbage(&mut self, _count: usize) {}
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new`].
+    #[inline(never)]
+    fn new_object<T: GpuStruct>(
+        &mut self,
+        inner: T,
+        callback: impl for<'a> FnOnce(&'a T) -> T::Raw<'a>,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>> {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new(self.alloc_object()?, inner, callback)
+    }
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new_boxed`].
+    #[inline(never)]
+    fn new_boxed<T: GpuStruct>(
+        &mut self,
+        inner: Box<T>,
+        callback: impl for<'a> FnOnce(
+            &'a T,
+            &'a mut MaybeUninit<T::Raw<'a>>,
+        ) -> Result<&'a mut T::Raw<'a>>,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>> {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new_boxed(self.alloc_object()?, inner, callback)
+    }
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new_inplace`].
+    #[inline(never)]
+    fn new_inplace<T: GpuStruct>(
+        &mut self,
+        inner: T,
+        callback: impl for<'a> FnOnce(
+            &'a T,
+            &'a mut MaybeUninit<T::Raw<'a>>,
+        ) -> Result<&'a mut T::Raw<'a>>,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>> {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new_inplace(
+            self.alloc_object()?,
+            inner,
+            callback,
+        )
+    }
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new_default`].
+    #[inline(never)]
+    fn new_default<T: GpuStruct + Default>(
+        &mut self,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>>
+    where
+        for<'a> <T as GpuStruct>::Raw<'a>: Default + Zeroable,
+    {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new_default(self.alloc_object()?)
+    }
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new_init_prealloc`].
+    #[inline(never)]
+    fn new_init_prealloc<
+        'a,
+        T: GpuStruct,
+        I: Init<T, kernel::error::Error>,
+        R: PinInit<T::Raw<'a>, kernel::error::Error>,
+    >(
+        &mut self,
+        inner_init: impl FnOnce(GpuWeakPointer<T>) -> I,
+        raw_init: impl FnOnce(&'a T, GpuWeakPointer<T>) -> R,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>> {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new_init_prealloc(
+            self.alloc_object()?,
+            inner_init,
+            raw_init,
+        )
+    }
+
+    /// Allocate a new GpuStruct object. See [`GpuObject::new_init`].
+    #[inline(never)]
+    fn new_init<'a, T: GpuStruct, R: PinInit<T::Raw<'a>, F>, E, F>(
+        &mut self,
+        inner_init: impl Init<T, E>,
+        raw_init: impl FnOnce(&'a T, GpuWeakPointer<T>) -> R,
+    ) -> Result<GpuObject<T, GenericAlloc<T, Self::Raw>>>
+    where
+        kernel::error::Error: core::convert::From<E>,
+        kernel::error::Error: core::convert::From<F>,
+    {
+        GpuObject::<T, GenericAlloc<T, Self::Raw>>::new_init_prealloc(
+            self.alloc_object()?,
+            |_p| inner_init,
+            raw_init,
+        )
+    }
+
+    /// Allocate a generic buffer of the given size and alignment, applying the debug features if
+    /// enabled to tag it and detect overflows.
+    fn alloc_generic<T>(
+        &mut self,
+        size: usize,
+        align: usize,
+    ) -> Result<GenericAlloc<T, Self::Raw>> {
+        let padding = if debug_enabled(DebugFlags::DetectOverflows) {
+            size
+        } else {
+            0
+        };
+
+        let ret: GenericAlloc<T, Self::Raw> =
+            if self.cpu_maps() && debug_enabled(debug::DebugFlags::DebugAllocations) {
+                let debug_align = self.min_align().max(align);
+                let debug_len = mem::size_of::<AllocDebugData>();
+                let debug_offset = (debug_len * 2 + debug_align - 1) & !(debug_align - 1);
+
+                let alloc = self.alloc(size + debug_offset + padding, align)?;
+
+                let mut debug = AllocDebugData {
+                    state: STATE_LIVE,
+                    _pad: 0,
+                    size: size as u64,
+                    base_gpuva: alloc.gpu_ptr(),
+                    obj_gpuva: alloc.gpu_ptr() + debug_offset as u64,
+                    name: [0; 0x20],
+                };
+
+                let name = core::any::type_name::<T>().as_bytes();
+                let len = name.len().min(debug.name.len() - 1);
+                debug.name[..len].copy_from_slice(&name[..len]);
+
+                if let Some(p) = alloc.ptr() {
+                    unsafe {
+                        let p = p.as_ptr();
+                        p.write_bytes(0x42, debug_offset - 2 * debug_len);
+                        let cur = p.add(debug_offset - debug_len) as *mut AllocDebugData;
+                        let prev = p.add(debug_offset - 2 * debug_len) as *mut AllocDebugData;
+                        prev.copy_from(cur, 1);
+                        cur.copy_from(&debug, 1);
+                    };
+                }
+
+                GenericAlloc {
+                    alloc,
+                    alloc_size: size,
+                    debug_offset,
+                    padding,
+                    _p: PhantomData,
+                }
+            } else {
+                GenericAlloc {
+                    alloc: self.alloc(size + padding, align)?,
+                    alloc_size: size,
+                    debug_offset: 0,
+                    padding,
+                    _p: PhantomData,
+                }
+            };
+
+        if debug_enabled(DebugFlags::FillAllocations) {
+            if let Some(p) = ret.ptr() {
+                unsafe { (p.as_ptr() as *mut u8).write_bytes(0xaa, ret.size()) };
+            }
+        }
+
+        if padding != 0 {
+            if let Some(p) = ret.ptr() {
+                unsafe {
+                    (p.as_ptr() as *mut u8)
+                        .add(ret.size())
+                        .write_bytes(GUARD_MARKER, padding);
+                }
+            }
+        }
+
+        Ok(ret)
+    }
+
+    /// Allocate an object of a given type, without actually initializing the allocation.
+    ///
+    /// This is useful to directly call [`GpuObject::new_*`], without borrowing a reference to the
+    /// allocator for the entire duration (e.g. if further allocations need to happen inside the
+    /// callbacks).
+    fn alloc_object<T: GpuStruct>(&mut self) -> Result<GenericAlloc<T, Self::Raw>> {
+        let size = mem::size_of::<T::Raw<'static>>();
+        let align = mem::align_of::<T::Raw<'static>>();
+
+        self.alloc_generic(size, align)
+    }
+
+    /// Allocate an empty `GpuArray` of a given type and length.
+    fn array_empty<T: Sized + Default>(
+        &mut self,
+        count: usize,
+    ) -> Result<GpuArray<T, GenericAlloc<T, Self::Raw>>> {
+        let size = mem::size_of::<T>() * count;
+        let align = mem::align_of::<T>();
+
+        let alloc = self.alloc_generic(size, align)?;
+        GpuArray::<T, GenericAlloc<T, Self::Raw>>::empty(alloc, count)
+    }
+
+    /// Allocate an empty `GpuOnlyArray` of a given type and length.
+    fn array_gpuonly<T: Sized + Default>(
+        &mut self,
+        count: usize,
+    ) -> Result<GpuOnlyArray<T, GenericAlloc<T, Self::Raw>>> {
+        let size = mem::size_of::<T>() * count;
+        let align = mem::align_of::<T>();
+
+        let alloc = self.alloc_generic(size, align)?;
+        GpuOnlyArray::<T, GenericAlloc<T, Self::Raw>>::new(alloc, count)
+    }
+}
+
+/// A simple allocation backed by a separate GEM object.
+///
+/// # Invariants
+/// `ptr` is either None or a valid, non-null pointer to the CPU view of the object.
+/// `gpu_ptr` is the GPU-side VA of the object.
+pub(crate) struct SimpleAllocation {
+    dev: AsahiDevRef,
+    ptr: Option<NonNull<u8>>,
+    gpu_ptr: u64,
+    size: usize,
+    vm: mmu::Vm,
+    obj: crate::gem::ObjectRef,
+}
+
+/// SAFETY: `SimpleAllocation` just points to raw memory and should be safe to send across threads.
+unsafe impl Send for SimpleAllocation {}
+unsafe impl Sync for SimpleAllocation {}
+
+impl Drop for SimpleAllocation {
+    fn drop(&mut self) {
+        mod_dev_dbg!(
+            self.device(),
+            "SimpleAllocator: drop object @ {:#x}\n",
+            self.gpu_ptr()
+        );
+        if debug_enabled(DebugFlags::FillAllocations) {
+            if let Ok(vmap) = self.obj.vmap() {
+                vmap.as_mut_slice().fill(0x42);
+            }
+        }
+        self.obj.drop_vm_mappings(self.vm.id());
+    }
+}
+
+impl RawAllocation for SimpleAllocation {
+    fn ptr(&self) -> Option<NonNull<u8>> {
+        self.ptr
+    }
+    fn gpu_ptr(&self) -> u64 {
+        self.gpu_ptr
+    }
+    fn size(&self) -> usize {
+        self.size
+    }
+
+    fn device(&self) -> &AsahiDevice {
+        &self.dev
+    }
+}
+
+/// A simple allocator that allocates each object as its own GEM object, aligned to the end of a
+/// page.
+///
+/// This is very slow, but it has the advantage that over-reads by the firmware or GPU will fault on
+/// the guard page after the allocation, which can be useful to validate that the firmware's or
+/// GPU's idea of object size what we expect.
+pub(crate) struct SimpleAllocator {
+    dev: AsahiDevRef,
+    start: u64,
+    end: u64,
+    prot: u32,
+    vm: mmu::Vm,
+    min_align: usize,
+    cpu_maps: bool,
+}
+
+impl SimpleAllocator {
+    /// Create a new `SimpleAllocator` for a given address range and `Vm`.
+    #[allow(dead_code)]
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        vm: &mmu::Vm,
+        start: u64,
+        end: u64,
+        min_align: usize,
+        prot: u32,
+        _block_size: usize,
+        mut cpu_maps: bool,
+        _name: fmt::Arguments<'_>,
+        _keep_garbage: bool,
+    ) -> Result<SimpleAllocator> {
+        if debug_enabled(DebugFlags::ForceCPUMaps) {
+            cpu_maps = true;
+        }
+        Ok(SimpleAllocator {
+            dev: dev.into(),
+            vm: vm.clone(),
+            start,
+            end,
+            prot,
+            min_align,
+            cpu_maps,
+        })
+    }
+}
+
+impl Allocator for SimpleAllocator {
+    type Raw = SimpleAllocation;
+
+    fn device(&self) -> &AsahiDevice {
+        &self.dev
+    }
+
+    fn cpu_maps(&self) -> bool {
+        self.cpu_maps
+    }
+
+    fn min_align(&self) -> usize {
+        self.min_align
+    }
+
+    #[inline(never)]
+    fn alloc(&mut self, size: usize, align: usize) -> Result<SimpleAllocation> {
+        let size_aligned = (size + mmu::UAT_PGSZ - 1) & !mmu::UAT_PGMSK;
+        let align = self.min_align.max(align);
+        let offset = (size_aligned - size) & !(align - 1);
+
+        mod_dev_dbg!(
+            &self.dev,
+            "SimpleAllocator::new: size={:#x} size_al={:#x} al={:#x} off={:#x}\n",
+            size,
+            size_aligned,
+            align,
+            offset
+        );
+
+        let mut obj = crate::gem::new_kernel_object(&self.dev, size_aligned)?;
+        let p = obj.vmap()?.as_mut_ptr() as *mut u8;
+        if debug_enabled(DebugFlags::FillAllocations) {
+            obj.vmap()?.as_mut_slice().fill(0xde);
+        }
+        let iova = obj.map_into_range(
+            &self.vm,
+            self.start,
+            self.end,
+            self.min_align.max(mmu::UAT_PGSZ) as u64,
+            self.prot,
+            true,
+        )?;
+
+        let ptr = unsafe { p.add(offset) } as *mut u8;
+        let gpu_ptr = (iova + offset) as u64;
+
+        mod_dev_dbg!(
+            &self.dev,
+            "SimpleAllocator::new -> {:#?} / {:#?} | {:#x} / {:#x}\n",
+            p,
+            ptr,
+            iova,
+            gpu_ptr
+        );
+
+        Ok(SimpleAllocation {
+            dev: self.dev.clone(),
+            ptr: NonNull::new(ptr),
+            gpu_ptr,
+            size,
+            vm: self.vm.clone(),
+            obj,
+        })
+    }
+}
+
+/// Inner data for an allocation from the heap allocator.
+///
+/// This is wrapped in an `mm::Node`.
+pub(crate) struct HeapAllocationInner {
+    dev: AsahiDevRef,
+    ptr: Option<NonNull<u8>>,
+    real_size: usize,
+}
+
+/// SAFETY: `SimpleAllocation` just points to raw memory and should be safe to send across threads.
+unsafe impl Send for HeapAllocationInner {}
+unsafe impl Sync for HeapAllocationInner {}
+
+/// Outer view of a heap allocation.
+///
+/// This uses an Option<> so we can move the internal `Node` into the garbage pool when it gets
+/// dropped.
+///
+/// # Invariants
+/// The `Option` must always be `Some(...)` while this object is alive.
+pub(crate) struct HeapAllocation(Option<mm::Node<HeapAllocatorInner, HeapAllocationInner>>);
+
+impl Drop for HeapAllocation {
+    fn drop(&mut self) {
+        let node = self.0.take().unwrap();
+        let size = node.size();
+        let alloc = node.alloc_ref();
+
+        alloc.with(|a| {
+            if let Some(garbage) = a.garbage.as_mut() {
+                if garbage.push(node, GFP_KERNEL).is_err() {
+                    dev_err!(
+                        &a.dev,
+                        "HeapAllocation[{}]::drop: Failed to keep garbage\n",
+                        &*a.name,
+                    );
+                }
+                a.total_garbage += size as usize;
+                None
+            } else {
+                // We need to ensure node survives this scope, since dropping it
+                // will try to take the mm lock and deadlock us
+                Some(node)
+            }
+        });
+    }
+}
+
+impl mm::AllocInner<HeapAllocationInner> for HeapAllocatorInner {
+    fn drop_object(
+        &mut self,
+        start: u64,
+        _size: u64,
+        _color: usize,
+        obj: &mut HeapAllocationInner,
+    ) {
+        /* real_size == 0 means it's a guard node */
+        if obj.real_size > 0 {
+            mod_dev_dbg!(
+                obj.dev,
+                "HeapAllocator[{}]: drop object @ {:#x} ({} bytes)\n",
+                &*self.name,
+                start,
+                obj.real_size,
+            );
+            self.allocated -= obj.real_size;
+        }
+    }
+}
+
+impl RawAllocation for HeapAllocation {
+    // SAFETY: This function must always return a valid pointer.
+    // Since the HeapAllocation contains a reference to the
+    // backing_objects array that contains the object backing this pointer,
+    // and objects are only ever added to it, this pointer is guaranteed to
+    // remain valid for the lifetime of the HeapAllocation.
+    fn ptr(&self) -> Option<NonNull<u8>> {
+        self.0.as_ref().unwrap().ptr
+    }
+    // SAFETY: This function must always return a valid GPU pointer.
+    // See the explanation in ptr().
+    fn gpu_ptr(&self) -> u64 {
+        self.0.as_ref().unwrap().start()
+    }
+    fn size(&self) -> usize {
+        self.0.as_ref().unwrap().size() as usize
+    }
+    fn device(&self) -> &AsahiDevice {
+        &self.0.as_ref().unwrap().dev
+    }
+}
+
+/// Inner data for a heap allocator which uses the DRM MM range allocator to manage the heap.
+///
+/// This is wrapped by an `mm::Allocator`.
+struct HeapAllocatorInner {
+    dev: AsahiDevRef,
+    allocated: usize,
+    backing_objects: Vec<(crate::gem::ObjectRef, u64)>,
+    garbage: Option<Vec<mm::Node<HeapAllocatorInner, HeapAllocationInner>>>,
+    total_garbage: usize,
+    name: CString,
+    vm_id: u64,
+}
+
+/// A heap allocator which uses the DRM MM range allocator to manage its objects.
+///
+/// The heap is composed of a series of GEM objects. This implementation only ever grows the heap,
+/// never shrinks it.
+pub(crate) struct HeapAllocator {
+    dev: AsahiDevRef,
+    start: u64,
+    end: u64,
+    top: u64,
+    prot: u32,
+    vm: mmu::Vm,
+    min_align: usize,
+    block_size: usize,
+    cpu_maps: bool,
+    guard_nodes: Vec<mm::Node<HeapAllocatorInner, HeapAllocationInner>>,
+    mm: mm::Allocator<HeapAllocatorInner, HeapAllocationInner>,
+    name: CString,
+}
+
+impl HeapAllocator {
+    /// Create a new HeapAllocator for a given `Vm` and address range.
+    #[allow(dead_code)]
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        vm: &mmu::Vm,
+        start: u64,
+        end: u64,
+        min_align: usize,
+        prot: u32,
+        block_size: usize,
+        mut cpu_maps: bool,
+        name: fmt::Arguments<'_>,
+        keep_garbage: bool,
+    ) -> Result<HeapAllocator> {
+        if !min_align.is_power_of_two() {
+            return Err(EINVAL);
+        }
+        if debug_enabled(DebugFlags::ForceCPUMaps) {
+            cpu_maps = true;
+        }
+
+        let name = CString::try_from_fmt(name)?;
+
+        let inner = HeapAllocatorInner {
+            dev: dev.into(),
+            allocated: 0,
+            backing_objects: Vec::new(),
+            // TODO: This clearly needs a try_clone() or similar
+            name: CString::try_from_fmt(fmt!("{}", &*name))?,
+            vm_id: vm.id(),
+            garbage: if keep_garbage { Some(Vec::new()) } else { None },
+            total_garbage: 0,
+        };
+
+        let mm = mm::Allocator::new(start, end - start + 1, inner)?;
+
+        Ok(HeapAllocator {
+            dev: dev.into(),
+            vm: vm.clone(),
+            start,
+            end,
+            top: start,
+            prot,
+            min_align,
+            block_size: block_size.max(min_align),
+            cpu_maps,
+            guard_nodes: Vec::new(),
+            mm,
+            name,
+        })
+    }
+
+    /// Add a new backing block of the given size to this heap.
+    ///
+    /// If CPU mapping is enabled, this also adds a guard node to the range allocator to ensure that
+    /// objects cannot straddle backing block boundaries, since we cannot easily create a contiguous
+    /// CPU VA mapping for them. This can create some fragmentation. If CPU mapping is disabled, we
+    /// skip the guard blocks, since the GPU view of the heap is always contiguous.
+    fn add_block(&mut self, size: usize) -> Result {
+        let size_aligned = (size + mmu::UAT_PGSZ - 1) & !mmu::UAT_PGMSK;
+
+        mod_dev_dbg!(
+            &self.dev,
+            "HeapAllocator[{}]::add_block: size={:#x} size_al={:#x}\n",
+            &*self.name,
+            size,
+            size_aligned,
+        );
+
+        if self.top.saturating_add(size_aligned as u64) >= self.end {
+            dev_err!(
+                &self.dev,
+                "HeapAllocator[{}]::add_block: Exhausted VA space\n",
+                &*self.name,
+            );
+        }
+
+        let mut obj = crate::gem::new_kernel_object(&self.dev, size_aligned)?;
+        if self.cpu_maps && debug_enabled(DebugFlags::FillAllocations) {
+            obj.vmap()?.as_mut_slice().fill(0xde);
+        }
+
+        let gpu_ptr = self.top;
+        if let Err(e) = obj.map_at(&self.vm, gpu_ptr, self.prot, self.cpu_maps) {
+            dev_err!(
+                &self.dev,
+                "HeapAllocator[{}]::add_block: Failed to map at {:#x} ({:?})\n",
+                &*self.name,
+                gpu_ptr,
+                e
+            );
+            return Err(e);
+        }
+
+        self.mm
+            .with_inner(|inner| inner.backing_objects.reserve(1, GFP_KERNEL))?;
+
+        let mut new_top = self.top + size_aligned as u64;
+        if self.cpu_maps {
+            let guard = self.min_align.max(mmu::UAT_PGSZ);
+            mod_dev_dbg!(
+                &self.dev,
+                "HeapAllocator[{}]::add_block: Adding guard node {:#x}:{:#x}\n",
+                &*self.name,
+                new_top,
+                guard
+            );
+
+            let inner = HeapAllocationInner {
+                dev: self.dev.clone(),
+                ptr: None,
+                real_size: 0,
+            };
+
+            let node = match self.mm.reserve_node(inner, new_top, guard as u64, 0) {
+                Ok(a) => a,
+                Err(a) => {
+                    dev_err!(
+                        &self.dev,
+                        "HeapAllocator[{}]::add_block: Failed to reserve guard node {:#x}:{:#x}: {:?}\n",
+                        &*self.name,
+                        guard,
+                        new_top,
+                        a
+                    );
+                    return Err(EIO);
+                }
+            };
+
+            self.guard_nodes.push(node, GFP_KERNEL)?;
+
+            new_top += guard as u64;
+        }
+        mod_dev_dbg!(
+            &self.dev,
+            "HeapAllocator[{}]::add_block: top={:#x}\n",
+            &*self.name,
+            new_top
+        );
+
+        self.mm
+            .with_inner(|inner| inner.backing_objects.try_push((obj, gpu_ptr)))?;
+
+        self.top = new_top;
+
+        cls_dev_dbg!(
+            MemStats,
+            &self.dev,
+            "{} Heap: grow to {} bytes\n",
+            &*self.name,
+            self.top - self.start
+        );
+
+        Ok(())
+    }
+
+    /// Find the backing object index that backs a given GPU address.
+    fn find_obj(&mut self, addr: u64) -> Result<usize> {
+        self.mm.with_inner(|inner| {
+            inner
+                .backing_objects
+                .binary_search_by(|obj| {
+                    let start = obj.1;
+                    let end = obj.1 + obj.0.size() as u64;
+                    if start > addr {
+                        Ordering::Greater
+                    } else if end <= addr {
+                        Ordering::Less
+                    } else {
+                        Ordering::Equal
+                    }
+                })
+                .or(Err(ENOENT))
+        })
+    }
+}
+
+impl Allocator for HeapAllocator {
+    type Raw = HeapAllocation;
+
+    fn device(&self) -> &AsahiDevice {
+        &self.dev
+    }
+
+    fn cpu_maps(&self) -> bool {
+        self.cpu_maps
+    }
+
+    fn min_align(&self) -> usize {
+        self.min_align
+    }
+
+    fn alloc(&mut self, size: usize, align: usize) -> Result<HeapAllocation> {
+        if align != 0 && !align.is_power_of_two() {
+            return Err(EINVAL);
+        }
+        let align = self.min_align.max(align);
+        let size_aligned = (size + align - 1) & !(align - 1);
+
+        mod_dev_dbg!(
+            &self.dev,
+            "HeapAllocator[{}]::new: size={:#x} size_al={:#x}\n",
+            &*self.name,
+            size,
+            size_aligned,
+        );
+
+        let inner = HeapAllocationInner {
+            dev: self.dev.clone(),
+            ptr: None,
+            real_size: size,
+        };
+
+        let mut node = match self.mm.insert_node_generic(
+            inner,
+            size_aligned as u64,
+            align as u64,
+            0,
+            mm::InsertMode::Best,
+        ) {
+            Ok(a) => a,
+            Err(a) => {
+                dev_err!(
+                    &self.dev,
+                    "HeapAllocator[{}]::new: Failed to insert node of size {:#x} / align {:#x}: {:?}\n",
+                    &*self.name, size_aligned, align, a
+                );
+                return Err(a);
+            }
+        };
+
+        self.mm.with_inner(|inner| inner.allocated += size);
+
+        let mut new_object = false;
+        let start = node.start();
+        let end = start + node.size();
+        if end > self.top {
+            if start > self.top {
+                dev_warn!(
+                    self.dev,
+                    "HeapAllocator[{}]::alloc: top={:#x}, start={:#x}\n",
+                    &*self.name,
+                    self.top,
+                    start
+                );
+            }
+            let block_size = self.block_size.max((end - self.top) as usize);
+            self.add_block(block_size)?;
+            new_object = true;
+        }
+        assert!(end <= self.top);
+
+        if self.cpu_maps {
+            mod_dev_dbg!(
+                self.dev,
+                "HeapAllocator[{}]::alloc: mapping to CPU\n",
+                &*self.name
+            );
+
+            let idx = if new_object {
+                None
+            } else {
+                Some(match self.find_obj(start) {
+                    Ok(a) => a,
+                    Err(_) => {
+                        dev_warn!(
+                            self.dev,
+                            "HeapAllocator[{}]::alloc: Failed to find object at {:#x}\n",
+                            &*self.name,
+                            start
+                        );
+                        return Err(EIO);
+                    }
+                })
+            };
+            let (obj_start, obj_size, p) = self.mm.with_inner(|inner| -> Result<_> {
+                let idx = idx.unwrap_or(inner.backing_objects.len() - 1);
+                let obj = &mut inner.backing_objects[idx];
+                let p = obj.0.vmap()?.as_mut_ptr() as *mut u8;
+                Ok((obj.1, obj.0.size(), p))
+            })?;
+            assert!(obj_start <= start);
+            assert!(obj_start + obj_size as u64 >= end);
+            node.as_mut().inner_mut().ptr =
+                NonNull::new(unsafe { p.add((start - obj_start) as usize) });
+            mod_dev_dbg!(
+                self.dev,
+                "HeapAllocator[{}]::alloc: CPU pointer = {:?}\n",
+                &*self.name,
+                node.ptr
+            );
+        }
+
+        mod_dev_dbg!(
+            self.dev,
+            "HeapAllocator[{}]::alloc: Allocated {:#x} bytes @ {:#x}\n",
+            &*self.name,
+            end - start,
+            start
+        );
+
+        Ok(HeapAllocation(Some(node)))
+    }
+
+    fn garbage(&self) -> (usize, usize) {
+        self.mm.with_inner(|inner| {
+            if let Some(g) = inner.garbage.as_ref() {
+                (g.len(), inner.total_garbage)
+            } else {
+                (0, 0)
+            }
+        })
+    }
+
+    fn collect_garbage(&mut self, count: usize) {
+        // Take the garbage out of the inner block, so we can safely drop it without deadlocking
+        let mut garbage = Vec::new();
+
+        if garbage.try_reserve(count).is_err() {
+            dev_crit!(
+                self.dev,
+                "HeapAllocator[{}]:collect_garbage: failed to reserve space\n",
+                &*self.name,
+            );
+            return;
+        }
+
+        self.mm.with_inner(|inner| {
+            if let Some(g) = inner.garbage.as_mut() {
+                for node in g.drain(0..count) {
+                    inner.total_garbage -= node.size() as usize;
+                    garbage
+                        .try_push(node)
+                        .expect("try_push() failed after reserve()");
+                }
+            }
+        });
+    }
+}
+
+impl Drop for HeapAllocatorInner {
+    fn drop(&mut self) {
+        mod_dev_dbg!(
+            self.dev,
+            "HeapAllocator[{}]: dropping allocator\n",
+            &*self.name
+        );
+        if self.allocated > 0 {
+            // This should never happen
+            dev_crit!(
+                self.dev,
+                "HeapAllocator[{}]: dropping with {} bytes allocated\n",
+                &*self.name,
+                self.allocated
+            );
+        } else {
+            for mut obj in self.backing_objects.drain(..) {
+                obj.0.drop_vm_mappings(self.vm_id);
+            }
+        }
+    }
+}
diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs
new file mode 100644
index 00000000000000..5423c856abcea7
--- /dev/null
+++ b/drivers/gpu/drm/asahi/asahi.rs
@@ -0,0 +1,52 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![recursion_limit = "1024"]
+
+//! Driver for the Apple AGX GPUs found in Apple Silicon SoCs.
+
+mod alloc;
+mod buffer;
+mod channel;
+mod debug;
+mod driver;
+mod event;
+mod file;
+mod float;
+mod fw;
+mod gem;
+mod gpu;
+mod hw;
+mod initdata;
+mod mem;
+mod microseq;
+mod mmu;
+mod object;
+mod queue;
+mod regs;
+mod slotalloc;
+mod util;
+mod workqueue;
+
+use kernel::module_platform_driver;
+
+module_platform_driver! {
+    type: driver::AsahiDriver,
+    name: "asahi",
+    license: "Dual MIT/GPL",
+    params: {
+        debug_flags: u64 {
+            default: 0,
+            permissions: 0o644,
+            description: "Debug flags",
+        },
+        fault_control: u32 {
+            default: 0,
+            permissions: 0,
+            description: "Fault control (0x0: hard faults, 0xb: macOS default)",
+        },
+        initial_tvb_size: usize {
+            default: 0x8,
+            permissions: 0o644,
+            description: "Initial TVB size in blocks",
+        },
+    },
+}
diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
new file mode 100644
index 00000000000000..01c65cc9ba9223
--- /dev/null
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -0,0 +1,775 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Tiled Vertex Buffer management
+//!
+//! This module manages the Tiled Vertex Buffer, also known as the Parameter Buffer (in imgtec
+//! parlance) or the tiler heap (on other architectures). This buffer holds transformed primitive
+//! data between the vertex/tiling stage and the fragment stage.
+//!
+//! On AGX, the buffer is a heap of 128K blocks split into 32K pages (which must be aligned to a
+//! multiple of 32K in VA space). The buffer can be shared between multiple render jobs, and each
+//! will allocate pages from it during vertex processing and return them during fragment processing.
+//!
+//! If the buffer runs out of free pages, the vertex pass stops and a partial fragment pass occurs,
+//! spilling the intermediate render target state to RAM (a partial render). This is all managed
+//! transparently by the firmware. Since partial renders are less efficient, the kernel must grow
+//! the heap in response to feedback from the firmware to avoid partial renders in the future.
+//! Currently, we only ever grow the heap, and never shrink it.
+//!
+//! AGX also supports memoryless render targets, which can be used for intermediate results within
+//! a render pass. To support partial renders, it seems the GPU/firmware has the ability to borrow
+//! pages from the TVB buffer as a temporary render target buffer. Since this happens during a
+//! partial render itself, if the buffer runs out of space, it requires synchronous growth in
+//! response to a firmware interrupt. This is not currently supported, but may be in the future,
+//! though it is unclear whether it is worth the effort.
+//!
+//! This module is also in charge of managing the temporary objects associated with a single render
+//! pass, which includes the top-level tile array, the tail pointer cache, preemption buffers, and
+//! other miscellaneous structures collectively managed as a "scene".
+//!
+//! To avoid runaway memory usage, there is a maximum size for buffers (at that point it's unlikely
+//! that partial renders will incur much overhead over the buffer data access itself). This is
+//! different depending on whether memoryless render targets are in use, and is currently hardcoded.
+//! to the most common value used by macOS.
+
+use crate::debug::*;
+use crate::fw::buffer;
+use crate::fw::types::*;
+use crate::util::*;
+use crate::{alloc, fw, gpu, hw, mmu, slotalloc};
+use core::sync::atomic::Ordering;
+use kernel::prelude::*;
+use kernel::sync::{Arc, Mutex};
+use kernel::{c_str, static_lock_class};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Buffer;
+
+/// There are 127 GPU/firmware-side buffer manager slots (yes, 127, not 128).
+const NUM_BUFFERS: u32 = 127;
+
+/// Page size bits for buffer pages (32K). VAs must be aligned to this size.
+pub(crate) const PAGE_SHIFT: usize = 15;
+/// Page size for buffer pages.
+pub(crate) const PAGE_SIZE: usize = 1 << PAGE_SHIFT;
+/// Number of pages in a buffer block, which should be contiguous in VA space.
+pub(crate) const PAGES_PER_BLOCK: usize = 4;
+/// Size of a buffer block.
+pub(crate) const BLOCK_SIZE: usize = PAGE_SIZE * PAGES_PER_BLOCK;
+
+/// Metadata about the tiling configuration for a scene. This is computed in the `render` module.
+/// based on dimensions, tile size, and other info.
+pub(crate) struct TileInfo {
+    /// Tile count in the X dimension. Tiles are always 32x32.
+    pub(crate) tiles_x: u32,
+    /// Tile count in the Y dimension. Tiles are always 32x32.
+    pub(crate) tiles_y: u32,
+    /// Total tile count.
+    pub(crate) tiles: u32,
+    /// Micro-tile width (16 or 32).
+    pub(crate) utile_width: u32,
+    /// Micro-tile height (16 or 32).
+    pub(crate) utile_height: u32,
+    // Macro-tiles in the X dimension. Always 4.
+    //pub(crate) mtiles_x: u32,
+    // Macro-tiles in the Y dimension. Always 4.
+    //pub(crate) mtiles_y: u32,
+    /// Tiles per macro-tile in the X dimension.
+    pub(crate) tiles_per_mtile_x: u32,
+    /// Tiles per macro-tile in the Y dimension.
+    pub(crate) tiles_per_mtile_y: u32,
+    // Total tiles per macro-tile.
+    //pub(crate) tiles_per_mtile: u32,
+    /// Micro-tiles per macro-tile in the X dimension.
+    pub(crate) utiles_per_mtile_x: u32,
+    /// Micro-tiles per macro-tile in the Y dimension.
+    pub(crate) utiles_per_mtile_y: u32,
+    // Total micro-tiles per macro-tile.
+    //pub(crate) utiles_per_mtile: u32,
+    /// Size of the top-level tilemap, in bytes (for all layers, one cluster).
+    pub(crate) tilemap_size: usize,
+    /// Size of the Tail Pointer Cache, in bytes (for all layers * clusters).
+    pub(crate) tpc_size: usize,
+    /// Number of blocks in the clustering meta buffer (for clustering).
+    pub(crate) meta1_blocks: u32,
+    /// Minimum number of TVB blocks for this render.
+    pub(crate) min_tvb_blocks: usize,
+    /// Tiling parameter structure passed to firmware.
+    pub(crate) params: fw::vertex::raw::TilingParameters,
+}
+
+/// A single scene, representing a render pass and its required buffers.
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct Scene {
+    object: GpuObject<buffer::Scene::ver>,
+    slot: u32,
+    rebind: bool,
+    preempt2_off: usize,
+    preempt3_off: usize,
+    // Note: these are dead code only on some version variants.
+    // It's easier to do this than to propagate the version conditionals everywhere.
+    #[allow(dead_code)]
+    meta2_off: usize,
+    #[allow(dead_code)]
+    meta3_off: usize,
+    #[allow(dead_code)]
+    meta4_off: usize,
+}
+
+#[versions(AGX)]
+impl Scene::ver {
+    /// Returns true if the buffer was bound to a fresh manager slot, and therefore needs an init
+    /// command before a render.
+    pub(crate) fn rebind(&self) -> bool {
+        self.rebind
+    }
+
+    /// Returns the buffer manager slot this scene's buffer was bound to.
+    pub(crate) fn slot(&self) -> u32 {
+        self.slot
+    }
+
+    /// Returns the GPU pointer to the [`buffer::Scene::ver`].
+    pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, buffer::Scene::ver> {
+        self.object.gpu_pointer()
+    }
+
+    /// Returns the GPU weak pointer to the [`buffer::Scene::ver`].
+    pub(crate) fn weak_pointer(&self) -> GpuWeakPointer<buffer::Scene::ver> {
+        self.object.weak_pointer()
+    }
+
+    /// Returns the GPU weak pointer to the kernel-side temp buffer.
+    /// (purpose unknown...)
+    pub(crate) fn kernel_buffer_pointer(&self) -> GpuWeakPointer<[u8]> {
+        self.object.buffer.inner.lock().kernel_buffer.weak_pointer()
+    }
+
+    /// Returns the GPU pointer to the `buffer::Info::ver` object associated with this Scene.
+    pub(crate) fn buffer_pointer(&self) -> GpuPointer<'_, buffer::Info::ver> {
+        // We can't return the strong pointer directly since its lifetime crosses a lock, but we know
+        // its lifetime will be valid as long as &self since we hold a reference to the buffer,
+        // so just construct the strong pointer with the right lifetime here.
+        unsafe { self.weak_buffer_pointer().upgrade() }
+    }
+
+    /// Returns the GPU weak pointer to the `buffer::Info::ver` object associated with this Scene.
+    pub(crate) fn weak_buffer_pointer(&self) -> GpuWeakPointer<buffer::Info::ver> {
+        self.object.buffer.inner.lock().info.weak_pointer()
+    }
+
+    /// Returns the GPU pointer to the TVB heap metadata buffer.
+    pub(crate) fn tvb_heapmeta_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object.tvb_heapmeta.gpu_pointer()
+    }
+
+    /// Returns the GPU pointer to the top-level TVB tilemap buffer.
+    pub(crate) fn tvb_tilemap_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object.tvb_tilemap.gpu_pointer()
+    }
+
+    /// Returns the GPU pointer to the Tail Pointer Cache buffer.
+    pub(crate) fn tpc_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object.tpc.gpu_pointer()
+    }
+
+    /// Returns the GPU pointer to the first preemption scratch buffer.
+    pub(crate) fn preempt_buf_1_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object.preempt_buf.gpu_pointer()
+    }
+
+    /// Returns the GPU pointer to the second preemption scratch buffer.
+    pub(crate) fn preempt_buf_2_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object
+            .preempt_buf
+            .gpu_offset_pointer(self.preempt2_off)
+    }
+
+    /// Returns the GPU pointer to the third preemption scratch buffer.
+    pub(crate) fn preempt_buf_3_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object
+            .preempt_buf
+            .gpu_offset_pointer(self.preempt3_off)
+    }
+
+    /// Returns the GPU pointer to the per-cluster tilemap buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn cluster_tilemaps_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.tilemaps.gpu_pointer())
+    }
+
+    /// Returns the GPU pointer to the clustering metadata 1 buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn meta_1_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.meta.gpu_pointer())
+    }
+
+    /// Returns the GPU pointer to the clustering metadata 2 buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn meta_2_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.meta.gpu_offset_pointer(self.meta2_off))
+    }
+
+    /// Returns the GPU pointer to the clustering metadata 3 buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn meta_3_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.meta.gpu_offset_pointer(self.meta3_off))
+    }
+
+    /// Returns the GPU pointer to the clustering metadata 4 buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn meta_4_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.meta.gpu_offset_pointer(self.meta4_off))
+    }
+
+    /// Returns the number of TVB bytes used for this scene.
+    pub(crate) fn used_bytes(&self) -> usize {
+        self.object
+            .with(|raw, _inner| raw.total_page_count.load(Ordering::Relaxed) as usize * PAGE_SIZE)
+    }
+
+    /// Returns whether the TVB overflowed while rendering this scene.
+    pub(crate) fn overflowed(&self) -> bool {
+        self.object.with(|raw, _inner| {
+            raw.total_page_count.load(Ordering::Relaxed)
+                > raw.pass_page_count.load(Ordering::Relaxed)
+        })
+    }
+}
+
+#[versions(AGX)]
+impl Drop for Scene::ver {
+    fn drop(&mut self) {
+        let mut inner = self.object.buffer.inner.lock();
+        assert_ne!(inner.active_scenes, 0);
+        inner.active_scenes -= 1;
+
+        if inner.active_scenes == 0 {
+            mod_pr_debug!(
+                "Buffer: no scenes left, dropping slot {}",
+                inner.active_slot.take().unwrap().slot()
+            );
+            inner.active_slot = None;
+        }
+    }
+}
+
+/// Inner data for a single TVB buffer object.
+#[versions(AGX)]
+struct BufferInner {
+    info: GpuObject<buffer::Info::ver>,
+    ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+    ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+    blocks: Vec<GpuOnlyArray<u8>>,
+    max_blocks: usize,
+    max_blocks_nomemless: usize,
+    mgr: BufferManager::ver,
+    active_scenes: usize,
+    active_slot: Option<slotalloc::Guard<BufferSlotInner::ver>>,
+    last_token: Option<slotalloc::SlotToken>,
+    tpc: Option<Arc<GpuArray<u8>>>,
+    kernel_buffer: GpuArray<u8>,
+    stats: GpuObject<buffer::Stats>,
+    cfg: &'static hw::HwConfig,
+    preempt1_size: usize,
+    preempt2_size: usize,
+    preempt3_size: usize,
+    num_clusters: usize,
+}
+
+/// Locked and reference counted TVB buffer.
+#[versions(AGX)]
+pub(crate) struct Buffer {
+    inner: Arc<Mutex<BufferInner::ver>>,
+}
+
+#[versions(AGX)]
+impl Buffer::ver {
+    /// Create a new Buffer for a given VM, given the per-VM allocators.
+    pub(crate) fn new(
+        gpu: &dyn gpu::GpuManager,
+        alloc: &mut gpu::KernelAllocators,
+        ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+        ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+        mgr: &BufferManager::ver,
+    ) -> Result<Buffer::ver> {
+        // These are the typical max numbers on macOS.
+        // 8GB machines have this halved.
+        let max_size: usize = 862_322_688; // bytes
+        let max_size_nomemless = max_size / 3;
+
+        let max_blocks = max_size / BLOCK_SIZE;
+        let max_blocks_nomemless = max_size_nomemless / BLOCK_SIZE;
+        let max_pages = max_blocks * PAGES_PER_BLOCK;
+        let max_pages_nomemless = max_blocks_nomemless * PAGES_PER_BLOCK;
+
+        let num_clusters = gpu.get_dyncfg().id.num_clusters as usize;
+        let num_clusters_adj = if num_clusters > 1 {
+            num_clusters + 1
+        } else {
+            1
+        };
+
+        let preempt1_size = num_clusters_adj * gpu.get_cfg().preempt1_size;
+        let preempt2_size = num_clusters_adj * gpu.get_cfg().preempt2_size;
+        let preempt3_size = num_clusters_adj * gpu.get_cfg().preempt3_size;
+
+        let shared = &mut alloc.shared;
+        let info = alloc.private.new_init(
+            {
+                let ualloc_priv = &ualloc_priv;
+                try_init!(buffer::Info::ver {
+                    block_ctl: shared.new_default::<buffer::BlockControl>()?,
+                    counter: shared.new_default::<buffer::Counter>()?,
+                    page_list: ualloc_priv.lock().array_empty(max_pages)?,
+                    block_list: ualloc_priv.lock().array_empty(max_blocks * 2)?,
+                })
+            },
+            |inner, _p| {
+                try_init!(buffer::raw::Info::ver {
+                    gpu_counter: 0x0,
+                    unk_4: 0,
+                    last_id: 0x0,
+                    cur_id: -1,
+                    unk_10: 0x0,
+                    gpu_counter2: 0x0,
+                    unk_18: 0x0,
+                    #[ver(V < V13_0B4 || G >= G14X)]
+                    unk_1c: 0x0,
+                    page_list: inner.page_list.gpu_pointer(),
+                    page_list_size: (4 * max_pages).try_into()?,
+                    page_count: AtomicU32::new(0),
+                    max_blocks: max_blocks.try_into()?,
+                    block_count: AtomicU32::new(0),
+                    unk_38: 0x0,
+                    block_list: inner.block_list.gpu_pointer(),
+                    block_ctl: inner.block_ctl.gpu_pointer(),
+                    last_page: AtomicU32::new(0),
+                    gpu_page_ptr1: 0x0,
+                    gpu_page_ptr2: 0x0,
+                    unk_58: 0x0,
+                    block_size: BLOCK_SIZE as u32,
+                    unk_60: U64(0x0),
+                    counter: inner.counter.gpu_pointer(),
+                    unk_70: 0x0,
+                    unk_74: 0x0,
+                    unk_78: 0x0,
+                    unk_7c: 0x0,
+                    unk_80: 0x1,
+                    max_pages: max_pages.try_into()?,
+                    max_pages_nomemless: max_pages_nomemless.try_into()?,
+                    unk_8c: 0x0,
+                    unk_90: Default::default(),
+                })
+            },
+        )?;
+
+        // Technically similar to Scene below, let's play it safe.
+        let kernel_buffer = alloc.shared.array_empty(0x40)?;
+        let stats = alloc
+            .shared
+            .new_object(Default::default(), |_inner| buffer::raw::Stats {
+                reset: AtomicU32::from(1),
+                ..Default::default()
+            })?;
+
+        Ok(Buffer::ver {
+            inner: Arc::pin_init(
+                Mutex::new(BufferInner::ver {
+                    info,
+                    ualloc,
+                    ualloc_priv,
+                    blocks: Vec::new(),
+                    max_blocks,
+                    max_blocks_nomemless,
+                    mgr: mgr.clone(),
+                    active_scenes: 0,
+                    active_slot: None,
+                    last_token: None,
+                    tpc: None,
+                    kernel_buffer,
+                    stats,
+                    cfg: gpu.get_cfg(),
+                    preempt1_size,
+                    preempt2_size,
+                    preempt3_size,
+                    num_clusters,
+                }),
+                GFP_KERNEL,
+            )?,
+        })
+    }
+
+    /// Returns the total block count allocated to this Buffer.
+    pub(crate) fn block_count(&self) -> u32 {
+        self.inner.lock().blocks.len() as u32
+    }
+
+    /// Returns the total size in bytes allocated to this Buffer.
+    pub(crate) fn size(&self) -> usize {
+        self.block_count() as usize * BLOCK_SIZE
+    }
+
+    /// Automatically grow the Buffer based on feedback from the statistics.
+    pub(crate) fn auto_grow(&self) -> Result<bool> {
+        let inner = self.inner.lock();
+
+        let used_pages = inner.stats.with(|raw, _inner| {
+            let used = raw.max_pages.load(Ordering::Relaxed);
+            raw.reset.store(1, Ordering::Release);
+            used as usize
+        });
+
+        let need_blocks = div_ceil(used_pages * 2, PAGES_PER_BLOCK).min(inner.max_blocks_nomemless);
+        let want_blocks = div_ceil(used_pages * 3, PAGES_PER_BLOCK).min(inner.max_blocks_nomemless);
+
+        let cur_count = inner.blocks.len();
+
+        if need_blocks <= cur_count {
+            Ok(false)
+        } else {
+            // Grow to 3x requested size (same logic as macOS)
+            core::mem::drop(inner);
+            self.ensure_blocks(want_blocks)?;
+            Ok(true)
+        }
+    }
+
+    /// Synchronously grow the Buffer.
+    pub(crate) fn sync_grow(&self) {
+        let inner = self.inner.lock();
+
+        let cur_count = inner.blocks.len();
+        core::mem::drop(inner);
+        if self.ensure_blocks(cur_count + 10).is_err() {
+            pr_err!("BufferManager: Failed to grow buffer synchronously\n");
+        }
+    }
+
+    /// Ensure that the buffer has at least a certain minimum size in blocks.
+    pub(crate) fn ensure_blocks(&self, min_blocks: usize) -> Result<bool> {
+        let mut inner = self.inner.lock();
+
+        let cur_count = inner.blocks.len();
+        if cur_count >= min_blocks {
+            return Ok(false);
+        }
+        if min_blocks > inner.max_blocks {
+            return Err(ENOMEM);
+        }
+
+        let add_blocks = min_blocks - cur_count;
+        let new_count = min_blocks;
+
+        let mut new_blocks: Vec<GpuOnlyArray<u8>> = Vec::new();
+
+        // Allocate the new blocks first, so if it fails they will be dropped
+        let mut ualloc = inner.ualloc.lock();
+        for _i in 0..add_blocks {
+            new_blocks.push(ualloc.array_gpuonly(BLOCK_SIZE)?, GFP_KERNEL)?;
+        }
+        core::mem::drop(ualloc);
+
+        // Then actually commit them
+        inner.blocks.reserve(add_blocks, GFP_KERNEL)?;
+
+        for (i, block) in new_blocks.into_iter().enumerate() {
+            let page_num = (block.gpu_va().get() >> PAGE_SHIFT) as u32;
+
+            inner
+                .blocks
+                .push(block, GFP_KERNEL)
+                .expect("push() failed after reserve()");
+            inner.info.block_list[2 * (cur_count + i)] = page_num;
+            for j in 0..PAGES_PER_BLOCK {
+                inner.info.page_list[(cur_count + i) * PAGES_PER_BLOCK + j] = page_num + j as u32;
+            }
+        }
+
+        inner.info.block_ctl.with(|raw, _inner| {
+            raw.total.store(new_count as u32, Ordering::SeqCst);
+            raw.wptr.store(new_count as u32, Ordering::SeqCst);
+        });
+
+        /* Only do this update if the buffer manager is idle (which means we own it) */
+        if inner.active_scenes == 0 {
+            let page_count = (new_count * PAGES_PER_BLOCK) as u32;
+            inner.info.with(|raw, _inner| {
+                raw.page_count.store(page_count, Ordering::Relaxed);
+                raw.block_count.store(new_count as u32, Ordering::Relaxed);
+                raw.last_page.store(page_count - 1, Ordering::Relaxed);
+            });
+        }
+
+        Ok(true)
+    }
+
+    /// Create a new [`Scene::ver`] (render pass) using this buffer.
+    pub(crate) fn new_scene(
+        &self,
+        alloc: &mut gpu::KernelAllocators,
+        tile_info: &TileInfo,
+    ) -> Result<Scene::ver> {
+        let mut inner = self.inner.lock();
+
+        let tilemap_size = tile_info.tilemap_size;
+        let tpc_size = tile_info.tpc_size;
+
+        // TODO: what is this exactly?
+        mod_pr_debug!("Buffer: Allocating TVB buffers\n");
+
+        // This seems to be a list, with 4x2 bytes of headers and 8 bytes per entry.
+        // On single-cluster devices, the used length always seems to be 1.
+        // On M1 Ultra, it can grow and usually doesn't exceed 64 entries.
+        // macOS allocates a whole 64K * 0x80 for this, so let's go with
+        // that to be safe...
+        let user_buffer = inner.ualloc.lock().array_empty(if inner.num_clusters > 1 {
+            0x10080
+        } else {
+            0x80
+        })?;
+
+        let tvb_heapmeta = inner.ualloc.lock().array_empty(0x200)?;
+        let tvb_tilemap = inner.ualloc.lock().array_empty(tilemap_size)?;
+
+        mod_pr_debug!("Buffer: Allocating misc buffers\n");
+        let preempt_buf = inner
+            .ualloc
+            .lock()
+            .array_empty(inner.preempt1_size + inner.preempt2_size + inner.preempt3_size)?;
+
+        let tpc = match inner.tpc.as_ref() {
+            Some(buf) if buf.len() >= tpc_size => buf.clone(),
+            _ => {
+                // MacOS allocates this as shared GPU+FW, but
+                // priv seems to work and might be faster?
+                // Needs to be FW-writable anyway, so ualloc
+                // won't work.
+                let buf = Arc::try_new(
+                    inner
+                        .ualloc_priv
+                        .lock()
+                        .array_empty((tpc_size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK)?,
+                )?;
+                inner.tpc = Some(buf.clone());
+                buf
+            }
+        };
+
+        let mut meta1_size = 0;
+        let mut meta2_size = 0;
+        let mut meta3_size = 0;
+
+        let clustering = if inner.num_clusters > 1 {
+            let cfg = inner.cfg.clustering.as_ref().unwrap();
+
+            // Maybe: (4x4 macro tiles + 1 global page)*n, 32bit each (17*4*n)
+            // Unused on t602x?
+            meta1_size = align(tile_info.meta1_blocks as usize * cfg.meta1_blocksize, 0x80);
+            meta2_size = align(cfg.meta2_size, 0x80);
+            meta3_size = align(cfg.meta3_size, 0x80);
+            let meta4_size = cfg.meta4_size;
+
+            let meta_size = meta1_size + meta2_size + meta3_size + meta4_size;
+
+            mod_pr_debug!("Buffer: Allocating clustering buffers\n");
+            let tilemaps = inner
+                .ualloc
+                .lock()
+                .array_empty(cfg.max_splits * tilemap_size)?;
+            let meta = inner.ualloc.lock().array_empty(meta_size)?;
+            Some(buffer::ClusterBuffers { tilemaps, meta })
+        } else {
+            None
+        };
+
+        // Could be made strong, but we wind up with a deadlock if we try to grab the
+        // pointer through the inner.buffer path inside the closure.
+        let stats_pointer = inner.stats.weak_pointer();
+
+        let _gpu = &mut alloc.gpu;
+
+        // macOS allocates this as private. However, the firmware does not
+        // DC CIVAC this before reading it (like it does most other things),
+        // which causes odd cache incoherency bugs when combined with
+        // speculation on the firmware side (maybe). This doesn't happen
+        // on macOS because these structs are a circular pool that is mapped
+        // already initialized. Just mark this shared for now.
+        let scene = alloc.shared.new_init(
+            try_init!(buffer::Scene::ver {
+                user_buffer: user_buffer,
+                buffer: self.clone(),
+                tvb_heapmeta: tvb_heapmeta,
+                tvb_tilemap: tvb_tilemap,
+                tpc: tpc,
+                clustering: clustering,
+                preempt_buf: preempt_buf,
+                #[ver(G >= G14X)]
+                control_word: _gpu.array_empty(1)?,
+            }),
+            |inner, _p| {
+                try_init!(buffer::raw::Scene::ver {
+                    #[ver(G >= G14X)]
+                    control_word: inner.control_word.gpu_pointer(),
+                    #[ver(G >= G14X)]
+                    control_word2: inner.control_word.gpu_pointer(),
+                    pass_page_count: AtomicU32::new(0),
+                    unk_4: 0,
+                    unk_8: U64(0),
+                    unk_10: U64(0),
+                    user_buffer: inner.user_buffer.gpu_pointer(),
+                    unk_20: 0,
+                    #[ver(V >= V13_3)]
+                    unk_28: U64(0),
+                    stats: stats_pointer,
+                    total_page_count: AtomicU32::new(0),
+                    #[ver(G < G14X)]
+                    unk_30: U64(0),
+                    #[ver(G < G14X)]
+                    unk_38: U64(0),
+                })
+            },
+        )?;
+
+        let mut rebind = false;
+
+        if inner.active_slot.is_none() {
+            assert_eq!(inner.active_scenes, 0);
+
+            let slot = inner.mgr.0.get_inner(inner.last_token, |inner, mgr| {
+                inner.owners[mgr.slot() as usize] = Some(self.clone());
+                Ok(())
+            })?;
+            rebind = slot.changed();
+
+            mod_pr_debug!("Buffer: assigning slot {} (rebind={})", slot.slot(), rebind);
+
+            inner.last_token = Some(slot.token());
+            inner.active_slot = Some(slot);
+        }
+
+        inner.active_scenes += 1;
+
+        Ok(Scene::ver {
+            object: scene,
+            slot: inner.active_slot.as_ref().unwrap().slot(),
+            rebind,
+            preempt2_off: inner.preempt1_size,
+            preempt3_off: inner.preempt1_size + inner.preempt2_size,
+            meta2_off: meta1_size,
+            meta3_off: meta1_size + meta2_size,
+            meta4_off: meta1_size + meta2_size + meta3_size,
+        })
+    }
+
+    /// Increment the buffer manager usage count. Should we done once we know the Scene is ready
+    /// to be committed and used in commands submitted to the GPU.
+    pub(crate) fn increment(&self) {
+        let inner = self.inner.lock();
+        inner.info.counter.with(|raw, _inner| {
+            // We could use fetch_add, but the non-LSE atomic
+            // sequence Rust produces confuses the hypervisor.
+            // We have inner locked anyway, so this is not racy.
+            let v = raw.count.load(Ordering::Relaxed);
+            raw.count.store(v + 1, Ordering::Relaxed);
+        });
+    }
+
+    pub(crate) fn any_ref(&self) -> Arc<dyn core::any::Any + Send + Sync> {
+        self.inner.clone()
+    }
+}
+
+#[versions(AGX)]
+impl Clone for Buffer::ver {
+    fn clone(&self) -> Self {
+        Buffer::ver {
+            inner: self.inner.clone(),
+        }
+    }
+}
+
+#[versions(AGX)]
+struct BufferSlotInner();
+
+#[versions(AGX)]
+impl slotalloc::SlotItem for BufferSlotInner::ver {
+    type Data = BufferManagerInner::ver;
+
+    fn release(&mut self, data: &mut Self::Data, slot: u32) {
+        mod_pr_debug!("EventManager: Released slot {}\n", slot);
+        data.owners[slot as usize] = None;
+    }
+}
+
+/// Inner data for the event manager, to be protected by the SlotAllocator lock.
+#[versions(AGX)]
+pub(crate) struct BufferManagerInner {
+    owners: Vec<Option<Buffer::ver>>,
+}
+
+/// The GPU-global buffer manager, used to allocate and release buffer slots from the pool.
+#[versions(AGX)]
+pub(crate) struct BufferManager(slotalloc::SlotAllocator<BufferSlotInner::ver>);
+
+#[versions(AGX)]
+impl BufferManager::ver {
+    pub(crate) fn new() -> Result<BufferManager::ver> {
+        let mut owners = Vec::new();
+        for _i in 0..(NUM_BUFFERS as usize) {
+            owners.push(None, GFP_KERNEL)?;
+        }
+        Ok(BufferManager::ver(slotalloc::SlotAllocator::new(
+            NUM_BUFFERS,
+            BufferManagerInner::ver { owners },
+            |_inner, _slot| BufferSlotInner::ver(),
+            c_str!("BufferManager::SlotAllocator"),
+            static_lock_class!(),
+            static_lock_class!(),
+        )?))
+    }
+
+    /// Signals a Buffer to synchronously grow.
+    pub(crate) fn grow(&self, slot: u32) {
+        match self
+            .0
+            .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned())
+        {
+            Some(owner) => {
+                pr_info!(
+                    "BufferManager: Received synchronous grow request for slot {}, this is not generally expected\n",
+                    slot
+                );
+                owner.sync_grow();
+            }
+            None => {
+                pr_err!(
+                    "BufferManager: Received grow request for empty slot {}\n",
+                    slot
+                );
+            }
+        }
+    }
+}
+
+#[versions(AGX)]
+impl Clone for BufferManager::ver {
+    fn clone(&self) -> Self {
+        BufferManager::ver(self.0.clone())
+    }
+}
diff --git a/drivers/gpu/drm/asahi/channel.rs b/drivers/gpu/drm/asahi/channel.rs
new file mode 100644
index 00000000000000..e46c17f98a146f
--- /dev/null
+++ b/drivers/gpu/drm/asahi/channel.rs
@@ -0,0 +1,567 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU ring buffer channels
+//!
+//! The GPU firmware use a set of ring buffer channels to receive commands from the driver and send
+//! it notifications and status messages.
+//!
+//! These ring buffers mostly follow uniform conventions, so they share the same base
+//! implementation.
+
+use crate::debug::*;
+use crate::driver::{AsahiDevRef, AsahiDevice};
+use crate::fw::channels::*;
+use crate::fw::initdata::{raw, ChannelRing};
+use crate::fw::types::*;
+use crate::{buffer, event, gpu, mem};
+use core::time::Duration;
+use kernel::{
+    c_str,
+    delay::coarse_sleep,
+    prelude::*,
+    sync::Arc,
+    time::{clock, Now},
+};
+
+pub(crate) use crate::fw::channels::PipeType;
+
+/// A receive (FW->driver) channel.
+pub(crate) struct RxChannel<T: RxChannelState, U: Copy + Default>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug + Default + Zeroable,
+{
+    ring: ChannelRing<T, U>,
+    // FIXME: needs feature(generic_const_exprs)
+    //rptr: [u32; T::SUB_CHANNELS],
+    rptr: [u32; 6],
+    count: u32,
+}
+
+impl<T: RxChannelState, U: Copy + Default> RxChannel<T, U>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug + Default + Zeroable,
+{
+    /// Allocates a new receive channel with a given message count.
+    pub(crate) fn new(alloc: &mut gpu::KernelAllocators, count: usize) -> Result<RxChannel<T, U>> {
+        Ok(RxChannel {
+            ring: ChannelRing {
+                state: alloc.shared.new_default()?,
+                ring: alloc.shared.array_empty(T::SUB_CHANNELS * count)?,
+            },
+            rptr: Default::default(),
+            count: count as u32,
+        })
+    }
+
+    /// Receives a message on the specified sub-channel index, optionally leaving in the ring
+    /// buffer.
+    ///
+    /// Returns None if the channel is empty.
+    fn get_or_peek(&mut self, index: usize, peek: bool) -> Option<U> {
+        self.ring.state.with(|raw, _inner| {
+            let wptr = T::wptr(raw, index);
+            let rptr = &mut self.rptr[index];
+            if wptr == *rptr {
+                None
+            } else {
+                let off = self.count as usize * index;
+                let msg = self.ring.ring[off + *rptr as usize];
+                if !peek {
+                    *rptr = (*rptr + 1) % self.count;
+                    T::set_rptr(raw, index, *rptr);
+                }
+                Some(msg)
+            }
+        })
+    }
+
+    /// Receives a message on the specified sub-channel index, and dequeues it from the ring buffer.
+    ///
+    /// Returns None if the channel is empty.
+    pub(crate) fn get(&mut self, index: usize) -> Option<U> {
+        self.get_or_peek(index, false)
+    }
+
+    /// Peeks a message on the specified sub-channel index, leaving it in the ring buffer.
+    ///
+    /// Returns None if the channel is empty.
+    pub(crate) fn peek(&mut self, index: usize) -> Option<U> {
+        self.get_or_peek(index, true)
+    }
+}
+
+/// A transmit (driver->FW) channel.
+pub(crate) struct TxChannel<T: TxChannelState, U: Copy + Default>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug + Default + Zeroable,
+{
+    ring: ChannelRing<T, U>,
+    wptr: u32,
+    count: u32,
+}
+
+impl<T: TxChannelState, U: Copy + Default> TxChannel<T, U>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug + Default + Zeroable,
+{
+    /// Allocates a new cached transmit channel with a given message count.
+    pub(crate) fn new(alloc: &mut gpu::KernelAllocators, count: usize) -> Result<TxChannel<T, U>> {
+        Ok(TxChannel {
+            ring: ChannelRing {
+                state: alloc.shared.new_default()?,
+                ring: alloc.private.array_empty(count)?,
+            },
+            wptr: 0,
+            count: count as u32,
+        })
+    }
+
+    /// Allocates a new uncached transmit channel with a given message count.
+    pub(crate) fn new_uncached(
+        alloc: &mut gpu::KernelAllocators,
+        count: usize,
+    ) -> Result<TxChannel<T, U>> {
+        Ok(TxChannel {
+            ring: ChannelRing {
+                state: alloc.shared.new_default()?,
+                ring: alloc.shared.array_empty(count)?,
+            },
+            wptr: 0,
+            count: count as u32,
+        })
+    }
+
+    /// Send a message to the ring, returning a cookie with the ring buffer position.
+    ///
+    /// This will poll/block if the ring is full, which we don't really expect to happen.
+    pub(crate) fn put(&mut self, msg: &U) -> u32 {
+        self.ring.state.with(|raw, _inner| {
+            let next_wptr = (self.wptr + 1) % self.count;
+            let mut rptr = T::rptr(raw);
+            if next_wptr == rptr {
+                pr_err!(
+                    "TX ring buffer is full! Waiting... ({}, {})\n",
+                    next_wptr,
+                    rptr
+                );
+                // TODO: block properly on incoming messages?
+                while next_wptr == rptr {
+                    coarse_sleep(Duration::from_millis(8));
+                    rptr = T::rptr(raw);
+                }
+            }
+            self.ring.ring[self.wptr as usize] = *msg;
+            mem::sync();
+            T::set_wptr(raw, next_wptr);
+            self.wptr = next_wptr;
+        });
+        self.wptr
+    }
+
+    /// Wait for a previously submitted message to be popped off of the ring by the GPU firmware.
+    ///
+    /// This busy-loops, and is intended to be used for rare cases when we need to block for
+    /// completion of a cache management or invalidation operation synchronously (which
+    /// the firmware normally completes fast enough not to be worth sleeping for).
+    /// If the poll takes longer than 10ms, this switches to sleeping between polls.
+    pub(crate) fn wait_for(&mut self, wptr: u32, timeout_ms: u64) -> Result {
+        const MAX_FAST_POLL: u64 = 10;
+        let start = clock::KernelTime::now();
+        let timeout_fast = Duration::from_millis(timeout_ms.min(MAX_FAST_POLL));
+        let timeout_slow = Duration::from_millis(timeout_ms);
+        self.ring.state.with(|raw, _inner| {
+            while start.elapsed() < timeout_fast {
+                if T::rptr(raw) == wptr {
+                    return Ok(());
+                }
+                mem::sync();
+            }
+            while start.elapsed() < timeout_slow {
+                if T::rptr(raw) == wptr {
+                    return Ok(());
+                }
+                coarse_sleep(Duration::from_millis(5));
+                mem::sync();
+            }
+            Err(ETIMEDOUT)
+        })
+    }
+}
+
+/// Device Control channel for global device management commands.
+#[versions(AGX)]
+pub(crate) struct DeviceControlChannel {
+    dev: AsahiDevRef,
+    ch: TxChannel<ChannelState, DeviceControlMsg::ver>,
+}
+
+#[versions(AGX)]
+impl DeviceControlChannel::ver {
+    const COMMAND_TIMEOUT_MS: u64 = 1000;
+
+    /// Allocate a new Device Control channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<DeviceControlChannel::ver> {
+        Ok(DeviceControlChannel::ver {
+            dev: dev.into(),
+            ch: TxChannel::<ChannelState, DeviceControlMsg::ver>::new(alloc, 0x100)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<ChannelState, DeviceControlMsg::ver> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Submits a Device Control command.
+    pub(crate) fn send(&mut self, msg: &DeviceControlMsg::ver) -> u32 {
+        cls_dev_dbg!(DeviceControlCh, self.dev, "DeviceControl: {:?}\n", msg);
+        self.ch.put(msg)
+    }
+
+    /// Waits for a previously submitted Device Control command to complete.
+    pub(crate) fn wait_for(&mut self, wptr: u32) -> Result {
+        self.ch.wait_for(wptr, Self::COMMAND_TIMEOUT_MS)
+    }
+}
+
+/// Pipe channel to submit WorkQueue execution requests.
+#[versions(AGX)]
+pub(crate) struct PipeChannel {
+    dev: AsahiDevRef,
+    ch: TxChannel<ChannelState, PipeMsg::ver>,
+}
+
+#[versions(AGX)]
+impl PipeChannel::ver {
+    /// Allocate a new Pipe submission channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<PipeChannel::ver> {
+        Ok(PipeChannel::ver {
+            dev: dev.into(),
+            ch: TxChannel::<ChannelState, PipeMsg::ver>::new(alloc, 0x100)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<ChannelState, PipeMsg::ver> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Submits a Pipe kick command to the firmware.
+    pub(crate) fn send(&mut self, msg: &PipeMsg::ver) {
+        cls_dev_dbg!(PipeCh, self.dev, "Pipe: {:?}\n", msg);
+        self.ch.put(msg);
+    }
+}
+
+/// Firmware Control channel, used for secure cache flush requests.
+pub(crate) struct FwCtlChannel {
+    dev: AsahiDevRef,
+    ch: TxChannel<FwCtlChannelState, FwCtlMsg>,
+}
+
+impl FwCtlChannel {
+    const COMMAND_TIMEOUT_MS: u64 = 1000;
+
+    /// Allocate a new Firmware Control channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<FwCtlChannel> {
+        Ok(FwCtlChannel {
+            dev: dev.into(),
+            ch: TxChannel::<FwCtlChannelState, FwCtlMsg>::new_uncached(alloc, 0x100)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<FwCtlChannelState, FwCtlMsg> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Submits a Firmware Control command to the firmware.
+    pub(crate) fn send(&mut self, msg: &FwCtlMsg) -> u32 {
+        cls_dev_dbg!(FwCtlCh, self.dev, "FwCtl: {:?}\n", msg);
+        self.ch.put(msg)
+    }
+
+    /// Waits for a previously submitted Firmware Control command to complete.
+    pub(crate) fn wait_for(&mut self, wptr: u32) -> Result {
+        self.ch.wait_for(wptr, Self::COMMAND_TIMEOUT_MS)
+    }
+}
+
+/// Event channel, used to notify the driver of command completions, GPU faults and errors, and
+/// other events.
+#[versions(AGX)]
+pub(crate) struct EventChannel {
+    dev: AsahiDevRef,
+    ch: RxChannel<ChannelState, RawEventMsg>,
+    ev_mgr: Arc<event::EventManager>,
+    buf_mgr: buffer::BufferManager::ver,
+    gpu: Option<Arc<dyn gpu::GpuManager>>,
+}
+
+#[versions(AGX)]
+impl EventChannel::ver {
+    /// Allocate a new Event channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+        ev_mgr: Arc<event::EventManager>,
+        buf_mgr: buffer::BufferManager::ver,
+    ) -> Result<EventChannel::ver> {
+        Ok(EventChannel::ver {
+            dev: dev.into(),
+            ch: RxChannel::<ChannelState, RawEventMsg>::new(alloc, 0x100)?,
+            ev_mgr,
+            buf_mgr,
+            gpu: None,
+        })
+    }
+
+    /// Registers the managing `Gpu` instance that will handle events on this channel.
+    pub(crate) fn set_manager(&mut self, gpu: Arc<dyn gpu::GpuManager>) {
+        self.gpu = Some(gpu);
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<ChannelState, RawEventMsg> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Polls for new Event messages on this ring.
+    pub(crate) fn poll(&mut self) {
+        while let Some(msg) = self.ch.get(0) {
+            let tag = unsafe { msg.raw.0 };
+            match tag {
+                0..=EVENT_MAX => {
+                    let msg = unsafe { msg.msg };
+
+                    cls_dev_dbg!(EventCh, self.dev, "Event: {:?}\n", msg);
+                    match msg {
+                        EventMsg::Fault => match self.gpu.as_ref() {
+                            Some(gpu) => gpu.handle_fault(),
+                            None => {
+                                dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
+                            }
+                        },
+                        EventMsg::Timeout {
+                            counter,
+                            event_slot,
+                            ..
+                        } => match self.gpu.as_ref() {
+                            Some(gpu) => gpu.handle_timeout(counter, event_slot),
+                            None => {
+                                dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
+                            }
+                        },
+                        EventMsg::Flag { firing, .. } => {
+                            for (i, flags) in firing.iter().enumerate() {
+                                for j in 0..32 {
+                                    if flags & (1u32 << j) != 0 {
+                                        self.ev_mgr.signal((i * 32 + j) as u32);
+                                    }
+                                }
+                            }
+                        }
+                        EventMsg::GrowTVB {
+                            vm_slot,
+                            buffer_slot,
+                            counter,
+                            ..
+                        } => match self.gpu.as_ref() {
+                            Some(gpu) => {
+                                self.buf_mgr.grow(buffer_slot);
+                                gpu.ack_grow(buffer_slot, vm_slot, counter);
+                            }
+                            None => {
+                                dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
+                            }
+                        },
+                        msg => {
+                            dev_crit!(self.dev, "Unknown event message: {:?}\n", msg);
+                        }
+                    }
+                }
+                _ => {
+                    dev_warn!(self.dev, "Unknown event message: {:?}\n", unsafe {
+                        msg.raw
+                    });
+                }
+            }
+        }
+    }
+}
+
+/// Firmware Log channel. This one is pretty special, since it has 6 sub-channels (for different log
+/// levels), and it also uses a side buffer to actually hold the log messages, only passing around
+/// pointers in the main buffer.
+pub(crate) struct FwLogChannel {
+    dev: AsahiDevRef,
+    ch: RxChannel<FwLogChannelState, RawFwLogMsg>,
+    payload_buf: GpuArray<RawFwLogPayloadMsg>,
+}
+
+impl FwLogChannel {
+    const RING_SIZE: usize = 0x100;
+    const BUF_SIZE: usize = 0x100;
+
+    /// Allocate a new Firmware Log channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<FwLogChannel> {
+        Ok(FwLogChannel {
+            dev: dev.into(),
+            ch: RxChannel::<FwLogChannelState, RawFwLogMsg>::new(alloc, Self::RING_SIZE)?,
+            payload_buf: alloc
+                .shared
+                .array_empty(Self::BUF_SIZE * FwLogChannelState::SUB_CHANNELS)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<FwLogChannelState, RawFwLogMsg> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Returns the GPU pointers to the firmware log payload buffer.
+    pub(crate) fn get_buf(&self) -> GpuWeakPointer<[RawFwLogPayloadMsg]> {
+        self.payload_buf.weak_pointer()
+    }
+
+    /// Polls for new log messages on all sub-rings.
+    pub(crate) fn poll(&mut self) {
+        for i in 0..=FwLogChannelState::SUB_CHANNELS - 1 {
+            while let Some(msg) = self.ch.peek(i) {
+                cls_dev_dbg!(FwLogCh, self.dev, "FwLog{}: {:?}\n", i, msg);
+                if msg.msg_type != 2 {
+                    dev_warn!(self.dev, "Unknown FWLog{} message: {:?}\n", i, msg);
+                    self.ch.get(i);
+                    continue;
+                }
+                if msg.msg_index.0 as usize >= Self::BUF_SIZE {
+                    dev_warn!(
+                        self.dev,
+                        "FWLog{} message index out of bounds: {:?}\n",
+                        i,
+                        msg
+                    );
+                    self.ch.get(i);
+                    continue;
+                }
+                let index = Self::BUF_SIZE * i + msg.msg_index.0 as usize;
+                let payload = &self.payload_buf.as_slice()[index];
+                if payload.msg_type != 3 {
+                    dev_warn!(self.dev, "Unknown FWLog{} payload: {:?}\n", i, payload);
+                    self.ch.get(i);
+                    continue;
+                }
+                let msg = if let Some(end) = payload.msg.iter().position(|&r| r == 0) {
+                    CStr::from_bytes_with_nul(&(*payload.msg)[..end + 1])
+                        .unwrap_or(c_str!("cstr_err"))
+                } else {
+                    dev_warn!(
+                        self.dev,
+                        "FWLog{} payload not NUL-terminated: {:?}\n",
+                        i,
+                        payload
+                    );
+                    self.ch.get(i);
+                    continue;
+                };
+                match i {
+                    0 => dev_dbg!(self.dev, "FWLog: {}\n", msg),
+                    1 => dev_info!(self.dev, "FWLog: {}\n", msg),
+                    2 => dev_notice!(self.dev, "FWLog: {}\n", msg),
+                    3 => dev_warn!(self.dev, "FWLog: {}\n", msg),
+                    4 => dev_err!(self.dev, "FWLog: {}\n", msg),
+                    5 => dev_crit!(self.dev, "FWLog: {}\n", msg),
+                    _ => (),
+                };
+                self.ch.get(i);
+            }
+        }
+    }
+}
+
+pub(crate) struct KTraceChannel {
+    dev: AsahiDevRef,
+    ch: RxChannel<ChannelState, RawKTraceMsg>,
+}
+
+/// KTrace channel, used to receive detailed execution trace markers from the firmware.
+/// We currently disable this in initdata, so no messages are expected here at this time.
+impl KTraceChannel {
+    /// Allocate a new KTrace channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<KTraceChannel> {
+        Ok(KTraceChannel {
+            dev: dev.into(),
+            ch: RxChannel::<ChannelState, RawKTraceMsg>::new(alloc, 0x200)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<ChannelState, RawKTraceMsg> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Polls for new KTrace messages on this ring.
+    pub(crate) fn poll(&mut self) {
+        while let Some(msg) = self.ch.get(0) {
+            cls_dev_dbg!(KTraceCh, self.dev, "KTrace: {:?}\n", msg);
+        }
+    }
+}
+
+/// Statistics channel, reporting power-related statistics to the driver.
+/// Not really implemented other than debug logs yet...
+#[versions(AGX)]
+pub(crate) struct StatsChannel {
+    dev: AsahiDevRef,
+    ch: RxChannel<ChannelState, RawStatsMsg::ver>,
+}
+
+#[versions(AGX)]
+impl StatsChannel::ver {
+    /// Allocate a new Statistics channel.
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+    ) -> Result<StatsChannel::ver> {
+        Ok(StatsChannel::ver {
+            dev: dev.into(),
+            ch: RxChannel::<ChannelState, RawStatsMsg::ver>::new(alloc, 0x100)?,
+        })
+    }
+
+    /// Returns the raw `ChannelRing` structure to pass to firmware.
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<ChannelState, RawStatsMsg::ver> {
+        self.ch.ring.to_raw()
+    }
+
+    /// Polls for new statistics messages on this ring.
+    pub(crate) fn poll(&mut self) {
+        while let Some(msg) = self.ch.get(0) {
+            let tag = unsafe { msg.raw.0 };
+            match tag {
+                0..=STATS_MAX::ver => {
+                    let msg = unsafe { msg.msg };
+                    cls_dev_dbg!(StatsCh, self.dev, "Stats: {:?}\n", msg);
+                }
+                _ => {
+                    pr_warn!("Unknown stats message: {:?}\n", unsafe { msg.raw });
+                }
+            }
+        }
+    }
+}
diff --git a/drivers/gpu/drm/asahi/debug.rs b/drivers/gpu/drm/asahi/debug.rs
new file mode 100644
index 00000000000000..f03a3f991d74a4
--- /dev/null
+++ b/drivers/gpu/drm/asahi/debug.rs
@@ -0,0 +1,131 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![allow(dead_code)]
+
+//! Debug enable/disable flags and convenience macros
+
+#[allow(unused_imports)]
+pub(crate) use super::{cls_dev_dbg, cls_pr_debug, debug, mod_dev_dbg, mod_pr_debug};
+use core::sync::atomic::{AtomicU64, Ordering};
+
+static DEBUG_FLAGS: AtomicU64 = AtomicU64::new(0);
+
+/// Debug flag bit indices
+pub(crate) enum DebugFlags {
+    // 0-3: Memory-related debug
+    Mmu = 0,
+    Alloc = 1,
+    Gem = 2,
+    Object = 3,
+
+    // 4-7: Firmware objects and resources
+    Event = 4,
+    Buffer = 5,
+    WorkQueue = 6,
+
+    // 8-13: DRM interface, rendering, compute, GPU globals
+    Gpu = 8,
+    File = 9,
+    Queue = 10,
+    Render = 11,
+    Compute = 12,
+
+    // 14-15: Misc stats
+    MemStats = 14,
+    TVBStats = 15,
+
+    // 16-22: Channels
+    FwLogCh = 16,
+    KTraceCh = 17,
+    StatsCh = 18,
+    EventCh = 19,
+    PipeCh = 20,
+    DeviceControlCh = 21,
+    FwCtlCh = 22,
+
+    // 32-35: Allocator debugging
+    FillAllocations = 32,
+    DebugAllocations = 33,
+    DetectOverflows = 34,
+    ForceCPUMaps = 35,
+
+    // 36-: Behavior flags
+    ConservativeTlbi = 36,
+    KeepGpuPowered = 37,
+    WaitForPowerOff = 38,
+    NoGpuRecovery = 39,
+    DisableClustering = 40,
+
+    // 48-: Misc
+    Debug0 = 48,
+    Debug1 = 49,
+    Debug2 = 50,
+    Debug3 = 51,
+    Debug4 = 52,
+    Debug5 = 53,
+    Debug6 = 54,
+    Debug7 = 55,
+
+    OopsOnGpuCrash = 63,
+}
+
+/// Update the cached global debug flags from the module parameter
+pub(crate) fn update_debug_flags() {
+    let flags = {
+        let lock = crate::THIS_MODULE.kernel_param_lock();
+        *crate::debug_flags.read(&lock)
+    };
+
+    DEBUG_FLAGS.store(flags, Ordering::Relaxed);
+}
+
+/// Check whether debug is enabled for a given flag
+#[inline(always)]
+pub(crate) fn debug_enabled(flag: DebugFlags) -> bool {
+    DEBUG_FLAGS.load(Ordering::Relaxed) & 1 << (flag as usize) != 0
+}
+
+/// Run some code only if debug is enabled for the calling module
+#[macro_export]
+macro_rules! debug {
+    ($($arg:tt)*) => {
+        if $crate::debug::debug_enabled(DEBUG_CLASS) {
+            $($arg)*
+        }
+    };
+}
+
+/// pr_info!() if debug is enabled for the calling module
+#[macro_export]
+macro_rules! mod_pr_debug (
+    ($($arg:tt)*) => (
+        $crate::debug! { ::kernel::pr_info! ( $($arg)* ); }
+    )
+);
+
+/// dev_info!() if debug is enabled for the calling module
+#[macro_export]
+macro_rules! mod_dev_dbg (
+    ($($arg:tt)*) => (
+        $crate::debug! { ::kernel::dev_info! ( $($arg)* ); }
+    )
+);
+
+/// pr_info!() if debug is enabled for a specific module
+#[macro_export]
+macro_rules! cls_pr_debug (
+    ($cls:ident, $($arg:tt)*) => (
+        if $crate::debug::debug_enabled($crate::debug::DebugFlags::$cls) {
+            ::kernel::pr_info! ( $($arg)* );
+        }
+    )
+);
+
+/// dev_info!() if debug is enabled for a specific module
+#[macro_export]
+macro_rules! cls_dev_dbg (
+    ($cls:ident, $($arg:tt)*) => (
+        if $crate::debug::debug_enabled($crate::debug::DebugFlags::$cls) {
+            ::kernel::dev_info! ( $($arg)* );
+        }
+    )
+);
diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs
new file mode 100644
index 00000000000000..0fa8b533651069
--- /dev/null
+++ b/drivers/gpu/drm/asahi/driver.rs
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Top-level GPU driver implementation.
+
+use kernel::{
+    c_str, device, drm, drm::drv, drm::ioctl, error::code, error::Result, of, platform, prelude::*,
+    sync::Arc,
+};
+
+use crate::{debug, file, gem, gpu, hw, regs};
+
+use kernel::device::RawDevice;
+use kernel::macros::vtable;
+use kernel::types::ARef;
+
+/// Driver metadata
+const INFO: drv::DriverInfo = drv::DriverInfo {
+    major: 0,
+    minor: 0,
+    patchlevel: 0,
+    name: c_str!("asahi"),
+    desc: c_str!("Apple AGX Graphics"),
+    date: c_str!("20220831"),
+};
+
+/// Device data for the driver registration.
+///
+/// Holds a reference to the top-level `GpuManager` object.
+pub(crate) struct AsahiData {
+    pub(crate) dev: ARef<device::Device>,
+    pub(crate) gpu: Arc<dyn gpu::GpuManager>,
+}
+
+/// Convenience type alias for the `device::Data` type for this driver.
+type DeviceData = device::Data<drv::Registration<AsahiDriver>, regs::Resources, AsahiData>;
+
+/// Empty struct representing this driver.
+pub(crate) struct AsahiDriver;
+
+/// Convenience type alias for the DRM device type for this driver.
+pub(crate) type AsahiDevice = kernel::drm::device::Device<AsahiDriver>;
+pub(crate) type AsahiDevRef = ARef<AsahiDevice>;
+
+/// DRM Driver implementation for `AsahiDriver`.
+#[vtable]
+impl drv::Driver for AsahiDriver {
+    /// Our `DeviceData` type, reference-counted
+    type Data = Arc<DeviceData>;
+    /// Our `File` type.
+    type File = file::File;
+    /// Our `Object` type.
+    type Object = gem::Object;
+
+    const INFO: drv::DriverInfo = INFO;
+    const FEATURES: u32 =
+        drv::FEAT_GEM | drv::FEAT_RENDER | drv::FEAT_SYNCOBJ | drv::FEAT_SYNCOBJ_TIMELINE;
+
+    kernel::declare_drm_ioctls! {
+        (ASAHI_GET_PARAMS,      drm_asahi_get_params,
+                          ioctl::RENDER_ALLOW, crate::file::File::get_params),
+        (ASAHI_VM_CREATE,       drm_asahi_vm_create,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::vm_create),
+        (ASAHI_VM_DESTROY,      drm_asahi_vm_destroy,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::vm_destroy),
+        (ASAHI_GEM_CREATE,      drm_asahi_gem_create,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_create),
+        (ASAHI_GEM_MMAP_OFFSET, drm_asahi_gem_mmap_offset,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_mmap_offset),
+        (ASAHI_GEM_BIND,        drm_asahi_gem_bind,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::gem_bind),
+        (ASAHI_QUEUE_CREATE,    drm_asahi_queue_create,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::queue_create),
+        (ASAHI_QUEUE_DESTROY,   drm_asahi_queue_destroy,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::queue_destroy),
+        (ASAHI_SUBMIT,          drm_asahi_submit,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::submit),
+    }
+}
+
+// OF Device ID table.
+kernel::define_of_id_table! {ASAHI_ID_TABLE, &'static hw::HwConfig, [
+    (of::DeviceId::Compatible(b"apple,agx-t8103"), Some(&hw::t8103::HWCONFIG)),
+    (of::DeviceId::Compatible(b"apple,agx-t8112"), Some(&hw::t8112::HWCONFIG)),
+    (of::DeviceId::Compatible(b"apple,agx-t6000"), Some(&hw::t600x::HWCONFIG_T6000)),
+    (of::DeviceId::Compatible(b"apple,agx-t6001"), Some(&hw::t600x::HWCONFIG_T6001)),
+    (of::DeviceId::Compatible(b"apple,agx-t6002"), Some(&hw::t600x::HWCONFIG_T6002)),
+    (of::DeviceId::Compatible(b"apple,agx-t6020"), Some(&hw::t602x::HWCONFIG_T6020)),
+    (of::DeviceId::Compatible(b"apple,agx-t6021"), Some(&hw::t602x::HWCONFIG_T6021)),
+]}
+
+/// Platform Driver implementation for `AsahiDriver`.
+impl platform::Driver for AsahiDriver {
+    /// Our `DeviceData` type, reference-counted
+    type Data = Arc<DeviceData>;
+    /// Data associated with each hardware ID.
+    type IdInfo = &'static hw::HwConfig;
+
+    // Assign the above OF ID table to this driver.
+    kernel::driver_of_id_table!(ASAHI_ID_TABLE);
+
+    /// Device probe function.
+    fn probe(
+        pdev: &mut platform::Device,
+        id_info: Option<&Self::IdInfo>,
+    ) -> Result<Arc<DeviceData>> {
+        debug::update_debug_flags();
+
+        let dev = device::Device::from_dev(pdev);
+
+        dev_info!(dev, "Probing...\n");
+
+        let cfg = id_info.ok_or(ENODEV)?;
+
+        pdev.set_dma_masks((1 << cfg.uat_oas) - 1)?;
+
+        let res = regs::Resources::new(pdev)?;
+
+        // Initialize misc MMIO
+        res.init_mmio()?;
+
+        // Start the coprocessor CPU, so UAT can initialize the handoff
+        res.start_cpu()?;
+
+        let node = dev.of_node().ok_or(EIO)?;
+        let compat: Vec<u32> = node.get_property(c_str!("apple,firmware-compat"))?;
+
+        let reg = drm::drv::Registration::<AsahiDriver>::new(&dev)?;
+        let gpu = match (cfg.gpu_gen, cfg.gpu_variant, compat.as_slice()) {
+            (hw::GpuGen::G13, _, &[12, 3, 0]) => {
+                gpu::GpuManagerG13V12_3::new(reg.device(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }
+            (hw::GpuGen::G14, hw::GpuVariant::G, &[12, 4, 0]) => {
+                gpu::GpuManagerG14V12_4::new(reg.device(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }
+            (hw::GpuGen::G13, _, &[13, 5, 0]) => {
+                gpu::GpuManagerG13V13_5::new(reg.device(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }
+            (hw::GpuGen::G14, hw::GpuVariant::G, &[13, 5, 0]) => {
+                gpu::GpuManagerG14V13_5::new(reg.device(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }
+            (hw::GpuGen::G14, _, &[13, 5, 0]) => {
+                gpu::GpuManagerG14XV13_5::new(reg.device(), &res, cfg)? as Arc<dyn gpu::GpuManager>
+            }
+            _ => {
+                dev_info!(
+                    dev,
+                    "Unsupported GPU/firmware combination ({:?}, {:?}, {:?})\n",
+                    cfg.gpu_gen,
+                    cfg.gpu_variant,
+                    compat
+                );
+                return Err(ENODEV);
+            }
+        };
+
+        let data =
+            kernel::new_device_data!(reg, res, AsahiData { dev, gpu }, "Asahi::Registrations")?;
+
+        let data: Arc<DeviceData> = data.into();
+
+        data.gpu.init()?;
+
+        kernel::drm_device_register!(
+            // TODO: Expose an API to get a pinned reference here
+            unsafe { Pin::new_unchecked(&mut *data.registrations().ok_or(ENXIO)?) },
+            data.clone(),
+            0
+        )?;
+
+        dev_info!(data.dev, "Probed!\n");
+        Ok(data)
+    }
+}
+
+// Export the OF ID table as a module ID table, to make modpost/autoloading work.
+kernel::module_of_id_table!(MOD_TABLE, ASAHI_ID_TABLE);
diff --git a/drivers/gpu/drm/asahi/event.rs b/drivers/gpu/drm/asahi/event.rs
new file mode 100644
index 00000000000000..cb4387c483b401
--- /dev/null
+++ b/drivers/gpu/drm/asahi/event.rs
@@ -0,0 +1,233 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU event manager
+//!
+//! The GPU firmware manages work completion by using event objects (Apple calls them "stamps"),
+//! which are monotonically incrementing counters. There are a fixed number of objects, and
+//! they are managed with a `SlotAllocator`.
+//!
+//! This module manages the set of available events and lets users compute expected values.
+//! It also manages signaling owners when the GPU firmware reports that an event fired.
+
+use crate::debug::*;
+use crate::fw::types::*;
+use crate::{gpu, slotalloc, workqueue};
+use core::cmp;
+use core::sync::atomic::Ordering;
+use kernel::prelude::*;
+use kernel::sync::Arc;
+use kernel::{c_str, static_lock_class};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Event;
+
+/// Number of events managed by the firmware.
+const NUM_EVENTS: u32 = 128;
+
+/// Inner data associated with a given event slot.
+pub(crate) struct EventInner {
+    /// CPU pointer to the driver notification event stamp
+    stamp: *const AtomicU32,
+    /// GPU pointer to the driver notification event stamp
+    gpu_stamp: GpuWeakPointer<Stamp>,
+    /// GPU pointer to the firmware-internal event stamp
+    gpu_fw_stamp: GpuWeakPointer<FwStamp>,
+}
+
+/// SAFETY: The event slots are safe to send across threads.
+unsafe impl Send for EventInner {}
+
+/// Alias for an event token, which allows requesting the same event.
+pub(crate) type Token = slotalloc::SlotToken;
+/// Alias for an allocated `Event` that has a slot.
+pub(crate) type Event = slotalloc::Guard<EventInner>;
+
+/// Represents a given stamp value for an event.
+#[derive(Eq, PartialEq, Copy, Clone, Debug)]
+#[repr(transparent)]
+pub(crate) struct EventValue(u32);
+
+impl EventValue {
+    /// Returns the `EventValue` that succeeds this one.
+    pub(crate) fn next(&self) -> EventValue {
+        EventValue(self.0.wrapping_add(0x100))
+    }
+
+    /// Increments this `EventValue` in place.
+    pub(crate) fn increment(&mut self) {
+        self.0 = self.0.wrapping_add(0x100);
+    }
+
+    /* Not used
+    /// Increments this `EventValue` in place by a certain count.
+    pub(crate) fn add(&mut self, val: u32) {
+        self.0 = self
+            .0
+            .wrapping_add(val.checked_mul(0x100).expect("Adding too many events"));
+    }
+    */
+
+    /// Increments this `EventValue` in place by a certain count.
+    pub(crate) fn sub(&mut self, val: u32) {
+        self.0 = self
+            .0
+            .wrapping_sub(val.checked_mul(0x100).expect("Subtracting too many events"));
+    }
+
+    /// Computes the delta between this event and another event.
+    pub(crate) fn delta(&self, other: &EventValue) -> i32 {
+        (self.0.wrapping_sub(other.0) as i32) >> 8
+    }
+}
+
+impl PartialOrd for EventValue {
+    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+impl Ord for EventValue {
+    fn cmp(&self, other: &Self) -> cmp::Ordering {
+        self.delta(other).cmp(&0)
+    }
+}
+
+impl EventInner {
+    /// Returns the GPU pointer to the driver notification stamp
+    pub(crate) fn stamp_pointer(&self) -> GpuWeakPointer<Stamp> {
+        self.gpu_stamp
+    }
+
+    /// Returns the GPU pointer to the firmware internal stamp
+    pub(crate) fn fw_stamp_pointer(&self) -> GpuWeakPointer<FwStamp> {
+        self.gpu_fw_stamp
+    }
+
+    /// Fetches the current event value from shared memory
+    pub(crate) fn current(&self) -> EventValue {
+        // SAFETY: The pointer is always valid as constructed in
+        // EventManager below, and outside users cannot construct
+        // new EventInners, nor move or copy them, and Guards as
+        // returned by the SlotAllocator hold a reference to the
+        // SlotAllocator containing the EventManagerInner, which
+        // keeps the GpuObject the stamp is contained within alive.
+        EventValue(unsafe { &*self.stamp }.load(Ordering::Acquire))
+    }
+}
+
+impl slotalloc::SlotItem for EventInner {
+    type Data = EventManagerInner;
+
+    fn release(&mut self, data: &mut Self::Data, slot: u32) {
+        mod_pr_debug!("EventManager: Released slot {}\n", slot);
+        data.owners[slot as usize] = None;
+    }
+}
+
+/// Inner data for the event manager, to be protected by the SlotAllocator lock.
+pub(crate) struct EventManagerInner {
+    stamps: GpuArray<Stamp>,
+    fw_stamps: GpuArray<FwStamp>,
+    // Note: Use dyn to avoid having to version this entire module.
+    owners: Vec<Option<Arc<dyn workqueue::WorkQueue + Send + Sync>>>,
+}
+
+/// Top-level EventManager object.
+pub(crate) struct EventManager {
+    alloc: slotalloc::SlotAllocator<EventInner>,
+}
+
+impl EventManager {
+    /// Create a new EventManager.
+    #[inline(never)]
+    pub(crate) fn new(alloc: &mut gpu::KernelAllocators) -> Result<EventManager> {
+        let mut owners = Vec::new();
+        for _i in 0..(NUM_EVENTS as usize) {
+            owners.push(None, GFP_KERNEL)?;
+        }
+        let inner = EventManagerInner {
+            stamps: alloc.shared.array_empty(NUM_EVENTS as usize)?,
+            fw_stamps: alloc.private.array_empty(NUM_EVENTS as usize)?,
+            owners,
+        };
+
+        Ok(EventManager {
+            alloc: slotalloc::SlotAllocator::new(
+                NUM_EVENTS,
+                inner,
+                |inner: &mut EventManagerInner, slot| EventInner {
+                    stamp: &inner.stamps[slot as usize].0,
+                    gpu_stamp: inner.stamps.weak_item_pointer(slot as usize),
+                    gpu_fw_stamp: inner.fw_stamps.weak_item_pointer(slot as usize),
+                },
+                c_str!("EventManager::SlotAllocator"),
+                static_lock_class!(),
+                static_lock_class!(),
+            )?,
+        })
+    }
+
+    /// Gets a free `Event`, optionally trying to reuse the last one allocated by this caller.
+    pub(crate) fn get(
+        &self,
+        token: Option<Token>,
+        owner: Arc<dyn workqueue::WorkQueue + Send + Sync>,
+    ) -> Result<Event> {
+        let ev = self.alloc.get_inner(token, |inner, ev| {
+            mod_pr_debug!(
+                "EventManager: Registered owner {:p} on slot {}\n",
+                &*owner,
+                ev.slot()
+            );
+            inner.owners[ev.slot() as usize] = Some(owner);
+            Ok(())
+        })?;
+        Ok(ev)
+    }
+
+    /// Signals an event by slot, indicating completion (of one or more commands).
+    pub(crate) fn signal(&self, slot: u32) {
+        match self
+            .alloc
+            .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned())
+        {
+            Some(owner) => {
+                owner.signal();
+            }
+            None => {
+                mod_pr_debug!("EventManager: Received event for empty slot {}\n", slot);
+            }
+        }
+    }
+
+    /// Marks the owner of an event as having lost its work due to a GPU error.
+    pub(crate) fn mark_error(&self, slot: u32, wait_value: u32, error: workqueue::WorkError) {
+        match self
+            .alloc
+            .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned())
+        {
+            Some(owner) => {
+                owner.mark_error(EventValue(wait_value), error);
+            }
+            None => {
+                pr_err!("Received error for empty slot {}\n", slot);
+            }
+        }
+    }
+
+    /// Fail all commands, used when the GPU crashes.
+    pub(crate) fn fail_all(&self, error: workqueue::WorkError) {
+        let mut owners: Vec<Arc<dyn workqueue::WorkQueue + Send + Sync>> = Vec::new();
+
+        self.alloc.with_inner(|inner| {
+            for wq in inner.owners.iter().filter_map(|o| o.as_ref()).cloned() {
+                if owners.push(wq, GFP_KERNEL).is_err() {
+                    pr_err!("Failed to signal failure to WorkQueue\n");
+                }
+            }
+        });
+
+        for wq in owners {
+            wq.fail_all(error);
+        }
+    }
+}
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
new file mode 100644
index 00000000000000..fbe24b2e646152
--- /dev/null
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -0,0 +1,763 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![allow(clippy::unusual_byte_groupings)]
+
+//! File implementation, which represents a single DRM client.
+//!
+//! This is in charge of managing the resources associated with one GPU client, including an
+//! arbitrary number of submission queues and Vm objects, and reporting hardware/driver
+//! information to userspace and accepting submissions.
+
+use crate::debug::*;
+use crate::driver::AsahiDevice;
+use crate::{alloc, buffer, driver, gem, mmu, queue};
+use core::mem::MaybeUninit;
+use kernel::dma_fence::RawDmaFence;
+use kernel::drm::gem::BaseObject;
+use kernel::error::code::*;
+use kernel::io_buffer::{IoBufferReader, IoBufferWriter};
+use kernel::prelude::*;
+use kernel::sync::{Arc, Mutex};
+use kernel::user_ptr::UserSlicePtr;
+use kernel::{dma_fence, drm, uapi, xarray};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::File;
+
+const MAX_SYNCS_PER_SUBMISSION: u32 = 64;
+const MAX_COMMANDS_PER_SUBMISSION: u32 = 64;
+pub(crate) const MAX_COMMANDS_IN_FLIGHT: u32 = 1024;
+
+/// A client instance of an `mmu::Vm` address space.
+struct Vm {
+    ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+    ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+    vm: mmu::Vm,
+    dummy_obj: gem::ObjectRef,
+}
+
+impl Drop for Vm {
+    fn drop(&mut self) {
+        // Mappings create a reference loop, make sure to break it.
+        self.dummy_obj.drop_vm_mappings(self.vm.id());
+    }
+}
+
+/// Sync object from userspace.
+pub(crate) struct SyncItem {
+    pub(crate) syncobj: drm::syncobj::SyncObj,
+    pub(crate) fence: Option<dma_fence::Fence>,
+    pub(crate) chain_fence: Option<dma_fence::FenceChain>,
+    pub(crate) timeline_value: u64,
+}
+
+impl SyncItem {
+    fn parse_one(file: &DrmFile, data: uapi::drm_asahi_sync, out: bool) -> Result<SyncItem> {
+        if data.extensions != 0 {
+            return Err(EINVAL);
+        }
+
+        match data.sync_type {
+            uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_SYNCOBJ => {
+                if data.timeline_value != 0 {
+                    return Err(EINVAL);
+                }
+                let syncobj = drm::syncobj::SyncObj::lookup_handle(file, data.handle)?;
+
+                Ok(SyncItem {
+                    fence: if out {
+                        None
+                    } else {
+                        Some(syncobj.fence_get().ok_or(EINVAL)?)
+                    },
+                    syncobj,
+                    chain_fence: None,
+                    timeline_value: data.timeline_value,
+                })
+            }
+            uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ => {
+                let syncobj = drm::syncobj::SyncObj::lookup_handle(file, data.handle)?;
+                let fence = if out {
+                    None
+                } else {
+                    syncobj
+                        .fence_get()
+                        .ok_or(EINVAL)?
+                        .chain_find_seqno(data.timeline_value)?
+                };
+
+                Ok(SyncItem {
+                    fence,
+                    syncobj,
+                    chain_fence: if out {
+                        Some(dma_fence::FenceChain::new()?)
+                    } else {
+                        None
+                    },
+                    timeline_value: data.timeline_value,
+                })
+            }
+            _ => Err(EINVAL),
+        }
+    }
+
+    fn parse_array(file: &DrmFile, ptr: u64, count: u32, out: bool) -> Result<Vec<SyncItem>> {
+        let mut vec = Vec::with_capacity(count as usize, GFP_KERNEL)?;
+
+        const STRIDE: usize = core::mem::size_of::<uapi::drm_asahi_sync>();
+        let size = STRIDE * count as usize;
+
+        // SAFETY: We only read this once, so there are no TOCTOU issues.
+        let mut reader = unsafe { UserSlicePtr::new(ptr as usize as *mut _, size).reader() };
+
+        for _i in 0..count {
+            let mut sync: MaybeUninit<uapi::drm_asahi_sync> = MaybeUninit::uninit();
+
+            // SAFETY: The size of `sync` is STRIDE
+            unsafe { reader.read_raw(sync.as_mut_ptr() as *mut u8, STRIDE)? };
+
+            // SAFETY: All bit patterns in the struct are valid
+            let sync = unsafe { sync.assume_init() };
+
+            vec.push(SyncItem::parse_one(file, sync, out)?, GFP_KERNEL)?;
+        }
+
+        Ok(vec)
+    }
+}
+
+/// State associated with a client.
+pub(crate) struct File {
+    id: u64,
+    vms: xarray::XArray<Box<Vm>>,
+    queues: xarray::XArray<Arc<Mutex<Box<dyn queue::Queue>>>>,
+}
+
+/// Convenience type alias for our DRM `File` type.
+pub(crate) type DrmFile = drm::file::File<File>;
+
+/// Start address of the 32-bit USC address space.
+const VM_SHADER_START: u64 = 0x11_00000000;
+/// End address of the 32-bit USC address space.
+const VM_SHADER_END: u64 = 0x11_ffffffff;
+/// Start address of the general user mapping region.
+const VM_USER_START: u64 = 0x20_00000000;
+/// End address of the general user mapping region.
+const VM_USER_END: u64 = 0x5f_ffffffff;
+
+/// Start address of the kernel-managed GPU-only mapping region.
+const VM_DRV_GPU_START: u64 = 0x60_00000000;
+/// End address of the kernel-managed GPU-only mapping region.
+const VM_DRV_GPU_END: u64 = 0x60_ffffffff;
+/// Start address of the kernel-managed GPU/FW shared mapping region.
+const VM_DRV_GPUFW_START: u64 = 0x61_00000000;
+/// End address of the kernel-managed GPU/FW shared mapping region.
+const VM_DRV_GPUFW_END: u64 = 0x61_ffffffff;
+/// Address of a special dummy page?
+const VM_UNK_PAGE: u64 = 0x6f_ffff8000;
+
+impl drm::file::DriverFile for File {
+    type Driver = driver::AsahiDriver;
+
+    /// Create a new `File` instance for a fresh client.
+    fn open(device: &AsahiDevice) -> Result<Pin<Box<Self>>> {
+        debug::update_debug_flags();
+
+        let gpu = &device.data().gpu;
+        let id = gpu.ids().file.next();
+
+        mod_dev_dbg!(device, "[File {}]: DRM device opened\n", id);
+        Ok(Box::into_pin(Box::new(
+            Self {
+                id,
+                vms: xarray::XArray::new(xarray::flags::ALLOC1),
+                queues: xarray::XArray::new(xarray::flags::ALLOC1),
+            },
+            GFP_KERNEL,
+        )?))
+    }
+}
+
+impl File {
+    fn vms(self: Pin<&Self>) -> Pin<&xarray::XArray<Box<Vm>>> {
+        // SAFETY: Structural pinned projection for vms.
+        // We never move out of this field.
+        unsafe { self.map_unchecked(|s| &s.vms) }
+    }
+
+    #[allow(clippy::type_complexity)]
+    fn queues(self: Pin<&Self>) -> Pin<&xarray::XArray<Arc<Mutex<Box<dyn queue::Queue>>>>> {
+        // SAFETY: Structural pinned projection for queues.
+        // We never move out of this field.
+        unsafe { self.map_unchecked(|s| &s.queues) }
+    }
+
+    /// IOCTL: get_param: Get a driver parameter value.
+    pub(crate) fn get_params(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_get_params,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        mod_dev_dbg!(device, "[File {}]: IOCTL: get_params\n", file.inner().id);
+
+        let gpu = &device.data().gpu;
+
+        if data.extensions != 0 || data.param_group != 0 || data.pad != 0 {
+            return Err(EINVAL);
+        }
+
+        if gpu.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        let mut params = uapi::drm_asahi_params_global {
+            unstable_uabi_version: uapi::DRM_ASAHI_UNSTABLE_UABI_VERSION,
+            pad0: 0,
+
+            feat_compat: gpu.get_cfg().gpu_feat_compat,
+            feat_incompat: gpu.get_cfg().gpu_feat_incompat,
+
+            gpu_generation: gpu.get_dyncfg().id.gpu_gen as u32,
+            gpu_variant: gpu.get_dyncfg().id.gpu_variant as u32,
+            gpu_revision: gpu.get_dyncfg().id.gpu_rev as u32,
+            chip_id: gpu.get_cfg().chip_id,
+
+            num_dies: gpu.get_dyncfg().id.max_dies,
+            num_clusters_total: gpu.get_dyncfg().id.num_clusters,
+            num_cores_per_cluster: gpu.get_dyncfg().id.num_cores,
+            num_frags_per_cluster: gpu.get_dyncfg().id.num_frags,
+            num_gps_per_cluster: gpu.get_dyncfg().id.num_gps,
+            num_cores_total_active: gpu.get_dyncfg().id.total_active_cores,
+            core_masks: [0; uapi::DRM_ASAHI_MAX_CLUSTERS as usize],
+
+            vm_page_size: mmu::UAT_PGSZ as u32,
+            pad1: 0,
+            vm_user_start: VM_USER_START,
+            vm_user_end: VM_USER_END,
+            vm_shader_start: VM_SHADER_START,
+            vm_shader_end: VM_SHADER_END,
+
+            max_syncs_per_submission: MAX_SYNCS_PER_SUBMISSION,
+            max_commands_per_submission: MAX_COMMANDS_PER_SUBMISSION,
+            max_commands_in_flight: MAX_COMMANDS_IN_FLIGHT,
+            max_attachments: crate::microseq::MAX_ATTACHMENTS as u32,
+
+            timer_frequency_hz: gpu.get_cfg().base_clock_hz,
+            min_frequency_khz: gpu.get_dyncfg().pwr.min_frequency_khz(),
+            max_frequency_khz: gpu.get_dyncfg().pwr.max_frequency_khz(),
+            max_power_mw: gpu.get_dyncfg().pwr.max_power_mw,
+
+            result_render_size: core::mem::size_of::<uapi::drm_asahi_result_render>() as u32,
+            result_compute_size: core::mem::size_of::<uapi::drm_asahi_result_compute>() as u32,
+        };
+
+        for (i, mask) in gpu.get_dyncfg().id.core_masks.iter().enumerate() {
+            *(params.core_masks.get_mut(i).ok_or(EIO)?) = (*mask).into();
+        }
+
+        let size = core::mem::size_of::<uapi::drm_asahi_params_global>().min(data.size.try_into()?);
+
+        // SAFETY: We only write to this userptr once, so there are no TOCTOU issues.
+        let mut params_writer =
+            unsafe { UserSlicePtr::new(data.pointer as usize as *mut _, size).writer() };
+
+        // SAFETY: `size` is at most the sizeof of `params`
+        unsafe { params_writer.write_raw(&params as *const _ as *const u8, size)? };
+
+        Ok(0)
+    }
+
+    /// IOCTL: vm_create: Create a new `Vm`.
+    pub(crate) fn vm_create(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_vm_create,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.extensions != 0 {
+            return Err(EINVAL);
+        }
+
+        let gpu = &device.data().gpu;
+        let file_id = file.inner().id;
+        let vm = gpu.new_vm(file_id)?;
+
+        let resv = file.inner().vms().reserve()?;
+        let id: u32 = resv.index().try_into()?;
+
+        mod_dev_dbg!(device, "[File {} VM {}]: VM Create\n", file_id, id);
+        mod_dev_dbg!(
+            device,
+            "[File {} VM {}]: Creating allocators\n",
+            file_id,
+            id
+        );
+        let ualloc = Arc::pin_init(Mutex::new(alloc::DefaultAllocator::new(
+            device,
+            &vm,
+            VM_DRV_GPU_START,
+            VM_DRV_GPU_END,
+            buffer::PAGE_SIZE,
+            mmu::PROT_GPU_SHARED_RW,
+            512 * 1024,
+            true,
+            fmt!("File {} VM {} GPU Shared", file_id, id),
+            false,
+        )?))?;
+        let ualloc_priv = Arc::pin_init(Mutex::new(alloc::DefaultAllocator::new(
+            device,
+            &vm,
+            VM_DRV_GPUFW_START,
+            VM_DRV_GPUFW_END,
+            buffer::PAGE_SIZE,
+            mmu::PROT_GPU_FW_PRIV_RW,
+            64 * 1024,
+            true,
+            fmt!("File {} VM {} GPU FW Private", file_id, id),
+            false,
+        )?))?;
+
+        mod_dev_dbg!(
+            device,
+            "[File {} VM {}]: Creating dummy object\n",
+            file_id,
+            id
+        );
+        let mut dummy_obj = gem::new_kernel_object(device, 0x4000)?;
+        dummy_obj.vmap()?.as_mut_slice().fill(0);
+        dummy_obj.map_at(&vm, VM_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
+
+        mod_dev_dbg!(device, "[File {} VM {}]: VM created\n", file_id, id);
+        resv.store(Box::new(Vm {
+            ualloc,
+            ualloc_priv,
+            vm,
+            dummy_obj,
+        })?)?;
+
+        data.vm_id = id;
+
+        Ok(0)
+    }
+
+    /// IOCTL: vm_destroy: Destroy a `Vm`.
+    pub(crate) fn vm_destroy(
+        _device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_vm_destroy,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.extensions != 0 {
+            return Err(EINVAL);
+        }
+
+        if file.inner().vms().remove(data.vm_id as usize).is_none() {
+            Err(ENOENT)
+        } else {
+            Ok(0)
+        }
+    }
+
+    /// IOCTL: gem_create: Create a new GEM object.
+    pub(crate) fn gem_create(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_create,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        mod_dev_dbg!(
+            device,
+            "[File {}]: IOCTL: gem_create size={:#x?}\n",
+            file.inner().id,
+            data.size
+        );
+
+        if data.extensions != 0
+            || (data.flags & !(uapi::ASAHI_GEM_WRITEBACK | uapi::ASAHI_GEM_VM_PRIVATE)) != 0
+            || (data.flags & uapi::ASAHI_GEM_VM_PRIVATE == 0 && data.vm_id != 0)
+        {
+            return Err(EINVAL);
+        }
+
+        let vm_id = if data.flags & uapi::ASAHI_GEM_VM_PRIVATE != 0 {
+            Some(
+                file.inner()
+                    .vms()
+                    .get(data.vm_id.try_into()?)
+                    .ok_or(ENOENT)?
+                    .borrow()
+                    .vm
+                    .id(),
+            )
+        } else {
+            None
+        };
+
+        let bo = gem::new_object(device, data.size.try_into()?, data.flags, vm_id)?;
+
+        let handle = bo.gem.create_handle(file)?;
+        data.handle = handle;
+
+        mod_dev_dbg!(
+            device,
+            "[File {}]: IOCTL: gem_create size={:#x} handle={:#x?}\n",
+            file.inner().id,
+            data.size,
+            data.handle
+        );
+
+        Ok(0)
+    }
+
+    /// IOCTL: gem_mmap_offset: Assign an mmap offset to a GEM object.
+    pub(crate) fn gem_mmap_offset(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_mmap_offset,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        mod_dev_dbg!(
+            device,
+            "[File {}]: IOCTL: gem_mmap_offset handle={:#x?}\n",
+            file.inner().id,
+            data.handle
+        );
+
+        if data.extensions != 0 || data.flags != 0 {
+            return Err(EINVAL);
+        }
+
+        let bo = gem::lookup_handle(file, data.handle)?;
+        data.offset = bo.gem.create_mmap_offset()?;
+        Ok(0)
+    }
+
+    /// IOCTL: gem_bind: Map or unmap a GEM object into a Vm.
+    pub(crate) fn gem_bind(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_bind,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        mod_dev_dbg!(
+            device,
+            "[File {} VM {}]: IOCTL: gem_bind op={:?} handle={:#x?} flags={:#x?} {:#x?}:{:#x?} -> {:#x?}\n",
+            file.inner().id,
+            data.vm_id,
+            data.op,
+            data.handle,
+            data.flags,
+            data.offset,
+            data.range,
+            data.addr
+        );
+
+        if data.extensions != 0 {
+            return Err(EINVAL);
+        }
+
+        match data.op {
+            uapi::drm_asahi_bind_op_ASAHI_BIND_OP_BIND => Self::do_gem_bind(device, data, file),
+            uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND => Err(ENOTSUPP),
+            uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND_ALL => {
+                Self::do_gem_unbind_all(device, data, file)
+            }
+            _ => Err(EINVAL),
+        }
+    }
+
+    pub(crate) fn do_gem_bind(
+        _device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_bind,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.offset != 0 {
+            return Err(EINVAL); // Not supported yet
+        }
+
+        if (data.addr | data.range) as usize & mmu::UAT_PGMSK != 0 {
+            return Err(EINVAL); // Must be page aligned
+        }
+
+        if (data.flags & !(uapi::ASAHI_BIND_READ | uapi::ASAHI_BIND_WRITE)) != 0 {
+            return Err(EINVAL);
+        }
+
+        let mut bo = gem::lookup_handle(file, data.handle)?;
+
+        if data.range != bo.size().try_into()? {
+            return Err(EINVAL); // Not supported yet
+        }
+
+        let start = data.addr;
+        let end = data.addr + data.range - 1;
+
+        if (VM_SHADER_START..=VM_SHADER_END).contains(&start) {
+            if !(VM_SHADER_START..=VM_SHADER_END).contains(&end) {
+                return Err(EINVAL); // Invalid map range
+            }
+        } else if (VM_USER_START..=VM_USER_END).contains(&start) {
+            if !(VM_USER_START..=VM_USER_END).contains(&end) {
+                return Err(EINVAL); // Invalid map range
+            }
+        } else {
+            return Err(EINVAL); // Invalid map range
+        }
+
+        // Just in case
+        if end >= VM_DRV_GPU_START {
+            return Err(EINVAL);
+        }
+
+        let prot = if data.flags & uapi::ASAHI_BIND_READ != 0 {
+            if data.flags & uapi::ASAHI_BIND_WRITE != 0 {
+                mmu::PROT_GPU_SHARED_RW
+            } else {
+                mmu::PROT_GPU_SHARED_RO
+            }
+        } else if data.flags & uapi::ASAHI_BIND_WRITE != 0 {
+            mmu::PROT_GPU_SHARED_WO
+        } else {
+            return Err(EINVAL); // Must specify one of ASAHI_BIND_{READ,WRITE}
+        };
+
+        // Clone it immediately so we aren't holding the XArray lock
+        let vm = file
+            .inner()
+            .vms()
+            .get(data.vm_id.try_into()?)
+            .ok_or(ENOENT)?
+            .borrow()
+            .vm
+            .clone();
+
+        bo.map_at(&vm, start, prot, true)?;
+
+        Ok(0)
+    }
+
+    pub(crate) fn do_gem_unbind_all(
+        _device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_bind,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.flags != 0 || data.offset != 0 || data.range != 0 || data.addr != 0 {
+            return Err(EINVAL);
+        }
+
+        let mut bo = gem::lookup_handle(file, data.handle)?;
+
+        if data.vm_id == 0 {
+            bo.drop_file_mappings(file.inner().id);
+        } else {
+            let vm_id = file
+                .inner()
+                .vms()
+                .get(data.vm_id.try_into()?)
+                .ok_or(ENOENT)?
+                .borrow()
+                .vm
+                .id();
+            bo.drop_vm_mappings(vm_id);
+        }
+
+        Ok(0)
+    }
+
+    /// IOCTL: queue_create: Create a new command submission queue of a given type.
+    pub(crate) fn queue_create(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_queue_create,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        let file_id = file.inner().id;
+
+        mod_dev_dbg!(
+            device,
+            "[File {} VM {}]: Creating queue caps={:?} prio={:?} flags={:#x?}\n",
+            file_id,
+            data.vm_id,
+            data.queue_caps,
+            data.priority,
+            data.flags,
+        );
+
+        if data.extensions != 0
+            || data.flags != 0
+            || data.priority > 3
+            || data.queue_caps == 0
+            || (data.queue_caps
+                & !(uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_RENDER
+                    | uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_BLIT
+                    | uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_COMPUTE))
+                != 0
+        {
+            return Err(EINVAL);
+        }
+
+        let resv = file.inner().queues().reserve()?;
+        let file_vm = file
+            .inner()
+            .vms()
+            .get(data.vm_id.try_into()?)
+            .ok_or(ENOENT)?;
+        let vm = file_vm.borrow().vm.clone();
+        let ualloc = file_vm.borrow().ualloc.clone();
+        let ualloc_priv = file_vm.borrow().ualloc_priv.clone();
+        // Drop the vms lock eagerly
+        core::mem::drop(file_vm);
+
+        let queue =
+            device
+                .data()
+                .gpu
+                .new_queue(vm, ualloc, ualloc_priv, data.priority, data.queue_caps)?;
+
+        data.queue_id = resv.index().try_into()?;
+        resv.store(Arc::pin_init(Mutex::new(queue), GFP_KERNEL)?)?;
+
+        Ok(0)
+    }
+
+    /// IOCTL: queue_destroy: Destroy a command submission queue.
+    pub(crate) fn queue_destroy(
+        _device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_queue_destroy,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.extensions != 0 {
+            return Err(EINVAL);
+        }
+
+        if file
+            .inner()
+            .queues()
+            .remove(data.queue_id as usize)
+            .is_none()
+        {
+            Err(ENOENT)
+        } else {
+            Ok(0)
+        }
+    }
+
+    /// IOCTL: submit: Submit GPU work to a command submission queue.
+    pub(crate) fn submit(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_submit,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.extensions != 0
+            || data.flags != 0
+            || data.in_sync_count > MAX_SYNCS_PER_SUBMISSION
+            || data.out_sync_count > MAX_SYNCS_PER_SUBMISSION
+            || data.command_count > MAX_COMMANDS_PER_SUBMISSION
+        {
+            return Err(EINVAL);
+        }
+
+        debug::update_debug_flags();
+
+        let gpu = &device.data().gpu;
+        gpu.update_globals();
+
+        // Upgrade to Arc<T> to drop the XArray lock early
+        let queue: Arc<Mutex<Box<dyn queue::Queue>>> = file
+            .inner()
+            .queues()
+            .get(data.queue_id.try_into()?)
+            .ok_or(ENOENT)?
+            .borrow()
+            .into();
+
+        let id = gpu.ids().submission.next();
+        mod_dev_dbg!(
+            device,
+            "[File {} Queue {}]: IOCTL: submit (submission ID: {})\n",
+            file.inner().id,
+            data.queue_id,
+            id
+        );
+
+        mod_dev_dbg!(
+            device,
+            "[File {} Queue {}]: IOCTL: submit({}): Parsing in_syncs\n",
+            file.inner().id,
+            data.queue_id,
+            id
+        );
+        let in_syncs = SyncItem::parse_array(file, data.in_syncs, data.in_sync_count, false)?;
+        mod_dev_dbg!(
+            device,
+            "[File {} Queue {}]: IOCTL: submit({}): Parsing out_syncs\n",
+            file.inner().id,
+            data.queue_id,
+            id
+        );
+        let out_syncs = SyncItem::parse_array(file, data.out_syncs, data.out_sync_count, true)?;
+
+        let result_buf = if data.result_handle != 0 {
+            mod_dev_dbg!(
+                device,
+                "[File {} Queue {}]: IOCTL: submit({}): Looking up result_handle {}\n",
+                file.inner().id,
+                data.queue_id,
+                id,
+                data.result_handle
+            );
+            Some(gem::lookup_handle(file, data.result_handle)?)
+        } else {
+            None
+        };
+
+        mod_dev_dbg!(
+            device,
+            "[File {} Queue {}]: IOCTL: submit({}): Parsing commands\n",
+            file.inner().id,
+            data.queue_id,
+            id
+        );
+        let mut commands = Vec::with_capacity(data.command_count as usize, GFP_KERNEL)?;
+
+        const STRIDE: usize = core::mem::size_of::<uapi::drm_asahi_command>();
+        let size = STRIDE * data.command_count as usize;
+
+        // SAFETY: We only read this once, so there are no TOCTOU issues.
+        let mut reader =
+            unsafe { UserSlicePtr::new(data.commands as usize as *mut _, size).reader() };
+
+        for _i in 0..data.command_count {
+            let mut cmd: MaybeUninit<uapi::drm_asahi_command> = MaybeUninit::uninit();
+
+            // SAFETY: The size of `sync` is STRIDE
+            unsafe { reader.read_raw(cmd.as_mut_ptr() as *mut u8, STRIDE)? };
+
+            // SAFETY: All bit patterns in the struct are valid
+            commands.push(unsafe { cmd.assume_init() }, GFP_KERNEL)?;
+        }
+
+        let ret = queue
+            .lock()
+            .submit(id, in_syncs, out_syncs, result_buf, commands);
+
+        match ret {
+            Err(ERESTARTSYS) => Err(ERESTARTSYS),
+            Err(e) => {
+                dev_info!(
+                    device,
+                    "[File {} Queue {}]: IOCTL: submit failed! (submission ID: {} err: {:?})\n",
+                    file.inner().id,
+                    data.queue_id,
+                    id,
+                    e
+                );
+                Err(e)
+            }
+            Ok(_) => Ok(0),
+        }
+    }
+
+    /// Returns the unique file ID for this `File`.
+    pub(crate) fn file_id(&self) -> u64 {
+        self.id
+    }
+}
+
+impl Drop for File {
+    fn drop(&mut self) {
+        mod_pr_debug!("[File {}]: Closing...\n", self.id);
+    }
+}
diff --git a/drivers/gpu/drm/asahi/float.rs b/drivers/gpu/drm/asahi/float.rs
new file mode 100644
index 00000000000000..f35134123a08d9
--- /dev/null
+++ b/drivers/gpu/drm/asahi/float.rs
@@ -0,0 +1,383 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Basic soft floating-point support
+//!
+//! The GPU firmware requires a large number of power-related configuration values, many of which
+//! are IEEE 754 32-bit floating point values. These values change not only between GPU/SoC
+//! variants, but also between specific hardware platforms using these SoCs, so they must be
+//! derived from device tree properties. There are many redundant values computed from the same
+//! inputs with simple add/sub/mul/div calculations, plus a few values that are actually specific
+//! to each individual device depending on its binning and fused voltage configuration, so it
+//! doesn't make sense to store the final values to be passed to the firmware in the device tree.
+//!
+//! Therefore, we need a way to perform floating-point calculations in the kernel.
+//!
+//! Using the actual FPU from kernel mode is asking for trouble, since there is no way to bound
+//! the execution of FPU instructions to a controlled section of code without outright putting it
+//! in its own compilation unit, which is quite painful for Rust. Since these calculations only
+//! have to happen at initialization time and there is no need for performance, let's use a simple
+//! software float implementation instead.
+//!
+//! This implementation makes no attempt to be fully IEEE754 compliant, but it's good enough and
+//! gives bit-identical results to macOS in the vast majority of cases, with one or two exceptions
+//! related to slightly non-compliant rounding.
+
+use core::ops;
+use kernel::{init::Zeroable, of, prelude::*};
+
+/// An IEEE754-compatible floating point number implemented in software.
+#[derive(Default, Debug, Copy, Clone)]
+pub(crate) struct F32(u32);
+
+unsafe impl Zeroable for F32 {}
+
+#[derive(Default, Debug, Copy, Clone)]
+struct F32U {
+    sign: bool,
+    exp: i32,
+    frac: i64,
+}
+
+impl F32 {
+    /// Convert a raw 32-bit representation into an F32
+    pub(crate) const fn from_bits(u: u32) -> F32 {
+        F32(u)
+    }
+
+    // Convert a `f32` value into an F32
+    //
+    // This must ONLY be used in const context. Use the `f32!{}` macro to do it safely.
+    #[doc(hidden)]
+    pub(crate) const fn from_f32(v: f32) -> F32 {
+        F32(unsafe { core::mem::transmute(v) })
+    }
+
+    // Convert an F32 into a `f32` value
+    //
+    // For testing only.
+    #[doc(hidden)]
+    #[cfg(test)]
+    pub(crate) fn to_f32(self) -> f32 {
+        f32::from_bits(self.0)
+    }
+
+    const fn unpack(&self) -> F32U {
+        F32U {
+            sign: self.0 & (1 << 31) != 0,
+            exp: ((self.0 >> 23) & 0xff) as i32 - 127,
+            frac: (((self.0 & 0x7fffff) | 0x800000) as i64) << 9,
+        }
+        .norm()
+    }
+}
+
+/// Safely construct an `F32` out of a constant floating-point value.
+///
+/// This ensures that the conversion happens in const context, so no floating point operations are
+/// emitted.
+#[macro_export]
+macro_rules! f32 {
+    ([$($val:expr),*]) => {{
+        [$(f32!($val)),*]
+    }};
+    ($val:expr) => {{
+        const _K: $crate::float::F32 = $crate::float::F32::from_f32($val);
+        _K
+    }};
+}
+
+impl ops::Neg for F32 {
+    type Output = F32;
+
+    fn neg(self) -> F32 {
+        F32(self.0 ^ (1 << 31))
+    }
+}
+
+impl ops::Add<F32> for F32 {
+    type Output = F32;
+
+    fn add(self, rhs: F32) -> F32 {
+        self.unpack().add(rhs.unpack()).pack()
+    }
+}
+
+impl ops::Sub<F32> for F32 {
+    type Output = F32;
+
+    fn sub(self, rhs: F32) -> F32 {
+        self.unpack().add((-rhs).unpack()).pack()
+    }
+}
+
+impl ops::Mul<F32> for F32 {
+    type Output = F32;
+
+    fn mul(self, rhs: F32) -> F32 {
+        self.unpack().mul(rhs.unpack()).pack()
+    }
+}
+
+impl ops::Div<F32> for F32 {
+    type Output = F32;
+
+    fn div(self, rhs: F32) -> F32 {
+        self.unpack().div(rhs.unpack()).pack()
+    }
+}
+
+macro_rules! from_ints {
+    ($u:ty, $i:ty) => {
+        impl From<$i> for F32 {
+            fn from(v: $i) -> F32 {
+                F32U::from_i64(v as i64).pack()
+            }
+        }
+        impl From<$u> for F32 {
+            fn from(v: $u) -> F32 {
+                F32U::from_u64(v as u64).pack()
+            }
+        }
+    };
+}
+
+from_ints!(u8, i8);
+from_ints!(u16, i16);
+from_ints!(u32, i32);
+from_ints!(u64, i64);
+
+impl F32U {
+    const INFINITY: F32U = f32!(f32::INFINITY).unpack();
+    const NEG_INFINITY: F32U = f32!(f32::NEG_INFINITY).unpack();
+
+    fn from_i64(v: i64) -> F32U {
+        F32U {
+            sign: v < 0,
+            exp: 32,
+            frac: v.abs(),
+        }
+        .norm()
+    }
+
+    fn from_u64(mut v: u64) -> F32U {
+        let mut exp = 32;
+        if v >= (1 << 63) {
+            exp = 31;
+            v >>= 1;
+        }
+        F32U {
+            sign: false,
+            exp,
+            frac: v as i64,
+        }
+        .norm()
+    }
+
+    fn shr(&mut self, shift: i32) {
+        if shift > 63 {
+            self.exp = 0;
+            self.frac = 0;
+        } else {
+            self.frac >>= shift;
+        }
+    }
+
+    fn align(a: &mut F32U, b: &mut F32U) {
+        if a.exp > b.exp {
+            b.shr(a.exp - b.exp);
+            b.exp = a.exp;
+        } else {
+            a.shr(b.exp - a.exp);
+            a.exp = b.exp;
+        }
+    }
+
+    fn mul(self, other: F32U) -> F32U {
+        F32U {
+            sign: self.sign != other.sign,
+            exp: self.exp + other.exp,
+            frac: ((self.frac >> 8) * (other.frac >> 8)) >> 16,
+        }
+    }
+
+    fn div(self, other: F32U) -> F32U {
+        if other.frac == 0 || self.is_inf() {
+            if self.sign {
+                F32U::NEG_INFINITY
+            } else {
+                F32U::INFINITY
+            }
+        } else {
+            F32U {
+                sign: self.sign != other.sign,
+                exp: self.exp - other.exp,
+                frac: ((self.frac << 24) / (other.frac >> 8)),
+            }
+        }
+    }
+
+    fn add(mut self, mut other: F32U) -> F32U {
+        F32U::align(&mut self, &mut other);
+        if self.sign == other.sign {
+            self.frac += other.frac;
+        } else {
+            self.frac -= other.frac;
+        }
+        if self.frac < 0 {
+            self.sign = !self.sign;
+            self.frac = -self.frac;
+        }
+        self
+    }
+
+    const fn norm(mut self) -> F32U {
+        let lz = self.frac.leading_zeros() as i32;
+        if lz > 31 {
+            self.frac <<= lz - 31;
+            self.exp -= lz - 31;
+        } else if lz < 31 {
+            self.frac >>= 31 - lz;
+            self.exp += 31 - lz;
+        }
+
+        if self.is_zero() {
+            return F32U {
+                sign: self.sign,
+                frac: 0,
+                exp: 0,
+            };
+        }
+        self
+    }
+
+    const fn is_zero(&self) -> bool {
+        self.frac == 0 || self.exp < -126
+    }
+
+    const fn is_inf(&self) -> bool {
+        self.exp > 127
+    }
+
+    const fn pack(mut self) -> F32 {
+        self = self.norm();
+        if !self.is_zero() {
+            self.frac += 0x100;
+            self = self.norm();
+        }
+
+        if self.is_inf() {
+            if self.sign {
+                return f32!(f32::NEG_INFINITY);
+            } else {
+                return f32!(f32::INFINITY);
+            }
+        } else if self.is_zero() {
+            if self.sign {
+                return f32!(-0.0);
+            } else {
+                return f32!(0.0);
+            }
+        }
+
+        F32(if self.sign { 1u32 << 31 } else { 0u32 }
+            | ((self.exp + 127) as u32) << 23
+            | ((self.frac >> 9) & 0x7fffff) as u32)
+    }
+}
+
+impl<'a> TryFrom<of::Property<'a>> for F32 {
+    type Error = Error;
+
+    fn try_from(p: of::Property<'_>) -> core::result::Result<F32, Self::Error> {
+        let bits: u32 = p.try_into()?;
+        Ok(F32::from_bits(bits))
+    }
+}
+
+impl of::PropertyUnit for F32 {
+    const UNIT_SIZE: usize = 4;
+
+    fn from_bytes(data: &[u8]) -> Result<Self> {
+        Ok(F32::from_bits(<u32 as of::PropertyUnit>::from_bytes(data)?))
+    }
+}
+
+// TODO: Make this an actual test and figure out how to make it run.
+#[cfg(test)]
+mod tests {
+    #[test]
+    fn test_all() {
+        fn add(a: f32, b: f32) {
+            println!(
+                "{} + {} = {} {}",
+                a,
+                b,
+                (F32::from_f32(a) + F32::from_f32(b)).to_f32(),
+                a + b
+            );
+        }
+        fn sub(a: f32, b: f32) {
+            println!(
+                "{} - {} = {} {}",
+                a,
+                b,
+                (F32::from_f32(a) - F32::from_f32(b)).to_f32(),
+                a - b
+            );
+        }
+        fn mul(a: f32, b: f32) {
+            println!(
+                "{} * {} = {} {}",
+                a,
+                b,
+                (F32::from_f32(a) * F32::from_f32(b)).to_f32(),
+                a * b
+            );
+        }
+        fn div(a: f32, b: f32) {
+            println!(
+                "{} / {} = {} {}",
+                a,
+                b,
+                (F32::from_f32(a) / F32::from_f32(b)).to_f32(),
+                a / b
+            );
+        }
+
+        fn test(a: f32, b: f32) {
+            add(a, b);
+            sub(a, b);
+            mul(a, b);
+            div(a, b);
+        }
+
+        test(1.123, 7.567);
+        test(1.123, 1.456);
+        test(7.567, 1.123);
+        test(1.123, -7.567);
+        test(1.123, -1.456);
+        test(7.567, -1.123);
+        test(-1.123, -7.567);
+        test(-1.123, -1.456);
+        test(-7.567, -1.123);
+        test(1000.123, 0.001);
+        test(1000.123, 0.0000001);
+        test(0.0012, 1000.123);
+        test(0.0000001, 1000.123);
+        test(0., 0.);
+        test(0., 1.);
+        test(1., 0.);
+        test(1., 1.);
+        test(2., f32::INFINITY);
+        test(2., f32::NEG_INFINITY);
+        test(f32::INFINITY, 2.);
+        test(f32::NEG_INFINITY, 2.);
+        test(f32::NEG_INFINITY, 2.);
+        test(f32::MAX, 2.);
+        test(f32::MIN, 2.);
+        test(f32::MIN_POSITIVE, 2.);
+        test(2., f32::MAX);
+        test(2., f32::MIN);
+        test(2., f32::MIN_POSITIVE);
+    }
+}
diff --git a/drivers/gpu/drm/asahi/fw/buffer.rs b/drivers/gpu/drm/asahi/fw/buffer.rs
new file mode 100644
index 00000000000000..fafee8357a4fb2
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/buffer.rs
@@ -0,0 +1,180 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU tiled vertex buffer control firmware structures
+
+use super::types::*;
+use super::workqueue;
+use crate::{default_zeroed, no_debug, trivial_gpustruct};
+use kernel::sync::Arc;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct BlockControl {
+        pub(crate) total: AtomicU32,
+        pub(crate) wptr: AtomicU32,
+        pub(crate) unk: AtomicU32,
+        pub(crate) pad: Pad<0x34>,
+    }
+    default_zeroed!(BlockControl);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Counter {
+        pub(crate) count: AtomicU32,
+        __pad: Pad<0x3c>,
+    }
+    default_zeroed!(Counter);
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct Stats {
+        pub(crate) max_pages: AtomicU32,
+        pub(crate) max_b: AtomicU32,
+        pub(crate) overflow_count: AtomicU32,
+        pub(crate) gpu_c: AtomicU32,
+        pub(crate) __pad0: Pad<0x10>,
+        pub(crate) reset: AtomicU32,
+        pub(crate) __pad1: Pad<0x1c>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Info<'a> {
+        pub(crate) gpu_counter: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) last_id: i32,
+        pub(crate) cur_id: i32,
+        pub(crate) unk_10: u32,
+        pub(crate) gpu_counter2: u32,
+        pub(crate) unk_18: u32,
+
+        #[ver(V < V13_0B4 || G >= G14X)]
+        pub(crate) unk_1c: u32,
+
+        pub(crate) page_list: GpuPointer<'a, &'a [u32]>,
+        pub(crate) page_list_size: u32,
+        pub(crate) page_count: AtomicU32,
+        pub(crate) max_blocks: u32,
+        pub(crate) block_count: AtomicU32,
+        pub(crate) unk_38: u32,
+        pub(crate) block_list: GpuPointer<'a, &'a [u32]>,
+        pub(crate) block_ctl: GpuPointer<'a, super::BlockControl>,
+        pub(crate) last_page: AtomicU32,
+        pub(crate) gpu_page_ptr1: u32,
+        pub(crate) gpu_page_ptr2: u32,
+        pub(crate) unk_58: u32,
+        pub(crate) block_size: u32,
+        pub(crate) unk_60: U64,
+        pub(crate) counter: GpuPointer<'a, super::Counter>,
+        pub(crate) unk_70: u32,
+        pub(crate) unk_74: u32,
+        pub(crate) unk_78: u32,
+        pub(crate) unk_7c: u32,
+        pub(crate) unk_80: u32,
+        pub(crate) max_pages: u32,
+        pub(crate) max_pages_nomemless: u32,
+        pub(crate) unk_8c: u32,
+        pub(crate) unk_90: Array<0x30, u8>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Scene<'a> {
+        #[ver(G >= G14X)]
+        pub(crate) control_word: GpuPointer<'a, &'a [u32]>,
+        #[ver(G >= G14X)]
+        pub(crate) control_word2: GpuPointer<'a, &'a [u32]>,
+        pub(crate) pass_page_count: AtomicU32,
+        pub(crate) unk_4: u32,
+        pub(crate) unk_8: U64,
+        pub(crate) unk_10: U64,
+        pub(crate) user_buffer: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unk_20: u32,
+        #[ver(V >= V13_3)]
+        pub(crate) unk_28: U64,
+        pub(crate) stats: GpuWeakPointer<super::Stats>,
+        pub(crate) total_page_count: AtomicU32,
+        #[ver(G < G14X)]
+        pub(crate) unk_30: U64, // pad
+        #[ver(G < G14X)]
+        pub(crate) unk_38: U64, // pad
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct InitBuffer<'a> {
+        pub(crate) tag: workqueue::CommandType,
+        pub(crate) vm_slot: u32,
+        pub(crate) buffer_slot: u32,
+        pub(crate) unk_c: u32,
+        pub(crate) block_count: u32,
+        pub(crate) buffer: GpuPointer<'a, super::Info::ver>,
+        pub(crate) stamp_value: EventValue,
+    }
+}
+
+trivial_gpustruct!(BlockControl);
+trivial_gpustruct!(Counter);
+trivial_gpustruct!(Stats);
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct Info {
+    pub(crate) block_ctl: GpuObject<BlockControl>,
+    pub(crate) counter: GpuObject<Counter>,
+    pub(crate) page_list: GpuArray<u32>,
+    pub(crate) block_list: GpuArray<u32>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for Info::ver {
+    type Raw<'a> = raw::Info::ver<'a>;
+}
+
+pub(crate) struct ClusterBuffers {
+    pub(crate) tilemaps: GpuArray<u8>,
+    pub(crate) meta: GpuArray<u8>,
+}
+
+#[versions(AGX)]
+pub(crate) struct Scene {
+    pub(crate) user_buffer: GpuArray<u8>,
+    pub(crate) buffer: crate::buffer::Buffer::ver,
+    pub(crate) tvb_heapmeta: GpuArray<u8>,
+    pub(crate) tvb_tilemap: GpuArray<u8>,
+    pub(crate) tpc: Arc<GpuArray<u8>>,
+    pub(crate) clustering: Option<ClusterBuffers>,
+    pub(crate) preempt_buf: GpuArray<u8>,
+    #[ver(G >= G14X)]
+    pub(crate) control_word: GpuArray<u32>,
+}
+
+#[versions(AGX)]
+no_debug!(Scene::ver);
+
+#[versions(AGX)]
+impl GpuStruct for Scene::ver {
+    type Raw<'a> = raw::Scene::ver<'a>;
+}
+
+#[versions(AGX)]
+pub(crate) struct InitBuffer {
+    pub(crate) scene: Arc<crate::buffer::Scene::ver>,
+}
+
+#[versions(AGX)]
+no_debug!(InitBuffer::ver);
+
+#[versions(AGX)]
+impl workqueue::Command for InitBuffer::ver {}
+
+#[versions(AGX)]
+impl GpuStruct for InitBuffer::ver {
+    type Raw<'a> = raw::InitBuffer::ver<'a>;
+}
diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
new file mode 100644
index 00000000000000..edee8b1b9d5eea
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -0,0 +1,406 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU communication channel firmware structures (ring buffers)
+
+use super::types::*;
+use crate::default_zeroed;
+use core::sync::atomic::Ordering;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct ChannelState<'a> {
+        pub(crate) read_ptr: AtomicU32,
+        __pad0: Pad<0x1c>,
+        pub(crate) write_ptr: AtomicU32,
+        __pad1: Pad<0xc>,
+        _p: PhantomData<&'a ()>,
+    }
+    default_zeroed!(<'a>, ChannelState<'a>);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct FwCtlChannelState<'a> {
+        pub(crate) read_ptr: AtomicU32,
+        __pad0: Pad<0xc>,
+        pub(crate) write_ptr: AtomicU32,
+        __pad1: Pad<0xc>,
+        _p: PhantomData<&'a ()>,
+    }
+    default_zeroed!(<'a>, FwCtlChannelState<'a>);
+}
+
+pub(crate) trait RxChannelState: GpuStruct + Debug + Default
+where
+    for<'a> <Self as GpuStruct>::Raw<'a>: Default + Zeroable,
+{
+    const SUB_CHANNELS: usize;
+
+    fn wptr(raw: &Self::Raw<'_>, index: usize) -> u32;
+    fn set_rptr(raw: &Self::Raw<'_>, index: usize, rptr: u32);
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct ChannelState {}
+
+impl GpuStruct for ChannelState {
+    type Raw<'a> = raw::ChannelState<'a>;
+}
+
+impl RxChannelState for ChannelState {
+    const SUB_CHANNELS: usize = 1;
+
+    fn wptr(raw: &Self::Raw<'_>, _index: usize) -> u32 {
+        raw.write_ptr.load(Ordering::Acquire)
+    }
+
+    fn set_rptr(raw: &Self::Raw<'_>, _index: usize, rptr: u32) {
+        raw.read_ptr.store(rptr, Ordering::Release);
+    }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct FwLogChannelState {}
+
+impl GpuStruct for FwLogChannelState {
+    type Raw<'a> = Array<6, raw::ChannelState<'a>>;
+}
+
+impl RxChannelState for FwLogChannelState {
+    const SUB_CHANNELS: usize = 6;
+
+    fn wptr(raw: &Self::Raw<'_>, index: usize) -> u32 {
+        raw[index].write_ptr.load(Ordering::Acquire)
+    }
+
+    fn set_rptr(raw: &Self::Raw<'_>, index: usize, rptr: u32) {
+        raw[index].read_ptr.store(rptr, Ordering::Release);
+    }
+}
+
+#[derive(Debug, Default)]
+pub(crate) struct FwCtlChannelState {}
+
+impl GpuStruct for FwCtlChannelState {
+    type Raw<'a> = raw::FwCtlChannelState<'a>;
+}
+
+pub(crate) trait TxChannelState: GpuStruct + Debug + Default {
+    fn rptr(raw: &Self::Raw<'_>) -> u32;
+    fn set_wptr(raw: &Self::Raw<'_>, wptr: u32);
+}
+
+impl TxChannelState for ChannelState {
+    fn rptr(raw: &Self::Raw<'_>) -> u32 {
+        raw.read_ptr.load(Ordering::Acquire)
+    }
+
+    fn set_wptr(raw: &Self::Raw<'_>, wptr: u32) {
+        raw.write_ptr.store(wptr, Ordering::Release);
+    }
+}
+
+impl TxChannelState for FwCtlChannelState {
+    fn rptr(raw: &Self::Raw<'_>) -> u32 {
+        raw.read_ptr.load(Ordering::Acquire)
+    }
+
+    fn set_wptr(raw: &Self::Raw<'_>, wptr: u32) {
+        raw.write_ptr.store(wptr, Ordering::Release);
+    }
+}
+
+#[derive(Debug, Copy, Clone, Default)]
+#[repr(u32)]
+pub(crate) enum PipeType {
+    #[default]
+    Vertex = 0,
+    Fragment = 1,
+    Compute = 2,
+}
+
+#[versions(AGX)]
+#[derive(Debug, Copy, Clone, Default)]
+#[repr(C)]
+pub(crate) struct RunWorkQueueMsg {
+    pub(crate) pipe_type: PipeType,
+    pub(crate) work_queue: Option<GpuWeakPointer<super::workqueue::QueueInfo::ver>>,
+    pub(crate) wptr: u32,
+    pub(crate) event_slot: u32,
+    pub(crate) is_new: bool,
+    #[ver(V >= V13_2 && G == G14)]
+    pub(crate) __pad: Pad<0x2b>,
+    #[ver(V < V13_2 || G != G14)]
+    pub(crate) __pad: Pad<0x1b>,
+}
+
+#[versions(AGX)]
+pub(crate) type PipeMsg = RunWorkQueueMsg::ver;
+
+#[versions(AGX)]
+pub(crate) const DEVICECONTROL_SZ: usize = {
+    #[ver(V < V13_2 || G != G14)]
+    {
+        0x2c
+    }
+    #[ver(V >= V13_2 && G == G14)]
+    {
+        0x3c
+    }
+};
+
+// TODO: clean up when arbitrary_enum_discriminant is stable
+// https://github.com/rust-lang/rust/issues/60553
+
+#[versions(AGX)]
+#[derive(Debug, Copy, Clone)]
+#[repr(C, u32)]
+#[allow(dead_code)]
+pub(crate) enum DeviceControlMsg {
+    Unk00(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk01(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk02(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk03(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk04(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk05(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk06(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk07(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk08(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk09(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk0a(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk0b(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk0c(Array<DEVICECONTROL_SZ::ver, u8>),
+    GrowTVBAck {
+        unk_4: u32,
+        buffer_slot: u32,
+        vm_slot: u32,
+        counter: u32,
+        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x10 }>,
+    },
+    Unk0e(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk0f(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk10(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk11(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk12(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk13(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk14(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk15(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk16(Array<DEVICECONTROL_SZ::ver, u8>),
+    #[ver(V >= V13_3)]
+    Unk17(Array<DEVICECONTROL_SZ::ver, u8>),
+    DestroyContext {
+        unk_4: u32,
+        ctx_23: u8,
+        #[ver(V < V13_3)]
+        __pad0: Pad<3>,
+        unk_c: U32,
+        unk_10: U32,
+        ctx_0: u8,
+        ctx_1: u8,
+        ctx_4: u8,
+        #[ver(V < V13_3)]
+        __pad1: Pad<1>,
+        #[ver(V < V13_3)]
+        unk_18: u32,
+        gpu_context: Option<GpuWeakPointer<super::workqueue::GpuContextData>>,
+        #[ver(V < V13_3)]
+        __pad2: Pad<{ DEVICECONTROL_SZ::ver - 0x20 }>,
+        #[ver(V >= V13_3)]
+        __pad2: Pad<{ DEVICECONTROL_SZ::ver - 0x18 }>,
+    },
+    Unk18(Array<DEVICECONTROL_SZ::ver, u8>),
+    Initialize(Pad<DEVICECONTROL_SZ::ver>),
+}
+
+#[versions(AGX)]
+default_zeroed!(DeviceControlMsg::ver);
+
+#[derive(Copy, Clone, Default, Debug)]
+#[repr(C)]
+#[allow(dead_code)]
+pub(crate) struct FwCtlMsg {
+    pub(crate) addr: U64,
+    pub(crate) unk_8: u32,
+    pub(crate) slot: u32,
+    pub(crate) page_count: u16,
+    pub(crate) unk_12: u16,
+}
+
+pub(crate) const EVENT_SZ: usize = 0x34;
+
+#[derive(Debug, Copy, Clone)]
+#[repr(C, u32)]
+#[allow(dead_code)]
+pub(crate) enum EventMsg {
+    Fault,
+    Flag {
+        firing: [u32; 4],
+        unk_14: u16,
+    },
+    Unk2(Array<EVENT_SZ, u8>),
+    Unk3(Array<EVENT_SZ, u8>),
+    Timeout {
+        counter: u32,
+        unk_8: u32,
+        event_slot: i32,
+    },
+    Unk5(Array<EVENT_SZ, u8>),
+    Unk6(Array<EVENT_SZ, u8>),
+    GrowTVB {
+        vm_slot: u32,
+        buffer_slot: u32,
+        counter: u32,
+    }, // Max discriminant: 0x7
+}
+
+pub(crate) const EVENT_MAX: u32 = 0x7;
+
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub(crate) union RawEventMsg {
+    pub(crate) raw: (u32, Array<EVENT_SZ, u8>),
+    pub(crate) msg: EventMsg,
+}
+
+default_zeroed!(RawEventMsg);
+
+#[derive(Debug, Copy, Clone, Default)]
+#[repr(C)]
+pub(crate) struct RawFwLogMsg {
+    pub(crate) msg_type: u32,
+    __pad0: u32,
+    pub(crate) msg_index: U64,
+    __pad1: Pad<0x28>,
+}
+
+#[derive(Debug, Copy, Clone, Default)]
+#[repr(C)]
+pub(crate) struct RawFwLogPayloadMsg {
+    pub(crate) msg_type: u32,
+    pub(crate) seq_no: u32,
+    pub(crate) timestamp: U64,
+    pub(crate) msg: Array<0xc8, u8>,
+}
+
+#[derive(Debug, Copy, Clone, Default)]
+#[repr(C)]
+pub(crate) struct RawKTraceMsg {
+    pub(crate) msg_type: u32,
+    pub(crate) timestamp: U64,
+    pub(crate) args: Array<4, U64>,
+    pub(crate) code: u8,
+    pub(crate) channel: u8,
+    __pad: Pad<1>,
+    pub(crate) thread: u8,
+    pub(crate) unk_flag: U64,
+}
+
+#[versions(AGX)]
+pub(crate) const STATS_SZ: usize = {
+    #[ver(V < V13_0B4)]
+    {
+        0x2c
+    }
+    #[ver(V >= V13_0B4)]
+    {
+        0x3c
+    }
+};
+
+#[versions(AGX)]
+#[derive(Debug, Copy, Clone)]
+#[repr(C, u32)]
+#[allow(dead_code)]
+pub(crate) enum StatsMsg {
+    Power {
+        // 0x00
+        __pad: Pad<0x18>,
+        power: U64,
+    },
+    Unk1(Array<{ STATS_SZ::ver }, u8>),
+    PowerOn {
+        // 0x02
+        off_time: U64,
+    },
+    PowerOff {
+        // 0x03
+        on_time: U64,
+    },
+    Utilization {
+        // 0x04
+        timestamp: U64,
+        util1: u32,
+        util2: u32,
+        util3: u32,
+        util4: u32,
+    },
+    Unk5(Array<{ STATS_SZ::ver }, u8>),
+    Unk6(Array<{ STATS_SZ::ver }, u8>),
+    Unk7(Array<{ STATS_SZ::ver }, u8>),
+    Unk8(Array<{ STATS_SZ::ver }, u8>),
+    AvgPower {
+        // 0x09
+        active_cs: U64,
+        unk2: u32,
+        unk3: u32,
+        unk4: u32,
+        avg_power: u32,
+    },
+    Temperature {
+        // 0x0a
+        __pad: Pad<0x8>,
+        raw_value: u32,
+        scale: u32,
+        tmin: u32,
+        tmax: u32,
+    },
+    PowerState {
+        // 0x0b
+        timestamp: U64,
+        last_busy_ts: U64,
+        active: u32,
+        poweroff: u32,
+        unk1: u32,
+        pstate: u32,
+        unk2: u32,
+        unk3: u32,
+    },
+    FwBusy {
+        // 0x0c
+        timestamp: U64,
+        busy: u32,
+    },
+    PState {
+        // 0x0d
+        __pad: Pad<0x8>,
+        ps_min: u32,
+        unk1: u32,
+        ps_max: u32,
+        unk2: u32,
+    },
+    TempSensor {
+        // 0x0e
+        __pad: Pad<0x4>,
+        sensor_id: u32,
+        raw_value: u32,
+        scale: u32,
+        tmin: u32,
+        tmax: u32,
+    }, // Max discriminant: 0xe
+}
+
+#[versions(AGX)]
+pub(crate) const STATS_MAX: u32 = 0xe;
+
+#[versions(AGX)]
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub(crate) union RawStatsMsg {
+    pub(crate) raw: (u32, Array<{ STATS_SZ::ver }, u8>),
+    pub(crate) msg: StatsMsg::ver,
+}
+
+#[versions(AGX)]
+default_zeroed!(RawStatsMsg::ver);
diff --git a/drivers/gpu/drm/asahi/fw/compute.rs b/drivers/gpu/drm/asahi/fw/compute.rs
new file mode 100644
index 00000000000000..2a616ca349b9bd
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/compute.rs
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU compute job firmware structures
+
+use super::types::*;
+use super::{event, job, workqueue};
+use crate::{microseq, mmu};
+use kernel::sync::Arc;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters1<'a> {
+        pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>,
+        pub(crate) encoder: U64,
+        pub(crate) preempt_buf2: GpuPointer<'a, &'a [u8]>,
+        pub(crate) preempt_buf3: GpuPointer<'a, &'a [u8]>,
+        pub(crate) preempt_buf4: GpuPointer<'a, &'a [u8]>,
+        pub(crate) preempt_buf5: GpuPointer<'a, &'a [u8]>,
+        pub(crate) pipeline_base: U64,
+        pub(crate) unk_38: U64,
+        pub(crate) helper_program: u32,
+        pub(crate) unk_44: u32,
+        pub(crate) helper_arg: U64,
+        pub(crate) unk_50: u32,
+        pub(crate) unk_54: u32,
+        pub(crate) unk_58: u32,
+        pub(crate) unk_5c: u32,
+        pub(crate) iogpu_unk_40: u32,
+        pub(crate) __pad: Pad<0xfc>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters2<'a> {
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_0_0: u32,
+        pub(crate) unk_0: Array<0x24, u8>,
+        pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>,
+        pub(crate) encoder_end: U64,
+        pub(crate) unk_34: Array<0x20, u8>,
+        pub(crate) unk_g14x: u32,
+        pub(crate) unk_58: u32,
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_5c: u32,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RunCompute<'a> {
+        pub(crate) tag: workqueue::CommandType,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) counter: U64,
+
+        pub(crate) unk_4: u32,
+        pub(crate) vm_slot: u32,
+        pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>,
+        pub(crate) unk_pointee: u32,
+        #[ver(G < G14X)]
+        pub(crate) __pad0: Array<0x50, u8>,
+        #[ver(G < G14X)]
+        pub(crate) job_params1: JobParameters1<'a>,
+        #[ver(G >= G14X)]
+        pub(crate) registers: job::raw::RegisterArray,
+        pub(crate) __pad1: Array<0x20, u8>,
+        pub(crate) microsequence: GpuPointer<'a, &'a [u8]>,
+        pub(crate) microsequence_size: u32,
+        pub(crate) job_params2: JobParameters2::ver<'a>,
+        pub(crate) encoder_params: job::raw::EncoderParams,
+        pub(crate) meta: job::raw::JobMeta,
+        pub(crate) cur_ts: U64,
+        pub(crate) start_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) end_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) unk_2c0: u32,
+        pub(crate) unk_2c4: u32,
+        pub(crate) unk_2c8: u32,
+        pub(crate) unk_2cc: u32,
+        pub(crate) client_sequence: u8,
+        pub(crate) pad_2d1: Array<3, u8>,
+        pub(crate) unk_2d4: u32,
+        pub(crate) unk_2d8: u8,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_ts: U64,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_2e1: Array<0x1c, u8>,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_flag: U32,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_pad: Array<0x10, u8>,
+    }
+}
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct RunCompute {
+    pub(crate) notifier: Arc<GpuObject<event::Notifier::ver>>,
+    pub(crate) preempt_buf: GpuArray<u8>,
+    pub(crate) micro_seq: microseq::MicroSequence,
+    pub(crate) vm_bind: mmu::VmBind,
+    pub(crate) timestamps: Arc<GpuObject<job::JobTimestamps>>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for RunCompute::ver {
+    type Raw<'a> = raw::RunCompute::ver<'a>;
+}
+
+#[versions(AGX)]
+impl workqueue::Command for RunCompute::ver {}
diff --git a/drivers/gpu/drm/asahi/fw/event.rs b/drivers/gpu/drm/asahi/fw/event.rs
new file mode 100644
index 00000000000000..fbf65ab6d97624
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/event.rs
@@ -0,0 +1,100 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU events control structures & stamps
+
+use super::types::*;
+use crate::{default_zeroed, trivial_gpustruct};
+use core::sync::atomic::Ordering;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug, Clone, Copy, Default)]
+    #[repr(C)]
+    pub(crate) struct LinkedListHead {
+        pub(crate) prev: Option<GpuWeakPointer<LinkedListHead>>,
+        pub(crate) next: Option<GpuWeakPointer<LinkedListHead>>,
+    }
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct NotifierList {
+        pub(crate) list_head: LinkedListHead,
+        pub(crate) unkptr_10: U64,
+    }
+    default_zeroed!(NotifierList);
+
+    #[versions(AGX)]
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct NotifierState {
+        unk_14: u32,
+        unk_18: U64,
+        unk_20: u32,
+        vm_slot: u32,
+        has_vtx: u32,
+        pstamp_vtx: Array<4, U64>,
+        has_frag: u32,
+        pstamp_frag: Array<4, U64>,
+        has_comp: u32,
+        pstamp_comp: Array<4, U64>,
+        #[ver(G >= G14 && V < V13_0B4)]
+        unk_98_g14_0: Array<0x14, u8>,
+        in_list: u32,
+        list_head: LinkedListHead,
+        #[ver(G >= G14 && V < V13_0B4)]
+        unk_a8_g14_0: Pad<4>,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_buf: Array<0x8, u8>, // Init to all-ff
+    }
+
+    #[versions(AGX)]
+    impl Default for NotifierState::ver {
+        fn default() -> Self {
+            #[allow(unused_mut)]
+            let mut s: Self = unsafe { core::mem::zeroed() };
+            #[ver(V >= V13_0B4)]
+            s.unk_buf = Array::new([0xff; 0x8]);
+            s
+        }
+    }
+
+    #[derive(Debug)]
+    #[repr(transparent)]
+    pub(crate) struct Threshold(AtomicU64);
+    default_zeroed!(Threshold);
+
+    impl Threshold {
+        pub(crate) fn increment(&self) {
+            // We could use fetch_add, but the non-LSE atomic
+            // sequence Rust produces confuses the hypervisor.
+            let v = self.0.load(Ordering::Relaxed);
+            self.0.store(v + 1, Ordering::Relaxed);
+        }
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Notifier<'a> {
+        pub(crate) threshold: GpuPointer<'a, super::Threshold>,
+        pub(crate) generation: AtomicU32,
+        pub(crate) cur_count: AtomicU32,
+        pub(crate) unk_10: AtomicU32,
+        pub(crate) state: NotifierState::ver,
+    }
+}
+
+trivial_gpustruct!(Threshold);
+trivial_gpustruct!(NotifierList);
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct Notifier {
+    pub(crate) threshold: GpuObject<Threshold>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for Notifier::ver {
+    type Raw<'a> = raw::Notifier::ver<'a>;
+}
diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs
new file mode 100644
index 00000000000000..078c7cfed9c0f0
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/fragment.rs
@@ -0,0 +1,285 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU fragment job firmware structures
+
+use super::types::*;
+use super::{event, job, workqueue};
+use crate::{buffer, fw, microseq, mmu};
+use kernel::sync::Arc;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct ClearPipelineBinding {
+        pub(crate) pipeline_bind: U64,
+        pub(crate) address: U64,
+    }
+
+    #[derive(Debug, Clone, Copy, Default)]
+    #[repr(C)]
+    pub(crate) struct StorePipelineBinding {
+        pub(crate) unk_0: U64,
+        pub(crate) unk_8: u32,
+        pub(crate) pipeline_bind: u32,
+        pub(crate) unk_10: u32,
+        pub(crate) address: u32,
+        pub(crate) unk_18: u32,
+        pub(crate) unk_1c_padding: u32,
+    }
+
+    impl StorePipelineBinding {
+        pub(crate) fn new(pipeline_bind: u32, address: u32) -> StorePipelineBinding {
+            StorePipelineBinding {
+                pipeline_bind,
+                address,
+                ..Default::default()
+            }
+        }
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct ArrayAddr {
+        pub(crate) ptr: U64,
+        pub(crate) unk_padding: U64,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct AuxFBInfo {
+        pub(crate) iogpu_unk_214: u32,
+        pub(crate) unk2: u32,
+        pub(crate) width: u32,
+        pub(crate) height: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk3: U64,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters1<'a> {
+        pub(crate) utile_config: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) clear_pipeline: ClearPipelineBinding,
+        pub(crate) ppp_multisamplectl: U64,
+        pub(crate) scissor_array: U64,
+        pub(crate) depth_bias_array: U64,
+        pub(crate) aux_fb_info: AuxFBInfo::ver,
+        pub(crate) depth_dimensions: U64,
+        pub(crate) visibility_result_buffer: U64,
+        pub(crate) zls_ctrl: U64,
+
+        #[ver(G >= G14)]
+        pub(crate) unk_58_g14_0: U64,
+        #[ver(G >= G14)]
+        pub(crate) unk_58_g14_8: U64,
+
+        pub(crate) depth_buffer_ptr1: U64,
+        pub(crate) depth_buffer_ptr2: U64,
+        pub(crate) stencil_buffer_ptr1: U64,
+        pub(crate) stencil_buffer_ptr2: U64,
+
+        #[ver(G >= G14)]
+        pub(crate) unk_68_g14_0: Array<0x20, u8>,
+
+        pub(crate) unk_78: Array<0x4, U64>,
+        pub(crate) depth_meta_buffer_ptr1: U64,
+        pub(crate) unk_a0: U64,
+        pub(crate) depth_meta_buffer_ptr2: U64,
+        pub(crate) unk_b0: U64,
+        pub(crate) stencil_meta_buffer_ptr1: U64,
+        pub(crate) unk_c0: U64,
+        pub(crate) stencil_meta_buffer_ptr2: U64,
+        pub(crate) unk_d0: U64,
+        pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>,
+        pub(crate) mtile_stride_dwords: U64,
+        pub(crate) tvb_heapmeta_2: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tile_config: U64,
+        pub(crate) aux_fb: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unk_108: Array<0x6, U64>,
+        pub(crate) pipeline_base: U64,
+        pub(crate) unk_140: U64,
+        pub(crate) unk_148: U64,
+        pub(crate) unk_150: U64,
+        pub(crate) unk_158: U64,
+        pub(crate) unk_160: U64,
+
+        #[ver(G < G14)]
+        pub(crate) __pad: Pad<0x1d8>,
+        #[ver(G >= G14)]
+        pub(crate) __pad: Pad<0x1a8>,
+        #[ver(V < V13_0B4)]
+        pub(crate) __pad1: Pad<0x8>,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters2 {
+        pub(crate) store_pipeline_bind: u32,
+        pub(crate) store_pipeline_addr: u32,
+        pub(crate) unk_8: u32,
+        pub(crate) unk_c: u32,
+        pub(crate) merge_upper_x: F32,
+        pub(crate) merge_upper_y: F32,
+        pub(crate) unk_18: U64,
+        pub(crate) utiles_per_mtile_y: u16,
+        pub(crate) utiles_per_mtile_x: u16,
+        pub(crate) unk_24: u32,
+        pub(crate) tile_counts: u32,
+        pub(crate) tib_blocks: u32,
+        pub(crate) isp_bgobjdepth: u32,
+        pub(crate) isp_bgobjvals: u32,
+        pub(crate) unk_38: u32,
+        pub(crate) unk_3c: u32,
+        pub(crate) unk_40: u32,
+        pub(crate) __pad: Pad<0xac>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters3 {
+        pub(crate) depth_bias_array: ArrayAddr,
+        pub(crate) scissor_array: ArrayAddr,
+        pub(crate) visibility_result_buffer: U64,
+        pub(crate) unk_118: U64,
+        pub(crate) unk_120: Array<0x25, U64>,
+        pub(crate) unk_reload_pipeline: ClearPipelineBinding,
+        pub(crate) unk_258: U64,
+        pub(crate) unk_260: U64,
+        pub(crate) unk_268: U64,
+        pub(crate) unk_270: U64,
+        pub(crate) reload_pipeline: ClearPipelineBinding,
+        pub(crate) zls_ctrl: U64,
+        pub(crate) unk_290: U64,
+        pub(crate) depth_buffer_ptr1: U64,
+        pub(crate) unk_2a0: U64,
+        pub(crate) unk_2a8: U64,
+        pub(crate) depth_buffer_ptr2: U64,
+        pub(crate) depth_buffer_ptr3: U64,
+        pub(crate) depth_meta_buffer_ptr3: U64,
+        pub(crate) stencil_buffer_ptr1: U64,
+        pub(crate) unk_2d0: U64,
+        pub(crate) unk_2d8: U64,
+        pub(crate) stencil_buffer_ptr2: U64,
+        pub(crate) stencil_buffer_ptr3: U64,
+        pub(crate) stencil_meta_buffer_ptr3: U64,
+        pub(crate) unk_2f8: Array<2, U64>,
+        pub(crate) tib_blocks: u32,
+        pub(crate) unk_30c: u32,
+        pub(crate) aux_fb_info: AuxFBInfo::ver,
+        pub(crate) tile_config: U64,
+        pub(crate) unk_328_padding: Array<0x8, u8>,
+        pub(crate) unk_partial_store_pipeline: StorePipelineBinding,
+        pub(crate) partial_store_pipeline: StorePipelineBinding,
+        pub(crate) isp_bgobjdepth: u32,
+        pub(crate) isp_bgobjvals: u32,
+        pub(crate) sample_size: u32,
+        pub(crate) unk_37c: u32,
+        pub(crate) unk_380: U64,
+        pub(crate) unk_388: U64,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_390_0: U64,
+
+        pub(crate) depth_dimensions: U64,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RunFragment<'a> {
+        pub(crate) tag: workqueue::CommandType,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) counter: U64,
+
+        pub(crate) vm_slot: u32,
+        pub(crate) unk_8: u32,
+        pub(crate) microsequence: GpuPointer<'a, &'a [u8]>,
+        pub(crate) microsequence_size: u32,
+        pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>,
+        pub(crate) buffer: GpuPointer<'a, fw::buffer::Info::ver>,
+        pub(crate) scene: GpuPointer<'a, fw::buffer::Scene::ver>,
+        pub(crate) unk_buffer_buf: GpuWeakPointer<[u8]>,
+        pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>,
+        pub(crate) ppp_multisamplectl: U64,
+        pub(crate) samples: u32,
+        pub(crate) tiles_per_mtile_y: u16,
+        pub(crate) tiles_per_mtile_x: u16,
+        pub(crate) unk_50: U64,
+        pub(crate) unk_58: U64,
+        pub(crate) merge_upper_x: F32,
+        pub(crate) merge_upper_y: F32,
+        pub(crate) unk_68: U64,
+        pub(crate) tile_count: U64,
+
+        #[ver(G < G14X)]
+        pub(crate) job_params1: JobParameters1::ver<'a>,
+        #[ver(G < G14X)]
+        pub(crate) job_params2: JobParameters2,
+        #[ver(G >= G14X)]
+        pub(crate) registers: job::raw::RegisterArray,
+
+        pub(crate) job_params3: JobParameters3::ver,
+        pub(crate) unk_758_flag: u32,
+        pub(crate) unk_75c_flag: u32,
+        pub(crate) unk_buf: Array<0x110, u8>,
+        pub(crate) busy_flag: u32,
+        pub(crate) tvb_overflow_count: u32,
+        pub(crate) unk_878: u32,
+        pub(crate) encoder_params: job::raw::EncoderParams,
+        pub(crate) process_empty_tiles: u32,
+        pub(crate) no_clear_pipeline_textures: u32,
+        pub(crate) msaa_zs: u32,
+        pub(crate) unk_pointee: u32,
+        #[ver(V >= V13_3)]
+        pub(crate) unk_v13_3: u32,
+        pub(crate) meta: job::raw::JobMeta,
+        pub(crate) unk_after_meta: u32,
+        pub(crate) unk_buf_0: U64,
+        pub(crate) unk_buf_8: U64,
+        pub(crate) unk_buf_10: U64,
+        pub(crate) cur_ts: U64,
+        pub(crate) start_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) end_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) unk_914: u32,
+        pub(crate) unk_918: U64,
+        pub(crate) unk_920: u32,
+        pub(crate) client_sequence: u8,
+        pub(crate) pad_925: Array<3, u8>,
+        pub(crate) unk_928: u32,
+        pub(crate) unk_92c: u8,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_ts: U64,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_92d_8: Array<0x1b, u8>,
+    }
+}
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct RunFragment {
+    pub(crate) notifier: Arc<GpuObject<event::Notifier::ver>>,
+    pub(crate) scene: Arc<buffer::Scene::ver>,
+    pub(crate) micro_seq: microseq::MicroSequence,
+    pub(crate) vm_bind: mmu::VmBind,
+    pub(crate) aux_fb: GpuArray<u8>,
+    pub(crate) timestamps: Arc<GpuObject<job::RenderTimestamps>>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for RunFragment::ver {
+    type Raw<'a> = raw::RunFragment::ver<'a>;
+}
+
+#[versions(AGX)]
+impl workqueue::Command for RunFragment::ver {}
diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
new file mode 100644
index 00000000000000..c9df1121847470
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -0,0 +1,1325 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU initialization / global structures
+
+use super::channels;
+use super::types::*;
+use crate::{default_zeroed, gem, no_debug, trivial_gpustruct};
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct ChannelRing<T: GpuStruct + Debug + Default, U: Copy> {
+        pub(crate) state: Option<GpuWeakPointer<T>>,
+        pub(crate) ring: Option<GpuWeakPointer<[U]>>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct PipeChannels {
+        pub(crate) vtx: ChannelRing<channels::ChannelState, channels::PipeMsg::ver>,
+        pub(crate) frag: ChannelRing<channels::ChannelState, channels::PipeMsg::ver>,
+        pub(crate) comp: ChannelRing<channels::ChannelState, channels::PipeMsg::ver>,
+    }
+    #[versions(AGX)]
+    default_zeroed!(PipeChannels::ver);
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct FwStatusFlags {
+        pub(crate) halt_count: AtomicU32,
+        __pad0: Pad<0xc>,
+        pub(crate) halted: AtomicU32,
+        __pad1: Pad<0xc>,
+        pub(crate) resume: AtomicU32,
+        __pad2: Pad<0xc>,
+        pub(crate) unk_40: u32,
+        __pad3: Pad<0xc>,
+        pub(crate) unk_ctr: u32,
+        __pad4: Pad<0xc>,
+        pub(crate) unk_60: u32,
+        __pad5: Pad<0xc>,
+        pub(crate) unk_70: u32,
+        __pad6: Pad<0xc>,
+    }
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct FwStatus {
+        pub(crate) fwctl_channel: ChannelRing<channels::FwCtlChannelState, channels::FwCtlMsg>,
+        pub(crate) flags: FwStatusFlags,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataShared1 {
+        pub(crate) table: Array<16, i32>,
+        pub(crate) unk_44: Array<0x60, u8>,
+        pub(crate) unk_a4: u32,
+        pub(crate) unk_a8: u32,
+    }
+    default_zeroed!(HwDataShared1);
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct HwDataShared2Curve {
+        pub(crate) unk_0: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) t1: Array<16, u16>,
+        pub(crate) t2: Array<16, i16>,
+        pub(crate) t3: Array<8, Array<16, i32>>,
+    }
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct HwDataShared2G14 {
+        pub(crate) unk_0: Array<5, u32>,
+        pub(crate) unk_14: u32,
+        pub(crate) unk_18: Array<8, u32>,
+        pub(crate) curve1: HwDataShared2Curve,
+        pub(crate) curve2: HwDataShared2Curve,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataShared2 {
+        pub(crate) table: Array<10, i32>,
+        pub(crate) unk_28: Array<0x10, u8>,
+        pub(crate) g14: HwDataShared2G14,
+        pub(crate) unk_500: u32,
+        pub(crate) unk_504: u32,
+        pub(crate) unk_508: u32,
+        pub(crate) unk_50c: u32,
+    }
+    default_zeroed!(HwDataShared2);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataShared3 {
+        pub(crate) unk_0: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) unk_8: u32,
+        pub(crate) table: Array<16, u32>,
+        pub(crate) unk_4c: u32,
+    }
+    default_zeroed!(HwDataShared3);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataA130Extra {
+        pub(crate) unk_0: Array<0x38, u8>,
+        pub(crate) unk_38: u32,
+        pub(crate) unk_3c: u32,
+        pub(crate) gpu_se_inactive_threshold: u32,
+        pub(crate) unk_44: u32,
+        pub(crate) gpu_se_engagement_criteria: i32,
+        pub(crate) gpu_se_reset_criteria: u32,
+        pub(crate) unk_50: u32,
+        pub(crate) unk_54: u32,
+        pub(crate) unk_58: u32,
+        pub(crate) unk_5c: u32,
+        pub(crate) gpu_se_filter_a_neg: F32,
+        pub(crate) gpu_se_filter_1_a_neg: F32,
+        pub(crate) gpu_se_filter_a: F32,
+        pub(crate) gpu_se_filter_1_a: F32,
+        pub(crate) gpu_se_ki_dt: F32,
+        pub(crate) gpu_se_ki_1_dt: F32,
+        pub(crate) unk_78: F32,
+        pub(crate) unk_7c: F32,
+        pub(crate) gpu_se_kp: F32,
+        pub(crate) gpu_se_kp_1: F32,
+        pub(crate) unk_88: u32,
+        pub(crate) unk_8c: u32,
+        pub(crate) max_pstate_scaled_1: u32,
+        pub(crate) unk_94: u32,
+        pub(crate) unk_98: u32,
+        pub(crate) unk_9c: F32,
+        pub(crate) unk_a0: u32,
+        pub(crate) unk_a4: u32,
+        pub(crate) gpu_se_filter_time_constant_ms: u32,
+        pub(crate) gpu_se_filter_time_constant_1_ms: u32,
+        pub(crate) gpu_se_filter_time_constant_clks: U64,
+        pub(crate) gpu_se_filter_time_constant_1_clks: U64,
+        pub(crate) unk_c0: u32,
+        pub(crate) unk_c4: F32,
+        pub(crate) unk_c8: Array<0x4c, u8>,
+        pub(crate) unk_114: F32,
+        pub(crate) unk_118: u32,
+        pub(crate) unk_11c: u32,
+        pub(crate) unk_120: u32,
+        pub(crate) unk_124: u32,
+        pub(crate) max_pstate_scaled_2: u32,
+        pub(crate) unk_12c: Array<0x8c, u8>,
+    }
+    default_zeroed!(HwDataA130Extra);
+
+    #[repr(C)]
+    pub(crate) struct T81xxData {
+        pub(crate) unk_d8c: u32,
+        pub(crate) unk_d90: u32,
+        pub(crate) unk_d94: u32,
+        pub(crate) unk_d98: u32,
+        pub(crate) unk_d9c: F32,
+        pub(crate) unk_da0: u32,
+        pub(crate) unk_da4: F32,
+        pub(crate) unk_da8: u32,
+        pub(crate) unk_dac: F32,
+        pub(crate) unk_db0: u32,
+        pub(crate) unk_db4: u32,
+        pub(crate) unk_db8: F32,
+        pub(crate) unk_dbc: F32,
+        pub(crate) unk_dc0: u32,
+        pub(crate) unk_dc4: u32,
+        pub(crate) unk_dc8: u32,
+        pub(crate) max_pstate_scaled: u32,
+    }
+    default_zeroed!(T81xxData);
+
+    #[versions(AGX)]
+    #[derive(Default, Copy, Clone)]
+    #[repr(C)]
+    pub(crate) struct PowerZone {
+        pub(crate) val: F32,
+        pub(crate) target: u32,
+        pub(crate) target_off: u32,
+        pub(crate) filter_tc_x4: u32,
+        pub(crate) filter_tc_xperiod: u32,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_10: u32,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_14: u32,
+        pub(crate) filter_a_neg: F32,
+        pub(crate) filter_a: F32,
+        pub(crate) pad: u32,
+    }
+
+    #[versions(AGX)]
+    const MAX_CORES_PER_CLUSTER: usize = {
+        #[ver(G >= G14X)]
+        {
+            16
+        }
+        #[ver(G < G14X)]
+        {
+            8
+        }
+    };
+
+    #[derive(Debug, Default)]
+    #[repr(C)]
+    pub(crate) struct AuxLeakCoef {
+        pub(crate) afr_1: Array<2, F32>,
+        pub(crate) cs_1: Array<2, F32>,
+        pub(crate) afr_2: Array<2, F32>,
+        pub(crate) cs_2: Array<2, F32>,
+    }
+
+    #[versions(AGX)]
+    #[repr(C)]
+    pub(crate) struct HwDataA {
+        pub(crate) unk_0: u32,
+        pub(crate) clocks_per_period: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) clocks_per_period_2: u32,
+
+        pub(crate) unk_8: u32,
+        pub(crate) pwr_status: AtomicU32,
+        pub(crate) unk_10: F32,
+        pub(crate) unk_14: u32,
+        pub(crate) unk_18: u32,
+        pub(crate) unk_1c: u32,
+        pub(crate) unk_20: u32,
+        pub(crate) unk_24: u32,
+        pub(crate) actual_pstate: u32,
+        pub(crate) tgt_pstate: u32,
+        pub(crate) unk_30: u32,
+        pub(crate) cur_pstate: u32,
+        pub(crate) unk_38: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_3c_0: u32,
+
+        pub(crate) base_pstate_scaled: u32,
+        pub(crate) unk_40: u32,
+        pub(crate) max_pstate_scaled: u32,
+        pub(crate) unk_48: u32,
+        pub(crate) min_pstate_scaled: u32,
+        pub(crate) freq_mhz: F32,
+        pub(crate) unk_54: Array<0x20, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_74_0: u32,
+
+        pub(crate) sram_k: Array<0x10, F32>,
+        pub(crate) unk_b4: Array<0x100, u8>,
+        pub(crate) unk_1b4: u32,
+        pub(crate) temp_c: u32,
+        pub(crate) avg_power_mw: u32,
+        pub(crate) update_ts: U64,
+        pub(crate) unk_1c8: u32,
+        pub(crate) unk_1cc: Array<0x478, u8>,
+        pub(crate) pad_644: Pad<0x8>,
+        pub(crate) unk_64c: u32,
+        pub(crate) unk_650: u32,
+        pub(crate) pad_654: u32,
+        pub(crate) pwr_filter_a_neg: F32,
+        pub(crate) pad_65c: u32,
+        pub(crate) pwr_filter_a: F32,
+        pub(crate) pad_664: u32,
+        pub(crate) pwr_integral_gain: F32,
+        pub(crate) pad_66c: u32,
+        pub(crate) pwr_integral_min_clamp: F32,
+        pub(crate) max_power_1: F32,
+        pub(crate) pwr_proportional_gain: F32,
+        pub(crate) pad_67c: u32,
+        pub(crate) pwr_pstate_related_k: F32,
+        pub(crate) pwr_pstate_max_dc_offset: i32,
+        pub(crate) unk_688: u32,
+        pub(crate) max_pstate_scaled_2: u32,
+        pub(crate) pad_690: u32,
+        pub(crate) unk_694: u32,
+        pub(crate) max_power_2: u32,
+        pub(crate) pad_69c: Pad<0x18>,
+        pub(crate) unk_6b4: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_6b8_0: Array<0x10, u8>,
+
+        pub(crate) max_pstate_scaled_3: u32,
+        pub(crate) unk_6bc: u32,
+        pub(crate) pad_6c0: Pad<0x14>,
+        pub(crate) ppm_filter_tc_periods_x4: u32,
+        pub(crate) unk_6d8: u32,
+        pub(crate) pad_6dc: u32,
+        pub(crate) ppm_filter_a_neg: F32,
+        pub(crate) pad_6e4: u32,
+        pub(crate) ppm_filter_a: F32,
+        pub(crate) pad_6ec: u32,
+        pub(crate) ppm_ki_dt: F32,
+        pub(crate) pad_6f4: u32,
+        pub(crate) pwr_integral_min_clamp_2: u32,
+        pub(crate) unk_6fc: F32,
+        pub(crate) ppm_kp: F32,
+        pub(crate) pad_704: u32,
+        pub(crate) unk_708: u32,
+        pub(crate) pwr_min_duty_cycle: u32,
+        pub(crate) max_pstate_scaled_4: u32,
+        pub(crate) unk_714: u32,
+        pub(crate) pad_718: u32,
+        pub(crate) unk_71c: F32,
+        pub(crate) max_power_3: u32,
+        pub(crate) cur_power_mw_2: u32,
+        pub(crate) ppm_filter_tc_ms: u32,
+        pub(crate) unk_72c: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) ppm_filter_tc_clks: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_730_4: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_730_8: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_730_c: u32,
+
+        pub(crate) unk_730: F32,
+        pub(crate) unk_734: u32,
+        pub(crate) unk_738: u32,
+        pub(crate) unk_73c: u32,
+        pub(crate) unk_740: u32,
+        pub(crate) unk_744: u32,
+        pub(crate) unk_748: Array<0x4, F32>,
+        pub(crate) unk_758: u32,
+        pub(crate) perf_tgt_utilization: u32,
+        pub(crate) pad_760: u32,
+        pub(crate) perf_boost_min_util: u32,
+        pub(crate) perf_boost_ce_step: u32,
+        pub(crate) perf_reset_iters: u32,
+        pub(crate) pad_770: u32,
+        pub(crate) unk_774: u32,
+        pub(crate) unk_778: u32,
+        pub(crate) perf_filter_drop_threshold: u32,
+        pub(crate) perf_filter_a_neg: F32,
+        pub(crate) perf_filter_a2_neg: F32,
+        pub(crate) perf_filter_a: F32,
+        pub(crate) perf_filter_a2: F32,
+        pub(crate) perf_ki: F32,
+        pub(crate) perf_ki2: F32,
+        pub(crate) perf_integral_min_clamp: F32,
+        pub(crate) unk_79c: F32,
+        pub(crate) perf_kp: F32,
+        pub(crate) perf_kp2: F32,
+        pub(crate) boost_state_unk_k: F32,
+        pub(crate) base_pstate_scaled_2: u32,
+        pub(crate) max_pstate_scaled_5: u32,
+        pub(crate) base_pstate_scaled_3: u32,
+        pub(crate) pad_7b8: u32,
+        pub(crate) perf_cur_utilization: F32,
+        pub(crate) perf_tgt_utilization_2: u32,
+        pub(crate) pad_7c4: Pad<0x18>,
+        pub(crate) unk_7dc: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_7e0_0: Array<0x10, u8>,
+
+        pub(crate) base_pstate_scaled_4: u32,
+        pub(crate) pad_7e4: u32,
+        pub(crate) unk_7e8: Array<0x14, u8>,
+        pub(crate) unk_7fc: F32,
+        pub(crate) pwr_min_duty_cycle_2: F32,
+        pub(crate) max_pstate_scaled_6: F32,
+        pub(crate) max_freq_mhz: u32,
+        pub(crate) pad_80c: u32,
+        pub(crate) unk_810: u32,
+        pub(crate) pad_814: u32,
+        pub(crate) pwr_min_duty_cycle_3: u32,
+        pub(crate) unk_81c: u32,
+        pub(crate) pad_820: u32,
+        pub(crate) min_pstate_scaled_4: F32,
+        pub(crate) max_pstate_scaled_7: u32,
+        pub(crate) unk_82c: u32,
+        pub(crate) unk_alpha_neg: F32,
+        pub(crate) unk_alpha: F32,
+        pub(crate) unk_838: u32,
+        pub(crate) unk_83c: u32,
+        pub(crate) pad_840: Pad<0x2c>,
+        pub(crate) unk_86c: u32,
+        pub(crate) fast_die0_sensor_mask: U64,
+        #[ver(G >= G14X)]
+        pub(crate) fast_die1_sensor_mask: U64,
+        pub(crate) fast_die0_release_temp_cc: u32,
+        pub(crate) unk_87c: i32,
+        pub(crate) unk_880: u32,
+        pub(crate) unk_884: u32,
+        pub(crate) pad_888: u32,
+        pub(crate) unk_88c: u32,
+        pub(crate) pad_890: u32,
+        pub(crate) unk_894: F32,
+        pub(crate) pad_898: u32,
+        pub(crate) fast_die0_ki_dt: F32,
+        pub(crate) pad_8a0: u32,
+        pub(crate) unk_8a4: u32,
+        pub(crate) unk_8a8: F32,
+        pub(crate) fast_die0_kp: F32,
+        pub(crate) pad_8b0: u32,
+        pub(crate) unk_8b4: u32,
+        pub(crate) pwr_min_duty_cycle_4: u32,
+        pub(crate) max_pstate_scaled_8: u32,
+        pub(crate) max_pstate_scaled_9: u32,
+        pub(crate) fast_die0_prop_tgt_delta: u32,
+        pub(crate) unk_8c8: u32,
+        pub(crate) unk_8cc: u32,
+        pub(crate) pad_8d0: Pad<0x14>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_8e4_0: Array<0x10, u8>,
+
+        pub(crate) unk_8e4: u32,
+        pub(crate) unk_8e8: u32,
+        pub(crate) max_pstate_scaled_10: u32,
+        pub(crate) unk_8f0: u32,
+        pub(crate) unk_8f4: u32,
+        pub(crate) pad_8f8: u32,
+        pub(crate) pad_8fc: u32,
+        pub(crate) unk_900: Array<0x24, u8>,
+
+        pub(crate) unk_coef_a1: Array<8, Array<MAX_CORES_PER_CLUSTER::ver, F32>>,
+        pub(crate) unk_coef_a2: Array<8, Array<MAX_CORES_PER_CLUSTER::ver, F32>>,
+
+        pub(crate) pad_b24: Pad<0x70>,
+        pub(crate) max_pstate_scaled_11: u32,
+        pub(crate) freq_with_off: u32,
+        pub(crate) unk_b9c: u32,
+        pub(crate) unk_ba0: U64,
+        pub(crate) unk_ba8: U64,
+        pub(crate) unk_bb0: u32,
+        pub(crate) unk_bb4: u32,
+
+        #[ver(V >= V13_3)]
+        pub(crate) pad_bb8_0: Pad<0x200>,
+        #[ver(V >= V13_5)]
+        pub(crate) pad_bb8_200: Pad<0x8>,
+
+        pub(crate) pad_bb8: Pad<0x74>,
+        pub(crate) unk_c2c: u32,
+        pub(crate) power_zone_count: u32,
+        pub(crate) max_power_4: u32,
+        pub(crate) max_power_5: u32,
+        pub(crate) max_power_6: u32,
+        pub(crate) unk_c40: u32,
+        pub(crate) unk_c44: F32,
+        pub(crate) avg_power_target_filter_a_neg: F32,
+        pub(crate) avg_power_target_filter_a: F32,
+        pub(crate) avg_power_target_filter_tc_x4: u32,
+        pub(crate) avg_power_target_filter_tc_xperiod: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) avg_power_target_filter_tc_clks: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_c58_4: u32,
+
+        pub(crate) power_zones: Array<5, PowerZone::ver>,
+        pub(crate) avg_power_filter_tc_periods_x4: u32,
+        pub(crate) unk_cfc: u32,
+        pub(crate) unk_d00: u32,
+        pub(crate) avg_power_filter_a_neg: F32,
+        pub(crate) unk_d08: u32,
+        pub(crate) avg_power_filter_a: F32,
+        pub(crate) unk_d10: u32,
+        pub(crate) avg_power_ki_dt: F32,
+        pub(crate) unk_d18: u32,
+        pub(crate) unk_d1c: u32,
+        pub(crate) unk_d20: F32,
+        pub(crate) avg_power_kp: F32,
+        pub(crate) unk_d28: u32,
+        pub(crate) unk_d2c: u32,
+        pub(crate) avg_power_min_duty_cycle: u32,
+        pub(crate) max_pstate_scaled_12: u32,
+        pub(crate) max_pstate_scaled_13: u32,
+        pub(crate) unk_d3c: u32,
+        pub(crate) max_power_7: F32,
+        pub(crate) max_power_8: u32,
+        pub(crate) unk_d48: u32,
+        pub(crate) avg_power_filter_tc_ms: u32,
+        pub(crate) unk_d50: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) avg_power_filter_tc_clks: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_d54_4: Array<0xc, u8>,
+
+        pub(crate) unk_d54: Array<0x10, u8>,
+        pub(crate) max_pstate_scaled_14: u32,
+        pub(crate) unk_d68: Array<0x24, u8>,
+
+        pub(crate) t81xx_data: T81xxData,
+
+        pub(crate) unk_dd0: Array<0x40, u8>,
+
+        #[ver(V >= V13_2)]
+        pub(crate) unk_e10_pad: Array<0x10, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_e10_0: HwDataA130Extra,
+
+        pub(crate) unk_e10: Array<0xc, u8>,
+
+        pub(crate) fast_die0_sensor_mask_2: U64,
+        #[ver(G >= G14X)]
+        pub(crate) fast_die1_sensor_mask_2: U64,
+
+        pub(crate) unk_e24: u32,
+        pub(crate) unk_e28: u32,
+        pub(crate) unk_e2c: Pad<0x1c>,
+        pub(crate) unk_coef_b1: Array<8, Array<MAX_CORES_PER_CLUSTER::ver, F32>>,
+        pub(crate) unk_coef_b2: Array<8, Array<MAX_CORES_PER_CLUSTER::ver, F32>>,
+
+        #[ver(G >= G14X)]
+        pub(crate) pad_1048_0: Pad<0x600>,
+
+        pub(crate) pad_1048: Pad<0x5e4>,
+
+        pub(crate) fast_die0_sensor_mask_alt: U64,
+        #[ver(G >= G14X)]
+        pub(crate) fast_die1_sensor_mask_alt: U64,
+        #[ver(V < V13_0B4)]
+        pub(crate) fast_die0_sensor_present: U64,
+
+        pub(crate) unk_163c: u32,
+
+        pub(crate) unk_1640: Array<0x2000, u8>,
+
+        #[ver(G >= G14X)]
+        pub(crate) unk_3640_0: Array<0x2000, u8>,
+
+        pub(crate) unk_3640: u32,
+        pub(crate) unk_3644: u32,
+        pub(crate) hws1: HwDataShared1,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_hws2: Array<16, u16>,
+
+        pub(crate) hws2: HwDataShared2,
+        pub(crate) unk_3c00: u32,
+        pub(crate) unk_3c04: u32,
+        pub(crate) hws3: HwDataShared3,
+        pub(crate) unk_3c58: Array<0x3c, u8>,
+        pub(crate) unk_3c94: u32,
+        pub(crate) unk_3c98: U64,
+        pub(crate) unk_3ca0: U64,
+        pub(crate) unk_3ca8: U64,
+        pub(crate) unk_3cb0: U64,
+        pub(crate) ts_last_idle: U64,
+        pub(crate) ts_last_poweron: U64,
+        pub(crate) ts_last_poweroff: U64,
+        pub(crate) unk_3cd0: U64,
+        pub(crate) unk_3cd8: U64,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_3ce0_0: u32,
+
+        pub(crate) unk_3ce0: u32,
+        pub(crate) unk_3ce4: u32,
+        pub(crate) unk_3ce8: u32,
+        pub(crate) unk_3cec: u32,
+        pub(crate) unk_3cf0: u32,
+        pub(crate) core_leak_coef: Array<8, F32>,
+        pub(crate) sram_leak_coef: Array<8, F32>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) aux_leak_coef: AuxLeakCoef,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_3d34_0: Array<0x18, u8>,
+
+        pub(crate) unk_3d34: Array<0x38, u8>,
+    }
+    #[versions(AGX)]
+    default_zeroed!(HwDataA::ver);
+    #[versions(AGX)]
+    no_debug!(HwDataA::ver);
+
+    #[derive(Debug, Default, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct IOMapping {
+        pub(crate) phys_addr: U64,
+        pub(crate) virt_addr: U64,
+        pub(crate) size: u32,
+        pub(crate) range_size: u32,
+        pub(crate) readwrite: U64,
+    }
+
+    #[versions(AGX)]
+    const IO_MAPPING_COUNT: usize = {
+        #[ver(V < V13_0B4)]
+        {
+            0x14
+        }
+        #[ver(V >= V13_0B4 && V < V13_3)]
+        {
+            0x17
+        }
+        #[ver(V >= V13_3 && V < V13_5)]
+        {
+            0x18
+        }
+        #[ver(V >= V13_5)]
+        {
+            0x19
+        }
+    };
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataBAuxPStates {
+        pub(crate) cs_max_pstate: u32,
+        pub(crate) cs_frequencies: Array<0x10, u32>,
+        pub(crate) cs_voltages: Array<0x10, Array<0x2, u32>>,
+        pub(crate) cs_voltages_sram: Array<0x10, Array<0x2, u32>>,
+        pub(crate) cs_unkpad: u32,
+        pub(crate) afr_max_pstate: u32,
+        pub(crate) afr_frequencies: Array<0x8, u32>,
+        pub(crate) afr_voltages: Array<0x8, Array<0x2, u32>>,
+        pub(crate) afr_voltages_sram: Array<0x8, Array<0x2, u32>>,
+        pub(crate) afr_unkpad: u32,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct HwDataB {
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_0: U64,
+
+        pub(crate) unk_8: U64,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_10: U64,
+
+        pub(crate) unk_18: U64,
+        pub(crate) unk_20: U64,
+        pub(crate) unk_28: U64,
+        pub(crate) unk_30: U64,
+        pub(crate) unkptr_38: U64,
+        pub(crate) pad_40: Pad<0x20>,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) yuv_matrices: Array<0xf, Array<3, Array<4, i16>>>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) yuv_matrices: Array<0x3f, Array<3, Array<4, i16>>>,
+
+        pub(crate) pad_1c8: Pad<0x8>,
+        pub(crate) io_mappings: Array<IO_MAPPING_COUNT::ver, IOMapping>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) sgx_sram_ptr: U64,
+
+        pub(crate) chip_id: u32,
+        pub(crate) unk_454: u32,
+        pub(crate) unk_458: u32,
+        pub(crate) unk_45c: u32,
+        pub(crate) unk_460: u32,
+        pub(crate) unk_464: u32,
+        pub(crate) unk_468: u32,
+        pub(crate) unk_46c: u32,
+        pub(crate) unk_470: u32,
+        pub(crate) unk_474: u32,
+        pub(crate) unk_478: u32,
+        pub(crate) unk_47c: u32,
+        pub(crate) unk_480: u32,
+        pub(crate) unk_484: u32,
+        pub(crate) unk_488: u32,
+        pub(crate) unk_48c: u32,
+        pub(crate) base_clock_khz: u32,
+        pub(crate) power_sample_period: u32,
+        pub(crate) pad_498: Pad<0x4>,
+        pub(crate) unk_49c: u32,
+        pub(crate) unk_4a0: u32,
+        pub(crate) unk_4a4: u32,
+        pub(crate) pad_4a8: Pad<0x4>,
+        pub(crate) unk_4ac: u32,
+        pub(crate) pad_4b0: Pad<0x8>,
+        pub(crate) unk_4b8: u32,
+        pub(crate) unk_4bc: Array<0x4, u8>,
+        pub(crate) unk_4c0: u32,
+        pub(crate) unk_4c4: u32,
+        pub(crate) unk_4c8: u32,
+        pub(crate) unk_4cc: u32,
+        pub(crate) unk_4d0: u32,
+        pub(crate) unk_4d4: u32,
+        pub(crate) unk_4d8: Array<0x4, u8>,
+        pub(crate) unk_4dc: u32,
+        pub(crate) unk_4e0: U64,
+        pub(crate) unk_4e8: u32,
+        pub(crate) unk_4ec: u32,
+        pub(crate) unk_4f0: u32,
+        pub(crate) unk_4f4: u32,
+        pub(crate) unk_4f8: u32,
+        pub(crate) unk_4fc: u32,
+        pub(crate) unk_500: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_504_0: u32,
+
+        pub(crate) unk_504: u32,
+        pub(crate) unk_508: u32,
+        pub(crate) unk_50c: u32,
+        pub(crate) unk_510: u32,
+        pub(crate) unk_514: u32,
+        pub(crate) unk_518: u32,
+        pub(crate) unk_51c: u32,
+        pub(crate) unk_520: u32,
+        pub(crate) unk_524: u32,
+        pub(crate) unk_528: u32,
+        pub(crate) unk_52c: u32,
+        pub(crate) unk_530: u32,
+        pub(crate) unk_534: u32,
+        pub(crate) unk_538: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_53c_0: u32,
+
+        pub(crate) num_frags: u32,
+        pub(crate) unk_540: u32,
+        pub(crate) unk_544: u32,
+        pub(crate) unk_548: u32,
+        pub(crate) unk_54c: u32,
+        pub(crate) unk_550: u32,
+        pub(crate) unk_554: u32,
+        pub(crate) uat_ttb_base: U64,
+        pub(crate) gpu_core_id: u32,
+        pub(crate) gpu_rev_id: u32,
+        pub(crate) num_cores: u32,
+        pub(crate) max_pstate: u32,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) num_pstates: u32,
+
+        pub(crate) frequencies: Array<0x10, u32>,
+        pub(crate) voltages: Array<0x10, [u32; 0x8]>,
+        pub(crate) voltages_sram: Array<0x10, [u32; 0x8]>,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_9f4_0: Pad<64>,
+
+        pub(crate) sram_k: Array<0x10, F32>,
+        pub(crate) unk_9f4: Array<0x10, u32>,
+        pub(crate) rel_max_powers: Array<0x10, u32>,
+        pub(crate) rel_boost_freqs: Array<0x10, u32>,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_arr_0: Array<32, u32>,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) min_sram_volt: u32,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_ab8: u32,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_abc: u32,
+
+        #[ver(V < V13_0B4)]
+        pub(crate) unk_ac0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) aux_ps: HwDataBAuxPStates,
+
+        #[ver(V >= V13_3)]
+        pub(crate) pad_ac4_0: Array<0x44c, u8>,
+
+        pub(crate) pad_ac4: Pad<0x8>,
+        pub(crate) unk_acc: u32,
+        pub(crate) unk_ad0: u32,
+        pub(crate) pad_ad4: Pad<0x10>,
+        pub(crate) unk_ae4: Array<0x4, u32>,
+        pub(crate) pad_af4: Pad<0x4>,
+        pub(crate) unk_af8: u32,
+        pub(crate) pad_afc: Pad<0x8>,
+        pub(crate) unk_b04: u32,
+        pub(crate) unk_b08: u32,
+        pub(crate) unk_b0c: u32,
+
+        #[ver(G >= G14X)]
+        pub(crate) pad_b10_0: Array<0x8, u8>,
+
+        pub(crate) unk_b10: u32,
+        pub(crate) pad_b14: Pad<0x8>,
+        pub(crate) unk_b1c: u32,
+        pub(crate) unk_b20: u32,
+        pub(crate) unk_b24: u32,
+        pub(crate) unk_b28: u32,
+        pub(crate) unk_b2c: u32,
+        pub(crate) unk_b30: u32,
+        pub(crate) unk_b34: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_b38_0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_b38_4: u32,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_b38_8: u32,
+
+        pub(crate) unk_b38: Array<0xc, u32>,
+        pub(crate) unk_b68: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_b6c: Array<0xd0, u8>,
+
+        #[ver(G >= G14X)]
+        pub(crate) unk_c3c_0: Array<0x8, u8>,
+
+        #[ver(G < G14X && V >= V13_5)]
+        pub(crate) unk_c3c_8: Array<0x10, u8>,
+
+        #[ver(V >= V13_5)]
+        pub(crate) unk_c3c_18: Array<0x20, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_c3c: u32,
+    }
+    #[versions(AGX)]
+    default_zeroed!(HwDataB::ver);
+
+    #[derive(Debug)]
+    #[repr(C, packed)]
+    pub(crate) struct GpuStatsVtx {
+        // This changes all the time and we don't use it, let's just make it a big buffer
+        pub(crate) opaque: Array<0x3000, u8>,
+    }
+    default_zeroed!(GpuStatsVtx);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct GpuStatsFrag {
+        // This changes all the time and we don't use it, let's just make it a big buffer
+        pub(crate) opaque: Array<0x3000, u8>,
+    }
+    default_zeroed!(GpuStatsFrag);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct GpuGlobalStatsVtx {
+        pub(crate) total_cmds: u32,
+        pub(crate) stats: GpuStatsVtx,
+    }
+    default_zeroed!(GpuGlobalStatsVtx);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct GpuGlobalStatsFrag {
+        pub(crate) total_cmds: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) stats: GpuStatsFrag,
+    }
+    default_zeroed!(GpuGlobalStatsFrag);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct GpuStatsComp {
+        // This changes all the time and we don't use it, let's just make it a big buffer
+        pub(crate) opaque: Array<0x3000, u8>,
+    }
+    default_zeroed!(GpuStatsComp);
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RuntimeScratch {
+        pub(crate) unk_280: Array<0x6800, u8>,
+        pub(crate) unk_6a80: u32,
+        pub(crate) gpu_idle: u32,
+        pub(crate) unkpad_6a88: Pad<0x14>,
+        pub(crate) unk_6a9c: u32,
+        pub(crate) unk_ctr0: u32,
+        pub(crate) unk_ctr1: u32,
+        pub(crate) unk_6aa8: u32,
+        pub(crate) unk_6aac: u32,
+        pub(crate) unk_ctr2: u32,
+        pub(crate) unk_6ab4: u32,
+        pub(crate) unk_6ab8: u32,
+        pub(crate) unk_6abc: u32,
+        pub(crate) unk_6ac0: u32,
+        pub(crate) unk_6ac4: u32,
+        pub(crate) unk_ctr3: u32,
+        pub(crate) unk_6acc: u32,
+        pub(crate) unk_6ad0: u32,
+        pub(crate) unk_6ad4: u32,
+        pub(crate) unk_6ad8: u32,
+        pub(crate) unk_6adc: u32,
+        pub(crate) unk_6ae0: u32,
+        pub(crate) unk_6ae4: u32,
+        pub(crate) unk_6ae8: u32,
+        pub(crate) unk_6aec: u32,
+        pub(crate) unk_6af0: u32,
+        pub(crate) unk_ctr4: u32,
+        pub(crate) unk_ctr5: u32,
+        pub(crate) unk_6afc: u32,
+        pub(crate) pad_6b00: Pad<0x38>,
+
+        #[ver(G >= G14X)]
+        pub(crate) pad_6b00_extra: Array<0x4800, u8>,
+
+        pub(crate) unk_6b38: u32,
+        pub(crate) pad_6b3c: Pad<0x84>,
+    }
+    #[versions(AGX)]
+    default_zeroed!(RuntimeScratch::ver);
+
+    #[versions(AGX)]
+    #[repr(C)]
+    pub(crate) struct RuntimePointers<'a> {
+        pub(crate) pipes: Array<4, PipeChannels::ver>,
+
+        pub(crate) device_control:
+            ChannelRing<channels::ChannelState, channels::DeviceControlMsg::ver>,
+        pub(crate) event: ChannelRing<channels::ChannelState, channels::RawEventMsg>,
+        pub(crate) fw_log: ChannelRing<channels::FwLogChannelState, channels::RawFwLogMsg>,
+        pub(crate) ktrace: ChannelRing<channels::ChannelState, channels::RawKTraceMsg>,
+        pub(crate) stats: ChannelRing<channels::ChannelState, channels::RawStatsMsg::ver>,
+
+        pub(crate) __pad0: Pad<0x50>,
+        pub(crate) unk_160: U64,
+        pub(crate) unk_168: U64,
+        pub(crate) stats_vtx: GpuPointer<'a, super::GpuGlobalStatsVtx>,
+        pub(crate) stats_frag: GpuPointer<'a, super::GpuGlobalStatsFrag>,
+        pub(crate) stats_comp: GpuPointer<'a, super::GpuStatsComp>,
+        pub(crate) hwdata_a: GpuPointer<'a, super::HwDataA::ver>,
+        pub(crate) unkptr_190: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unkptr_198: GpuPointer<'a, &'a [u8]>,
+        pub(crate) hwdata_b: GpuPointer<'a, super::HwDataB::ver>,
+        pub(crate) hwdata_b_2: GpuPointer<'a, super::HwDataB::ver>,
+        pub(crate) fwlog_buf: Option<GpuWeakPointer<[channels::RawFwLogPayloadMsg]>>,
+        pub(crate) unkptr_1b8: GpuPointer<'a, &'a [u8]>,
+
+        #[ver(G < G14X)]
+        pub(crate) unkptr_1c0: GpuPointer<'a, &'a [u8]>,
+        #[ver(G < G14X)]
+        pub(crate) unkptr_1c8: GpuPointer<'a, &'a [u8]>,
+
+        pub(crate) unk_1d0: u32,
+        pub(crate) unk_1d4: u32,
+        pub(crate) unk_1d8: Array<0x3c, u8>,
+        pub(crate) buffer_mgr_ctl_gpu_addr: U64,
+        pub(crate) buffer_mgr_ctl_fw_addr: U64,
+        pub(crate) __pad1: Pad<0x5c>,
+        pub(crate) gpu_scratch: RuntimeScratch::ver,
+    }
+    #[versions(AGX)]
+    no_debug!(RuntimePointers::ver<'_>);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct PendingStamp {
+        pub(crate) info: AtomicU32,
+        pub(crate) wait_value: AtomicU32,
+    }
+    default_zeroed!(PendingStamp);
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C, packed)]
+    pub(crate) struct FaultInfo {
+        pub(crate) unk_0: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) queue_uuid: u32,
+        pub(crate) unk_c: u32,
+        pub(crate) unk_10: u32,
+        pub(crate) unk_14: u32,
+    }
+    default_zeroed!(FaultInfo);
+
+    #[versions(AGX)]
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C, packed)]
+    pub(crate) struct GlobalsSub {
+        pub(crate) unk_54: u16,
+        pub(crate) unk_56: u16,
+        pub(crate) unk_58: u16,
+        pub(crate) unk_5a: U32,
+        pub(crate) unk_5e: U32,
+        pub(crate) unk_62: U32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_66_0: Array<0xc, u8>,
+
+        pub(crate) unk_66: U32,
+        pub(crate) unk_6a: Array<0x16, u8>,
+    }
+    #[versions(AGX)]
+    default_zeroed!(GlobalsSub::ver);
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct PowerZoneGlobal {
+        pub(crate) target: u32,
+        pub(crate) target_off: u32,
+        pub(crate) filter_tc: u32,
+    }
+    default_zeroed!(PowerZoneGlobal);
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Globals {
+        pub(crate) ktrace_enable: u32,
+        pub(crate) unk_4: Array<0x20, u8>,
+
+        #[ver(V >= V13_2)]
+        pub(crate) unk_24_0: u32,
+
+        pub(crate) unk_24: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) debug: u32,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_28_4: u32,
+
+        pub(crate) unk_28: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_2c_0: u32,
+
+        pub(crate) unk_2c: u32,
+        pub(crate) unk_30: u32,
+        pub(crate) unk_34: u32,
+        pub(crate) unk_38: Array<0x1c, u8>,
+
+        pub(crate) sub: GlobalsSub::ver,
+
+        pub(crate) unk_80: Array<0xf80, u8>,
+        pub(crate) unk_1000: Array<0x7000, u8>,
+        pub(crate) unk_8000: Array<0x900, u8>,
+
+        #[ver(G >= G14X)]
+        pub(crate) unk_8900_pad: Array<0x484c, u8>,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_8900_pad2: Array<0x54, u8>,
+
+        pub(crate) unk_8900: u32,
+        pub(crate) pending_submissions: AtomicU32,
+        pub(crate) max_power: u32,
+        pub(crate) max_pstate_scaled: u32,
+        pub(crate) max_pstate_scaled_2: u32,
+        pub(crate) unk_8914: u32,
+        pub(crate) unk_8918: u32,
+        pub(crate) max_pstate_scaled_3: u32,
+        pub(crate) unk_8920: u32,
+        pub(crate) power_zone_count: u32,
+        pub(crate) avg_power_filter_tc_periods: u32,
+        pub(crate) avg_power_ki_dt: F32,
+        pub(crate) avg_power_kp: F32,
+        pub(crate) avg_power_min_duty_cycle: u32,
+        pub(crate) avg_power_target_filter_tc: u32,
+        pub(crate) power_zones: Array<5, PowerZoneGlobal>,
+        pub(crate) unk_8978: Array<0x44, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_89bc_0: Array<0x3c, u8>,
+
+        pub(crate) unk_89bc: u32,
+        pub(crate) fast_die0_release_temp: u32,
+        pub(crate) unk_89c4: i32,
+        pub(crate) fast_die0_prop_tgt_delta: u32,
+        pub(crate) fast_die0_kp: F32,
+        pub(crate) fast_die0_ki_dt: F32,
+        pub(crate) unk_89d4: Array<0xc, u8>,
+        pub(crate) unk_89e0: u32,
+        pub(crate) max_power_2: u32,
+        pub(crate) ppm_kp: F32,
+        pub(crate) ppm_ki_dt: F32,
+        pub(crate) unk_89f0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_89f4_0: Array<0x8, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_89f4_8: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_89f4_c: Array<0x50, u8>,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_89f4_5c: Array<0xc, u8>,
+
+        pub(crate) unk_89f4: u32,
+        pub(crate) hws1: HwDataShared1,
+        pub(crate) hws2: HwDataShared2,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_hws2_0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_hws2_4: Array<0x8, F32>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_hws2_24: u32,
+
+        pub(crate) unk_hws2_28: u32,
+
+        pub(crate) hws3: HwDataShared3,
+        pub(crate) unk_9004: Array<8, u8>,
+        pub(crate) unk_900c: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_9010_0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_9010_4: Array<0x14, u8>,
+
+        pub(crate) unk_9010: Array<0x2c, u8>,
+        pub(crate) unk_903c: u32,
+        pub(crate) unk_9040: Array<0xc0, u8>,
+        pub(crate) unk_9100: Array<0x6f00, u8>,
+        pub(crate) unk_10000: Array<0xe50, u8>,
+        pub(crate) unk_10e50: u32,
+        pub(crate) unk_10e54: Array<0x2c, u8>,
+
+        #[ver((G >= G14X && V < V13_3) || (G <= G14 && V >= V13_3))]
+        pub(crate) unk_x_pad: Array<0x4, u8>,
+
+        pub(crate) fault_control: u32,
+        pub(crate) do_init: u32,
+        pub(crate) unk_10e88: Array<0x188, u8>,
+        pub(crate) idle_ts: U64,
+        pub(crate) idle_unk: U64,
+        pub(crate) unk_11020: u32,
+        pub(crate) unk_11024: u32,
+        pub(crate) unk_11028: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_1102c_0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_1102c_4: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_1102c_8: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_1102c_c: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_1102c_10: u32,
+
+        pub(crate) unk_1102c: u32,
+        pub(crate) idle_off_delay_ms: AtomicU32,
+        pub(crate) fender_idle_off_delay_ms: u32,
+        pub(crate) fw_early_wake_timeout_ms: u32,
+        #[ver(V == V13_3)]
+        pub(crate) ps_pad_0: Pad<0x8>,
+        pub(crate) pending_stamps: Array<0x100, PendingStamp>,
+        #[ver(V != V13_3)]
+        pub(crate) ps_pad_0: Pad<0x8>,
+        pub(crate) unkpad_ps: Pad<0x78>,
+        pub(crate) unk_117bc: u32,
+        pub(crate) fault_info: FaultInfo,
+        pub(crate) counter: u32,
+        pub(crate) unk_118dc: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_118e0_0: Array<0x9c, u8>,
+
+        #[ver(G >= G14X)]
+        pub(crate) unk_118e0_9c: Array<0x580, u8>,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_118e0_9c_x: Array<0x8, u8>,
+
+        pub(crate) unk_118e0: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_118e4_0: u32,
+
+        pub(crate) unk_118e4: u32,
+        pub(crate) unk_118e8: u32,
+        pub(crate) unk_118ec: Array<0x400, u8>,
+        pub(crate) unk_11cec: Array<0x54, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_11d40: Array<0x19c, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_11edc: u32,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_11ee0: Array<0x1c, u8>,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_11efc: u32,
+
+        #[ver(V >= V13_3)]
+        pub(crate) unk_11f00: Array<0x280, u8>,
+    }
+    #[versions(AGX)]
+    default_zeroed!(Globals::ver);
+
+    #[derive(Debug, Default, Clone, Copy)]
+    #[repr(C, packed)]
+    pub(crate) struct UatLevelInfo {
+        pub(crate) unk_3: u8,
+        pub(crate) unk_1: u8,
+        pub(crate) unk_2: u8,
+        pub(crate) index_shift: u8,
+        pub(crate) num_entries: u16,
+        pub(crate) unk_4: u16,
+        pub(crate) unk_8: U64,
+        pub(crate) unk_10: U64,
+        pub(crate) index_mask: U64,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct InitData<'a> {
+        #[ver(V >= V13_0B4)]
+        pub(crate) ver_info: Array<0x4, u16>,
+
+        pub(crate) unk_buf: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unk_8: u32,
+        pub(crate) unk_c: u32,
+        pub(crate) runtime_pointers: GpuPointer<'a, super::RuntimePointers::ver>,
+        pub(crate) globals: GpuPointer<'a, super::Globals::ver>,
+        pub(crate) fw_status: GpuPointer<'a, super::FwStatus>,
+        pub(crate) uat_page_size: u16,
+        pub(crate) uat_page_bits: u8,
+        pub(crate) uat_num_levels: u8,
+        pub(crate) uat_level_info: Array<0x3, UatLevelInfo>,
+        pub(crate) __pad0: Pad<0x14>,
+        pub(crate) host_mapped_fw_allocations: u32,
+        pub(crate) unk_ac: u32,
+        pub(crate) unk_b0: u32,
+        pub(crate) unk_b4: u32,
+        pub(crate) unk_b8: u32,
+    }
+}
+
+#[derive(Debug)]
+pub(crate) struct ChannelRing<T: GpuStruct + Debug + Default, U: Copy>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug,
+{
+    pub(crate) state: GpuObject<T>,
+    pub(crate) ring: GpuArray<U>,
+}
+
+impl<T: GpuStruct + Debug + Default, U: Copy> ChannelRing<T, U>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Debug,
+{
+    pub(crate) fn to_raw(&self) -> raw::ChannelRing<T, U> {
+        raw::ChannelRing {
+            state: Some(self.state.weak_pointer()),
+            ring: Some(self.ring.weak_pointer()),
+        }
+    }
+}
+
+trivial_gpustruct!(FwStatus);
+trivial_gpustruct!(GpuGlobalStatsVtx);
+trivial_gpustruct!(GpuGlobalStatsFrag);
+trivial_gpustruct!(GpuStatsComp);
+
+#[versions(AGX)]
+trivial_gpustruct!(HwDataA::ver);
+
+#[versions(AGX)]
+trivial_gpustruct!(HwDataB::ver);
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct Stats {
+    pub(crate) vtx: GpuObject<GpuGlobalStatsVtx>,
+    pub(crate) frag: GpuObject<GpuGlobalStatsFrag>,
+    pub(crate) comp: GpuObject<GpuStatsComp>,
+}
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct RuntimePointers {
+    pub(crate) stats: Stats::ver,
+
+    pub(crate) hwdata_a: GpuObject<HwDataA::ver>,
+    pub(crate) unkptr_190: GpuArray<u8>,
+    pub(crate) unkptr_198: GpuArray<u8>,
+    pub(crate) hwdata_b: GpuObject<HwDataB::ver>,
+
+    pub(crate) unkptr_1b8: GpuArray<u8>,
+    pub(crate) unkptr_1c0: GpuArray<u8>,
+    pub(crate) unkptr_1c8: GpuArray<u8>,
+
+    pub(crate) buffer_mgr_ctl: gem::ObjectRef,
+}
+
+#[versions(AGX)]
+impl GpuStruct for RuntimePointers::ver {
+    type Raw<'a> = raw::RuntimePointers::ver<'a>;
+}
+
+#[versions(AGX)]
+trivial_gpustruct!(Globals::ver);
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct InitData {
+    pub(crate) unk_buf: GpuArray<u8>,
+    pub(crate) runtime_pointers: GpuObject<RuntimePointers::ver>,
+    pub(crate) globals: GpuObject<Globals::ver>,
+    pub(crate) fw_status: GpuObject<FwStatus>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for InitData::ver {
+    type Raw<'a> = raw::InitData::ver<'a>;
+}
diff --git a/drivers/gpu/drm/asahi/fw/job.rs b/drivers/gpu/drm/asahi/fw/job.rs
new file mode 100644
index 00000000000000..aff96d921355a2
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/job.rs
@@ -0,0 +1,114 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Common GPU job firmware structures
+
+use super::types::*;
+use crate::{default_zeroed, trivial_gpustruct};
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct JobMeta {
+        pub(crate) unk_0: u16,
+        pub(crate) unk_2: u8,
+        pub(crate) no_preemption: u8,
+        pub(crate) stamp: GpuWeakPointer<Stamp>,
+        pub(crate) fw_stamp: GpuWeakPointer<FwStamp>,
+        pub(crate) stamp_value: EventValue,
+        pub(crate) stamp_slot: u32,
+        pub(crate) evctl_index: u32,
+        pub(crate) flush_stamps: u32,
+        pub(crate) uuid: u32,
+        pub(crate) event_seq: u32,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct EncoderParams {
+        pub(crate) unk_8: u32,
+        pub(crate) sync_grow: u32,
+        pub(crate) unk_10: u32,
+        pub(crate) encoder_id: u32,
+        pub(crate) unk_18: u32,
+        pub(crate) unk_mask: u32,
+        pub(crate) sampler_array: U64,
+        pub(crate) sampler_count: u32,
+        pub(crate) sampler_max: u32,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobTimestamps {
+        pub(crate) start: AtomicU64,
+        pub(crate) end: AtomicU64,
+    }
+    default_zeroed!(JobTimestamps);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RenderTimestamps {
+        pub(crate) vtx: JobTimestamps,
+        pub(crate) frag: JobTimestamps,
+    }
+    default_zeroed!(RenderTimestamps);
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Register {
+        pub(crate) number: u32,
+        pub(crate) value: U64,
+    }
+    default_zeroed!(Register);
+
+    impl Register {
+        fn new(number: u32, value: u64) -> Register {
+            Register {
+                number,
+                value: U64(value),
+            }
+        }
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RegisterArray {
+        pub(crate) registers: Array<128, Register>,
+        pub(crate) pad: Array<0x100, u8>,
+
+        pub(crate) addr: GpuWeakPointer<Array<128, Register>>,
+        pub(crate) count: u16,
+        pub(crate) length: u16,
+        pub(crate) unk_pad: u32,
+    }
+
+    impl RegisterArray {
+        pub(crate) fn new(
+            self_ptr: GpuWeakPointer<Array<128, Register>>,
+            cb: impl FnOnce(&mut RegisterArray),
+        ) -> RegisterArray {
+            let mut array = RegisterArray {
+                registers: Default::default(),
+                pad: Default::default(),
+                addr: self_ptr,
+                count: 0,
+                length: 0,
+                unk_pad: 0,
+            };
+
+            cb(&mut array);
+
+            array
+        }
+
+        pub(crate) fn add(&mut self, number: u32, value: u64) {
+            self.registers[self.count as usize] = Register::new(number, value);
+            self.count += 1;
+            self.length += core::mem::size_of::<Register>() as u16;
+        }
+    }
+}
+
+trivial_gpustruct!(JobTimestamps);
+trivial_gpustruct!(RenderTimestamps);
diff --git a/drivers/gpu/drm/asahi/fw/microseq.rs b/drivers/gpu/drm/asahi/fw/microseq.rs
new file mode 100644
index 00000000000000..70c069fee95e83
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/microseq.rs
@@ -0,0 +1,399 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU firmware microsequence operations
+
+use super::types::*;
+use super::{buffer, compute, fragment, initdata, job, vertex, workqueue};
+use crate::default_zeroed;
+
+pub(crate) trait Operation {}
+
+#[derive(Debug, Copy, Clone)]
+#[repr(u32)]
+enum OpCode {
+    WaitForIdle = 0x01,
+    WaitForIdle2 = 0x02,
+    RetireStamp = 0x18,
+    #[allow(dead_code)]
+    Timestamp = 0x19,
+    StartVertex = 0x22,
+    FinalizeVertex = 0x23,
+    StartFragment = 0x24,
+    FinalizeFragment = 0x25,
+    StartCompute = 0x29,
+    FinalizeCompute = 0x2a,
+}
+
+#[derive(Debug, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum Pipe {
+    Vertex = 1 << 0,
+    Fragment = 1 << 8,
+    Compute = 1 << 15,
+}
+
+pub(crate) const MAX_ATTACHMENTS: usize = 16;
+
+#[derive(Debug, Clone, Copy)]
+#[repr(C)]
+pub(crate) struct Attachment {
+    pub(crate) address: U64,
+    pub(crate) size: u32,
+    pub(crate) unk_c: u16,
+    pub(crate) unk_e: u16,
+}
+default_zeroed!(Attachment);
+
+#[derive(Debug, Clone, Copy, Default)]
+#[repr(C)]
+pub(crate) struct Attachments {
+    pub(crate) list: Array<MAX_ATTACHMENTS, Attachment>,
+    pub(crate) count: u32,
+}
+
+#[derive(Debug, Copy, Clone)]
+#[repr(transparent)]
+pub(crate) struct OpHeader(u32);
+
+impl OpHeader {
+    const fn new(opcode: OpCode) -> OpHeader {
+        OpHeader(opcode as u32)
+    }
+    const fn with_args(opcode: OpCode, args: u32) -> OpHeader {
+        OpHeader(opcode as u32 | args)
+    }
+}
+
+macro_rules! simple_op {
+    ($name:ident) => {
+        #[derive(Debug, Copy, Clone)]
+        pub(crate) struct $name(OpHeader);
+
+        impl $name {
+            pub(crate) const HEADER: $name = $name(OpHeader::new(OpCode::$name));
+        }
+    };
+}
+
+pub(crate) mod op {
+    use super::*;
+
+    simple_op!(StartVertex);
+    simple_op!(FinalizeVertex);
+    simple_op!(StartFragment);
+    simple_op!(FinalizeFragment);
+    simple_op!(StartCompute);
+    simple_op!(FinalizeCompute);
+    simple_op!(WaitForIdle2);
+
+    #[derive(Debug, Copy, Clone)]
+    pub(crate) struct RetireStamp(OpHeader);
+    impl RetireStamp {
+        pub(crate) const HEADER: RetireStamp =
+            RetireStamp(OpHeader::with_args(OpCode::RetireStamp, 0x40000000));
+    }
+
+    #[derive(Debug, Copy, Clone)]
+    pub(crate) struct WaitForIdle(OpHeader);
+    impl WaitForIdle {
+        pub(crate) const fn new(pipe: Pipe) -> WaitForIdle {
+            WaitForIdle(OpHeader::with_args(OpCode::WaitForIdle, (pipe as u32) << 8))
+        }
+    }
+
+    #[derive(Debug, Copy, Clone)]
+    pub(crate) struct Timestamp(OpHeader);
+    impl Timestamp {
+        #[allow(dead_code)]
+        pub(crate) const fn new(flag: bool) -> Timestamp {
+            Timestamp(OpHeader::with_args(OpCode::Timestamp, (flag as u32) << 31))
+        }
+    }
+}
+
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct WaitForIdle {
+    pub(crate) header: op::WaitForIdle,
+}
+
+impl Operation for WaitForIdle {}
+
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct WaitForIdle2 {
+    pub(crate) header: op::WaitForIdle2,
+}
+
+impl Operation for WaitForIdle2 {}
+
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct RetireStamp {
+    pub(crate) header: op::RetireStamp,
+}
+
+impl Operation for RetireStamp {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct Timestamp<'a> {
+    pub(crate) header: op::Timestamp,
+    pub(crate) cur_ts: GpuWeakPointer<U64>,
+    pub(crate) start_ts: GpuWeakPointer<Option<GpuPointer<'a, AtomicU64>>>,
+    pub(crate) update_ts: GpuWeakPointer<Option<GpuPointer<'a, AtomicU64>>>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) unk_24: U64,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_ts: GpuWeakPointer<U64>,
+
+    pub(crate) uuid: u32,
+    pub(crate) unk_30_padding: u32,
+}
+
+#[versions(AGX)]
+impl<'a> Operation for Timestamp::ver<'a> {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct StartVertex<'a> {
+    pub(crate) header: op::StartVertex,
+    pub(crate) tiling_params: Option<GpuWeakPointer<vertex::raw::TilingParameters>>,
+    pub(crate) job_params1: Option<GpuWeakPointer<vertex::raw::JobParameters1::ver<'a>>>,
+    #[ver(G >= G14X)]
+    pub(crate) registers: GpuWeakPointer<job::raw::RegisterArray>,
+    pub(crate) buffer: GpuWeakPointer<buffer::Info::ver>,
+    pub(crate) scene: GpuWeakPointer<buffer::Scene::ver>,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsVtx>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) vm_slot: u32,
+    pub(crate) unk_38: u32,
+    pub(crate) event_generation: u32,
+    pub(crate) buffer_slot: u32,
+    pub(crate) unk_44: u32,
+    pub(crate) event_seq: U64,
+    pub(crate) unk_50: u32,
+    pub(crate) unk_pointer: GpuWeakPointer<u32>,
+    pub(crate) unk_job_buf: GpuWeakPointer<U64>,
+    pub(crate) unk_64: u32,
+    pub(crate) unk_68: u32,
+    pub(crate) uuid: u32,
+    pub(crate) attachments: Attachments,
+    pub(crate) padding: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) counter: U64,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) notifier_buf: GpuWeakPointer<Array<0x8, u8>>,
+
+    pub(crate) unk_178: u32,
+}
+
+#[versions(AGX)]
+impl<'a> Operation for StartVertex::ver<'a> {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct FinalizeVertex {
+    pub(crate) header: op::FinalizeVertex,
+    pub(crate) scene: GpuWeakPointer<buffer::Scene::ver>,
+    pub(crate) buffer: GpuWeakPointer<buffer::Info::ver>,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsVtx>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) vm_slot: u32,
+    pub(crate) unk_28: u32,
+    pub(crate) unk_pointer: GpuWeakPointer<u32>,
+    pub(crate) unk_34: u32,
+    pub(crate) uuid: u32,
+    pub(crate) fw_stamp: GpuWeakPointer<FwStamp>,
+    pub(crate) stamp_value: EventValue,
+    pub(crate) unk_48: U64,
+    pub(crate) unk_50: u32,
+    pub(crate) unk_54: u32,
+    pub(crate) unk_58: U64,
+    pub(crate) unk_60: u32,
+    pub(crate) unk_64: u32,
+    pub(crate) unk_68: u32,
+
+    #[ver(G >= G14 && V < V13_0B4)]
+    pub(crate) unk_68_g14: U64,
+
+    pub(crate) restart_branch_offset: i32,
+    pub(crate) has_attachments: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_74: Array<0x10, u8>,
+}
+
+#[versions(AGX)]
+impl Operation for FinalizeVertex::ver {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct StartFragment<'a> {
+    pub(crate) header: op::StartFragment,
+    pub(crate) job_params2: Option<GpuWeakPointer<fragment::raw::JobParameters2>>,
+    pub(crate) job_params1: Option<GpuWeakPointer<fragment::raw::JobParameters1::ver<'a>>>,
+    #[ver(G >= G14X)]
+    pub(crate) registers: GpuWeakPointer<job::raw::RegisterArray>,
+    pub(crate) scene: GpuPointer<'a, buffer::Scene::ver>,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag>,
+    pub(crate) busy_flag: GpuWeakPointer<u32>,
+    pub(crate) tvb_overflow_count: GpuWeakPointer<u32>,
+    pub(crate) unk_pointer: GpuWeakPointer<u32>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) work_item: GpuWeakPointer<fragment::RunFragment::ver>,
+    pub(crate) vm_slot: u32,
+    pub(crate) unk_50: u32,
+    pub(crate) event_generation: u32,
+    pub(crate) buffer_slot: u32,
+    pub(crate) sync_grow: u32,
+    pub(crate) event_seq: U64,
+    pub(crate) unk_68: u32,
+    pub(crate) unk_758_flag: GpuWeakPointer<u32>,
+    pub(crate) unk_job_buf: GpuWeakPointer<U64>,
+    #[ver(V >= V13_3)]
+    pub(crate) unk_7c_0: U64,
+    pub(crate) unk_7c: u32,
+    pub(crate) unk_80: u32,
+    pub(crate) unk_84: u32,
+    pub(crate) uuid: u32,
+    pub(crate) attachments: Attachments,
+    pub(crate) padding: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) counter: U64,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) notifier_buf: GpuWeakPointer<Array<0x8, u8>>,
+}
+
+#[versions(AGX)]
+impl<'a> Operation for StartFragment::ver<'a> {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct FinalizeFragment {
+    pub(crate) header: op::FinalizeFragment,
+    pub(crate) uuid: u32,
+    pub(crate) unk_8: u32,
+    pub(crate) fw_stamp: GpuWeakPointer<FwStamp>,
+    pub(crate) stamp_value: EventValue,
+    pub(crate) unk_18: u32,
+    pub(crate) scene: GpuWeakPointer<buffer::Scene::ver>,
+    pub(crate) buffer: GpuWeakPointer<buffer::Info::ver>,
+    pub(crate) unk_2c: U64,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag>,
+    pub(crate) unk_pointer: GpuWeakPointer<u32>,
+    pub(crate) busy_flag: GpuWeakPointer<u32>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) work_item: GpuWeakPointer<fragment::RunFragment::ver>,
+    pub(crate) vm_slot: u32,
+    pub(crate) unk_60: u32,
+    pub(crate) unk_758_flag: GpuWeakPointer<u32>,
+    #[ver(V >= V13_3)]
+    pub(crate) unk_6c_0: U64,
+    pub(crate) unk_6c: U64,
+    pub(crate) unk_74: U64,
+    pub(crate) unk_7c: U64,
+    pub(crate) unk_84: U64,
+    pub(crate) unk_8c: U64,
+
+    #[ver(G == G14 && V < V13_0B4)]
+    pub(crate) unk_8c_g14: U64,
+
+    pub(crate) restart_branch_offset: i32,
+    pub(crate) has_attachments: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_9c: Array<0x10, u8>,
+}
+
+#[versions(AGX)]
+impl Operation for FinalizeFragment::ver {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct StartCompute<'a> {
+    pub(crate) header: op::StartCompute,
+    pub(crate) unk_pointer: GpuWeakPointer<u32>,
+    pub(crate) job_params1: Option<GpuWeakPointer<compute::raw::JobParameters1<'a>>>,
+    #[ver(G >= G14X)]
+    pub(crate) registers: GpuWeakPointer<job::raw::RegisterArray>,
+    pub(crate) stats: GpuWeakPointer<initdata::GpuStatsComp>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) vm_slot: u32,
+    pub(crate) unk_28: u32,
+    pub(crate) event_generation: u32,
+    pub(crate) event_seq: U64,
+    pub(crate) unk_38: u32,
+    pub(crate) job_params2: GpuWeakPointer<compute::raw::JobParameters2::ver<'a>>,
+    pub(crate) unk_44: u32,
+    pub(crate) uuid: u32,
+    pub(crate) attachments: Attachments,
+    pub(crate) padding: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_flag: GpuWeakPointer<U32>,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) counter: U64,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) notifier_buf: GpuWeakPointer<Array<0x8, u8>>,
+}
+
+#[versions(AGX)]
+impl<'a> Operation for StartCompute::ver<'a> {}
+
+#[versions(AGX)]
+#[derive(Debug)]
+#[repr(C)]
+pub(crate) struct FinalizeCompute<'a> {
+    pub(crate) header: op::FinalizeCompute,
+    pub(crate) stats: GpuWeakPointer<initdata::GpuStatsComp>,
+    pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
+    pub(crate) vm_slot: u32,
+    #[ver(V < V13_0B4)]
+    pub(crate) unk_18: u32,
+    pub(crate) job_params2: GpuWeakPointer<compute::raw::JobParameters2::ver<'a>>,
+    pub(crate) unk_24: u32,
+    pub(crate) uuid: u32,
+    pub(crate) fw_stamp: GpuWeakPointer<FwStamp>,
+    pub(crate) stamp_value: EventValue,
+    pub(crate) unk_38: u32,
+    pub(crate) unk_3c: u32,
+    pub(crate) unk_40: u32,
+    pub(crate) unk_44: u32,
+    pub(crate) unk_48: u32,
+    pub(crate) unk_4c: u32,
+    pub(crate) unk_50: u32,
+    pub(crate) unk_54: u32,
+    pub(crate) unk_58: u32,
+
+    #[ver(G == G14 && V < V13_0B4)]
+    pub(crate) unk_5c_g14: U64,
+
+    pub(crate) restart_branch_offset: i32,
+    pub(crate) has_attachments: u32,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_64: Array<0xd, u8>,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_flag: GpuWeakPointer<U32>,
+
+    #[ver(V >= V13_0B4)]
+    pub(crate) unk_79: Array<0x7, u8>,
+}
+
+#[versions(AGX)]
+impl<'a> Operation for FinalizeCompute::ver<'a> {}
diff --git a/drivers/gpu/drm/asahi/fw/mod.rs b/drivers/gpu/drm/asahi/fw/mod.rs
new file mode 100644
index 00000000000000..a5649aa20d3a8e
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/mod.rs
@@ -0,0 +1,15 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Firmware structures for Apple AGX GPUs
+
+pub(crate) mod buffer;
+pub(crate) mod channels;
+pub(crate) mod compute;
+pub(crate) mod event;
+pub(crate) mod fragment;
+pub(crate) mod initdata;
+pub(crate) mod job;
+pub(crate) mod microseq;
+pub(crate) mod types;
+pub(crate) mod vertex;
+pub(crate) mod workqueue;
diff --git a/drivers/gpu/drm/asahi/fw/types.rs b/drivers/gpu/drm/asahi/fw/types.rs
new file mode 100644
index 00000000000000..fd6cdd29fc1a13
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/types.rs
@@ -0,0 +1,212 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Common types for firmware structure definitions
+
+use crate::{alloc, object};
+use core::fmt;
+use core::ops::{Deref, DerefMut, Index, IndexMut};
+
+pub(crate) use crate::event::EventValue;
+pub(crate) use crate::object::{GpuPointer, GpuStruct, GpuWeakPointer};
+pub(crate) use crate::{f32, float::F32};
+
+pub(crate) use ::alloc::boxed::Box;
+pub(crate) use core::fmt::Debug;
+pub(crate) use core::marker::PhantomData;
+pub(crate) use core::sync::atomic::{AtomicI32, AtomicU32, AtomicU64};
+pub(crate) use kernel::init::Zeroable;
+pub(crate) use kernel::macros::versions;
+
+// Make the trait visible
+pub(crate) use crate::alloc::Allocator as _Allocator;
+
+/// General allocator type used for the driver
+pub(crate) type Allocator = alloc::DefaultAllocator;
+
+/// General GpuObject type used for the driver
+pub(crate) type GpuObject<T> =
+    object::GpuObject<T, alloc::GenericAlloc<T, alloc::DefaultAllocation>>;
+
+/// General GpuArray type used for the driver
+pub(crate) type GpuArray<T> = object::GpuArray<T, alloc::GenericAlloc<T, alloc::DefaultAllocation>>;
+
+/// General GpuOnlyArray type used for the driver
+pub(crate) type GpuOnlyArray<T> =
+    object::GpuOnlyArray<T, alloc::GenericAlloc<T, alloc::DefaultAllocation>>;
+
+/// A stamp slot that is shared between firmware and the driver.
+#[derive(Debug, Default)]
+#[repr(transparent)]
+pub(crate) struct Stamp(pub(crate) AtomicU32);
+
+/// A stamp slot that is for private firmware use.
+///
+/// This is a separate type to guard against pointer type confusion.
+#[derive(Debug, Default)]
+#[repr(transparent)]
+pub(crate) struct FwStamp(pub(crate) AtomicU32);
+
+/// An unaligned u64 type.
+///
+/// This is useful to avoid having to pack firmware structures entirely, since that is incompatible
+/// with `#[derive(Debug)]` and atomics.
+#[derive(Copy, Clone, Default)]
+#[repr(C, packed(1))]
+pub(crate) struct U64(pub(crate) u64);
+
+unsafe impl Zeroable for U64 {}
+
+impl fmt::Debug for U64 {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let v = self.0;
+        f.write_fmt(format_args!("{:#x}", v))
+    }
+}
+
+/// An unaligned u32 type.
+///
+/// This is useful to avoid having to pack firmware structures entirely, since that is incompatible
+/// with `#[derive(Debug)]` and atomics.
+#[derive(Copy, Clone, Default)]
+#[repr(C, packed(1))]
+pub(crate) struct U32(pub(crate) u32);
+
+unsafe impl Zeroable for U32 {}
+
+impl fmt::Debug for U32 {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        let v = self.0;
+        f.write_fmt(format_args!("{:#x}", v))
+    }
+}
+
+/// Create a dummy `Debug` implementation, for when we need it but it's too painful to write by
+/// hand or not very useful.
+#[macro_export]
+macro_rules! no_debug {
+    ($type:ty) => {
+        impl ::core::fmt::Debug for $type {
+            fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
+                write!(f, "...")
+            }
+        }
+    };
+}
+
+/// Implement Zeroable for a given type (and Default along with it).
+///
+/// # Safety
+///
+/// This macro must only be used if a type only contains primitive types which can be
+/// zero-initialized, FFI structs intended to be zero-initialized, or other types which
+/// impl Zeroable.
+#[macro_export]
+macro_rules! default_zeroed {
+    (<$($lt:lifetime),*>, $type:ty) => {
+        impl<$($lt),*> Default for $type {
+            fn default() -> $type {
+                ::kernel::init::Zeroable::zeroed()
+            }
+        }
+        // SAFETY: The user is responsible for ensuring this is safe.
+        unsafe impl<$($lt),*> ::kernel::init::Zeroable for $type {}
+    };
+    ($type:ty) => {
+        impl Default for $type {
+            fn default() -> $type {
+                ::kernel::init::Zeroable::zeroed()
+            }
+        }
+        // SAFETY: The user is responsible for ensuring this is safe.
+        unsafe impl ::kernel::init::Zeroable for $type {}
+    };
+}
+
+/// A convenience type for a number of padding bytes. Hidden from Debug formatting.
+#[derive(Copy, Clone)]
+#[repr(C, packed)]
+pub(crate) struct Pad<const N: usize>([u8; N]);
+
+/// SAFETY: Primitive type, safe to zero-init.
+unsafe impl<const N: usize> Zeroable for Pad<N> {}
+
+impl<const N: usize> Default for Pad<N> {
+    fn default() -> Self {
+        Zeroable::zeroed()
+    }
+}
+
+impl<const N: usize> fmt::Debug for Pad<N> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        f.write_fmt(format_args!("<pad>"))
+    }
+}
+
+/// A convenience type for a fixed-sized array with Default/Zeroable impls.
+#[derive(Copy, Clone)]
+#[repr(C)]
+pub(crate) struct Array<const N: usize, T>([T; N]);
+
+impl<const N: usize, T> Array<N, T> {
+    pub(crate) fn new(data: [T; N]) -> Self {
+        Self(data)
+    }
+}
+
+// SAFETY: Arrays of Zeroable values can be safely Zeroable.
+unsafe impl<const N: usize, T: Zeroable> Zeroable for Array<N, T> {}
+
+impl<const N: usize, T: Zeroable> Default for Array<N, T> {
+    fn default() -> Self {
+        Zeroable::zeroed()
+    }
+}
+
+impl<const N: usize, T> Index<usize> for Array<N, T> {
+    type Output = T;
+
+    fn index(&self, index: usize) -> &Self::Output {
+        &self.0[index]
+    }
+}
+
+impl<const N: usize, T> IndexMut<usize> for Array<N, T> {
+    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
+        &mut self.0[index]
+    }
+}
+
+impl<const N: usize, T> Deref for Array<N, T> {
+    type Target = [T; N];
+
+    fn deref(&self) -> &Self::Target {
+        &self.0
+    }
+}
+
+impl<const N: usize, T> DerefMut for Array<N, T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.0
+    }
+}
+
+impl<const N: usize, T: Sized + fmt::Debug> fmt::Debug for Array<N, T> {
+    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+        self.0.fmt(f)
+    }
+}
+
+/// Convenience macro to define an identically-named trivial GpuStruct with no inner fields for a
+/// given raw type name.
+#[macro_export]
+macro_rules! trivial_gpustruct {
+    ($type:ident) => {
+        #[derive(Debug)]
+        pub(crate) struct $type {}
+
+        impl GpuStruct for $type {
+            type Raw<'a> = raw::$type;
+        }
+        $crate::default_zeroed!($type);
+    };
+}
diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
new file mode 100644
index 00000000000000..2a94f7e5ffdd89
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU vertex job firmware structures
+
+use super::types::*;
+use super::{event, job, workqueue};
+use crate::{buffer, fw, microseq, mmu};
+use kernel::sync::Arc;
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug, Default, Copy, Clone)]
+    #[repr(C)]
+    pub(crate) struct TilingParameters {
+        pub(crate) rgn_size: u32,
+        pub(crate) unk_4: u32,
+        pub(crate) ppp_ctrl: u32,
+        pub(crate) x_max: u16,
+        pub(crate) y_max: u16,
+        pub(crate) te_screen: u32,
+        pub(crate) te_mtile1: u32,
+        pub(crate) te_mtile2: u32,
+        pub(crate) tiles_per_mtile: u32,
+        pub(crate) tpc_stride: u32,
+        pub(crate) unk_24: u32,
+        pub(crate) unk_28: u32,
+        pub(crate) __pad: Pad<0x74>,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters1<'a> {
+        pub(crate) unk_0: U64,
+        pub(crate) unk_8: F32,
+        pub(crate) unk_c: F32,
+        pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>,
+        #[ver(G < G14)]
+        pub(crate) tvb_cluster_tilemaps: Option<GpuPointer<'a, &'a [u8]>>,
+        pub(crate) tpc: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>,
+        pub(crate) iogpu_unk_54: U64,
+        pub(crate) iogpu_unk_56: U64,
+        #[ver(G < G14)]
+        pub(crate) tvb_cluster_meta1: Option<GpuPointer<'a, &'a [u8]>>,
+        pub(crate) utile_config: u32,
+        pub(crate) unk_4c: u32,
+        pub(crate) ppp_multisamplectl: U64,
+        pub(crate) tvb_heapmeta_2: GpuPointer<'a, &'a [u8]>,
+        #[ver(G < G14)]
+        pub(crate) unk_60: U64,
+        #[ver(G < G14)]
+        pub(crate) core_mask: Array<2, u32>,
+        pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>,
+        pub(crate) preempt_buf2: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unk_80: U64,
+        pub(crate) preempt_buf3: GpuPointer<'a, &'a [u8]>,
+        pub(crate) encoder_addr: U64,
+        #[ver(G < G14)]
+        pub(crate) tvb_cluster_meta2: Option<GpuPointer<'a, &'a [u8]>>,
+        #[ver(G < G14)]
+        pub(crate) tvb_cluster_meta3: Option<GpuPointer<'a, &'a [u8]>>,
+        #[ver(G < G14)]
+        pub(crate) tiling_control: u32,
+        #[ver(G < G14)]
+        pub(crate) unk_ac: u32,
+        pub(crate) unk_b0: Array<6, U64>,
+        pub(crate) pipeline_base: U64,
+        #[ver(G < G14)]
+        pub(crate) tvb_cluster_meta4: Option<GpuPointer<'a, &'a [u8]>>,
+        #[ver(G < G14)]
+        pub(crate) unk_f0: U64,
+        pub(crate) unk_f8: U64,
+        pub(crate) unk_100: Array<3, U64>,
+        pub(crate) unk_118: u32,
+        #[ver(G >= G14)]
+        pub(crate) __pad: Pad<{ 8 * 9 + 0x268 }>,
+        #[ver(G < G14)]
+        pub(crate) __pad: Pad<0x268>,
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct JobParameters2<'a> {
+        pub(crate) unk_480: Array<4, u32>,
+        pub(crate) unk_498: U64,
+        pub(crate) unk_4a0: u32,
+        pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>,
+        pub(crate) unk_4ac: u32,
+        pub(crate) unk_4b0: U64,
+        pub(crate) unk_4b8: u32,
+        pub(crate) unk_4bc: U64,
+        pub(crate) unk_4c4_padding: Array<0x48, u8>,
+        pub(crate) unk_50c: u32,
+        pub(crate) unk_510: U64,
+        pub(crate) unk_518: U64,
+        pub(crate) unk_520: U64,
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RunVertex<'a> {
+        pub(crate) tag: workqueue::CommandType,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) counter: U64,
+
+        pub(crate) vm_slot: u32,
+        pub(crate) unk_8: u32,
+        pub(crate) notifier: GpuPointer<'a, event::Notifier::ver>,
+        pub(crate) buffer_slot: u32,
+        pub(crate) unk_1c: u32,
+        pub(crate) buffer: GpuPointer<'a, fw::buffer::Info::ver>,
+        pub(crate) scene: GpuPointer<'a, fw::buffer::Scene::ver>,
+        pub(crate) unk_buffer_buf: GpuWeakPointer<[u8]>,
+        pub(crate) unk_34: u32,
+
+        #[ver(G < G14X)]
+        pub(crate) job_params1: JobParameters1::ver<'a>,
+        #[ver(G < G14X)]
+        pub(crate) tiling_params: TilingParameters,
+        #[ver(G >= G14X)]
+        pub(crate) registers: job::raw::RegisterArray,
+
+        pub(crate) tpc: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tpc_size: U64,
+        pub(crate) microsequence: GpuPointer<'a, &'a [u8]>,
+        pub(crate) microsequence_size: u32,
+        pub(crate) fragment_stamp_slot: u32,
+        pub(crate) fragment_stamp_value: EventValue,
+        pub(crate) unk_pointee: u32,
+        pub(crate) unk_pad: u32,
+        pub(crate) job_params2: JobParameters2<'a>,
+        pub(crate) encoder_params: job::raw::EncoderParams,
+        pub(crate) unk_55c: u32,
+        pub(crate) unk_560: u32,
+        pub(crate) sync_grow: u32,
+        pub(crate) unk_568: u32,
+        pub(crate) unk_56c: u32,
+        pub(crate) meta: job::raw::JobMeta,
+        pub(crate) unk_after_meta: u32,
+        pub(crate) unk_buf_0: U64,
+        pub(crate) unk_buf_8: U64,
+        pub(crate) unk_buf_10: U64,
+        pub(crate) cur_ts: U64,
+        pub(crate) start_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) end_ts: Option<GpuPointer<'a, AtomicU64>>,
+        pub(crate) unk_5c4: u32,
+        pub(crate) unk_5c8: u32,
+        pub(crate) unk_5cc: u32,
+        pub(crate) unk_5d0: u32,
+        pub(crate) client_sequence: u8,
+        pub(crate) pad_5d5: Array<3, u8>,
+        pub(crate) unk_5d8: u32,
+        pub(crate) unk_5dc: u8,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_ts: U64,
+
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_5dd_8: Array<0x1b, u8>,
+    }
+}
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct RunVertex {
+    pub(crate) notifier: Arc<GpuObject<event::Notifier::ver>>,
+    pub(crate) scene: Arc<buffer::Scene::ver>,
+    pub(crate) micro_seq: microseq::MicroSequence,
+    pub(crate) vm_bind: mmu::VmBind,
+    pub(crate) timestamps: Arc<GpuObject<job::RenderTimestamps>>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for RunVertex::ver {
+    type Raw<'a> = raw::RunVertex::ver<'a>;
+}
+
+#[versions(AGX)]
+impl workqueue::Command for RunVertex::ver {}
diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs
new file mode 100644
index 00000000000000..8ad8bb1b0eee22
--- /dev/null
+++ b/drivers/gpu/drm/asahi/fw/workqueue.rs
@@ -0,0 +1,176 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU work queue firmware structes
+
+use super::event;
+use super::types::*;
+use crate::event::EventValue;
+use crate::{default_zeroed, trivial_gpustruct};
+use kernel::sync::Arc;
+
+#[derive(Debug)]
+#[repr(u32)]
+pub(crate) enum CommandType {
+    RunVertex = 0,
+    RunFragment = 1,
+    #[allow(dead_code)]
+    RunBlitter = 2,
+    RunCompute = 3,
+    Barrier = 4,
+    InitBuffer = 6,
+}
+
+pub(crate) trait Command: GpuStruct + Send + Sync {}
+
+pub(crate) mod raw {
+    use super::*;
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct Barrier {
+        pub(crate) tag: CommandType,
+        pub(crate) wait_stamp: GpuWeakPointer<FwStamp>,
+        pub(crate) wait_value: EventValue,
+        pub(crate) wait_slot: u32,
+        pub(crate) stamp_self: EventValue,
+        pub(crate) uuid: u32,
+        pub(crate) barrier_type: u32,
+        // G14X addition
+        pub(crate) padding: Pad<0x20>,
+    }
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct GpuContextData {
+        pub(crate) unk_0: u8,
+        pub(crate) unk_1: u8,
+        unk_2: Array<0x2, u8>,
+        pub(crate) unk_4: u8,
+        pub(crate) unk_5: u8,
+        unk_6: Array<0x18, u8>,
+        pub(crate) unk_1e: u8,
+        pub(crate) unk_1f: u8,
+        unk_20: Array<0x3, u8>,
+        pub(crate) unk_23: u8,
+        unk_24: Array<0x1c, u8>,
+    }
+
+    impl Default for GpuContextData {
+        fn default() -> Self {
+            Self {
+                unk_0: 0xff,
+                unk_1: 0xff,
+                unk_2: Default::default(),
+                unk_4: 0,
+                unk_5: 1,
+                unk_6: Default::default(),
+                unk_1e: 0xff,
+                unk_1f: 0,
+                unk_20: Default::default(),
+                unk_23: 2,
+                unk_24: Default::default(),
+            }
+        }
+    }
+
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct RingState {
+        pub(crate) gpu_doneptr: AtomicU32,
+        __pad0: Pad<0xc>,
+        pub(crate) unk_10: AtomicU32,
+        __pad1: Pad<0xc>,
+        pub(crate) unk_20: AtomicU32,
+        __pad2: Pad<0xc>,
+        pub(crate) gpu_rptr: AtomicU32,
+        __pad3: Pad<0xc>,
+        pub(crate) cpu_wptr: AtomicU32,
+        __pad4: Pad<0xc>,
+        pub(crate) rb_size: u32,
+        __pad5: Pad<0xc>,
+        // This isn't part of the structure, but it's here as a
+        // debugging hack so we can inspect what ring position
+        // the driver considered complete and freeable.
+        pub(crate) cpu_freeptr: AtomicU32,
+        __pad6: Pad<0xc>,
+    }
+    default_zeroed!(RingState);
+
+    #[derive(Debug, Clone, Copy)]
+    #[repr(C)]
+    pub(crate) struct Priority(u32, u32, U64, u32, u32, u32);
+
+    pub(crate) const PRIORITY: [Priority; 4] = [
+        Priority(0, 0, U64(0xffff_ffff_ffff_0000), 1, 0, 1),
+        Priority(1, 1, U64(0xffff_ffff_0000_0000), 0, 0, 0),
+        Priority(2, 2, U64(0xffff_0000_0000_0000), 0, 0, 2),
+        Priority(3, 3, U64(0x0000_0000_0000_0000), 0, 0, 3),
+    ];
+
+    impl Default for Priority {
+        fn default() -> Priority {
+            PRIORITY[2]
+        }
+    }
+
+    #[versions(AGX)]
+    #[derive(Debug)]
+    #[repr(C)]
+    pub(crate) struct QueueInfo<'a> {
+        pub(crate) state: GpuPointer<'a, super::RingState>,
+        pub(crate) ring: GpuPointer<'a, &'a [u64]>,
+        pub(crate) notifier_list: GpuPointer<'a, event::NotifierList>,
+        pub(crate) gpu_buf: GpuPointer<'a, &'a [u8]>,
+        pub(crate) gpu_rptr1: AtomicU32,
+        pub(crate) gpu_rptr2: AtomicU32,
+        pub(crate) gpu_rptr3: AtomicU32,
+        pub(crate) event_id: AtomicI32,
+        pub(crate) priority: Priority,
+        pub(crate) unk_4c: i32,
+        pub(crate) uuid: u32,
+        pub(crate) unk_54: i32,
+        pub(crate) unk_58: U64,
+        pub(crate) busy: AtomicU32,
+        pub(crate) __pad: Pad<0x20>,
+        pub(crate) unk_84_state: AtomicU32,
+        pub(crate) unk_88: u32,
+        pub(crate) unk_8c: u32,
+        pub(crate) unk_90: u32,
+        pub(crate) unk_94: u32,
+        pub(crate) pending: AtomicU32,
+        pub(crate) unk_9c: u32,
+        #[ver(V >= V13_2 && G < G14X)]
+        pub(crate) unk_a0_0: u32,
+        pub(crate) gpu_context: GpuPointer<'a, super::GpuContextData>,
+        pub(crate) unk_a8: U64,
+        #[ver(V >= V13_2 && G < G14X)]
+        pub(crate) unk_b0: u32,
+    }
+}
+
+trivial_gpustruct!(Barrier);
+trivial_gpustruct!(RingState);
+
+impl Command for Barrier {}
+
+pub(crate) struct GpuContextData {
+    pub(crate) _buffer: Option<Arc<dyn core::any::Any + Send + Sync>>,
+}
+impl GpuStruct for GpuContextData {
+    type Raw<'a> = raw::GpuContextData;
+}
+
+#[versions(AGX)]
+#[derive(Debug)]
+pub(crate) struct QueueInfo {
+    pub(crate) state: GpuObject<RingState>,
+    pub(crate) ring: GpuArray<u64>,
+    pub(crate) gpu_buf: GpuArray<u8>,
+    pub(crate) notifier_list: Arc<GpuObject<event::NotifierList>>,
+    pub(crate) gpu_context: Arc<crate::workqueue::GpuContext>,
+}
+
+#[versions(AGX)]
+impl GpuStruct for QueueInfo::ver {
+    type Raw<'a> = raw::QueueInfo::ver<'a>;
+}
diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs
new file mode 100644
index 00000000000000..b7b6640940e04b
--- /dev/null
+++ b/drivers/gpu/drm/asahi/gem.rs
@@ -0,0 +1,274 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Asahi driver GEM object implementation
+//!
+//! Basic wrappers and adaptations between generic GEM shmem objects and this driver's
+//! view of what a GPU buffer object is. It is in charge of keeping track of all mappings for
+//! each GEM object so we can remove them when a client (File) or a Vm are destroyed, as well as
+//! implementing RTKit buffers on top of GEM objects for firmware use.
+
+use kernel::{
+    drm::{gem, gem::shmem},
+    error::Result,
+    prelude::*,
+    soc::apple::rtkit,
+    sync::Mutex,
+    uapi,
+};
+
+use kernel::drm::gem::BaseObject;
+
+use core::sync::atomic::{AtomicU64, Ordering};
+
+use crate::{debug::*, driver::AsahiDevice, file::DrmFile, mmu, util::*};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Gem;
+
+/// Represents the inner data of a GEM object for this driver.
+#[pin_data]
+pub(crate) struct DriverObject {
+    /// Whether this is a kernel-created object.
+    kernel: bool,
+    /// Object creation flags.
+    flags: u32,
+    /// VM ID for VM-private objects.
+    vm_id: Option<u64>,
+    /// Locked list of mapping tuples: (file_id, vm_id, mapping)
+    #[pin]
+    mappings: Mutex<Vec<(u64, u64, crate::mmu::Mapping)>>,
+    /// ID for debug
+    id: u64,
+}
+
+/// Type alias for the shmem GEM object type for this driver.
+pub(crate) type Object = shmem::Object<DriverObject>;
+
+/// Type alias for the SGTable type for this driver.
+pub(crate) type SGTable = shmem::SGTable<DriverObject>;
+
+/// A shared reference to a GEM object for this driver.
+pub(crate) struct ObjectRef {
+    /// The underlying GEM object reference
+    pub(crate) gem: gem::ObjectRef<shmem::Object<DriverObject>>,
+    /// The kernel-side VMap of this object, if needed
+    vmap: Option<shmem::VMap<DriverObject>>,
+}
+
+crate::no_debug!(ObjectRef);
+
+static GEM_ID: AtomicU64 = AtomicU64::new(0);
+
+impl DriverObject {
+    /// Drop all object mappings for a given file ID.
+    ///
+    /// Used on file close.
+    fn drop_file_mappings(&self, file_id: u64) {
+        let mut mappings = self.mappings.lock();
+        for (index, (mapped_fid, _mapped_vmid, _mapping)) in mappings.iter().enumerate() {
+            if *mapped_fid == file_id {
+                mappings.swap_remove(index);
+                return;
+            }
+        }
+    }
+
+    /// Drop all object mappings for a given VM ID.
+    ///
+    /// Used on VM destroy.
+    fn drop_vm_mappings(&self, vm_id: u64) {
+        let mut mappings = self.mappings.lock();
+        for (index, (_mapped_fid, mapped_vmid, _mapping)) in mappings.iter().enumerate() {
+            if *mapped_vmid == vm_id {
+                mappings.swap_remove(index);
+                return;
+            }
+        }
+    }
+}
+
+impl ObjectRef {
+    /// Create a new wrapper for a raw GEM object reference.
+    pub(crate) fn new(gem: gem::ObjectRef<shmem::Object<DriverObject>>) -> ObjectRef {
+        ObjectRef { gem, vmap: None }
+    }
+
+    /// Return the `VMap` for this object, creating it if necessary.
+    pub(crate) fn vmap(&mut self) -> Result<&mut shmem::VMap<DriverObject>> {
+        if self.vmap.is_none() {
+            self.vmap = Some(self.gem.vmap()?);
+        }
+        Ok(self.vmap.as_mut().unwrap())
+    }
+
+    /// Return the IOVA of this object at which it is mapped in a given `Vm` identified by its ID,
+    /// if it is mapped in that `Vm`.
+    pub(crate) fn iova(&self, vm_id: u64) -> Option<usize> {
+        let mappings = self.gem.mappings.lock();
+        for (_mapped_fid, mapped_vmid, mapping) in mappings.iter() {
+            if *mapped_vmid == vm_id {
+                return Some(mapping.iova());
+            }
+        }
+
+        None
+    }
+
+    /// Returns the size of an object in bytes
+    pub(crate) fn size(&self) -> usize {
+        self.gem.size()
+    }
+
+    /// Maps an object into a given `Vm` at any free address within a given range.
+    ///
+    /// Returns Err(EBUSY) if there is already a mapping.
+    pub(crate) fn map_into_range(
+        &mut self,
+        vm: &crate::mmu::Vm,
+        start: u64,
+        end: u64,
+        alignment: u64,
+        prot: u32,
+        guard: bool,
+    ) -> Result<usize> {
+        let vm_id = vm.id();
+
+        if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
+            return Err(EINVAL);
+        }
+
+        let mut mappings = self.gem.mappings.lock();
+        for (_mapped_fid, mapped_vmid, _mapping) in mappings.iter() {
+            if *mapped_vmid == vm_id {
+                return Err(EBUSY);
+            }
+        }
+
+        let sgt = self.gem.sg_table()?;
+        let new_mapping =
+            vm.map_in_range(self.gem.size(), sgt, alignment, start, end, prot, guard)?;
+
+        let iova = new_mapping.iova();
+        mappings.try_push((vm.file_id(), vm_id, new_mapping))?;
+        Ok(iova)
+    }
+
+    /// Maps an object into a given `Vm` at a specific address.
+    ///
+    /// Returns Err(EBUSY) if there is already a mapping.
+    /// Returns Err(ENOSPC) if the requested address is already busy.
+    pub(crate) fn map_at(
+        &mut self,
+        vm: &crate::mmu::Vm,
+        addr: u64,
+        prot: u32,
+        guard: bool,
+    ) -> Result {
+        let vm_id = vm.id();
+
+        if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
+            return Err(EINVAL);
+        }
+
+        let mut mappings = self.gem.mappings.lock();
+        for (_mapped_fid, mapped_vmid, _mapping) in mappings.iter() {
+            if *mapped_vmid == vm_id {
+                return Err(EBUSY);
+            }
+        }
+
+        let sgt = self.gem.sg_table()?;
+        let new_mapping = vm.map_at(addr, self.gem.size(), sgt, prot, guard)?;
+
+        let iova = new_mapping.iova();
+        assert!(iova == addr as usize);
+        mappings.try_push((vm.file_id(), vm_id, new_mapping))?;
+        Ok(())
+    }
+
+    /// Drop all mappings for this object owned by a given `Vm` identified by its ID.
+    pub(crate) fn drop_vm_mappings(&mut self, vm_id: u64) {
+        self.gem.drop_vm_mappings(vm_id);
+    }
+
+    /// Drop all mappings for this object owned by a given `File` identified by its ID.
+    pub(crate) fn drop_file_mappings(&mut self, file_id: u64) {
+        self.gem.drop_file_mappings(file_id);
+    }
+}
+
+/// Create a new kernel-owned GEM object.
+pub(crate) fn new_kernel_object(dev: &AsahiDevice, size: usize) -> Result<ObjectRef> {
+    let mut gem = shmem::Object::<DriverObject>::new(dev, align(size, mmu::UAT_PGSZ))?;
+    gem.kernel = true;
+    gem.flags = 0;
+
+    gem.set_exportable(false);
+
+    mod_pr_debug!("DriverObject new kernel object id={}\n", gem.id);
+    Ok(ObjectRef::new(gem.into_ref()))
+}
+
+/// Create a new user-owned GEM object with the given flags.
+pub(crate) fn new_object(
+    dev: &AsahiDevice,
+    size: usize,
+    flags: u32,
+    vm_id: Option<u64>,
+) -> Result<ObjectRef> {
+    let mut gem = shmem::Object::<DriverObject>::new(dev, align(size, mmu::UAT_PGSZ))?;
+    gem.kernel = false;
+    gem.flags = flags;
+    gem.vm_id = vm_id;
+
+    gem.set_exportable(vm_id.is_none());
+    gem.set_wc(flags & uapi::ASAHI_GEM_WRITEBACK == 0);
+
+    mod_pr_debug!(
+        "DriverObject new user object: vm_id={:?} id={}\n",
+        vm_id,
+        gem.id
+    );
+    Ok(ObjectRef::new(gem.into_ref()))
+}
+
+/// Look up a GEM object handle for a `File` and return an `ObjectRef` for it.
+pub(crate) fn lookup_handle(file: &DrmFile, handle: u32) -> Result<ObjectRef> {
+    Ok(ObjectRef::new(shmem::Object::lookup_handle(file, handle)?))
+}
+
+impl gem::BaseDriverObject<Object> for DriverObject {
+    type Initializer = impl PinInit<Self, Error>;
+
+    /// Callback to create the inner data of a GEM object
+    fn new(_dev: &AsahiDevice, _size: usize) -> Self::Initializer {
+        let id = GEM_ID.fetch_add(1, Ordering::Relaxed);
+        mod_pr_debug!("DriverObject::new id={}\n", id);
+        try_pin_init!(DriverObject {
+            kernel: false,
+            flags: 0,
+            vm_id: None,
+            mappings <- Mutex::new(Vec::new()),
+            id,
+        })
+    }
+
+    /// Callback to drop all mappings for a GEM object owned by a given `File`
+    fn close(obj: &Object, file: &DrmFile) {
+        mod_pr_debug!("DriverObject::close vm_id={:?} id={}\n", obj.vm_id, obj.id);
+        obj.drop_file_mappings(file.inner().file_id());
+    }
+}
+
+impl shmem::DriverObject for DriverObject {
+    type Driver = crate::driver::AsahiDriver;
+}
+
+impl rtkit::Buffer for ObjectRef {
+    fn iova(&self) -> Result<usize> {
+        self.iova(0).ok_or(EIO)
+    }
+    fn buf(&mut self) -> Result<&mut [u8]> {
+        let vmap = self.vmap.as_mut().ok_or(ENOMEM)?;
+        Ok(vmap.as_mut_slice())
+    }
+}
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
new file mode 100644
index 00000000000000..b39321c4c26fda
--- /dev/null
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -0,0 +1,1385 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Top-level GPU manager
+//!
+//! This module is the root of all GPU firmware management for a given driver instance. It is
+//! responsible for initialization, owning the top-level managers (events, UAT, etc.), and
+//! communicating with the raw RtKit endpoints to send and receive messages to/from the GPU
+//! firmware.
+//!
+//! It is also the point where diverging driver firmware/GPU variants (using the versions macro)
+//! are unified, so that the top level of the driver itself (in `driver`) does not have to concern
+//! itself with version dependence.
+
+use core::any::Any;
+use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
+use core::time::Duration;
+
+use kernel::{
+    c_str,
+    delay::coarse_sleep,
+    error::code::*,
+    macros::versions,
+    prelude::*,
+    soc::apple::rtkit,
+    sync::{
+        lock::{mutex::MutexBackend, Guard},
+        Arc, Mutex, UniqueArc,
+    },
+    time::{clock, Now},
+    types::ForeignOwnable,
+};
+
+use crate::alloc::Allocator;
+use crate::debug::*;
+use crate::driver::{AsahiDevRef, AsahiDevice};
+use crate::fw::channels::PipeType;
+use crate::fw::types::{U32, U64};
+use crate::{
+    alloc, buffer, channel, event, fw, gem, hw, initdata, mem, mmu, queue, regs, workqueue,
+};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Gpu;
+
+/// Firmware endpoint for init & incoming notifications.
+const EP_FIRMWARE: u8 = 0x20;
+
+/// Doorbell endpoint for work/message submissions.
+const EP_DOORBELL: u8 = 0x21;
+
+/// Initialize the GPU firmware.
+const MSG_INIT: u64 = 0x81 << 48;
+const INIT_DATA_MASK: u64 = (1 << 44) - 1;
+
+/// TX channel doorbell.
+const MSG_TX_DOORBELL: u64 = 0x83 << 48;
+/// Firmware control channel doorbell.
+const MSG_FWCTL: u64 = 0x84 << 48;
+// /// Halt the firmware (?).
+// const MSG_HALT: u64 = 0x85 << 48;
+
+/// Receive channel doorbell notification.
+const MSG_RX_DOORBELL: u64 = 0x42 << 48;
+
+/// Doorbell number for firmware kicks/wakeups.
+const DOORBELL_KICKFW: u64 = 0x10;
+/// Doorbell number for device control channel kicks.
+const DOORBELL_DEVCTRL: u64 = 0x11;
+
+// Upper kernel half VA address ranges.
+/// Private (cached) firmware structure VA range base.
+const IOVA_KERN_PRIV_BASE: u64 = 0xffffffa000000000;
+/// Private (cached) firmware structure VA range top.
+const IOVA_KERN_PRIV_TOP: u64 = 0xffffffa5ffffffff;
+/// Private (cached) GPU-RO firmware structure VA range base.
+const IOVA_KERN_GPU_RO_BASE: u64 = 0xffffffa600000000;
+/// Private (cached) GPU-RO firmware structure VA range top.
+const IOVA_KERN_GPU_RO_TOP: u64 = 0xffffffa7ffffffff;
+/// Shared (uncached) firmware structure VA range base.
+const IOVA_KERN_SHARED_BASE: u64 = 0xffffffa800000000;
+/// Shared (uncached) firmware structure VA range top.
+const IOVA_KERN_SHARED_TOP: u64 = 0xffffffa9ffffffff;
+/// Shared (uncached) read-only firmware structure VA range base.
+const IOVA_KERN_SHARED_RO_BASE: u64 = 0xffffffaa00000000;
+/// Shared (uncached) read-only firmware structure VA range top.
+const IOVA_KERN_SHARED_RO_TOP: u64 = 0xffffffabffffffff;
+/// GPU/FW shared structure VA range base.
+const IOVA_KERN_GPU_BASE: u64 = 0xffffffac00000000;
+/// GPU/FW shared structure VA range top.
+const IOVA_KERN_GPU_TOP: u64 = 0xffffffadffffffff;
+/// GPU/FW shared structure VA range base.
+const IOVA_KERN_RTKIT_BASE: u64 = 0xffffffae00000000;
+/// GPU/FW shared structure VA range top.
+const IOVA_KERN_RTKIT_TOP: u64 = 0xffffffae0fffffff;
+
+/// GPU/FW buffer manager control address (context 0 low)
+pub(crate) const IOVA_KERN_GPU_BUFMGR_LOW: u64 = 0x20_0000_0000;
+/// GPU/FW buffer manager control address (context 0 high)
+pub(crate) const IOVA_KERN_GPU_BUFMGR_HIGH: u64 = 0xffffffaeffff0000;
+
+/// Timeout for entering the halt state after a fault or request.
+const HALT_ENTER_TIMEOUT: Duration = Duration::from_millis(100);
+
+/// Maximum amount of firmware-private memory garbage allowed before collection.
+/// Collection flushes the FW cache and is expensive, so this needs to be
+/// reasonably high.
+const MAX_FW_ALLOC_GARBAGE: usize = 16 * 1024 * 1024;
+
+/// Global allocators used for kernel-half structures.
+pub(crate) struct KernelAllocators {
+    pub(crate) private: alloc::DefaultAllocator,
+    pub(crate) shared: alloc::DefaultAllocator,
+    pub(crate) shared_ro: alloc::DefaultAllocator,
+    #[allow(dead_code)]
+    pub(crate) gpu: alloc::DefaultAllocator,
+    pub(crate) gpu_ro: alloc::DefaultAllocator,
+}
+
+/// Receive (GPU->driver) ring buffer channels.
+#[versions(AGX)]
+#[pin_data]
+struct RxChannels {
+    event: channel::EventChannel::ver,
+    fw_log: channel::FwLogChannel,
+    ktrace: channel::KTraceChannel,
+    stats: channel::StatsChannel::ver,
+}
+
+/// GPU work submission pipe channels (driver->GPU).
+#[versions(AGX)]
+struct PipeChannels {
+    pub(crate) vtx: Vec<Pin<Box<Mutex<channel::PipeChannel::ver>>>>,
+    pub(crate) frag: Vec<Pin<Box<Mutex<channel::PipeChannel::ver>>>>,
+    pub(crate) comp: Vec<Pin<Box<Mutex<channel::PipeChannel::ver>>>>,
+}
+
+/// Misc command transmit (driver->GPU) channels.
+#[versions(AGX)]
+#[pin_data]
+struct TxChannels {
+    pub(crate) device_control: channel::DeviceControlChannel::ver,
+}
+
+/// Number of work submission pipes per type, one for each priority level.
+const NUM_PIPES: usize = 4;
+
+/// A generic monotonically incrementing ID used to uniquely identify object instances within the
+/// driver.
+pub(crate) struct ID(AtomicU64);
+
+impl ID {
+    /// Create a new ID counter with a given value.
+    fn new(val: u64) -> ID {
+        ID(AtomicU64::new(val))
+    }
+
+    /// Fetch the next unique ID.
+    pub(crate) fn next(&self) -> u64 {
+        self.0.fetch_add(1, Ordering::Relaxed)
+    }
+}
+
+impl Default for ID {
+    /// IDs default to starting at 2, as 0/1 are considered reserved for the system.
+    fn default() -> Self {
+        Self::new(2)
+    }
+}
+
+/// A guard representing one active submission on the GPU. When dropped, decrements the active
+/// submission count.
+pub(crate) struct OpGuard(Arc<dyn GpuManagerPriv>);
+
+impl Drop for OpGuard {
+    fn drop(&mut self) {
+        self.0.end_op();
+    }
+}
+
+/// Set of global sequence IDs used in the driver.
+#[derive(Default)]
+pub(crate) struct SequenceIDs {
+    /// `File` instance ID.
+    pub(crate) file: ID,
+    /// `Vm` instance ID.
+    pub(crate) vm: ID,
+    /// Submission instance ID.
+    pub(crate) submission: ID,
+    /// `Queue` instance ID.
+    pub(crate) queue: ID,
+}
+
+/// Top-level GPU manager that owns all the global state relevant to the driver instance.
+#[versions(AGX)]
+#[pin_data]
+pub(crate) struct GpuManager {
+    dev: AsahiDevRef,
+    cfg: &'static hw::HwConfig,
+    dyncfg: hw::DynConfig,
+    pub(crate) initdata: fw::types::GpuObject<fw::initdata::InitData::ver>,
+    uat: mmu::Uat,
+    crashed: AtomicBool,
+    #[pin]
+    alloc: Mutex<KernelAllocators>,
+    io_mappings: Vec<mmu::Mapping>,
+    #[pin]
+    rtkit: Mutex<Option<rtkit::RtKit<GpuManager::ver>>>,
+    #[pin]
+    rx_channels: Mutex<RxChannels::ver>,
+    #[pin]
+    tx_channels: Mutex<TxChannels::ver>,
+    #[pin]
+    fwctl_channel: Mutex<channel::FwCtlChannel>,
+    pipes: PipeChannels::ver,
+    event_manager: Arc<event::EventManager>,
+    buffer_mgr: buffer::BufferManager::ver,
+    ids: SequenceIDs,
+    #[pin]
+    garbage_work: Mutex<Vec<Box<dyn workqueue::GenSubmittedWork>>>,
+    #[allow(clippy::vec_box)]
+    #[pin]
+    garbage_contexts: Mutex<Vec<Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>>>,
+}
+
+/// Trait used to abstract the firmware/GPU-dependent variants of the GpuManager.
+pub(crate) trait GpuManager: Send + Sync {
+    /// Cast as an Any type.
+    fn as_any(&self) -> &dyn Any;
+    /// Cast Arc<Self> as an Any type.
+    fn arc_as_any(self: Arc<Self>) -> Arc<dyn Any + Sync + Send>;
+    /// Initialize the GPU.
+    fn init(&self) -> Result;
+    /// Update the GPU globals from global info
+    ///
+    /// TODO: Unclear what can and cannot be updated like this.
+    fn update_globals(&self);
+    /// Get a reference to the KernelAllocators.
+    fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend>;
+    /// Create a new `Vm` given a unique `File` ID.
+    fn new_vm(&self, file_id: u64) -> Result<mmu::Vm>;
+    /// Bind a `Vm` to an available slot and return the `VmBind`.
+    fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind>;
+    /// Create a new user command queue.
+    fn new_queue(
+        &self,
+        vm: mmu::Vm,
+        ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+        ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+        priority: u32,
+        caps: u32,
+    ) -> Result<Box<dyn queue::Queue>>;
+    /// Return a reference to the global `SequenceIDs` instance.
+    fn ids(&self) -> &SequenceIDs;
+    /// Kick the firmware (wake it up if asleep).
+    ///
+    /// This should be useful to reduce latency on work submission, so we can ask the firmware to
+    /// wake up while we do some preparatory work for the work submission.
+    fn kick_firmware(&self) -> Result;
+    /// Flush the entire firmware cache.
+    ///
+    /// TODO: Does this actually work?
+    fn flush_fw_cache(&self) -> Result;
+    /// Handle a GPU work timeout event.
+    fn handle_timeout(&self, counter: u32, event_slot: i32);
+    /// Handle a GPU fault event.
+    fn handle_fault(&self);
+    /// Acknowledge a Buffer grow op.
+    fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32);
+    /// Wait for the GPU to become idle and power off.
+    fn wait_for_poweroff(&self, timeout: usize) -> Result;
+    /// Send a firmware control command (secure cache flush).
+    fn fwctl(&self, msg: fw::channels::FwCtlMsg) -> Result;
+    /// Get the static GPU configuration for this SoC.
+    fn get_cfg(&self) -> &'static hw::HwConfig;
+    /// Get the dynamic GPU configuration for this SoC.
+    fn get_dyncfg(&self) -> &hw::DynConfig;
+    /// Register completed work as garbage
+    fn add_completed_work(&self, work: Vec<Box<dyn workqueue::GenSubmittedWork>>);
+    /// Register an unused context as garbage
+    fn free_context(&self, data: Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>);
+    /// Check whether the GPU is crashed
+    fn is_crashed(&self) -> bool;
+}
+
+/// Private generic trait for functions that don't need to escape this module.
+trait GpuManagerPriv {
+    /// Decrement the pending submission counter.
+    fn end_op(&self);
+}
+
+#[versions(AGX)]
+#[vtable]
+impl rtkit::Operations for GpuManager::ver {
+    type Data = Arc<GpuManager::ver>;
+    type Buffer = gem::ObjectRef;
+
+    fn recv_message(data: <Self::Data as ForeignOwnable>::Borrowed<'_>, ep: u8, msg: u64) {
+        let dev = &data.dev;
+        //dev_info!(dev, "RtKit message: {:#x}:{:#x}\n", ep, msg);
+
+        if ep != EP_FIRMWARE || msg != MSG_RX_DOORBELL {
+            dev_err!(dev, "Unknown message: {:#x}:{:#x}\n", ep, msg);
+            return;
+        }
+
+        let mut ch = data.rx_channels.lock();
+
+        ch.fw_log.poll();
+        ch.ktrace.poll();
+        ch.stats.poll();
+        ch.event.poll();
+    }
+
+    fn crashed(data: <Self::Data as ForeignOwnable>::Borrowed<'_>) {
+        let dev = &data.dev;
+
+        data.crashed.store(true, Ordering::Relaxed);
+
+        if debug_enabled(DebugFlags::OopsOnGpuCrash) {
+            panic!("GPU firmware crashed");
+        } else {
+            dev_err!(dev, "GPU firmware crashed, failing all jobs\n");
+            data.event_manager.fail_all(workqueue::WorkError::NoDevice);
+        }
+    }
+
+    fn shmem_alloc(
+        data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        size: usize,
+    ) -> Result<Self::Buffer> {
+        let dev = &data.dev;
+        mod_dev_dbg!(dev, "shmem_alloc() {:#x} bytes\n", size);
+
+        let mut obj = gem::new_kernel_object(dev, size)?;
+        obj.vmap()?;
+        let iova = obj.map_into_range(
+            data.uat.kernel_vm(),
+            IOVA_KERN_RTKIT_BASE,
+            IOVA_KERN_RTKIT_TOP,
+            mmu::UAT_PGSZ as u64,
+            mmu::PROT_FW_SHARED_RW,
+            true,
+        )?;
+        mod_dev_dbg!(dev, "shmem_alloc() -> VA {:#x}\n", iova);
+        Ok(obj)
+    }
+}
+
+#[versions(AGX)]
+impl GpuManager::ver {
+    /// Create a new GpuManager of this version/GPU combination.
+    #[inline(never)]
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        res: &regs::Resources,
+        cfg: &'static hw::HwConfig,
+    ) -> Result<Arc<GpuManager::ver>> {
+        let uat = Self::make_uat(dev, cfg)?;
+        let dyncfg = Self::make_dyncfg(dev, res, cfg, &uat)?;
+
+        let mut alloc = KernelAllocators {
+            private: alloc::DefaultAllocator::new(
+                dev,
+                uat.kernel_vm(),
+                IOVA_KERN_PRIV_BASE,
+                IOVA_KERN_PRIV_TOP,
+                0x80,
+                mmu::PROT_FW_PRIV_RW,
+                1024 * 1024,
+                true,
+                fmt!("Kernel Private"),
+                true,
+            )?,
+            shared: alloc::DefaultAllocator::new(
+                dev,
+                uat.kernel_vm(),
+                IOVA_KERN_SHARED_BASE,
+                IOVA_KERN_SHARED_TOP,
+                0x80,
+                mmu::PROT_FW_SHARED_RW,
+                1024 * 1024,
+                true,
+                fmt!("Kernel Shared"),
+                false,
+            )?,
+            shared_ro: alloc::DefaultAllocator::new(
+                dev,
+                uat.kernel_vm(),
+                IOVA_KERN_SHARED_RO_BASE,
+                IOVA_KERN_SHARED_RO_TOP,
+                0x80,
+                mmu::PROT_FW_SHARED_RO,
+                64 * 1024,
+                true,
+                fmt!("Kernel RO Shared"),
+                false,
+            )?,
+            gpu: alloc::DefaultAllocator::new(
+                dev,
+                uat.kernel_vm(),
+                IOVA_KERN_GPU_BASE,
+                IOVA_KERN_GPU_TOP,
+                0x80,
+                mmu::PROT_GPU_FW_SHARED_RW,
+                64 * 1024,
+                true,
+                fmt!("Kernel GPU Shared"),
+                false,
+            )?,
+            gpu_ro: alloc::DefaultAllocator::new(
+                dev,
+                uat.kernel_vm(),
+                IOVA_KERN_GPU_RO_BASE,
+                IOVA_KERN_GPU_RO_TOP,
+                0x80,
+                mmu::PROT_GPU_RO_FW_PRIV_RW,
+                1024 * 1024,
+                true,
+                fmt!("Kernel GPU RO Shared"),
+                true,
+            )?,
+        };
+
+        let event_manager = Self::make_event_manager(&mut alloc)?;
+        let mut initdata = Self::make_initdata(dev, cfg, &dyncfg, &mut alloc)?;
+
+        initdata.runtime_pointers.buffer_mgr_ctl.map_at(
+            uat.kernel_lower_vm(),
+            IOVA_KERN_GPU_BUFMGR_LOW,
+            mmu::PROT_GPU_SHARED_RW,
+            false,
+        )?;
+        initdata.runtime_pointers.buffer_mgr_ctl.map_at(
+            uat.kernel_vm(),
+            IOVA_KERN_GPU_BUFMGR_HIGH,
+            mmu::PROT_FW_SHARED_RW,
+            false,
+        )?;
+
+        let mut mgr = Self::make_mgr(dev, cfg, dyncfg, uat, alloc, event_manager, initdata)?;
+
+        {
+            let fwctl = mgr.fwctl_channel.lock();
+            let p_fwctl = fwctl.to_raw();
+            core::mem::drop(fwctl);
+
+            mgr.as_mut()
+                .initdata_mut()
+                .fw_status
+                .with_mut(|raw, _inner| {
+                    raw.fwctl_channel = p_fwctl;
+                });
+        }
+
+        {
+            let txc = mgr.tx_channels.lock();
+            let p_device_control = txc.device_control.to_raw();
+            core::mem::drop(txc);
+
+            let rxc = mgr.rx_channels.lock();
+            let p_event = rxc.event.to_raw();
+            let p_fw_log = rxc.fw_log.to_raw();
+            let p_ktrace = rxc.ktrace.to_raw();
+            let p_stats = rxc.stats.to_raw();
+            let p_fwlog_buf = rxc.fw_log.get_buf();
+            core::mem::drop(rxc);
+
+            mgr.as_mut()
+                .initdata_mut()
+                .runtime_pointers
+                .with_mut(|raw, _inner| {
+                    raw.device_control = p_device_control;
+                    raw.event = p_event;
+                    raw.fw_log = p_fw_log;
+                    raw.ktrace = p_ktrace;
+                    raw.stats = p_stats;
+                    raw.fwlog_buf = Some(p_fwlog_buf);
+                });
+        }
+
+        let mut p_pipes: Vec<fw::initdata::raw::PipeChannels::ver> = Vec::new();
+
+        for ((v, f), c) in mgr
+            .pipes
+            .vtx
+            .iter()
+            .zip(&mgr.pipes.frag)
+            .zip(&mgr.pipes.comp)
+        {
+            p_pipes.push(
+                fw::initdata::raw::PipeChannels::ver {
+                    vtx: v.lock().to_raw(),
+                    frag: f.lock().to_raw(),
+                    comp: c.lock().to_raw(),
+                },
+                GFP_KERNEL,
+            )?;
+        }
+
+        mgr.as_mut()
+            .initdata_mut()
+            .runtime_pointers
+            .with_mut(|raw, _inner| {
+                for (i, p) in p_pipes.into_iter().enumerate() {
+                    raw.pipes[i].vtx = p.vtx;
+                    raw.pipes[i].frag = p.frag;
+                    raw.pipes[i].comp = p.comp;
+                }
+            });
+
+        for (i, map) in cfg.io_mappings.iter().enumerate() {
+            if let Some(map) = map.as_ref() {
+                Self::iomap(&mut mgr, i, map)?;
+            }
+        }
+
+        #[ver(V >= V13_0B4)]
+        if let Some(base) = cfg.sram_base {
+            let size = cfg.sram_size.unwrap() as usize;
+
+            let mapping =
+                mgr.uat
+                    .kernel_vm()
+                    .map_io(base as usize, size, mmu::PROT_FW_SHARED_RW)?;
+
+            mgr.as_mut()
+                .initdata_mut()
+                .runtime_pointers
+                .hwdata_b
+                .with_mut(|raw, _| {
+                    raw.sgx_sram_ptr = U64(mapping.iova() as u64);
+                });
+
+            mgr.as_mut().io_mappings_mut().push(mapping, GFP_KERNEL)?;
+        }
+
+        let mgr = Arc::from(mgr);
+
+        let rtkit = rtkit::RtKit::<GpuManager::ver>::new(dev, None, 0, mgr.clone())?;
+
+        *mgr.rtkit.lock() = Some(rtkit);
+
+        {
+            let mut rxc = mgr.rx_channels.lock();
+            rxc.event.set_manager(mgr.clone());
+        }
+
+        Ok(mgr)
+    }
+
+    /// Return a mutable reference to the initdata member
+    fn initdata_mut(
+        self: Pin<&mut Self>,
+    ) -> &mut fw::types::GpuObject<fw::initdata::InitData::ver> {
+        // SAFETY: initdata does not require structural pinning.
+        unsafe { &mut self.get_unchecked_mut().initdata }
+    }
+
+    /// Return a mutable reference to the io_mappings member
+    fn io_mappings_mut(self: Pin<&mut Self>) -> &mut Vec<mmu::Mapping> {
+        // SAFETY: io_mappings does not require structural pinning.
+        unsafe { &mut self.get_unchecked_mut().io_mappings }
+    }
+
+    /// Build the entire GPU InitData structure tree and return it as a boxed GpuObject.
+    fn make_initdata(
+        dev: &AsahiDevice,
+        cfg: &'static hw::HwConfig,
+        dyncfg: &hw::DynConfig,
+        alloc: &mut KernelAllocators,
+    ) -> Result<Box<fw::types::GpuObject<fw::initdata::InitData::ver>>> {
+        let mut builder = initdata::InitDataBuilder::ver::new(dev, alloc, cfg, dyncfg);
+        builder.build()
+    }
+
+    /// Create a fresh boxed Uat instance.
+    ///
+    /// Force disable inlining to avoid blowing up the stack.
+    #[inline(never)]
+    fn make_uat(dev: &AsahiDevice, cfg: &'static hw::HwConfig) -> Result<Box<mmu::Uat>> {
+        // G14X has a new thing in the Scene structure that unfortunately requires
+        // write access from user contexts. Hopefully it's not security-sensitive.
+        #[ver(G >= G14X)]
+        let map_kernel_to_user = true;
+        #[ver(G < G14X)]
+        let map_kernel_to_user = false;
+
+        Ok(Box::new(
+            mmu::Uat::new(dev, cfg, map_kernel_to_user)?,
+            GFP_KERNEL,
+        )?)
+    }
+
+    /// Actually create the final GpuManager instance, as a UniqueArc.
+    ///
+    /// Force disable inlining to avoid blowing up the stack.
+    #[inline(never)]
+    fn make_mgr(
+        dev: &AsahiDevice,
+        cfg: &'static hw::HwConfig,
+        dyncfg: Box<hw::DynConfig>,
+        uat: Box<mmu::Uat>,
+        mut alloc: KernelAllocators,
+        event_manager: Arc<event::EventManager>,
+        initdata: Box<fw::types::GpuObject<fw::initdata::InitData::ver>>,
+    ) -> Result<Pin<UniqueArc<GpuManager::ver>>> {
+        let mut pipes = PipeChannels::ver {
+            vtx: Vec::new(),
+            frag: Vec::new(),
+            comp: Vec::new(),
+        };
+
+        for _i in 0..=NUM_PIPES - 1 {
+            pipes.vtx.push(
+                Box::pin_init(
+                    Mutex::new_named(
+                        channel::PipeChannel::ver::new(dev, &mut alloc)?,
+                        c_str!("pipe_vtx"),
+                    ),
+                    GFP_KERNEL,
+                )?,
+                GFP_KERNEL,
+            )?;
+            pipes.frag.push(
+                Box::pin_init(
+                    Mutex::new_named(
+                        channel::PipeChannel::ver::new(dev, &mut alloc)?,
+                        c_str!("pipe_frag"),
+                    ),
+                    GFP_KERNEL,
+                )?,
+                GFP_KERNEL,
+            )?;
+            pipes.comp.push(
+                Box::pin_init(
+                    Mutex::new_named(
+                        channel::PipeChannel::ver::new(dev, &mut alloc)?,
+                        c_str!("pipe_comp"),
+                    ),
+                    GFP_KERNEL,
+                )?,
+                GFP_KERNEL,
+            )?;
+        }
+
+        let fwctl_channel = channel::FwCtlChannel::new(dev, &mut alloc)?;
+
+        let buffer_mgr = buffer::BufferManager::ver::new()?;
+        let event_manager_clone = event_manager.clone();
+        let buffer_mgr_clone = buffer_mgr.clone();
+        let alloc_ref = &mut alloc;
+        let rx_channels = Box::init(
+            try_init!(RxChannels::ver {
+                event: channel::EventChannel::ver::new(
+                    dev,
+                    alloc_ref,
+                    event_manager_clone,
+                    buffer_mgr_clone,
+                )?,
+                fw_log: channel::FwLogChannel::new(dev, alloc_ref)?,
+                ktrace: channel::KTraceChannel::new(dev, alloc_ref)?,
+                stats: channel::StatsChannel::ver::new(dev, alloc_ref)?,
+            }),
+            GFP_KERNEL,
+        )?;
+
+        let alloc_ref = &mut alloc;
+        let tx_channels = Box::init(try_init!(TxChannels::ver {
+            device_control: channel::DeviceControlChannel::ver::new(dev, alloc_ref)?,
+        }))?;
+
+        let x = UniqueArc::pin_init(try_pin_init!(GpuManager::ver {
+            dev: dev.into(),
+            cfg,
+            dyncfg: *dyncfg,
+            initdata: *initdata,
+            uat: *uat,
+            io_mappings: Vec::new(),
+            rtkit <- Mutex::new_named(None, c_str!("rtkit")),
+            crashed: AtomicBool::new(false),
+            event_manager,
+            alloc <- Mutex::new_named(alloc, c_str!("alloc")),
+            fwctl_channel <- Mutex::new_named(fwctl_channel, c_str!("fwctl_channel")),
+            rx_channels <- Mutex::new_named(*rx_channels, c_str!("rx_channels")),
+            tx_channels <- Mutex::new_named(*tx_channels, c_str!("tx_channels")),
+            pipes,
+            buffer_mgr,
+            ids: Default::default(),
+            garbage_work <- Mutex::new_named(Vec::new(), c_str!("garbage_work")),
+            garbage_contexts <- Mutex::new_named(Vec::new(), c_str!("garbage_contexts")),
+        }))?;
+
+        Ok(x)
+    }
+
+        Ok(x)
+    }
+
+    /// Fetch and validate the GPU dynamic configuration from the device tree and hardware.
+    ///
+    /// Force disable inlining to avoid blowing up the stack.
+    #[inline(never)]
+    fn make_dyncfg(
+        dev: &AsahiDevice,
+        res: &regs::Resources,
+        cfg: &'static hw::HwConfig,
+        uat: &mmu::Uat,
+    ) -> Result<Box<hw::DynConfig>> {
+        let gpu_id = res.get_gpu_id()?;
+
+        dev_info!(dev, "GPU Information:\n");
+        dev_info!(
+            dev,
+            "  Type: {:?}{:?}\n",
+            gpu_id.gpu_gen,
+            gpu_id.gpu_variant
+        );
+        dev_info!(dev, "  Max dies: {}\n", gpu_id.max_dies);
+        dev_info!(dev, "  Clusters: {}\n", gpu_id.num_clusters);
+        dev_info!(
+            dev,
+            "  Cores: {} ({})\n",
+            gpu_id.num_cores,
+            gpu_id.num_cores * gpu_id.num_clusters
+        );
+        dev_info!(
+            dev,
+            "  Frags: {} ({})\n",
+            gpu_id.num_frags,
+            gpu_id.num_frags * gpu_id.num_clusters
+        );
+        dev_info!(
+            dev,
+            "  GPs: {} ({})\n",
+            gpu_id.num_gps,
+            gpu_id.num_gps * gpu_id.num_clusters
+        );
+        dev_info!(dev, "  Core masks: {:#x?}\n", gpu_id.core_masks);
+        dev_info!(dev, "  Active cores: {}\n", gpu_id.total_active_cores);
+
+        dev_info!(dev, "Getting configuration from device tree...\n");
+        let pwr_cfg = hw::PwrConfig::load(dev, cfg)?;
+        dev_info!(dev, "Dynamic configuration fetched\n");
+
+        if gpu_id.gpu_gen != cfg.gpu_gen || gpu_id.gpu_variant != cfg.gpu_variant {
+            dev_err!(
+                dev,
+                "GPU type mismatch (expected {:?}{:?}, found {:?}{:?})\n",
+                cfg.gpu_gen,
+                cfg.gpu_variant,
+                gpu_id.gpu_gen,
+                gpu_id.gpu_variant
+            );
+            return Err(EIO);
+        }
+        if gpu_id.num_clusters > cfg.max_num_clusters {
+            dev_err!(
+                dev,
+                "Too many clusters ({} > {})\n",
+                gpu_id.num_clusters,
+                cfg.max_num_clusters
+            );
+            return Err(EIO);
+        }
+        if gpu_id.num_cores > cfg.max_num_cores {
+            dev_err!(
+                dev,
+                "Too many cores ({} > {})\n",
+                gpu_id.num_cores,
+                cfg.max_num_cores
+            );
+            return Err(EIO);
+        }
+        if gpu_id.num_frags > cfg.max_num_frags {
+            dev_err!(
+                dev,
+                "Too many frags ({} > {})\n",
+                gpu_id.num_frags,
+                cfg.max_num_frags
+            );
+            return Err(EIO);
+        }
+        if gpu_id.num_gps > cfg.max_num_gps {
+            dev_err!(
+                dev,
+                "Too many GPs ({} > {})\n",
+                gpu_id.num_gps,
+                cfg.max_num_gps
+            );
+            return Err(EIO);
+        }
+
+        Ok(Box::try_new(hw::DynConfig {
+            pwr: pwr_cfg,
+            uat_ttb_base: uat.ttb_base(),
+            id: gpu_id,
+        })?)
+    }
+
+    /// Create the global GPU event manager, and return an `Arc<>` to it.
+    fn make_event_manager(alloc: &mut KernelAllocators) -> Result<Arc<event::EventManager>> {
+        Ok(Arc::try_new(event::EventManager::new(alloc)?)?)
+    }
+
+    /// Create a new MMIO mapping and add it to the mappings list in initdata at the specified
+    /// index.
+    fn iomap(
+        this: &mut Pin<UniqueArc<GpuManager::ver>>,
+        index: usize,
+        map: &hw::IOMapping,
+    ) -> Result {
+        let off = map.base & mmu::UAT_PGMSK;
+        let base = map.base - off;
+        let end = (map.base + map.size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK;
+        let mapping = this.uat.kernel_vm().map_io(
+            base,
+            end - base,
+            if map.writable {
+                mmu::PROT_FW_MMIO_RW
+            } else {
+                mmu::PROT_FW_MMIO_RO
+            },
+        )?;
+
+        this.as_mut()
+            .initdata_mut()
+            .runtime_pointers
+            .hwdata_b
+            .with_mut(|raw, _| {
+                raw.io_mappings[index] = fw::initdata::raw::IOMapping {
+                    phys_addr: U64(map.base as u64),
+                    virt_addr: U64((mapping.iova() + off) as u64),
+                    size: map.size as u32,
+                    range_size: map.range_size as u32,
+                    readwrite: U64(map.writable as u64),
+                };
+            });
+
+        this.as_mut().io_mappings_mut().try_push(mapping)?;
+        Ok(())
+    }
+
+    /// Mark work associated with currently in-progress event slots as failed, after a fault or
+    /// timeout.
+    fn mark_pending_events(&self, culprit_slot: Option<u32>, error: workqueue::WorkError) {
+        dev_err!(self.dev, "  Pending events:\n");
+
+        self.initdata.globals.with(|raw, _inner| {
+            for (index, i) in raw.pending_stamps.iter().enumerate() {
+                let info = i.info.load(Ordering::Relaxed);
+                let wait_value = i.wait_value.load(Ordering::Relaxed);
+
+                if info & 1 != 0 {
+                    #[ver(V >= V13_5)]
+                    let slot = (info >> 4) & 0x7f;
+                    #[ver(V < V13_5)]
+                    let slot = (info >> 3) & 0x7f;
+                    #[ver(V >= V13_5)]
+                    let flags = info & 0xf;
+                    #[ver(V < V13_5)]
+                    let flags = info & 0x7;
+                    dev_err!(
+                        self.dev,
+                        "    [{}:{}] flags={} value={:#x}\n",
+                        index,
+                        slot,
+                        flags,
+                        wait_value
+                    );
+                    let error = if culprit_slot.is_some() && culprit_slot != Some(slot) {
+                        workqueue::WorkError::Killed
+                    } else {
+                        error
+                    };
+                    self.event_manager.mark_error(slot, wait_value, error);
+                    i.info.store(0, Ordering::Relaxed);
+                    i.wait_value.store(0, Ordering::Relaxed);
+                }
+            }
+        });
+    }
+
+    /// Fetch the GPU MMU fault information from the hardware registers.
+    fn get_fault_info(&self) -> Option<regs::FaultInfo> {
+        let data = self.dev.data();
+
+        let res = match data.resources() {
+            Some(res) => res,
+            None => {
+                dev_err!(self.dev, "  Failed to acquire resources\n");
+                return None;
+            }
+        };
+
+        let info = res.get_fault_info(self.cfg);
+        if info.is_some() {
+            dev_err!(self.dev, "  Fault info: {:#x?}\n", info.as_ref().unwrap());
+        }
+        info
+    }
+
+    /// Resume the GPU firmware after it halts (due to a timeout, fault, or request).
+    fn recover(&self) {
+        self.initdata.fw_status.with(|raw, _inner| {
+            let halt_count = raw.flags.halt_count.load(Ordering::Relaxed);
+            let mut halted = raw.flags.halted.load(Ordering::Relaxed);
+            dev_err!(self.dev, "  Halt count: {}\n", halt_count);
+            dev_err!(self.dev, "  Halted: {}\n", halted);
+
+            if halted == 0 {
+                let start = clock::KernelTime::now();
+                while start.elapsed() < HALT_ENTER_TIMEOUT {
+                    halted = raw.flags.halted.load(Ordering::Relaxed);
+                    if halted != 0 {
+                        break;
+                    }
+                    mem::sync();
+                }
+                halted = raw.flags.halted.load(Ordering::Relaxed);
+            }
+
+            if debug_enabled(DebugFlags::NoGpuRecovery) {
+                dev_crit!(self.dev, "  GPU recovery is disabled, wedging forever!\n");
+            } else if halted != 0 {
+                dev_err!(self.dev, "  Attempting recovery...\n");
+                raw.flags.halted.store(0, Ordering::SeqCst);
+                raw.flags.resume.store(1, Ordering::SeqCst);
+            } else {
+                dev_err!(self.dev, "  Cannot recover.\n");
+            }
+        });
+    }
+
+    /// Return the packed GPU enabled core masks.
+    // Only used for some versions
+    #[allow(dead_code)]
+    pub(crate) fn core_masks_packed(&self) -> &[u32] {
+        self.dyncfg.id.core_masks_packed.as_slice()
+    }
+
+    /// Kick a submission pipe for a submitted job to tell the firmware to start processing it.
+    pub(crate) fn run_job(&self, job: workqueue::JobSubmission::ver<'_>) -> Result {
+        mod_dev_dbg!(self.dev, "GPU: run_job\n");
+
+        let pipe_type = job.pipe_type();
+        mod_dev_dbg!(self.dev, "GPU: run_job: pipe_type={:?}\n", pipe_type);
+
+        let pipes = match pipe_type {
+            PipeType::Vertex => &self.pipes.vtx,
+            PipeType::Fragment => &self.pipes.frag,
+            PipeType::Compute => &self.pipes.comp,
+        };
+
+        let index: usize = job.priority() as usize;
+        let mut pipe = pipes.get(index).ok_or(EIO)?.lock();
+
+        mod_dev_dbg!(self.dev, "GPU: run_job: run()\n");
+        job.run(&mut pipe);
+        mod_dev_dbg!(self.dev, "GPU: run_job: ring doorbell\n");
+
+        let mut guard = self.rtkit.lock();
+        let rtk = guard.as_mut().unwrap();
+        rtk.send_message(
+            EP_DOORBELL,
+            MSG_TX_DOORBELL | pipe_type as u64 | ((index as u64) << 2),
+        )?;
+        mod_dev_dbg!(self.dev, "GPU: run_job: done\n");
+
+        Ok(())
+    }
+
+    pub(crate) fn start_op(self: &Arc<GpuManager::ver>) -> Result<OpGuard> {
+        if self.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        let val = self
+            .initdata
+            .globals
+            .with(|raw, _inner| raw.pending_submissions.fetch_add(1, Ordering::Acquire));
+
+        mod_dev_dbg!(self.dev, "OP start (pending: {})\n", val + 1);
+        self.kick_firmware()?;
+        Ok(OpGuard(self.clone()))
+    }
+
+    fn invalidate_context(
+        &self,
+        context: &fw::types::GpuObject<fw::workqueue::GpuContextData>,
+    ) -> Result {
+        mod_dev_dbg!(
+            self.dev,
+            "Invalidating GPU context @ {:?}\n",
+            context.weak_pointer()
+        );
+
+        if self.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        let mut guard = self.alloc.lock();
+        let (garbage_count, _) = guard.private.garbage();
+        let (garbage_count_gpuro, _) = guard.gpu_ro.garbage();
+
+        let dc = context.with(
+            |raw, _inner| fw::channels::DeviceControlMsg::ver::DestroyContext {
+                unk_4: 0,
+                ctx_23: raw.unk_23,
+                #[ver(V < V13_3)]
+                __pad0: Default::default(),
+                unk_c: U32(0),
+                unk_10: U32(0),
+                ctx_0: raw.unk_0,
+                ctx_1: raw.unk_1,
+                ctx_4: raw.unk_4,
+                #[ver(V < V13_3)]
+                __pad1: Default::default(),
+                #[ver(V < V13_3)]
+                unk_18: 0,
+                gpu_context: Some(context.weak_pointer()),
+                __pad2: Default::default(),
+            },
+        );
+
+        mod_dev_dbg!(self.dev, "Context invalidation command: {:?}\n", &dc);
+
+        let mut txch = self.tx_channels.lock();
+
+        let token = txch.device_control.send(&dc);
+
+        {
+            let mut guard = self.rtkit.lock();
+            let rtk = guard.as_mut().unwrap();
+            rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?;
+        }
+
+        txch.device_control.wait_for(token)?;
+
+        mod_dev_dbg!(
+            self.dev,
+            "GPU context invalidated: {:?}\n",
+            context.weak_pointer()
+        );
+
+        // The invalidation does a cache flush, so it is okay to collect garbage
+        guard.private.collect_garbage(garbage_count);
+        guard.gpu_ro.collect_garbage(garbage_count_gpuro);
+
+        Ok(())
+    }
+}
+
+#[versions(AGX)]
+impl GpuManager for GpuManager::ver {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
+    fn arc_as_any(self: Arc<Self>) -> Arc<dyn Any + Sync + Send> {
+        self as Arc<dyn Any + Sync + Send>
+    }
+
+    fn init(&self) -> Result {
+        self.tx_channels.lock().device_control.send(
+            &fw::channels::DeviceControlMsg::ver::Initialize(Default::default()),
+        );
+
+        let initdata = self.initdata.gpu_va().get();
+        let mut guard = self.rtkit.lock();
+        let rtk = guard.as_mut().unwrap();
+
+        rtk.boot()?;
+        rtk.start_endpoint(EP_FIRMWARE)?;
+        rtk.start_endpoint(EP_DOORBELL)?;
+        rtk.send_message(EP_FIRMWARE, MSG_INIT | (initdata & INIT_DATA_MASK))?;
+        rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?;
+        core::mem::drop(guard);
+
+        self.kick_firmware()?;
+        Ok(())
+    }
+
+    fn update_globals(&self) {
+        let mut timeout: u32 = 2;
+        if debug_enabled(DebugFlags::WaitForPowerOff) {
+            timeout = 0;
+        } else if debug_enabled(DebugFlags::KeepGpuPowered) {
+            timeout = 5000;
+        }
+
+        self.initdata.globals.with(|raw, _inner| {
+            raw.idle_off_delay_ms.store(timeout, Ordering::Relaxed);
+        });
+    }
+
+    fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend> {
+        /*
+         * TODO: This should be done in a workqueue or something.
+         * Clean up completed jobs
+         */
+        self.garbage_work.lock().clear();
+
+        /* Clean up idle contexts */
+        let mut garbage_ctx = Vec::new();
+        core::mem::swap(&mut *self.garbage_contexts.lock(), &mut garbage_ctx);
+
+        for ctx in garbage_ctx {
+            if self.invalidate_context(&ctx).is_err() {
+                dev_err!(self.dev, "GpuContext: Failed to invalidate GPU context!\n");
+                if debug_enabled(DebugFlags::OopsOnGpuCrash) {
+                    panic!("GPU firmware timed out");
+                }
+            }
+        }
+
+        let mut guard = self.alloc.lock();
+        let (garbage_count, garbage_bytes) = guard.private.garbage();
+        if garbage_bytes > MAX_FW_ALLOC_GARBAGE {
+            mod_dev_dbg!(
+                self.dev,
+                "Collecting kalloc/private garbage ({} objects, {} bytes)\n",
+                garbage_count,
+                garbage_bytes
+            );
+            if self.flush_fw_cache().is_err() {
+                dev_err!(self.dev, "Failed to flush FW cache\n");
+            } else {
+                guard.private.collect_garbage(garbage_count);
+            }
+        }
+
+        let (garbage_count, garbage_bytes) = guard.gpu_ro.garbage();
+        if garbage_bytes > MAX_FW_ALLOC_GARBAGE {
+            mod_dev_dbg!(
+                self.dev,
+                "Collecting kalloc/gpuro garbage ({} objects, {} bytes)\n",
+                garbage_count,
+                garbage_bytes
+            );
+            if self.flush_fw_cache().is_err() {
+                dev_err!(self.dev, "Failed to flush FW cache\n");
+            } else {
+                guard.gpu_ro.collect_garbage(garbage_count);
+            }
+        }
+
+        guard
+    }
+
+    fn new_vm(&self, file_id: u64) -> Result<mmu::Vm> {
+        self.uat.new_vm(self.ids.vm.next(), file_id)
+    }
+
+    fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind> {
+        self.uat.bind(vm)
+    }
+
+    fn new_queue(
+        &self,
+        vm: mmu::Vm,
+        ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+        ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+        priority: u32,
+        caps: u32,
+    ) -> Result<Box<dyn queue::Queue>> {
+        let mut kalloc = self.alloc();
+        let id = self.ids.queue.next();
+        Ok(Box::new(
+            queue::Queue::ver::new(
+                &self.dev,
+                vm,
+                &mut kalloc,
+                ualloc,
+                ualloc_priv,
+                self.event_manager.clone(),
+                &self.buffer_mgr,
+                id,
+                priority,
+                caps,
+            )?,
+            GFP_KERNEL,
+        )?)
+    }
+
+    fn kick_firmware(&self) -> Result {
+        if self.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        let mut guard = self.rtkit.lock();
+        let rtk = guard.as_mut().unwrap();
+        rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_KICKFW)?;
+
+        Ok(())
+    }
+
+    fn flush_fw_cache(&self) -> Result {
+        mod_dev_dbg!(self.dev, "Flushing coprocessor data cache\n");
+
+        if self.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        // ctx_0 == 0xff or ctx_1 == 0xff cause no effect on context,
+        // but this command does a full cache flush too, so abuse it
+        // for that.
+
+        let dc = fw::channels::DeviceControlMsg::ver::DestroyContext {
+            unk_4: 0,
+
+            ctx_23: 0,
+            #[ver(V < V13_3)]
+            __pad0: Default::default(),
+            unk_c: U32(0),
+            unk_10: U32(0),
+            ctx_0: 0xff,
+            ctx_1: 0xff,
+            ctx_4: 0,
+            #[ver(V < V13_3)]
+            __pad1: Default::default(),
+            #[ver(V < V13_3)]
+            unk_18: 0,
+            gpu_context: None,
+            __pad2: Default::default(),
+        };
+
+        let mut txch = self.tx_channels.lock();
+
+        let token = txch.device_control.send(&dc);
+        {
+            let mut guard = self.rtkit.lock();
+            let rtk = guard.as_mut().unwrap();
+            rtk.send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)?;
+        }
+
+        txch.device_control.wait_for(token)?;
+        Ok(())
+    }
+
+    fn ids(&self) -> &SequenceIDs {
+        &self.ids
+    }
+
+    fn handle_timeout(&self, counter: u32, event_slot: i32) {
+        dev_err!(self.dev, " (\\________/) \n");
+        dev_err!(self.dev, "  |        |  \n");
+        dev_err!(self.dev, "'.| \\  , / |.'\n");
+        dev_err!(self.dev, "--| / (( \\ |--\n");
+        dev_err!(self.dev, ".'|  _-_-  |'.\n");
+        dev_err!(self.dev, "  |________|  \n");
+        dev_err!(self.dev, "** GPU timeout nya~!!!!! **\n");
+        dev_err!(self.dev, "  Event slot: {}\n", event_slot);
+        dev_err!(self.dev, "  Timeout count: {}\n", counter);
+
+        // If we have fault info, consider it a fault.
+        let error = match self.get_fault_info() {
+            Some(info) => workqueue::WorkError::Fault(info),
+            None => workqueue::WorkError::Timeout,
+        };
+        self.mark_pending_events(event_slot.try_into().ok(), error);
+        self.recover();
+    }
+
+    fn handle_fault(&self) {
+        dev_err!(self.dev, " (\\________/) \n");
+        dev_err!(self.dev, "  |        |  \n");
+        dev_err!(self.dev, "'.| \\  , / |.'\n");
+        dev_err!(self.dev, "--| / (( \\ |--\n");
+        dev_err!(self.dev, ".'|  _-_-  |'.\n");
+        dev_err!(self.dev, "  |________|  \n");
+        dev_err!(self.dev, "GPU fault nya~!!!!!\n");
+        let error = match self.get_fault_info() {
+            Some(info) => workqueue::WorkError::Fault(info),
+            None => workqueue::WorkError::Unknown,
+        };
+        self.mark_pending_events(None, error);
+        self.recover();
+    }
+
+    fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32) {
+        let dc = fw::channels::DeviceControlMsg::ver::GrowTVBAck {
+            unk_4: 1,
+            buffer_slot,
+            vm_slot,
+            counter,
+            __pad: Default::default(),
+        };
+
+        mod_dev_dbg!(self.dev, "TVB Grow Ack command: {:?}\n", &dc);
+
+        let mut txch = self.tx_channels.lock();
+
+        txch.device_control.send(&dc);
+        {
+            let mut guard = self.rtkit.lock();
+            let rtk = guard.as_mut().unwrap();
+            if rtk
+                .send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)
+                .is_err()
+            {
+                dev_err!(self.dev, "Failed to send TVB Grow Ack command\n");
+            }
+        }
+    }
+
+    fn wait_for_poweroff(&self, timeout: usize) -> Result {
+        self.initdata.runtime_pointers.hwdata_a.with(|raw, _inner| {
+            for _i in 0..timeout {
+                if raw.pwr_status.load(Ordering::Relaxed) == 4 {
+                    return Ok(());
+                }
+                coarse_sleep(Duration::from_millis(1));
+            }
+            Err(ETIMEDOUT)
+        })
+    }
+
+    fn fwctl(&self, msg: fw::channels::FwCtlMsg) -> Result {
+        if self.is_crashed() {
+            return Err(ENODEV);
+        }
+
+        let mut fwctl = self.fwctl_channel.lock();
+        let token = fwctl.send(&msg);
+        {
+            let mut guard = self.rtkit.lock();
+            let rtk = guard.as_mut().unwrap();
+            rtk.send_message(EP_DOORBELL, MSG_FWCTL)?;
+        }
+        fwctl.wait_for(token)?;
+        Ok(())
+    }
+
+    fn get_cfg(&self) -> &'static hw::HwConfig {
+        self.cfg
+    }
+
+    fn get_dyncfg(&self) -> &hw::DynConfig {
+        &self.dyncfg
+    }
+
+    fn add_completed_work(&self, work: Vec<Box<dyn workqueue::GenSubmittedWork>>) {
+        let mut garbage = self.garbage_work.lock();
+
+        if garbage.reserve(work.len(), GFP_KERNEL).is_err() {
+            dev_err!(
+                self.dev,
+                "Failed to reserve space for completed work, deadlock possible.\n"
+            );
+            return;
+        }
+
+        for i in work {
+            garbage
+                .push(i, GFP_KERNEL)
+                .expect("push() failed after reserve()");
+        }
+    }
+
+    fn free_context(&self, ctx: Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>) {
+        let mut garbage = self.garbage_contexts.lock();
+
+        if garbage.push(ctx, GFP_KERNEL).is_err() {
+            dev_err!(
+                self.dev,
+                "Failed to reserve space for freed context, deadlock possible.\n"
+            );
+        }
+    }
+
+    fn is_crashed(&self) -> bool {
+        self.crashed.load(Ordering::Relaxed)
+    }
+}
+
+#[versions(AGX)]
+impl GpuManagerPriv for GpuManager::ver {
+    fn end_op(&self) {
+        let val = self
+            .initdata
+            .globals
+            .with(|raw, _inner| raw.pending_submissions.fetch_sub(1, Ordering::Release));
+
+        mod_dev_dbg!(self.dev, "OP end (pending: {})\n", val - 1);
+    }
+}
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
new file mode 100644
index 00000000000000..0685f833b41a1d
--- /dev/null
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -0,0 +1,664 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Per-SoC hardware configuration structures
+//!
+//! This module contains the definitions used to store per-GPU and per-SoC configuration data.
+
+use crate::driver::AsahiDevice;
+use crate::fw::types::*;
+use alloc::vec::Vec;
+use kernel::c_str;
+use kernel::device::RawDevice;
+use kernel::prelude::*;
+
+const MAX_POWERZONES: usize = 5;
+
+pub(crate) mod t600x;
+pub(crate) mod t602x;
+pub(crate) mod t8103;
+pub(crate) mod t8112;
+
+/// GPU generation enumeration. Note: Part of the UABI.
+#[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum GpuGen {
+    G13 = 13,
+    G14 = 14,
+}
+
+/// GPU variant enumeration. Note: Part of the UABI.
+#[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum GpuVariant {
+    P = 'P' as u32,
+    G = 'G' as u32,
+    S = 'S' as u32,
+    C = 'C' as u32,
+    D = 'D' as u32,
+}
+
+/// GPU revision enumeration. Note: Part of the UABI.
+#[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum GpuRevision {
+    A0 = 0x00,
+    A1 = 0x01,
+    B0 = 0x10,
+    B1 = 0x11,
+    C0 = 0x20,
+    C1 = 0x21,
+}
+
+/// GPU core type enumeration. Note: Part of the firmware ABI.
+#[derive(Debug, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum GpuCore {
+    // Unknown = 0,
+    // G5P = 1,
+    // G5G = 2,
+    // G9P = 3,
+    // G9G = 4,
+    // G10P = 5,
+    // G11P = 6,
+    // G11M = 7,
+    // G11G = 8,
+    // G12P = 9,
+    // G13P = 10,
+    G13G = 11,
+    G13S = 12,
+    G13C = 13,
+    // G14P = 14,
+    G14G = 15,
+    G14S = 16,
+    G14C = 17,
+    // G15M = 18,
+    // G15P_AGX2 = 19,
+    // G15P = 20,
+}
+
+/// GPU revision ID. Note: Part of the firmware ABI.
+#[derive(Debug, PartialEq, Copy, Clone)]
+#[repr(u32)]
+pub(crate) enum GpuRevisionID {
+    // Unknown = 0,
+    A0 = 1,
+    A1 = 2,
+    B0 = 3,
+    B1 = 4,
+    C0 = 5,
+    C1 = 6,
+}
+
+/// GPU driver/hardware features, from the UABI.
+pub(crate) mod feat {
+    /// Backwards-compatible features.
+    pub(crate) mod compat {}
+
+    /// Backwards-incompatible features.
+    pub(crate) mod incompat {
+        use kernel::uapi;
+
+        /// Hardware requires Z/S compression to be mandatorily enabled.
+        pub(crate) const MANDATORY_ZS_COMPRESSION: u64 =
+            uapi::drm_asahi_feat_incompat_DRM_ASAHI_FEAT_MANDATORY_ZS_COMPRESSION as u64;
+    }
+}
+
+/// A single performance state of the GPU.
+#[derive(Debug)]
+pub(crate) struct PState {
+    /// Voltage in millivolts, per GPU cluster.
+    pub(crate) volt_mv: Vec<u32>,
+    /// Frequency in hertz.
+    pub(crate) freq_hz: u32,
+    /// Maximum power consumption of the GPU at this pstate, in milliwatts.
+    pub(crate) pwr_mw: u32,
+}
+
+impl PState {
+    pub(crate) fn max_volt_mv(&self) -> u32 {
+        *self.volt_mv.iter().max().expect("No voltages")
+    }
+}
+
+/// A power zone definition (we have no idea what this is but Apple puts them in the DT).
+#[allow(missing_docs)]
+#[derive(Debug, Copy, Clone)]
+pub(crate) struct PowerZone {
+    pub(crate) target: u32,
+    pub(crate) target_offset: u32,
+    pub(crate) filter_tc: u32,
+}
+
+/// An MMIO mapping used by the firmware.
+#[derive(Debug, Copy, Clone)]
+pub(crate) struct IOMapping {
+    /// Base physical address of the mapping.
+    pub(crate) base: usize,
+    /// Size of the mapping.
+    pub(crate) size: usize,
+    /// Range size of the mapping (for arrays?)
+    pub(crate) range_size: usize,
+    /// Whether the mapping should be writable.
+    pub(crate) writable: bool,
+}
+
+impl IOMapping {
+    /// Convenience constructor for a new IOMapping.
+    pub(crate) const fn new(
+        base: usize,
+        size: usize,
+        range_size: usize,
+        writable: bool,
+    ) -> IOMapping {
+        IOMapping {
+            base,
+            size,
+            range_size,
+            writable,
+        }
+    }
+}
+
+/// Unknown HwConfigA fields that vary from SoC to SoC.
+#[allow(missing_docs)]
+#[derive(Debug, Copy, Clone)]
+pub(crate) struct HwConfigA {
+    pub(crate) unk_87c: i32,
+    pub(crate) unk_8cc: u32,
+    pub(crate) unk_e24: u32,
+}
+
+/// Unknown HwConfigB fields that vary from SoC to SoC.
+#[allow(missing_docs)]
+#[derive(Debug, Copy, Clone)]
+pub(crate) struct HwConfigB {
+    pub(crate) unk_4e0: u64,
+    pub(crate) unk_534: u32,
+    pub(crate) unk_ab8: u32,
+    pub(crate) unk_abc: u32,
+    pub(crate) unk_b30: u32,
+}
+
+/// Render command configs that vary from SoC to SoC.
+#[derive(Debug, Copy, Clone)]
+pub(crate) struct HwRenderConfig {
+    /// Vertex/tiling-related configuration register (lsb: disable clustering)
+    pub(crate) tiling_control: u32,
+}
+
+#[derive(Debug)]
+pub(crate) struct HwConfigShared2Curves {
+    pub(crate) t1_coef: u32,
+    pub(crate) t2: &'static [i16],
+    pub(crate) t3_coefs: &'static [u32],
+    pub(crate) t3_scales: &'static [u32],
+}
+
+/// Static hardware clustering configuration for multi-cluster SoCs.
+#[derive(Debug)]
+pub(crate) struct HwClusteringConfig {
+    pub(crate) meta1_blocksize: usize,
+    pub(crate) meta2_size: usize,
+    pub(crate) meta3_size: usize,
+    pub(crate) meta4_size: usize,
+    pub(crate) max_splits: usize,
+}
+
+/// Static hardware configuration for a given SoC model.
+#[derive(Debug)]
+pub(crate) struct HwConfig {
+    /// Chip ID in hex format (e.g. 0x8103 for t8103).
+    pub(crate) chip_id: u32,
+    /// GPU generation.
+    pub(crate) gpu_gen: GpuGen,
+    /// GPU variant type.
+    pub(crate) gpu_variant: GpuVariant,
+    /// GPU core type ID (as known by the firmware).
+    pub(crate) gpu_core: GpuCore,
+    /// Compatible feature bitmask for this GPU.
+    pub(crate) gpu_feat_compat: u64,
+    /// Incompatible feature bitmask for this GPU.
+    pub(crate) gpu_feat_incompat: u64,
+
+    /// Base clock used used for timekeeping.
+    pub(crate) base_clock_hz: u32,
+    /// Output address space for the UAT on this SoC.
+    pub(crate) uat_oas: usize,
+    /// Number of dies on this SoC.
+    pub(crate) num_dies: u32,
+    /// Maximum number of clusters on this SoC.
+    pub(crate) max_num_clusters: u32,
+    /// Maximum number of cores per cluster for this GPU.
+    pub(crate) max_num_cores: u32,
+    /// Maximum number of frags per cluster for this GPU.
+    pub(crate) max_num_frags: u32,
+    /// Maximum number of GPs per cluster for this GPU.
+    pub(crate) max_num_gps: u32,
+
+    /// Required size of the first preemption buffer.
+    pub(crate) preempt1_size: usize,
+    /// Required size of the second preemption buffer.
+    pub(crate) preempt2_size: usize,
+    /// Required size of the third preemption buffer.
+    pub(crate) preempt3_size: usize,
+
+    /// Required size of the compute preemption buffer.
+    pub(crate) compute_preempt1_size: usize,
+
+    pub(crate) clustering: Option<HwClusteringConfig>,
+
+    /// Rendering-relevant configuration.
+    pub(crate) render: HwRenderConfig,
+
+    /// Misc HWDataA field values.
+    pub(crate) da: HwConfigA,
+    /// Misc HWDataB field values.
+    pub(crate) db: HwConfigB,
+    /// HwDataShared1.table.
+    pub(crate) shared1_tab: &'static [i32],
+    /// HwDataShared1.unk_a4.
+    pub(crate) shared1_a4: u32,
+    /// HwDataShared2.table.
+    pub(crate) shared2_tab: &'static [i32],
+    /// HwDataShared2.unk_508.
+    pub(crate) shared2_unk_508: u32,
+    /// HwDataShared2.unk_508.
+    pub(crate) shared2_curves: Option<HwConfigShared2Curves>,
+
+    /// HwDataShared3.unk_8.
+    pub(crate) shared3_unk: u32,
+    /// HwDataShared3.table.
+    pub(crate) shared3_tab: &'static [u32],
+
+    /// Globals.unk_hws2_0.
+    pub(crate) unk_hws2_0: u32,
+    /// Globals.unk_hws2_4.
+    pub(crate) unk_hws2_4: Option<[F32; 8]>,
+    /// Globals.unk_hws2_24.
+    pub(crate) unk_hws2_24: u32,
+    /// Globals.unk_54
+    pub(crate) global_unk_54: u16,
+
+    /// Constant related to SRAM voltages.
+    pub(crate) sram_k: F32,
+    /// Unknown per-cluster coefficients 1.
+    pub(crate) unk_coef_a: &'static [&'static [F32]],
+    /// Unknown per-cluster coefficients 2.
+    pub(crate) unk_coef_b: &'static [&'static [F32]],
+    /// Unknown table in Global struct.
+    pub(crate) global_tab: Option<&'static [u8]>,
+    /// Whether this GPU has CS/AFR performance states
+    pub(crate) has_csafr: bool,
+
+    /// Temperature sensor list (8 bits per sensor).
+    pub(crate) fast_sensor_mask: [u64; 2],
+    /// Temperature sensor list (alternate).
+    pub(crate) fast_sensor_mask_alt: [u64; 2],
+    /// Temperature sensor present bitmask.
+    pub(crate) fast_die0_sensor_present: u32,
+    /// Required MMIO mappings for this GPU/firmware.
+    pub(crate) io_mappings: &'static [Option<IOMapping>],
+    /// SRAM base
+    pub(crate) sram_base: Option<u64>,
+    /// SRAM size
+    pub(crate) sram_size: Option<u64>,
+}
+
+/// Dynamic (fetched from hardware/DT) configuration.
+#[derive(Debug)]
+pub(crate) struct DynConfig {
+    /// Base physical address of the UAT TTB (from DT reserved memory region).
+    pub(crate) uat_ttb_base: u64,
+    /// GPU ID configuration read from hardware.
+    pub(crate) id: GpuIdConfig,
+    /// Power calibration configuration for this specific chip/device.
+    pub(crate) pwr: PwrConfig,
+}
+
+/// Specific GPU ID configuration fetched from SGX MMIO registers.
+#[derive(Debug)]
+pub(crate) struct GpuIdConfig {
+    /// GPU generation (should match static config).
+    pub(crate) gpu_gen: GpuGen,
+    /// GPU variant type (should match static config).
+    pub(crate) gpu_variant: GpuVariant,
+    /// GPU silicon revision.
+    pub(crate) gpu_rev: GpuRevision,
+    /// GPU silicon revision ID (firmware enum).
+    pub(crate) gpu_rev_id: GpuRevisionID,
+    /// Maximum number of dies supported.
+    pub(crate) max_dies: u32,
+    /// Total number of GPU clusters.
+    pub(crate) num_clusters: u32,
+    /// Maximum number of GPU cores per cluster.
+    pub(crate) num_cores: u32,
+    /// Number of frags per cluster.
+    pub(crate) num_frags: u32,
+    /// Number of GPs per cluster.
+    pub(crate) num_gps: u32,
+    /// Total number of active cores for the whole GPU.
+    pub(crate) total_active_cores: u32,
+    /// Mask of active cores per cluster.
+    pub(crate) core_masks: Vec<u32>,
+    /// Packed mask of all active cores.
+    pub(crate) core_masks_packed: Vec<u32>,
+}
+
+/// Configurable CS/AFR GPU power settings from the device tree.
+#[derive(Debug)]
+pub(crate) struct CsAfrPwrConfig {
+    /// GPU CS performance state list.
+    pub(crate) perf_states_cs: Vec<PState>,
+    /// GPU AFR performance state list.
+    pub(crate) perf_states_afr: Vec<PState>,
+
+    /// CS leakage coefficient per die.
+    pub(crate) leak_coef_cs: Vec<F32>,
+    /// AFR leakage coefficient per die.
+    pub(crate) leak_coef_afr: Vec<F32>,
+
+    /// Minimum voltage for the CS/AFR SRAM power domain in microvolts.
+    pub(crate) min_sram_microvolt: u32,
+}
+
+/// Configurable GPU power settings from the device tree.
+#[derive(Debug)]
+pub(crate) struct PwrConfig {
+    /// GPU performance state list.
+    pub(crate) perf_states: Vec<PState>,
+    /// GPU power zone list.
+    pub(crate) power_zones: Vec<PowerZone>,
+
+    /// Core leakage coefficient per cluster.
+    pub(crate) core_leak_coef: Vec<F32>,
+    /// SRAM leakage coefficient per cluster.
+    pub(crate) sram_leak_coef: Vec<F32>,
+
+    pub(crate) csafr: Option<CsAfrPwrConfig>,
+
+    /// Maximum total power of the GPU in milliwatts.
+    pub(crate) max_power_mw: u32,
+    /// Maximum frequency of the GPU in megahertz.
+    pub(crate) max_freq_mhz: u32,
+
+    /// Minimum performance state to start at.
+    pub(crate) perf_base_pstate: u32,
+    /// Maximum enabled performance state.
+    pub(crate) perf_max_pstate: u32,
+
+    /// Minimum voltage for the SRAM power domain in microvolts.
+    pub(crate) min_sram_microvolt: u32,
+
+    // Most of these fields are just named after Apple ADT property names and we don't fully
+    // understand them. They configure various power-related PID loops and filters.
+    /// Average power filter time constant in milliseconds.
+    pub(crate) avg_power_filter_tc_ms: u32,
+    /// Average power filter PID integral gain?
+    pub(crate) avg_power_ki_only: F32,
+    /// Average power filter PID proportional gain?
+    pub(crate) avg_power_kp: F32,
+    pub(crate) avg_power_min_duty_cycle: u32,
+    /// Average power target filter time constant in periods.
+    pub(crate) avg_power_target_filter_tc: u32,
+    /// "Fast die0" (temperature?) PID integral gain.
+    pub(crate) fast_die0_integral_gain: F32,
+    /// "Fast die0" (temperature?) PID proportional gain.
+    pub(crate) fast_die0_proportional_gain: F32,
+    pub(crate) fast_die0_prop_tgt_delta: u32,
+    pub(crate) fast_die0_release_temp: u32,
+    /// Delay from the fender (?) becoming idle to powerdown
+    pub(crate) fender_idle_off_delay_ms: u32,
+    /// Timeout from firmware early wake to sleep if no work was submitted (?)
+    pub(crate) fw_early_wake_timeout_ms: u32,
+    /// Delay from the GPU becoming idle to powerdown
+    pub(crate) idle_off_delay_ms: u32,
+    /// Percent?
+    pub(crate) perf_boost_ce_step: u32,
+    /// Minimum utilization before performance state is increased in %.
+    pub(crate) perf_boost_min_util: u32,
+    pub(crate) perf_filter_drop_threshold: u32,
+    /// Performance PID filter time constant? (periods?)
+    pub(crate) perf_filter_time_constant: u32,
+    /// Performance PID filter time constant 2? (periods?)
+    pub(crate) perf_filter_time_constant2: u32,
+    /// Performance PID integral gain.
+    pub(crate) perf_integral_gain: F32,
+    /// Performance PID integral gain 2 (?).
+    pub(crate) perf_integral_gain2: F32,
+    pub(crate) perf_integral_min_clamp: u32,
+    /// Performance PID proportional gain.
+    pub(crate) perf_proportional_gain: F32,
+    /// Performance PID proportional gain 2 (?).
+    pub(crate) perf_proportional_gain2: F32,
+    pub(crate) perf_reset_iters: u32,
+    /// Target GPU utilization for the performance controller in %.
+    pub(crate) perf_tgt_utilization: u32,
+    /// Power sampling period in milliseconds.
+    pub(crate) power_sample_period: u32,
+    /// PPM (?) filter time constant in milliseconds.
+    pub(crate) ppm_filter_time_constant_ms: u32,
+    /// PPM (?) filter PID integral gain.
+    pub(crate) ppm_ki: F32,
+    /// PPM (?) filter PID proportional gain.
+    pub(crate) ppm_kp: F32,
+    /// Power consumption filter time constant (periods?)
+    pub(crate) pwr_filter_time_constant: u32,
+    /// Power consumption filter PID integral gain.
+    pub(crate) pwr_integral_gain: F32,
+    pub(crate) pwr_integral_min_clamp: u32,
+    pub(crate) pwr_min_duty_cycle: u32,
+    pub(crate) pwr_proportional_gain: F32,
+    /// Power sample period in base clocks, used when not an integer number of ms
+    pub(crate) pwr_sample_period_aic_clks: u32,
+
+    pub(crate) se_engagement_criteria: i32,
+    pub(crate) se_filter_time_constant: u32,
+    pub(crate) se_filter_time_constant_1: u32,
+    pub(crate) se_inactive_threshold: u32,
+    pub(crate) se_ki: F32,
+    pub(crate) se_ki_1: F32,
+    pub(crate) se_kp: F32,
+    pub(crate) se_kp_1: F32,
+    pub(crate) se_reset_criteria: u32,
+}
+
+impl PwrConfig {
+    fn load_opp(
+        dev: &AsahiDevice,
+        name: &CStr,
+        cfg: &HwConfig,
+        is_main: bool,
+    ) -> Result<Vec<PState>> {
+        let mut perf_states = Vec::new();
+
+        let node = dev.of_node().ok_or(EIO)?;
+        let opps = node.parse_phandle(name, 0).ok_or(EIO)?;
+
+        for opp in opps.children() {
+            let freq_hz: u64 = opp.get_property(c_str!("opp-hz"))?;
+            let mut volt_uv: Vec<u32> = opp.get_property(c_str!("opp-microvolt"))?;
+            let pwr_uw: u32 = if is_main {
+                opp.get_property(c_str!("opp-microwatt"))?
+            } else {
+                0
+            };
+
+            let voltage_count = if is_main {
+                cfg.max_num_clusters
+            } else {
+                cfg.num_dies
+            };
+
+            if volt_uv.len() != voltage_count as usize {
+                dev_err!(
+                    dev,
+                    "Invalid opp-microvolt length (expected {}, got {})\n",
+                    voltage_count,
+                    volt_uv.len()
+                );
+                return Err(EINVAL);
+            }
+
+            volt_uv.iter_mut().for_each(|a| *a /= 1000);
+            let volt_mv = volt_uv;
+
+            let pwr_mw = pwr_uw / 1000;
+
+            perf_states.push(
+                PState {
+                    freq_hz: freq_hz.try_into()?,
+                    volt_mv,
+                    pwr_mw,
+                },
+                GFP_KERNEL,
+            )?;
+        }
+
+        if perf_states.is_empty() {
+            Err(EINVAL)
+        } else {
+            Ok(perf_states)
+        }
+    }
+
+    /// Load the GPU power configuration from the device tree.
+    pub(crate) fn load(dev: &AsahiDevice, cfg: &HwConfig) -> Result<PwrConfig> {
+        let perf_states = Self::load_opp(dev, c_str!("operating-points-v2"), cfg, true)?;
+        let node = dev.of_node().ok_or(EIO)?;
+
+        macro_rules! prop {
+            ($prop:expr, $default:expr) => {{
+                node.get_opt_property(c_str!($prop))
+                    .map_err(|e| {
+                        dev_err!(dev, "Error reading property {}: {:?}\n", $prop, e);
+                        e
+                    })?
+                    .unwrap_or($default)
+            }};
+            ($prop:expr) => {{
+                node.get_property(c_str!($prop)).map_err(|e| {
+                    dev_err!(dev, "Error reading property {}: {:?}\n", $prop, e);
+                    e
+                })?
+            }};
+        }
+
+        let pz_data = prop!("apple,power-zones", Vec::new());
+
+        if pz_data.len() > 3 * MAX_POWERZONES || pz_data.len() % 3 != 0 {
+            dev_err!(dev, "Invalid apple,power-zones value\n");
+            return Err(EINVAL);
+        }
+
+        let pz_count = pz_data.len() / 3;
+        let mut power_zones = Vec::new();
+        for i in (0..pz_count).step_by(3) {
+            power_zones.push(
+                PowerZone {
+                    target: pz_data[i],
+                    target_offset: pz_data[i + 1],
+                    filter_tc: pz_data[i + 2],
+                },
+                GFP_KERNEL,
+            )?;
+        }
+
+        let core_leak_coef: Vec<F32> = prop!("apple,core-leak-coef");
+        let sram_leak_coef: Vec<F32> = prop!("apple,sram-leak-coef");
+
+        if core_leak_coef.len() != cfg.max_num_clusters as usize {
+            dev_err!(dev, "Invalid apple,core-leak-coef\n");
+            return Err(EINVAL);
+        }
+        if sram_leak_coef.len() != cfg.max_num_clusters as usize {
+            dev_err!(dev, "Invalid apple,sram_leak_coef\n");
+            return Err(EINVAL);
+        }
+
+        let csafr = if cfg.has_csafr {
+            Some(CsAfrPwrConfig {
+                perf_states_cs: Self::load_opp(dev, c_str!("apple,cs-opp"), cfg, false)?,
+                perf_states_afr: Self::load_opp(dev, c_str!("apple,afr-opp"), cfg, false)?,
+                leak_coef_cs: prop!("apple,cs-leak-coef"),
+                leak_coef_afr: prop!("apple,afr-leak-coef"),
+                min_sram_microvolt: prop!("apple,csafr-min-sram-microvolt"),
+            })
+        } else {
+            None
+        };
+
+        let power_sample_period: u32 = prop!("apple,power-sample-period");
+
+        Ok(PwrConfig {
+            core_leak_coef,
+            sram_leak_coef,
+
+            max_power_mw: perf_states.iter().map(|a| a.pwr_mw).max().unwrap(),
+            max_freq_mhz: perf_states.iter().map(|a| a.freq_hz).max().unwrap() / 1_000_000,
+
+            perf_base_pstate: prop!("apple,perf-base-pstate", 1),
+            perf_max_pstate: perf_states.len() as u32 - 1,
+            min_sram_microvolt: prop!("apple,min-sram-microvolt"),
+
+            avg_power_filter_tc_ms: prop!("apple,avg-power-filter-tc-ms"),
+            avg_power_ki_only: prop!("apple,avg-power-ki-only"),
+            avg_power_kp: prop!("apple,avg-power-kp"),
+            avg_power_min_duty_cycle: prop!("apple,avg-power-min-duty-cycle"),
+            avg_power_target_filter_tc: prop!("apple,avg-power-target-filter-tc"),
+            fast_die0_integral_gain: prop!("apple,fast-die0-integral-gain"),
+            fast_die0_proportional_gain: prop!("apple,fast-die0-proportional-gain"),
+            fast_die0_prop_tgt_delta: prop!("apple,fast-die0-prop-tgt-delta", 0),
+            fast_die0_release_temp: prop!("apple,fast-die0-release-temp", 80),
+            fender_idle_off_delay_ms: prop!("apple,fender-idle-off-delay-ms", 40),
+            fw_early_wake_timeout_ms: prop!("apple,fw-early-wake-timeout-ms", 5),
+            idle_off_delay_ms: prop!("apple,idle-off-delay-ms", 2),
+            perf_boost_ce_step: prop!("apple,perf-boost-ce-step", 25),
+            perf_boost_min_util: prop!("apple,perf-boost-min-util", 100),
+            perf_filter_drop_threshold: prop!("apple,perf-filter-drop-threshold"),
+            perf_filter_time_constant2: prop!("apple,perf-filter-time-constant2"),
+            perf_filter_time_constant: prop!("apple,perf-filter-time-constant"),
+            perf_integral_gain2: prop!("apple,perf-integral-gain2"),
+            perf_integral_gain: prop!("apple,perf-integral-gain", f32!(7.8956833)),
+            perf_integral_min_clamp: prop!("apple,perf-integral-min-clamp"),
+            perf_proportional_gain2: prop!("apple,perf-proportional-gain2"),
+            perf_proportional_gain: prop!("apple,perf-proportional-gain", f32!(14.707963)),
+            perf_reset_iters: prop!("apple,perf-reset-iters", 6),
+            perf_tgt_utilization: prop!("apple,perf-tgt-utilization"),
+            power_sample_period,
+            ppm_filter_time_constant_ms: prop!("apple,ppm-filter-time-constant-ms"),
+            ppm_ki: prop!("apple,ppm-ki"),
+            ppm_kp: prop!("apple,ppm-kp"),
+            pwr_filter_time_constant: prop!("apple,pwr-filter-time-constant", 313),
+            pwr_integral_gain: prop!("apple,pwr-integral-gain", f32!(0.0202129)),
+            pwr_integral_min_clamp: prop!("apple,pwr-integral-min-clamp", 0),
+            pwr_min_duty_cycle: prop!("apple,pwr-min-duty-cycle"),
+            pwr_proportional_gain: prop!("apple,pwr-proportional-gain", f32!(5.2831855)),
+            pwr_sample_period_aic_clks: prop!(
+                "apple,pwr-sample-period-aic-clks",
+                cfg.base_clock_hz / 1000 * power_sample_period
+            ),
+            se_engagement_criteria: prop!("apple,se-engagement-criteria", -1),
+            se_filter_time_constant: prop!("apple,se-filter-time-constant", 9),
+            se_filter_time_constant_1: prop!("apple,se-filter-time-constant-1", 3),
+            se_inactive_threshold: prop!("apple,se-inactive-threshold", 2500),
+            se_ki: prop!("apple,se-ki", f32!(-50.0)),
+            se_ki_1: prop!("apple,se-ki-1", f32!(-100.0)),
+            se_kp: prop!("apple,se-kp", f32!(-5.0)),
+            se_kp_1: prop!("apple,se-kp-1", f32!(-10.0)),
+            se_reset_criteria: prop!("apple,se-reset-criteria", 50),
+
+            perf_states,
+            power_zones,
+            csafr,
+        })
+    }
+
+    pub(crate) fn min_frequency_khz(&self) -> u32 {
+        self.perf_states[self.perf_base_pstate as usize].freq_hz / 1000
+    }
+
+    pub(crate) fn max_frequency_khz(&self) -> u32 {
+        self.perf_states[self.perf_max_pstate as usize].freq_hz / 1000
+    }
+}
diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs
new file mode 100644
index 00000000000000..a124ea9c6ec29d
--- /dev/null
+++ b/drivers/gpu/drm/asahi/hw/t600x.rs
@@ -0,0 +1,160 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Hardware configuration for t600x (M1 Pro/Max/Ultra) platforms.
+
+use crate::f32;
+
+use super::*;
+
+const fn iomaps(mcc_count: usize, has_die1: bool) -> [Option<IOMapping>; 20] {
+    [
+        Some(IOMapping::new(0x404d00000, 0x1c000, 0x1c000, true)), // Fender
+        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
+        Some(IOMapping::new(0x28e104000, 0x4000, 0x4000, true)),   // AICSWInt
+        Some(IOMapping::new(0x404000000, 0x20000, 0x20000, true)), // RGX
+        None,                                                      // UVD
+        None,                                                      // unused
+        None,                                                      // DisplayUnderrunWA
+        Some(IOMapping::new(0x28e494000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
+        None,                                                      // PMPDoorbell
+        Some(IOMapping::new(0x404d80000, 0x8000, 0x8000, true)),   // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
+        Some(IOMapping::new(
+            0x200000000,
+            mcc_count * 0xd8000,
+            0xd8000,
+            true,
+        )), // MCache registers
+        None,                                                      // AICBankedRegisters
+        None,                                                      // PMGRScratch
+        Some(IOMapping::new(0x2643c4000, 0x1000, 0x1000, true)), // NIA Special agent idle register die 0
+        if has_die1 {
+            // NIA Special agent idle register die 1
+            Some(IOMapping::new(0x22643c4000, 0x1000, 0x1000, true))
+        } else {
+            None
+        },
+        None,                                                     // CRE registers
+        None,                                                     // Streaming codec registers
+        Some(IOMapping::new(0x28e3d0000, 0x1000, 0x1000, true)),  // ?
+        Some(IOMapping::new(0x28e3c0000, 0x1000, 0x1000, false)), // ?
+    ]
+}
+
+pub(crate) const HWCONFIG_T6002: super::HwConfig = HwConfig {
+    chip_id: 0x6002,
+    gpu_gen: GpuGen::G13,
+    gpu_variant: GpuVariant::D,
+    gpu_core: GpuCore::G13C,
+    gpu_feat_compat: 0,
+    gpu_feat_incompat: feat::incompat::MANDATORY_ZS_COMPRESSION,
+
+    base_clock_hz: 24_000_000,
+    uat_oas: 42,
+    num_dies: 2,
+    max_num_clusters: 8,
+    max_num_cores: 8,
+    max_num_frags: 8,
+    max_num_gps: 4,
+
+    preempt1_size: 0x540,
+    preempt2_size: 0x280,
+    preempt3_size: 0x20,
+    compute_preempt1_size: 0x3bd00,
+    clustering: Some(HwClusteringConfig {
+        meta1_blocksize: 0x44,
+        meta2_size: 0xc0 * 8,
+        meta3_size: 0x280 * 8,
+        meta4_size: 0x30 * 16,
+        max_splits: 16,
+    }),
+
+    render: HwRenderConfig {
+        tiling_control: 0xa540,
+    },
+
+    da: HwConfigA {
+        unk_87c: 900,
+        unk_8cc: 11000,
+        unk_e24: 125,
+    },
+    db: HwConfigB {
+        unk_4e0: 4,
+        unk_534: 1,
+        unk_ab8: 0x2084,
+        unk_abc: 0x80,
+        unk_b30: 0,
+    },
+    shared1_tab: &[
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    ],
+    shared1_a4: 0xffff,
+    shared2_tab: &[-1, -1, -1, -1, 0x2aa, 0xaaa, -1, -1, 0, 0],
+    shared2_unk_508: 0xcc00001,
+    shared2_curves: None,
+    shared3_unk: 0,
+    shared3_tab: &[],
+    unk_hws2_0: 0,
+    unk_hws2_4: None,
+    unk_hws2_24: 0,
+    global_unk_54: 0xffff,
+    sram_k: f32!(1.02),
+    unk_coef_a: &[
+        &f32!([9.838]),
+        &f32!([9.819]),
+        &f32!([9.826]),
+        &f32!([9.799]),
+        &f32!([9.799]),
+        &f32!([9.826]),
+        &f32!([9.819]),
+        &f32!([9.838]),
+    ],
+    unk_coef_b: &[
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+        &f32!([13.0]),
+    ],
+    global_tab: Some(&[
+        0, 1, 2, 1, 1, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 1, 90, 75, 1, 1,
+    ]),
+    has_csafr: false,
+    fast_sensor_mask: [0x8080808080808080, 0],
+    fast_sensor_mask_alt: [0x9090909090909090, 0],
+    fast_die0_sensor_present: 0xff,
+    io_mappings: &iomaps(16, true),
+    sram_base: None,
+    sram_size: None,
+};
+
+pub(crate) const HWCONFIG_T6001: super::HwConfig = HwConfig {
+    chip_id: 0x6001,
+    gpu_variant: GpuVariant::C,
+    gpu_core: GpuCore::G13C,
+
+    num_dies: 1,
+    max_num_clusters: 4,
+    fast_sensor_mask: [0x80808080, 0],
+    fast_sensor_mask_alt: [0x90909090, 0],
+    fast_die0_sensor_present: 0x0f,
+    io_mappings: &iomaps(8, false),
+    ..HWCONFIG_T6002
+};
+
+pub(crate) const HWCONFIG_T6000: super::HwConfig = HwConfig {
+    chip_id: 0x6000,
+    gpu_variant: GpuVariant::S,
+    gpu_core: GpuCore::G13S,
+
+    max_num_clusters: 2,
+    fast_sensor_mask: [0x8080, 0],
+    fast_sensor_mask_alt: [0x9090, 0],
+    fast_die0_sensor_present: 0x03,
+    io_mappings: &iomaps(4, false),
+    ..HWCONFIG_T6001
+};
diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
new file mode 100644
index 00000000000000..f1c0151eb63f5c
--- /dev/null
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -0,0 +1,167 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Hardware configuration for t600x (M1 Pro/Max/Ultra) platforms.
+
+use crate::f32;
+
+use super::*;
+
+const fn iomaps(mcc_count: usize) -> [Option<IOMapping>; 24] {
+    [
+        Some(IOMapping::new(0x404d00000, 0x144000, 0x144000, true)), // Fender
+        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),    // AICTimer
+        Some(IOMapping::new(0x28e106000, 0x4000, 0x4000, true)),     // AICSWInt
+        Some(IOMapping::new(0x404000000, 0x20000, 0x20000, true)),   // RGX
+        None,                                                        // UVD
+        None,                                                        // unused
+        None,                                                        // DisplayUnderrunWA
+        Some(IOMapping::new(0x28e478000, 0x4000, 0x4000, false)), // AnalogTempSensorControllerRegs
+        None,                                                     // PMPDoorbell
+        Some(IOMapping::new(0x404e08000, 0x8000, 0x8000, true)),  // MetrologySensorRegs
+        None,                                                     // GMGIFAFRegs
+        Some(IOMapping::new(
+            0x200000000,
+            mcc_count * 0xd8000,
+            0xd8000,
+            true,
+        )), // MCache registers
+        Some(IOMapping::new(0x28e118000, 0x4000, 0x4000, false)), // AICBankedRegisters
+        None,                                                     // PMGRScratch
+        None, // NIA Special agent idle register die 0
+        None, // NIA Special agent idle register die 1
+        None, // CRE registers
+        None, // Streaming codec registers
+        Some(IOMapping::new(0x28e3d0000, 0x4000, 0x4000, true)), // ?
+        Some(IOMapping::new(0x28e3c0000, 0x4000, 0x4000, false)), // ?
+        Some(IOMapping::new(0x28e3d8000, 0x4000, 0x4000, true)), // ?
+        Some(IOMapping::new(0x404eac000, 0x4000, 0x4000, true)), // ?
+        None,
+        None,
+    ]
+}
+
+// TODO: Tentative
+pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
+    chip_id: 0x6022,
+    gpu_gen: GpuGen::G14,
+    gpu_variant: GpuVariant::D,
+    gpu_core: GpuCore::G14C,
+    gpu_feat_compat: 0,
+    gpu_feat_incompat: feat::incompat::MANDATORY_ZS_COMPRESSION,
+
+    base_clock_hz: 24_000_000,
+    uat_oas: 42,
+    num_dies: 2,
+    max_num_clusters: 8,
+    max_num_cores: 10,
+    max_num_frags: 10,
+    max_num_gps: 4,
+
+    preempt1_size: 0x540,
+    preempt2_size: 0x280,
+    preempt3_size: 0x40,
+    compute_preempt1_size: 0x25980, // CHECK for T6022
+    clustering: Some(HwClusteringConfig {
+        meta1_blocksize: 0x44,
+        meta2_size: 0xc0 * 8,
+        meta3_size: 0x280 * 8,
+        meta4_size: 0x10 * 64,
+        max_splits: 64,
+    }),
+
+    render: HwRenderConfig {
+        tiling_control: 0x180340,
+    },
+
+    da: HwConfigA {
+        unk_87c: 500,
+        unk_8cc: 11000,
+        unk_e24: 125,
+    },
+    db: HwConfigB {
+        unk_4e0: 4,
+        unk_534: 0,
+        unk_ab8: 0, // Unused
+        unk_abc: 0, // Unused
+        unk_b30: 0,
+    },
+    shared1_tab: &[
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    ],
+    shared1_a4: 0,
+    shared2_tab: &[0x800, 0x1555, -1, -1, -1, -1, -1, -1, 0xaaaaa, 0],
+    shared2_unk_508: 0xc00007,
+    shared2_curves: Some(HwConfigShared2Curves {
+        t1_coef: 11000,
+        t2: &[
+            0xf07, 0x4c0, 0x680, 0x8c0, 0xa80, 0xc40, 0xd80, 0xec0, 0xf40,
+        ],
+        t3_coefs: &[0, 20, 27, 36, 43, 50, 55, 60, 62],
+        t3_scales: &[9, 3209, 10400],
+    }),
+    shared3_unk: 8,
+    shared3_tab: &[
+        125, 125, 125, 125, 125, 125, 125, 125, 7500, 125, 125, 125, 125, 125, 125, 125,
+    ],
+    unk_hws2_0: 700,
+    unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.7, 0.9])),
+    unk_hws2_24: 6,
+    global_unk_54: 4000,
+    sram_k: f32!(1.02),
+    unk_coef_a: &[
+        &f32!([0.0, 8.2, 0.0, 6.9, 6.9]),
+        &f32!([0.0, 0.0, 0.0, 6.9, 6.9]),
+        &f32!([0.0, 8.2, 0.0, 6.9, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 6.9, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 6.9, 0.0]),
+        &f32!([0.0, 8.2, 0.0, 6.9, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 6.9, 6.9]),
+        &f32!([0.0, 8.2, 0.0, 6.9, 6.9]),
+    ],
+    unk_coef_b: &[
+        &f32!([0.0, 9.0, 0.0, 8.0, 8.0]),
+        &f32!([0.0, 0.0, 0.0, 8.0, 8.0]),
+        &f32!([0.0, 9.0, 0.0, 8.0, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 8.0, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 8.0, 0.0]),
+        &f32!([0.0, 9.0, 0.0, 8.0, 0.0]),
+        &f32!([0.0, 0.0, 0.0, 8.0, 8.0]),
+        &f32!([0.0, 9.0, 0.0, 8.0, 8.0]),
+    ],
+    global_tab: Some(&[
+        0, 2, 2, 1, 1, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 1, 90, 75, 1, 1,
+    ]),
+    has_csafr: true,
+    fast_sensor_mask: [0x40005000c000d00, 0x40005000c000d00],
+    fast_sensor_mask_alt: [0x140015001d001d00, 0x140015001d001d00],
+    fast_die0_sensor_present: 0, // Unused
+    io_mappings: &iomaps(16),
+    sram_base: Some(0x404d60000),
+    sram_size: Some(0x20000),
+};
+
+pub(crate) const HWCONFIG_T6021: super::HwConfig = HwConfig {
+    chip_id: 0x6021,
+    gpu_variant: GpuVariant::C,
+    gpu_core: GpuCore::G14C,
+
+    num_dies: 1,
+    max_num_clusters: 4,
+    fast_sensor_mask: [0x40005000c000d00, 0],
+    fast_sensor_mask_alt: [0x140015001d001d00, 0],
+    io_mappings: &iomaps(8),
+    ..HWCONFIG_T6022
+};
+
+pub(crate) const HWCONFIG_T6020: super::HwConfig = HwConfig {
+    chip_id: 0x6020,
+    gpu_variant: GpuVariant::S,
+    gpu_core: GpuCore::G14S,
+
+    max_num_clusters: 2,
+    fast_sensor_mask: [0xc000d00, 0],
+    fast_sensor_mask_alt: [0x1d001d00, 0],
+    io_mappings: &iomaps(4),
+    ..HWCONFIG_T6021
+};
diff --git a/drivers/gpu/drm/asahi/hw/t8103.rs b/drivers/gpu/drm/asahi/hw/t8103.rs
new file mode 100644
index 00000000000000..c8d6ae868a3175
--- /dev/null
+++ b/drivers/gpu/drm/asahi/hw/t8103.rs
@@ -0,0 +1,93 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Hardware configuration for t8103 platforms (M1).
+
+use crate::f32;
+
+use super::*;
+
+pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
+    chip_id: 0x8103,
+    gpu_gen: GpuGen::G13,
+    gpu_variant: GpuVariant::G,
+    gpu_core: GpuCore::G13G,
+    gpu_feat_compat: 0,
+    gpu_feat_incompat: 0,
+
+    base_clock_hz: 24_000_000,
+    uat_oas: 40,
+    num_dies: 1,
+    max_num_clusters: 1,
+    max_num_cores: 8,
+    max_num_frags: 8,
+    max_num_gps: 4,
+
+    preempt1_size: 0x540,
+    preempt2_size: 0x280,
+    preempt3_size: 0x20,
+    compute_preempt1_size: 0x7f80,
+    clustering: None,
+
+    render: HwRenderConfig {
+        // bit 0: disable clustering (always)
+        tiling_control: 0xa041,
+    },
+
+    da: HwConfigA {
+        unk_87c: -220,
+        unk_8cc: 9880,
+        unk_e24: 112,
+    },
+    db: HwConfigB {
+        unk_4e0: 0,
+        unk_534: 0,
+        unk_ab8: 0x48,
+        unk_abc: 0x8,
+        unk_b30: 0,
+    },
+    shared1_tab: &[
+        -1, 0x7282, 0x50ea, 0x370a, 0x25be, 0x1c1f, 0x16fb, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    ],
+    shared1_a4: 0xffff,
+    shared2_tab: &[0x800, 0x1555, -1, -1, -1, -1, -1, -1, 0, 0],
+    shared2_unk_508: 0xc00007,
+    shared2_curves: None,
+    shared3_unk: 0,
+    shared3_tab: &[],
+    unk_hws2_0: 0,
+    unk_hws2_4: None,
+    unk_hws2_24: 0,
+    global_unk_54: 0xffff,
+    sram_k: f32!(1.02),
+    unk_coef_a: &[],
+    unk_coef_b: &[],
+    global_tab: None,
+    has_csafr: false,
+    fast_sensor_mask: [0x12, 0],
+    fast_sensor_mask_alt: [0x12, 0],
+    fast_die0_sensor_present: 0x01,
+    io_mappings: &[
+        Some(IOMapping::new(0x204d00000, 0x1c000, 0x1c000, true)), // Fender
+        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
+        Some(IOMapping::new(0x23b104000, 0x4000, 0x4000, true)),   // AICSWInt
+        Some(IOMapping::new(0x204000000, 0x20000, 0x20000, true)), // RGX
+        None,                                                      // UVD
+        None,                                                      // unused
+        None,                                                      // DisplayUnderrunWA
+        Some(IOMapping::new(0x23b2e8000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
+        Some(IOMapping::new(0x23bc00000, 0x1000, 0x1000, true)),   // PMPDoorbell
+        Some(IOMapping::new(0x204d80000, 0x5000, 0x5000, true)),   // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
+        Some(IOMapping::new(0x200000000, 0xd6400, 0xd6400, true)), // MCache registers
+        None,                                                      // AICBankedRegisters
+        Some(IOMapping::new(0x23b738000, 0x1000, 0x1000, true)),   // PMGRScratch
+        None, // NIA Special agent idle register die 0
+        None, // NIA Special agent idle register die 1
+        None, // CRE registers
+        None, // Streaming codec registers
+        None, //
+        None, //
+    ],
+    sram_base: None,
+    sram_size: None,
+};
diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs
new file mode 100644
index 00000000000000..206f61180af8e3
--- /dev/null
+++ b/drivers/gpu/drm/asahi/hw/t8112.rs
@@ -0,0 +1,106 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Hardware configuration for t8112 platforms (M2).
+
+use crate::f32;
+
+use super::*;
+
+pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
+    chip_id: 0x8112,
+    gpu_gen: GpuGen::G14,
+    gpu_variant: GpuVariant::G,
+    gpu_core: GpuCore::G14G,
+    gpu_feat_compat: 0,
+    gpu_feat_incompat: 0,
+
+    base_clock_hz: 24_000_000,
+    uat_oas: 40,
+    num_dies: 1,
+    max_num_clusters: 1,
+    max_num_cores: 10,
+    max_num_frags: 10,
+    max_num_gps: 4,
+
+    preempt1_size: 0x540,
+    preempt2_size: 0x280,
+    preempt3_size: 0x20,
+    compute_preempt1_size: 0x10000, // TODO: Check
+    clustering: None,
+
+    render: HwRenderConfig {
+        // TODO: this is unused here, may be present in newer FW
+        tiling_control: 0xa041,
+    },
+
+    da: HwConfigA {
+        unk_87c: 900,
+        unk_8cc: 11000,
+        unk_e24: 125,
+    },
+    db: HwConfigB {
+        unk_4e0: 4,
+        unk_534: 0,
+        unk_ab8: 0x2048,
+        unk_abc: 0x4000,
+        unk_b30: 1,
+    },
+    shared1_tab: &[
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+        0xffff, 0xffff, 0xffff, 0xffff, 0xffff,
+    ],
+    shared1_a4: 0,
+    shared2_tab: &[-1, -1, -1, -1, -1, -1, -1, -1, 0xaa5aa, 0],
+    shared2_unk_508: 0xc00000,
+    shared2_curves: Some(HwConfigShared2Curves {
+        t1_coef: 7200,
+        t2: &[
+            0xf07, 0x4c0, 0x6c0, 0x8c0, 0xac0, 0xc40, 0xdc0, 0xec0, 0xf80,
+        ],
+        t3_coefs: &[0, 20, 28, 36, 44, 50, 56, 60, 63],
+        t3_scales: &[9, 3209, 10400],
+    }),
+    shared3_unk: 5,
+    shared3_tab: &[
+        10700, 10700, 10700, 10700, 10700, 6000, 1000, 1000, 1000, 10700, 10700, 10700, 10700,
+        10700, 10700, 10700,
+    ],
+    unk_hws2_0: 0,
+    unk_hws2_4: None,
+    unk_hws2_24: 0,
+    global_unk_54: 0xffff,
+
+    sram_k: f32!(1.02),
+    // 13.2: last coef changed from 6.6 to 5.3, assuming that was a fix we can backport
+    unk_coef_a: &[&f32!([0.0, 0.0, 0.0, 0.0, 5.3, 0.0, 5.3, /*6.6*/ 5.3])],
+    unk_coef_b: &[&f32!([0.0, 0.0, 0.0, 0.0, 5.3, 0.0, 5.3, /*6.6*/ 5.3])],
+    global_tab: None,
+    has_csafr: false,
+    fast_sensor_mask: [0x6800, 0],
+    fast_sensor_mask_alt: [0x6800, 0],
+    fast_die0_sensor_present: 0x02,
+    io_mappings: &[
+        Some(IOMapping::new(0x204d00000, 0x14000, 0x14000, true)), // Fender
+        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
+        Some(IOMapping::new(0x23b0c4000, 0x4000, 0x4000, true)),   // AICSWInt
+        Some(IOMapping::new(0x204000000, 0x20000, 0x20000, true)), // RGX
+        None,                                                      // UVD
+        None,                                                      // unused
+        None,                                                      // DisplayUnderrunWA
+        Some(IOMapping::new(0x23b2c0000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
+        None,                                                      // PMPDoorbell
+        Some(IOMapping::new(0x204d80000, 0x8000, 0x8000, true)),   // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
+        Some(IOMapping::new(0x200000000, 0xd6400, 0xd6400, true)), // MCache registers
+        None,                                                      // AICBankedRegisters
+        None,                                                      // PMGRScratch
+        None, // NIA Special agent idle register die 0
+        None, // NIA Special agent idle register die 1
+        Some(IOMapping::new(0x204e00000, 0x10000, 0x10000, true)), // CRE registers
+        Some(IOMapping::new(0x27d050000, 0x4000, 0x4000, true)), // Streaming codec registers
+        Some(IOMapping::new(0x23b3d0000, 0x1000, 0x1000, true)), //
+        Some(IOMapping::new(0x23b3c0000, 0x1000, 0x1000, true)), //
+    ],
+    sram_base: None,
+    sram_size: None,
+};
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
new file mode 100644
index 00000000000000..0d23208fdbe3ea
--- /dev/null
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -0,0 +1,901 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![allow(clippy::unusual_byte_groupings)]
+
+//! GPU initialization data builder.
+//!
+//! The root of all interaction between the GPU firmware and the host driver is a complex set of
+//! nested structures that we call InitData. This includes both GPU hardware/firmware configuration
+//! and the pointers to the ring buffers and global data fields that are used for communication at
+//! runtime.
+//!
+//! Many of these structures are poorly understood, so there are lots of hardcoded unknown values
+//! derived from observing the InitData structures that macOS generates.
+
+use crate::f32;
+use crate::fw::initdata::*;
+use crate::fw::types::*;
+use crate::{driver::AsahiDevice, gem, gpu, hw, mmu};
+use alloc::vec::Vec;
+use kernel::alloc::{box_ext::BoxExt, flags::*, vec_ext::VecExt};
+use kernel::error::{Error, Result};
+use kernel::macros::versions;
+use kernel::{init, init::Init, try_init};
+
+/// Builder helper for the global GPU InitData.
+#[versions(AGX)]
+pub(crate) struct InitDataBuilder<'a> {
+    dev: &'a AsahiDevice,
+    alloc: &'a mut gpu::KernelAllocators,
+    cfg: &'static hw::HwConfig,
+    dyncfg: &'a hw::DynConfig,
+}
+
+#[versions(AGX)]
+impl<'a> InitDataBuilder::ver<'a> {
+    /// Create a new InitData builder
+    pub(crate) fn new(
+        dev: &'a AsahiDevice,
+        alloc: &'a mut gpu::KernelAllocators,
+        cfg: &'static hw::HwConfig,
+        dyncfg: &'a hw::DynConfig,
+    ) -> InitDataBuilder::ver<'a> {
+        InitDataBuilder::ver {
+            dev,
+            alloc,
+            cfg,
+            dyncfg,
+        }
+    }
+
+    /// Create the HwDataShared1 structure, which is used in two places in InitData.
+    fn hw_shared1(cfg: &'static hw::HwConfig) -> impl Init<raw::HwDataShared1> {
+        init::chain(
+            init!(raw::HwDataShared1 {
+                unk_a4: cfg.shared1_a4,
+                ..Zeroable::zeroed()
+            }),
+            |ret| {
+                for (i, val) in cfg.shared1_tab.iter().enumerate() {
+                    ret.table[i] = *val;
+                }
+                Ok(())
+            },
+        )
+    }
+
+    fn init_curve(
+        curve: &mut raw::HwDataShared2Curve,
+        unk_0: u32,
+        unk_4: u32,
+        t1: &[u16],
+        t2: &[i16],
+        t3: &[Vec<i32>],
+    ) {
+        curve.unk_0 = unk_0;
+        curve.unk_4 = unk_4;
+        (*curve.t1)[..t1.len()].copy_from_slice(t1);
+        (*curve.t1)[t1.len()..].fill(t1[0]);
+        (*curve.t2)[..t2.len()].copy_from_slice(t2);
+        (*curve.t2)[t2.len()..].fill(t2[0]);
+        for (i, a) in curve.t3.iter_mut().enumerate() {
+            a.fill(0x3ffffff);
+            if i < t3.len() {
+                let b = &t3[i];
+                (**a)[..b.len()].copy_from_slice(b);
+            }
+        }
+    }
+
+    /// Create the HwDataShared2 structure, which is used in two places in InitData.
+    fn hw_shared2(
+        cfg: &'static hw::HwConfig,
+        dyncfg: &'a hw::DynConfig,
+    ) -> impl Init<raw::HwDataShared2, Error> + 'a {
+        init::chain(
+            try_init!(raw::HwDataShared2 {
+                unk_28: Array::new([0xff; 16]),
+                g14: Default::default(),
+                unk_508: cfg.shared2_unk_508,
+                ..Zeroable::zeroed()
+            }),
+            |ret| {
+                for (i, val) in cfg.shared2_tab.iter().enumerate() {
+                    ret.table[i] = *val;
+                }
+
+                let curve_cfg = match cfg.shared2_curves.as_ref() {
+                    None => return Ok(()),
+                    Some(a) => a,
+                };
+
+                let mut t1 = Vec::new();
+                let mut t3 = Vec::new();
+
+                for _ in 0..curve_cfg.t3_scales.len() {
+                    t3.try_push(Vec::new())?;
+                }
+
+                for (i, ps) in dyncfg.pwr.perf_states.iter().enumerate() {
+                    let t3_coef = curve_cfg.t3_coefs[i];
+                    if t3_coef == 0 {
+                        t1.try_push(0xffff)?;
+                        for j in t3.iter_mut() {
+                            j.try_push(0x3ffffff)?;
+                        }
+                        continue;
+                    }
+
+                    let f_mhz = (ps.freq_hz / 1000) as u64;
+                    let v_max = ps.max_volt_mv() as u64;
+
+                    t1.try_push(
+                        (1000000000 * (curve_cfg.t1_coef as u64) / (f_mhz * v_max))
+                            .try_into()
+                            .unwrap(),
+                        GFP_KERNEL,
+                    )?;
+
+                    for (j, scale) in curve_cfg.t3_scales.iter().enumerate() {
+                        t3[j].try_push(
+                            (t3_coef as u64 * 1000000000 * *scale as u64 / (f_mhz * v_max * 6))
+                                .try_into()
+                                .unwrap(),
+                        )?;
+                    }
+                }
+
+                ret.g14.unk_14 = 0x6000000;
+                Self::init_curve(
+                    &mut ret.g14.curve1,
+                    0,
+                    0x20000000,
+                    &[0xffff],
+                    &[0x0f07],
+                    &[],
+                );
+                Self::init_curve(&mut ret.g14.curve2, 7, 0x80000000, &t1, curve_cfg.t2, &t3);
+
+                Ok(())
+            },
+        )
+    }
+
+    /// Create the HwDataShared3 structure, which is used in two places in InitData.
+    fn hw_shared3(cfg: &'static hw::HwConfig) -> impl Init<raw::HwDataShared3> {
+        init::chain(init::zeroed::<raw::HwDataShared3>(), |ret| {
+            if !cfg.shared3_tab.is_empty() {
+                ret.unk_0 = 1;
+                ret.unk_4 = 500;
+                ret.unk_8 = cfg.shared3_unk;
+                ret.table.copy_from_slice(cfg.shared3_tab);
+                ret.unk_4c = 1;
+            }
+            Ok(())
+        })
+    }
+
+    /// Create an unknown T81xx-specific data structure.
+    fn t81xx_data(
+        cfg: &'static hw::HwConfig,
+        dyncfg: &'a hw::DynConfig,
+    ) -> impl Init<raw::T81xxData> {
+        let _perf_max_pstate = dyncfg.pwr.perf_max_pstate;
+
+        init::chain(init::zeroed::<raw::T81xxData>(), move |_ret| {
+            match cfg.chip_id {
+                0x8103 | 0x8112 => {
+                    #[ver(V < V13_3)]
+                    {
+                        _ret.unk_d8c = 0x80000000;
+                        _ret.unk_d90 = 4;
+                        _ret.unk_d9c = f32!(0.6);
+                        _ret.unk_da4 = f32!(0.4);
+                        _ret.unk_dac = f32!(0.38552);
+                        _ret.unk_db8 = f32!(65536.0);
+                        _ret.unk_dbc = f32!(13.56);
+                        _ret.max_pstate_scaled = 100 * _perf_max_pstate;
+                    }
+                }
+                _ => (),
+            }
+            Ok(())
+        })
+    }
+
+    /// Create the HwDataA structure. This mostly contains power-related configuration.
+    fn hwdata_a(&mut self) -> Result<GpuObject<HwDataA::ver>> {
+        let pwr = &self.dyncfg.pwr;
+        let period_ms = pwr.power_sample_period;
+        let period_s = F32::from(period_ms) / f32!(1000.0);
+        let ppm_filter_tc_periods = pwr.ppm_filter_time_constant_ms / period_ms;
+        #[ver(V >= V13_0B4)]
+        let ppm_filter_tc_ms_rounded = ppm_filter_tc_periods * period_ms;
+        let ppm_filter_a = f32!(1.0) / ppm_filter_tc_periods.into();
+        let perf_filter_a = f32!(1.0) / pwr.perf_filter_time_constant.into();
+        let perf_filter_a2 = f32!(1.0) / pwr.perf_filter_time_constant2.into();
+        let avg_power_target_filter_a = f32!(1.0) / pwr.avg_power_target_filter_tc.into();
+        let avg_power_filter_tc_periods = pwr.avg_power_filter_tc_ms / period_ms;
+        #[ver(V >= V13_0B4)]
+        let avg_power_filter_tc_ms_rounded = avg_power_filter_tc_periods * period_ms;
+        let avg_power_filter_a = f32!(1.0) / avg_power_filter_tc_periods.into();
+        let pwr_filter_a = f32!(1.0) / pwr.pwr_filter_time_constant.into();
+
+        let base_ps = pwr.perf_base_pstate;
+        let base_ps_scaled = 100 * base_ps;
+        let max_ps = pwr.perf_max_pstate;
+        let max_ps_scaled = 100 * max_ps;
+        let boost_ps_count = max_ps - base_ps;
+
+        #[allow(unused_variables)]
+        let base_clock_khz = self.cfg.base_clock_hz / 1000;
+        let clocks_per_period = pwr.pwr_sample_period_aic_clks;
+
+        #[allow(unused_variables)]
+        let clocks_per_period_coarse = self.cfg.base_clock_hz / 1000 * pwr.power_sample_period;
+
+        self.alloc.private.new_init(init::zeroed(), |_inner, _ptr| {
+            let cfg = &self.cfg;
+            let dyncfg = &self.dyncfg;
+            init::chain(
+                try_init!(raw::HwDataA::ver {
+                    clocks_per_period: clocks_per_period,
+                    #[ver(V >= V13_0B4)]
+                    clocks_per_period_2: clocks_per_period,
+                    pwr_status: AtomicU32::new(4),
+                    unk_10: f32!(1.0),
+                    actual_pstate: 1,
+                    tgt_pstate: 1,
+                    base_pstate_scaled: base_ps_scaled,
+                    unk_40: 1,
+                    max_pstate_scaled: max_ps_scaled,
+                    min_pstate_scaled: 100,
+                    unk_64c: 625,
+                    pwr_filter_a_neg: f32!(1.0) - pwr_filter_a,
+                    pwr_filter_a: pwr_filter_a,
+                    pwr_integral_gain: pwr.pwr_integral_gain,
+                    pwr_integral_min_clamp: pwr.pwr_integral_min_clamp.into(),
+                    max_power_1: pwr.max_power_mw.into(),
+                    pwr_proportional_gain: pwr.pwr_proportional_gain,
+                    pwr_pstate_related_k: -F32::from(max_ps_scaled) / pwr.max_power_mw.into(),
+                    pwr_pstate_max_dc_offset: pwr.pwr_min_duty_cycle as i32 - max_ps_scaled as i32,
+                    max_pstate_scaled_2: max_ps_scaled,
+                    max_power_2: pwr.max_power_mw,
+                    max_pstate_scaled_3: max_ps_scaled,
+                    ppm_filter_tc_periods_x4: ppm_filter_tc_periods * 4,
+                    ppm_filter_a_neg: f32!(1.0) - ppm_filter_a,
+                    ppm_filter_a: ppm_filter_a,
+                    ppm_ki_dt: pwr.ppm_ki * period_s,
+                    unk_6fc: f32!(65536.0),
+                    ppm_kp: pwr.ppm_kp,
+                    pwr_min_duty_cycle: pwr.pwr_min_duty_cycle,
+                    max_pstate_scaled_4: max_ps_scaled,
+                    unk_71c: f32!(0.0),
+                    max_power_3: pwr.max_power_mw,
+                    cur_power_mw_2: 0x0,
+                    ppm_filter_tc_ms: pwr.ppm_filter_time_constant_ms,
+                    #[ver(V >= V13_0B4)]
+                    ppm_filter_tc_clks: ppm_filter_tc_ms_rounded * base_clock_khz,
+                    perf_tgt_utilization: pwr.perf_tgt_utilization,
+                    perf_boost_min_util: pwr.perf_boost_min_util,
+                    perf_boost_ce_step: pwr.perf_boost_ce_step,
+                    perf_reset_iters: pwr.perf_reset_iters,
+                    unk_774: 6,
+                    unk_778: 1,
+                    perf_filter_drop_threshold: pwr.perf_filter_drop_threshold,
+                    perf_filter_a_neg: f32!(1.0) - perf_filter_a,
+                    perf_filter_a2_neg: f32!(1.0) - perf_filter_a2,
+                    perf_filter_a: perf_filter_a,
+                    perf_filter_a2: perf_filter_a2,
+                    perf_ki: pwr.perf_integral_gain,
+                    perf_ki2: pwr.perf_integral_gain2,
+                    perf_integral_min_clamp: pwr.perf_integral_min_clamp.into(),
+                    unk_79c: f32!(95.0),
+                    perf_kp: pwr.perf_proportional_gain,
+                    perf_kp2: pwr.perf_proportional_gain2,
+                    boost_state_unk_k: F32::from(boost_ps_count) / f32!(0.95),
+                    base_pstate_scaled_2: base_ps_scaled,
+                    max_pstate_scaled_5: max_ps_scaled,
+                    base_pstate_scaled_3: base_ps_scaled,
+                    perf_tgt_utilization_2: pwr.perf_tgt_utilization,
+                    base_pstate_scaled_4: base_ps_scaled,
+                    unk_7fc: f32!(65536.0),
+                    pwr_min_duty_cycle_2: pwr.pwr_min_duty_cycle.into(),
+                    max_pstate_scaled_6: max_ps_scaled.into(),
+                    max_freq_mhz: pwr.max_freq_mhz,
+                    pwr_min_duty_cycle_3: pwr.pwr_min_duty_cycle,
+                    min_pstate_scaled_4: f32!(100.0),
+                    max_pstate_scaled_7: max_ps_scaled,
+                    unk_alpha_neg: f32!(0.8),
+                    unk_alpha: f32!(0.2),
+                    fast_die0_sensor_mask: U64(cfg.fast_sensor_mask[0]),
+                    fast_die0_release_temp_cc: 100 * pwr.fast_die0_release_temp,
+                    unk_87c: cfg.da.unk_87c,
+                    unk_880: 0x4,
+                    unk_894: f32!(1.0),
+
+                    fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
+                    unk_8a8: f32!(65536.0),
+                    fast_die0_kp: pwr.fast_die0_proportional_gain,
+                    pwr_min_duty_cycle_4: pwr.pwr_min_duty_cycle,
+                    max_pstate_scaled_8: max_ps_scaled,
+                    max_pstate_scaled_9: max_ps_scaled,
+                    fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
+                    unk_8cc: cfg.da.unk_8cc,
+                    max_pstate_scaled_10: max_ps_scaled,
+                    max_pstate_scaled_11: max_ps_scaled,
+                    unk_c2c: 1,
+                    power_zone_count: pwr.power_zones.len() as u32,
+                    max_power_4: pwr.max_power_mw,
+                    max_power_5: pwr.max_power_mw,
+                    max_power_6: pwr.max_power_mw,
+                    avg_power_target_filter_a_neg: f32!(1.0) - avg_power_target_filter_a,
+                    avg_power_target_filter_a: avg_power_target_filter_a,
+                    avg_power_target_filter_tc_x4: 4 * pwr.avg_power_target_filter_tc,
+                    avg_power_target_filter_tc_xperiod: period_ms * pwr.avg_power_target_filter_tc,
+                    #[ver(V >= V13_0B4)]
+                    avg_power_target_filter_tc_clks: period_ms
+                        * pwr.avg_power_target_filter_tc
+                        * base_clock_khz,
+                    avg_power_filter_tc_periods_x4: 4 * avg_power_filter_tc_periods,
+                    avg_power_filter_a_neg: f32!(1.0) - avg_power_filter_a,
+                    avg_power_filter_a: avg_power_filter_a,
+                    avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
+                    unk_d20: f32!(65536.0),
+                    avg_power_kp: pwr.avg_power_kp,
+                    avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
+                    max_pstate_scaled_12: max_ps_scaled,
+                    max_pstate_scaled_13: max_ps_scaled,
+                    max_power_7: pwr.max_power_mw.into(),
+                    max_power_8: pwr.max_power_mw,
+                    avg_power_filter_tc_ms: pwr.avg_power_filter_tc_ms,
+                    #[ver(V >= V13_0B4)]
+                    avg_power_filter_tc_clks: avg_power_filter_tc_ms_rounded * base_clock_khz,
+                    max_pstate_scaled_14: max_ps_scaled,
+                    t81xx_data <- Self::t81xx_data(cfg, dyncfg),
+                    #[ver(V >= V13_0B4)]
+                    unk_e10_0 <- {
+                        let filter_a = f32!(1.0) / pwr.se_filter_time_constant.into();
+                        let filter_1_a = f32!(1.0) / pwr.se_filter_time_constant_1.into();
+                        try_init!(raw::HwDataA130Extra {
+                            unk_38: 4,
+                            unk_3c: 8000,
+                            gpu_se_inactive_threshold: pwr.se_inactive_threshold,
+                            gpu_se_engagement_criteria: pwr.se_engagement_criteria,
+                            gpu_se_reset_criteria: pwr.se_reset_criteria,
+                            unk_54: 50,
+                            unk_58: 0x1,
+                            gpu_se_filter_a_neg: f32!(1.0) - filter_a,
+                            gpu_se_filter_1_a_neg: f32!(1.0) - filter_1_a,
+                            gpu_se_filter_a: filter_a,
+                            gpu_se_filter_1_a: filter_1_a,
+                            gpu_se_ki_dt: pwr.se_ki * period_s,
+                            gpu_se_ki_1_dt: pwr.se_ki_1 * period_s,
+                            unk_7c: f32!(65536.0),
+                            gpu_se_kp: pwr.se_kp,
+                            gpu_se_kp_1: pwr.se_kp_1,
+
+                            #[ver(V >= V13_3)]
+                            unk_8c: 100,
+                            #[ver(V < V13_3)]
+                            unk_8c: 40,
+
+                            max_pstate_scaled_1: max_ps_scaled,
+                            unk_9c: f32!(8000.0),
+                            unk_a0: 1400,
+                            gpu_se_filter_time_constant_ms: pwr.se_filter_time_constant * period_ms,
+                            gpu_se_filter_time_constant_1_ms: pwr.se_filter_time_constant_1
+                                * period_ms,
+                            gpu_se_filter_time_constant_clks: U64((pwr.se_filter_time_constant
+                                * clocks_per_period_coarse)
+                                .into()),
+                            gpu_se_filter_time_constant_1_clks: U64((pwr
+                                .se_filter_time_constant_1
+                                * clocks_per_period_coarse)
+                                .into()),
+                            unk_c4: f32!(65536.0),
+                            unk_114: f32!(65536.0),
+                            unk_124: 40,
+                            max_pstate_scaled_2: max_ps_scaled,
+                            ..Zeroable::zeroed()
+                        })
+                    },
+                    fast_die0_sensor_mask_2: U64(cfg.fast_sensor_mask[0]),
+                    unk_e24: cfg.da.unk_e24,
+                    unk_e28: 1,
+                    fast_die0_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[0]),
+                    #[ver(V < V13_0B4)]
+                    fast_die0_sensor_present: U64(cfg.fast_die0_sensor_present as u64),
+                    unk_163c: 1,
+                    unk_3644: 0,
+                    hws1 <- Self::hw_shared1(cfg),
+                    hws2 <- Self::hw_shared2(cfg, dyncfg),
+                    hws3 <- Self::hw_shared3(cfg),
+                    unk_3ce8: 1,
+                    ..Zeroable::zeroed()
+                }),
+                |raw| {
+                    for i in 0..self.dyncfg.pwr.perf_states.len() {
+                        raw.sram_k[i] = self.cfg.sram_k;
+                    }
+
+                    for (i, coef) in pwr.core_leak_coef.iter().enumerate() {
+                        raw.core_leak_coef[i] = *coef;
+                    }
+
+                    for (i, coef) in pwr.sram_leak_coef.iter().enumerate() {
+                        raw.sram_leak_coef[i] = *coef;
+                    }
+
+                    #[ver(V >= V13_0B4)]
+                    if let Some(csafr) = pwr.csafr.as_ref() {
+                        for (i, coef) in csafr.leak_coef_afr.iter().enumerate() {
+                            raw.aux_leak_coef.cs_1[i] = *coef;
+                            raw.aux_leak_coef.cs_2[i] = *coef;
+                        }
+
+                        for (i, coef) in csafr.leak_coef_cs.iter().enumerate() {
+                            raw.aux_leak_coef.afr_1[i] = *coef;
+                            raw.aux_leak_coef.afr_2[i] = *coef;
+                        }
+                    }
+
+                    for i in 0..self.dyncfg.id.num_clusters as usize {
+                        if let Some(coef_a) = self.cfg.unk_coef_a.get(i) {
+                            (*raw.unk_coef_a1[i])[..coef_a.len()].copy_from_slice(coef_a);
+                            (*raw.unk_coef_a2[i])[..coef_a.len()].copy_from_slice(coef_a);
+                        }
+                        if let Some(coef_b) = self.cfg.unk_coef_b.get(i) {
+                            (*raw.unk_coef_b1[i])[..coef_b.len()].copy_from_slice(coef_b);
+                            (*raw.unk_coef_b2[i])[..coef_b.len()].copy_from_slice(coef_b);
+                        }
+                    }
+
+                    for (i, pz) in pwr.power_zones.iter().enumerate() {
+                        raw.power_zones[i].target = pz.target;
+                        raw.power_zones[i].target_off = pz.target - pz.target_offset;
+                        raw.power_zones[i].filter_tc_x4 = 4 * pz.filter_tc;
+                        raw.power_zones[i].filter_tc_xperiod = period_ms * pz.filter_tc;
+                        let filter_a = f32!(1.0) / pz.filter_tc.into();
+                        raw.power_zones[i].filter_a = filter_a;
+                        raw.power_zones[i].filter_a_neg = f32!(1.0) - filter_a;
+                        #[ver(V >= V13_0B4)]
+                        raw.power_zones[i].unk_10 = 1320000000;
+                    }
+
+                    #[ver(V >= V13_0B4 && G >= G14X)]
+                    for (i, j) in raw.hws2.g14.curve2.t1.iter().enumerate() {
+                        raw.unk_hws2[i] = if *j == 0xffff { 0 } else { j / 2 };
+                    }
+
+                    Ok(())
+                },
+            )
+        })
+    }
+
+    /// Create the HwDataB structure. This mostly contains GPU-related configuration.
+    fn hwdata_b(&mut self) -> Result<GpuObject<HwDataB::ver>> {
+        self.alloc.private.new_init(init::zeroed(), |_inner, _ptr| {
+            let cfg = &self.cfg;
+            let dyncfg = &self.dyncfg;
+            init::chain(
+                try_init!(raw::HwDataB::ver {
+                    // Userspace VA map related
+                    #[ver(V < V13_0B4)]
+                    unk_0: U64(0x13_00000000),
+                    unk_8: U64(0x14_00000000),
+                    #[ver(V < V13_0B4)]
+                    unk_10: U64(0x1_00000000),
+                    unk_18: U64(0xffc00000),
+                    unk_20: U64(0x11_00000000),
+                    unk_28: U64(0x11_00000000),
+                    // userspace address?
+                    unk_30: U64(0x6f_ffff8000),
+                    // unmapped?
+                    unkptr_38: U64(0xffffffa0_11800000),
+                    // TODO: yuv matrices
+                    chip_id: cfg.chip_id,
+                    unk_454: 0x1,
+                    unk_458: 0x1,
+                    unk_460: 0x1,
+                    unk_464: 0x1,
+                    unk_468: 0x1,
+                    unk_47c: 0x1,
+                    unk_484: 0x1,
+                    unk_48c: 0x1,
+                    base_clock_khz: cfg.base_clock_hz / 1000,
+                    power_sample_period: dyncfg.pwr.power_sample_period,
+                    unk_49c: 0x1,
+                    unk_4a0: 0x1,
+                    unk_4a4: 0x1,
+                    unk_4c0: 0x1f,
+                    unk_4e0: U64(cfg.db.unk_4e0),
+                    unk_4f0: 0x1,
+                    unk_4f4: 0x1,
+                    unk_504: 0x31,
+                    unk_524: 0x1, // use_secure_cache_flush
+                    unk_534: cfg.db.unk_534,
+                    num_frags: dyncfg.id.num_frags * dyncfg.id.num_clusters,
+                    unk_554: 0x1,
+                    uat_ttb_base: U64(dyncfg.uat_ttb_base),
+                    gpu_core_id: cfg.gpu_core as u32,
+                    gpu_rev_id: dyncfg.id.gpu_rev_id as u32,
+                    num_cores: dyncfg.id.num_cores * dyncfg.id.num_clusters,
+                    max_pstate: dyncfg.pwr.perf_states.len() as u32 - 1,
+                    #[ver(V < V13_0B4)]
+                    num_pstates: dyncfg.pwr.perf_states.len() as u32,
+                    #[ver(V < V13_0B4)]
+                    min_sram_volt: dyncfg.pwr.min_sram_microvolt / 1000,
+                    #[ver(V < V13_0B4)]
+                    unk_ab8: cfg.db.unk_ab8,
+                    #[ver(V < V13_0B4)]
+                    unk_abc: cfg.db.unk_abc,
+                    #[ver(V < V13_0B4)]
+                    unk_ac0: 0x1020,
+
+                    #[ver(V >= V13_0B4)]
+                    unk_ae4: Array::new([0x0, 0x3, 0x7, 0x7]),
+                    #[ver(V < V13_0B4)]
+                    unk_ae4: Array::new([0x0, 0xf, 0x3f, 0x3f]),
+                    unk_b10: 0x1,
+                    unk_b24: 0x1,
+                    unk_b28: 0x1,
+                    unk_b2c: 0x1,
+                    unk_b30: cfg.db.unk_b30,
+                    #[ver(V >= V13_0B4)]
+                    unk_b38_0: 1,
+                    #[ver(V >= V13_0B4)]
+                    unk_b38_4: 1,
+                    unk_b38: Array::new([0xffffffff; 12]),
+                    #[ver(V >= V13_0B4 && V < V13_3)]
+                    unk_c3c: 0x19,
+                    #[ver(V >= V13_3)]
+                    unk_c3c: 0x1a,
+                    ..Zeroable::zeroed()
+                }),
+                |raw| {
+                    #[ver(V >= V13_3)]
+                    for i in 0..16 {
+                        raw.unk_arr_0[i] = i as u32;
+                    }
+
+                    let base_ps = self.dyncfg.pwr.perf_base_pstate as usize;
+                    let max_ps = self.dyncfg.pwr.perf_max_pstate as usize;
+                    let base_freq = self.dyncfg.pwr.perf_states[base_ps].freq_hz;
+                    let max_freq = self.dyncfg.pwr.perf_states[max_ps].freq_hz;
+
+                    for (i, ps) in self.dyncfg.pwr.perf_states.iter().enumerate() {
+                        raw.frequencies[i] = ps.freq_hz / 1000000;
+                        for (j, mv) in ps.volt_mv.iter().enumerate() {
+                            let sram_mv = (*mv).max(self.dyncfg.pwr.min_sram_microvolt / 1000);
+                            raw.voltages[i][j] = *mv;
+                            raw.voltages_sram[i][j] = sram_mv;
+                        }
+                        for j in ps.volt_mv.len()..raw.voltages[i].len() {
+                            raw.voltages[i][j] = raw.voltages[i][0];
+                            raw.voltages_sram[i][j] = raw.voltages_sram[i][0];
+                        }
+                        raw.sram_k[i] = self.cfg.sram_k;
+                        raw.rel_max_powers[i] = ps.pwr_mw * 100 / self.dyncfg.pwr.max_power_mw;
+                        raw.rel_boost_freqs[i] = if i > base_ps {
+                            (ps.freq_hz - base_freq) / ((max_freq - base_freq) / 100)
+                        } else {
+                            0
+                        };
+                    }
+
+                    #[ver(V >= V13_0B4)]
+                    if let Some(csafr) = self.dyncfg.pwr.csafr.as_ref() {
+                        let aux = &mut raw.aux_ps;
+                        aux.cs_max_pstate = (csafr.perf_states_cs.len() - 1).try_into()?;
+                        aux.afr_max_pstate = (csafr.perf_states_afr.len() - 1).try_into()?;
+
+                        for (i, ps) in csafr.perf_states_cs.iter().enumerate() {
+                            aux.cs_frequencies[i] = ps.freq_hz / 1000000;
+                            for (j, mv) in ps.volt_mv.iter().enumerate() {
+                                let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
+                                aux.cs_voltages[i][j] = *mv;
+                                aux.cs_voltages_sram[i][j] = sram_mv;
+                            }
+                        }
+
+                        for (i, ps) in csafr.perf_states_afr.iter().enumerate() {
+                            aux.afr_frequencies[i] = ps.freq_hz / 1000000;
+                            for (j, mv) in ps.volt_mv.iter().enumerate() {
+                                let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
+                                aux.afr_voltages[i][j] = *mv;
+                                aux.afr_voltages_sram[i][j] = sram_mv;
+                            }
+                        }
+                    }
+
+                    Ok(())
+                },
+            )
+        })
+    }
+
+    /// Create the Globals structure, which contains global firmware config including more power
+    /// configuration data and globals used to exchange state between the firmware and driver.
+    fn globals(&mut self) -> Result<GpuObject<Globals::ver>> {
+        self.alloc.private.new_init(init::zeroed(), |_inner, _ptr| {
+            let cfg = &self.cfg;
+            let dyncfg = &self.dyncfg;
+            let pwr = &dyncfg.pwr;
+            let period_ms = pwr.power_sample_period;
+            let period_s = F32::from(period_ms) / f32!(1000.0);
+            let avg_power_filter_tc_periods = pwr.avg_power_filter_tc_ms / period_ms;
+
+            let max_ps = pwr.perf_max_pstate;
+            let max_ps_scaled = 100 * max_ps;
+
+            init::chain(
+                try_init!(raw::Globals::ver {
+                    //ktrace_enable: 0xffffffff,
+                    ktrace_enable: 0,
+                    #[ver(V >= V13_2)]
+                    unk_24_0: 3000,
+                    unk_24: 0,
+                    #[ver(V >= V13_0B4)]
+                    debug: 0,
+                    unk_28: 1,
+                    #[ver(G >= G14X)]
+                    unk_2c_0: 1,
+                    #[ver(V >= V13_0B4 && G < G14X)]
+                    unk_2c_0: 0,
+                    unk_2c: 1,
+                    unk_30: 0,
+                    unk_34: 120,
+                    sub <- try_init!(raw::GlobalsSub::ver {
+                        unk_54: cfg.global_unk_54,
+                        unk_56: 40,
+                        unk_58: 0xffff,
+                        unk_5e: U32(1),
+                        unk_66: U32(1),
+                        ..Zeroable::zeroed()
+                    }),
+                    unk_8900: 1,
+                    pending_submissions: AtomicU32::new(0),
+                    max_power: pwr.max_power_mw,
+                    max_pstate_scaled: max_ps_scaled,
+                    max_pstate_scaled_2: max_ps_scaled,
+                    max_pstate_scaled_3: max_ps_scaled,
+                    power_zone_count: pwr.power_zones.len() as u32,
+                    avg_power_filter_tc_periods: avg_power_filter_tc_periods,
+                    avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
+                    avg_power_kp: pwr.avg_power_kp,
+                    avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
+                    avg_power_target_filter_tc: pwr.avg_power_target_filter_tc,
+                    unk_89bc: cfg.da.unk_8cc,
+                    fast_die0_release_temp: 100 * pwr.fast_die0_release_temp,
+                    unk_89c4: cfg.da.unk_87c,
+                    fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
+                    fast_die0_kp: pwr.fast_die0_proportional_gain,
+                    fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
+                    unk_89e0: 1,
+                    max_power_2: pwr.max_power_mw,
+                    ppm_kp: pwr.ppm_kp,
+                    ppm_ki_dt: pwr.ppm_ki * period_s,
+                    #[ver(V >= V13_0B4)]
+                    unk_89f4_8: 1,
+                    unk_89f4: 0,
+                    hws1 <- Self::hw_shared1(cfg),
+                    hws2 <- Self::hw_shared2(cfg, dyncfg),
+                    hws3 <- Self::hw_shared3(cfg),
+                    #[ver(V >= V13_0B4)]
+                    unk_hws2_0: cfg.unk_hws2_0,
+                    #[ver(V >= V13_0B4)]
+                    unk_hws2_4: cfg.unk_hws2_4.map(Array::new).unwrap_or_default(),
+                    #[ver(V >= V13_0B4)]
+                    unk_hws2_24: cfg.unk_hws2_24,
+                    unk_900c: 1,
+                    #[ver(V >= V13_0B4)]
+                    unk_9010_0: 1,
+                    #[ver(V >= V13_0B4)]
+                    unk_903c: 1,
+                    #[ver(V < V13_0B4)]
+                    unk_903c: 0,
+                    fault_control: *crate::fault_control.read(),
+                    do_init: 1,
+                    unk_11020: 40,
+                    unk_11024: 10,
+                    unk_11028: 250,
+                    #[ver(V >= V13_0B4)]
+                    unk_1102c_0: 1,
+                    #[ver(V >= V13_0B4)]
+                    unk_1102c_4: 1,
+                    #[ver(V >= V13_0B4)]
+                    unk_1102c_8: 100,
+                    #[ver(V >= V13_0B4)]
+                    unk_1102c_c: 1,
+                    idle_off_delay_ms: AtomicU32::new(pwr.idle_off_delay_ms),
+                    fender_idle_off_delay_ms: pwr.fender_idle_off_delay_ms,
+                    fw_early_wake_timeout_ms: pwr.fw_early_wake_timeout_ms,
+                    unk_118e0: 40,
+                    #[ver(V >= V13_0B4)]
+                    unk_118e4_0: 50,
+                    #[ver(V >= V13_0B4)]
+                    unk_11edc: 0,
+                    #[ver(V >= V13_0B4)]
+                    unk_11efc: 0,
+                    ..Zeroable::zeroed()
+                }),
+                |raw| {
+                    for (i, pz) in self.dyncfg.pwr.power_zones.iter().enumerate() {
+                        raw.power_zones[i].target = pz.target;
+                        raw.power_zones[i].target_off = pz.target - pz.target_offset;
+                        raw.power_zones[i].filter_tc = pz.filter_tc;
+                    }
+
+                    if let Some(tab) = self.cfg.global_tab.as_ref() {
+                        for (i, x) in tab.iter().enumerate() {
+                            raw.unk_118ec[i] = *x;
+                        }
+                        raw.unk_118e8 = 1;
+                    }
+                    Ok(())
+                },
+            )
+        })
+    }
+
+    /// Create the RuntimePointers structure, which contains pointers to most of the other
+    /// structures including the ring buffer channels, statistics structures, and HwDataA/HwDataB.
+    fn runtime_pointers(&mut self) -> Result<GpuObject<RuntimePointers::ver>> {
+        let hwa = self.hwdata_a()?;
+        let hwb = self.hwdata_b()?;
+
+        let mut buffer_mgr_ctl = gem::new_kernel_object(self.dev, 0x4000)?;
+        buffer_mgr_ctl.vmap()?.as_mut_slice().fill(0);
+
+        GpuObject::new_init_prealloc(
+            self.alloc.private.alloc_object()?,
+            |_ptr| {
+                let alloc = &mut *self.alloc;
+                try_init!(RuntimePointers::ver {
+                    stats <- {
+                        let alloc = &mut *alloc;
+                        try_init!(Stats::ver {
+                            vtx: alloc.private.new_default::<GpuGlobalStatsVtx>()?,
+                            frag: alloc.private.new_default::<GpuGlobalStatsFrag>()?,
+                            comp: alloc.private.new_default::<GpuStatsComp>()?,
+                        })
+                    },
+
+                    hwdata_a: hwa,
+                    unkptr_190: alloc.private.array_empty(0x80)?,
+                    unkptr_198: alloc.private.array_empty(0xc0)?,
+                    hwdata_b: hwb,
+
+                    unkptr_1b8: alloc.private.array_empty(0x1000)?,
+                    unkptr_1c0: alloc.private.array_empty(0x300)?,
+                    unkptr_1c8: alloc.private.array_empty(0x1000)?,
+
+                    buffer_mgr_ctl,
+                })
+            },
+            |inner, _ptr| {
+                try_init!(raw::RuntimePointers::ver {
+                    pipes: Default::default(),
+                    device_control: Default::default(),
+                    event: Default::default(),
+                    fw_log: Default::default(),
+                    ktrace: Default::default(),
+                    stats: Default::default(),
+
+                    stats_vtx: inner.stats.vtx.gpu_pointer(),
+                    stats_frag: inner.stats.frag.gpu_pointer(),
+                    stats_comp: inner.stats.comp.gpu_pointer(),
+
+                    hwdata_a: inner.hwdata_a.gpu_pointer(),
+                    unkptr_190: inner.unkptr_190.gpu_pointer(),
+                    unkptr_198: inner.unkptr_198.gpu_pointer(),
+                    hwdata_b: inner.hwdata_b.gpu_pointer(),
+                    hwdata_b_2: inner.hwdata_b.gpu_pointer(),
+
+                    fwlog_buf: None,
+
+                    unkptr_1b8: inner.unkptr_1b8.gpu_pointer(),
+
+                    #[ver(G < G14X)]
+                    unkptr_1c0: inner.unkptr_1c0.gpu_pointer(),
+                    #[ver(G < G14X)]
+                    unkptr_1c8: inner.unkptr_1c8.gpu_pointer(),
+
+                    buffer_mgr_ctl_gpu_addr: U64(gpu::IOVA_KERN_GPU_BUFMGR_LOW),
+                    buffer_mgr_ctl_fw_addr: U64(gpu::IOVA_KERN_GPU_BUFMGR_HIGH),
+
+                    __pad0: Default::default(),
+                    unk_160: U64(0),
+                    unk_168: U64(0),
+                    unk_1d0: 0,
+                    unk_1d4: 0,
+                    unk_1d8: Default::default(),
+
+                    __pad1: Default::default(),
+                    gpu_scratch: raw::RuntimeScratch::ver {
+                        unk_6b38: 0xff,
+                        ..Default::default()
+                    },
+                })
+            },
+        )
+    }
+
+    /// Create the FwStatus structure, which is used to coordinate the firmware halt state between
+    /// the firmware and the driver.
+    fn fw_status(&mut self) -> Result<GpuObject<FwStatus>> {
+        self.alloc
+            .shared
+            .new_object(Default::default(), |_inner| Default::default())
+    }
+
+    /// Create one UatLevelInfo structure, which describes one level of translation for the UAT MMU.
+    fn uat_level_info(
+        cfg: &'static hw::HwConfig,
+        index_shift: usize,
+        num_entries: usize,
+    ) -> raw::UatLevelInfo {
+        raw::UatLevelInfo {
+            index_shift: index_shift as _,
+            unk_1: 14,
+            unk_2: 14,
+            unk_3: 8,
+            unk_4: 0x4000,
+            num_entries: num_entries as _,
+            unk_8: U64(1),
+            unk_10: U64(((1u64 << cfg.uat_oas) - 1) & !(mmu::UAT_PGMSK as u64)),
+            index_mask: U64(((num_entries - 1) << index_shift) as u64),
+        }
+    }
+
+    /// Build the top-level InitData object.
+    #[inline(never)]
+    pub(crate) fn build(&mut self) -> Result<Box<GpuObject<InitData::ver>>> {
+        let runtime_pointers = self.runtime_pointers()?;
+        let globals = self.globals()?;
+        let fw_status = self.fw_status()?;
+        let shared_ro = &mut self.alloc.shared_ro;
+
+        let obj = self.alloc.private.new_init(
+            try_init!(InitData::ver {
+                unk_buf: shared_ro.array_empty(0x4000)?,
+                runtime_pointers,
+                globals,
+                fw_status,
+            }),
+            |inner, _ptr| {
+                let cfg = &self.cfg;
+                try_init!(raw::InitData::ver {
+                    #[ver(V == V13_5 && G == G13)]
+                    ver_info: Array::new([1, 1, 16, 1]), // TODO
+                    #[ver(V == V13_5 && G == G14)]
+                    ver_info: Array::new([0x6ba0, 0x1f28, 0x601, 0xb0]),
+                    #[ver(V == V13_5 && G == G14X)]
+                    ver_info: Array::new([0xb390, 0x70f8, 0x601, 0xb0]),
+                    unk_buf: inner.unk_buf.gpu_pointer(),
+                    unk_8: 0,
+                    unk_c: 0,
+                    runtime_pointers: inner.runtime_pointers.gpu_pointer(),
+                    globals: inner.globals.gpu_pointer(),
+                    fw_status: inner.fw_status.gpu_pointer(),
+                    uat_page_size: 0x4000,
+                    uat_page_bits: 14,
+                    uat_num_levels: 3,
+                    uat_level_info: Array::new([
+                        Self::uat_level_info(cfg, 36, 8),
+                        Self::uat_level_info(cfg, 25, 2048),
+                        Self::uat_level_info(cfg, 14, 2048),
+                    ]),
+                    __pad0: Default::default(),
+                    host_mapped_fw_allocations: 1,
+                    unk_ac: 0,
+                    unk_b0: 0,
+                    unk_b4: 0,
+                    unk_b8: 0,
+                })
+            },
+        )?;
+        Ok(Box::new(obj, GFP_KERNEL)?)
+    }
+}
diff --git a/drivers/gpu/drm/asahi/mem.rs b/drivers/gpu/drm/asahi/mem.rs
new file mode 100644
index 00000000000000..05f9a796862450
--- /dev/null
+++ b/drivers/gpu/drm/asahi/mem.rs
@@ -0,0 +1,138 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! ARM64 low level memory operations.
+//!
+//! This GPU uses CPU-side `tlbi` outer-shareable instructions to manage its TLBs.
+//! Yes, really. Even though the VA address spaces are unrelated.
+//!
+//! Right now we pick our own ASIDs and don't coordinate with the CPU. This might result
+//! in needless TLB shootdowns on the CPU side... TODO: fix this.
+
+use core::arch::asm;
+use core::cmp::min;
+
+use crate::debug::*;
+use crate::mmu;
+
+type Asid = u8;
+
+/// Invalidate the entire GPU TLB.
+#[inline(always)]
+pub(crate) fn tlbi_all() {
+    unsafe {
+        asm!(".arch armv8.4-a", "tlbi vmalle1os",);
+    }
+}
+
+/// Invalidate all TLB entries for a given ASID.
+#[inline(always)]
+pub(crate) fn tlbi_asid(asid: Asid) {
+    if debug_enabled(DebugFlags::ConservativeTlbi) {
+        tlbi_all();
+        sync();
+        return;
+    }
+
+    unsafe {
+        asm!(
+            ".arch armv8.4-a",
+            "tlbi aside1os, {x}",
+            x = in(reg) ((asid as u64) << 48)
+        );
+    }
+}
+
+/// Invalidate a single page for a given ASID.
+#[inline(always)]
+pub(crate) fn tlbi_page(asid: Asid, va: usize) {
+    if debug_enabled(DebugFlags::ConservativeTlbi) {
+        tlbi_all();
+        sync();
+        return;
+    }
+
+    let val: u64 = ((asid as u64) << 48) | ((va as u64 >> 12) & 0xffffffffffc);
+    unsafe {
+        asm!(
+            ".arch armv8.4-a",
+            "tlbi vae1os, {x}",
+            x = in(reg) val
+        );
+    }
+}
+
+/// Invalidate a range of pages for a given ASID.
+#[inline(always)]
+pub(crate) fn tlbi_range(asid: Asid, va: usize, len: usize) {
+    if debug_enabled(DebugFlags::ConservativeTlbi) {
+        tlbi_all();
+        sync();
+        return;
+    }
+
+    if len == 0 {
+        return;
+    }
+
+    let start_pg = va >> mmu::UAT_PGBIT;
+    let end_pg = (va + len + mmu::UAT_PGMSK) >> mmu::UAT_PGBIT;
+
+    let mut val: u64 = ((asid as u64) << 48) | (2 << 46) | (start_pg as u64 & 0x1fffffffff);
+    let pages = end_pg - start_pg;
+
+    // Guess? It's possible that the page count is in terms of 4K pages
+    // when the CPU is in 4K mode...
+    #[cfg(CONFIG_ARM64_4K_PAGES)]
+    let pages = 4 * pages;
+
+    if pages == 1 {
+        tlbi_page(asid, va);
+        return;
+    }
+
+    // Page count is always in units of 2
+    let num = ((pages + 1) >> 1) as u64;
+    // base: 5 bits
+    // exp: 2 bits
+    // pages = (base + 1) << (5 * exp + 1)
+    // 0:00000 ->                     2 pages = 2 << 0
+    // 0:11111 ->                32 * 2 pages = 2 << 5
+    // 1:00000 ->            1 * 32 * 2 pages = 2 << 5
+    // 1:11111 ->           32 * 32 * 2 pages = 2 << 10
+    // 2:00000 ->       1 * 32 * 32 * 2 pages = 2 << 10
+    // 2:11111 ->      32 * 32 * 32 * 2 pages = 2 << 15
+    // 3:00000 ->  1 * 32 * 32 * 32 * 2 pages = 2 << 15
+    // 3:11111 -> 32 * 32 * 32 * 32 * 2 pages = 2 << 20
+    let exp = min(3, (64 - num.leading_zeros()) / 5);
+    let bits = 5 * exp;
+    let mut base = (num + (1 << bits) - 1) >> bits;
+
+    val |= (exp as u64) << 44;
+
+    while base > 32 {
+        unsafe {
+            asm!(
+                ".arch armv8.4-a",
+                "tlbi rvae1os, {x}",
+                x = in(reg) val | (31 << 39)
+            );
+        }
+        base -= 32;
+    }
+
+    unsafe {
+        asm!(
+            ".arch armv8.4-a",
+            "tlbi rvae1os, {x}",
+            x = in(reg) val | ((base - 1) << 39)
+        );
+    }
+}
+
+/// Issue a memory barrier (`dsb sy`).
+#[inline(always)]
+pub(crate) fn sync() {
+    unsafe {
+        asm!("dsb sy");
+    }
+}
diff --git a/drivers/gpu/drm/asahi/microseq.rs b/drivers/gpu/drm/asahi/microseq.rs
new file mode 100644
index 00000000000000..34074fa912f02f
--- /dev/null
+++ b/drivers/gpu/drm/asahi/microseq.rs
@@ -0,0 +1,61 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU Micro operation sequence builder
+//!
+//! As part of a single job submisssion to the GPU, the GPU firmware interprets a sequence of
+//! commands that we call a "microsequence". These are responsible for setting up the job execution,
+//! timestamping the process, waiting for completion, tearing up any resources, and signaling
+//! completion to the driver via the event stamp mechanism.
+//!
+//! Although the microsequences used by the macOS driver are usually quite uniform and simple, the
+//! firmware actually implements enough operations to make this interpreter Turing-complete (!).
+//! Most of those aren't implemented yet, since we don't need them, but they could come in handy in
+//! the future to do strange things or work around firmware bugs...
+//!
+//! This module simply implements a collection of microsequence operations that can be appended to
+//! and later concatenated into one buffer, ready for firmware execution.
+
+use crate::fw::microseq;
+pub(crate) use crate::fw::microseq::*;
+use crate::fw::types::*;
+use kernel::prelude::*;
+
+/// MicroSequence object type, which is just an opaque byte array.
+pub(crate) type MicroSequence = GpuArray<u8>;
+
+/// MicroSequence builder.
+pub(crate) struct Builder {
+    ops: Vec<u8>,
+}
+
+impl Builder {
+    /// Create a new Builder object
+    pub(crate) fn new() -> Builder {
+        Builder { ops: Vec::new() }
+    }
+
+    /// Get the relative offset from the current pointer to a given target offset.
+    ///
+    /// Used for relative jumps.
+    pub(crate) fn offset_to(&self, target: i32) -> i32 {
+        target - self.ops.len() as i32
+    }
+
+    /// Add an operation to the end of the sequence.
+    pub(crate) fn add<T: microseq::Operation>(&mut self, op: T) -> Result<i32> {
+        let off = self.ops.len();
+        let p: *const T = &op;
+        let p: *const u8 = p as *const u8;
+        let s: &[u8] = unsafe { core::slice::from_raw_parts(p, core::mem::size_of::<T>()) };
+        self.ops.extend_from_slice(s, GFP_KERNEL)?;
+        Ok(off as i32)
+    }
+
+    /// Collect all submitted operations into a finalized GPU object.
+    pub(crate) fn build(self, alloc: &mut Allocator) -> Result<MicroSequence> {
+        let mut array = alloc.array_empty::<u8>(self.ops.len())?;
+
+        array.as_mut_slice().clone_from_slice(self.ops.as_slice());
+        Ok(array)
+    }
+}
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
new file mode 100644
index 00000000000000..e859bb78b01275
--- /dev/null
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -0,0 +1,1262 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU UAT (MMU) management
+//!
+//! AGX GPUs use an MMU called the UAT, which is largely compatible with the ARM64 page table
+//! format. This module manages the global MMU structures, including a shared handoff structure
+//! that is used to coordinate VM management operations with the firmware, the TTBAT which points
+//! to currently active GPU VM contexts, as well as the individual `Vm` operations to map and
+//! unmap buffer objects into a single user or kernel address space.
+//!
+//! The actual page table management is delegated to the common kernel `io_pgtable` code.
+
+use core::fmt::Debug;
+use core::mem::size_of;
+use core::ptr::NonNull;
+use core::sync::atomic::{fence, AtomicU32, AtomicU64, AtomicU8, Ordering};
+use core::time::Duration;
+
+use kernel::{
+    bindings, c_str, delay, device,
+    drm::mm,
+    error::{to_result, Result},
+    io_pgtable,
+    io_pgtable::{prot, AppleUAT, IoPageTable},
+    prelude::*,
+    static_lock_class,
+    sync::{
+        lock::{mutex::MutexBackend, Guard},
+        Arc, Mutex,
+    },
+    time::{clock, Now},
+    types::ForeignOwnable,
+};
+
+use crate::debug::*;
+use crate::no_debug;
+use crate::{driver, fw, gem, hw, mem, slotalloc};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Mmu;
+
+/// PPL magic number for the handoff region
+const PPL_MAGIC: u64 = 0x4b1d000000000002;
+
+/// Number of supported context entries in the TTBAT
+const UAT_NUM_CTX: usize = 64;
+/// First context available for users
+const UAT_USER_CTX_START: usize = 1;
+/// Number of available user contexts
+const UAT_USER_CTX: usize = UAT_NUM_CTX - UAT_USER_CTX_START;
+
+/// Number of bits in a page offset.
+pub(crate) const UAT_PGBIT: usize = 14;
+/// UAT page size.
+pub(crate) const UAT_PGSZ: usize = 1 << UAT_PGBIT;
+/// UAT page offset mask.
+pub(crate) const UAT_PGMSK: usize = UAT_PGSZ - 1;
+
+type Pte = AtomicU64;
+
+/// Number of PTEs per page.
+const UAT_NPTE: usize = UAT_PGSZ / size_of::<Pte>();
+
+/// UAT input address space (user)
+pub(crate) const UAT_IAS: usize = 39;
+/// "Fake" kernel UAT input address space (one page level lower)
+pub(crate) const UAT_IAS_KERN: usize = 36;
+
+/// Lower/user base VA
+const IOVA_USER_BASE: usize = UAT_PGSZ;
+/// Lower/user top VA
+const IOVA_USER_TOP: usize = (1 << UAT_IAS) - 1;
+/// Upper/kernel base VA
+// const IOVA_TTBR1_BASE: usize = 0xffffff8000000000;
+/// Driver-managed kernel base VA
+const IOVA_KERN_BASE: usize = 0xffffffa000000000;
+/// Driver-managed kernel top VA
+const IOVA_KERN_TOP: usize = 0xffffffafffffffff;
+
+/// Range reserved for MMIO maps
+const IOVA_KERN_MMIO_BASE: usize = 0xffffffaf00000000;
+const IOVA_KERN_MMIO_TOP: usize = 0xffffffafffffffff;
+
+const TTBR_VALID: u64 = 0x1; // BIT(0)
+const TTBR_ASID_SHIFT: usize = 48;
+
+const PTE_TABLE: u64 = 0x3; // BIT(0) | BIT(1)
+
+// Mapping protection types
+
+// Note: prot::CACHE means "cache coherency", which for UAT means *uncached*,
+// since uncached mappings from the GFX ASC side are cache coherent with the AP cache.
+// Not having that flag means *cached noncoherent*.
+
+/// Firmware MMIO R/W
+pub(crate) const PROT_FW_MMIO_RW: u32 =
+    prot::PRIV | prot::READ | prot::WRITE | prot::CACHE | prot::MMIO;
+/// Firmware MMIO R/O
+pub(crate) const PROT_FW_MMIO_RO: u32 = prot::PRIV | prot::READ | prot::CACHE | prot::MMIO;
+/// Firmware shared (uncached) RW
+pub(crate) const PROT_FW_SHARED_RW: u32 = prot::PRIV | prot::READ | prot::WRITE | prot::CACHE;
+/// Firmware shared (uncached) RO
+pub(crate) const PROT_FW_SHARED_RO: u32 = prot::PRIV | prot::READ | prot::CACHE;
+/// Firmware private (cached) RW
+pub(crate) const PROT_FW_PRIV_RW: u32 = prot::PRIV | prot::READ | prot::WRITE;
+/*
+/// Firmware private (cached) RO
+pub(crate) const PROT_FW_PRIV_RO: u32 = prot::PRIV | prot::READ;
+*/
+/// Firmware/GPU shared (uncached) RW
+pub(crate) const PROT_GPU_FW_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE;
+/// Firmware/GPU shared (private) RW
+pub(crate) const PROT_GPU_FW_PRIV_RW: u32 = prot::READ | prot::WRITE;
+/// Firmware-RW/GPU-RO shared (private) RW
+pub(crate) const PROT_GPU_RO_FW_PRIV_RW: u32 = prot::PRIV | prot::WRITE;
+/// GPU shared/coherent RW
+pub(crate) const PROT_GPU_SHARED_RW: u32 = prot::READ | prot::WRITE | prot::CACHE | prot::NOEXEC;
+/// GPU shared/coherent RO
+pub(crate) const PROT_GPU_SHARED_RO: u32 = prot::READ | prot::CACHE | prot::NOEXEC;
+/// GPU shared/coherent WO
+pub(crate) const PROT_GPU_SHARED_WO: u32 = prot::WRITE | prot::CACHE | prot::NOEXEC;
+/*
+/// GPU private/noncoherent RW
+pub(crate) const PROT_GPU_PRIV_RW: u32 = prot::READ | prot::WRITE | prot::NOEXEC;
+/// GPU private/noncoherent RO
+pub(crate) const PROT_GPU_PRIV_RO: u32 = prot::READ | prot::NOEXEC;
+*/
+
+type PhysAddr = bindings::phys_addr_t;
+
+/// A pre-allocated memory region for UAT management
+struct UatRegion {
+    base: PhysAddr,
+    map: NonNull<core::ffi::c_void>,
+}
+
+/// It's safe to share UAT region records across threads.
+unsafe impl Send for UatRegion {}
+unsafe impl Sync for UatRegion {}
+
+/// Handoff region flush info structure
+#[repr(C)]
+struct FlushInfo {
+    state: AtomicU64,
+    addr: AtomicU64,
+    size: AtomicU64,
+}
+
+/// UAT Handoff region layout
+#[repr(C)]
+struct Handoff {
+    magic_ap: AtomicU64,
+    magic_fw: AtomicU64,
+
+    lock_ap: AtomicU8,
+    lock_fw: AtomicU8,
+    // Implicit padding: 2 bytes
+    turn: AtomicU32,
+    cur_slot: AtomicU32,
+    // Implicit padding: 4 bytes
+    flush: [FlushInfo; UAT_NUM_CTX + 1],
+
+    unk2: AtomicU8,
+    // Implicit padding: 7 bytes
+    unk3: AtomicU64,
+}
+
+const HANDOFF_SIZE: usize = size_of::<Handoff>();
+
+/// One VM slot in the TTBAT
+#[repr(C)]
+struct SlotTTBS {
+    ttb0: AtomicU64,
+    ttb1: AtomicU64,
+}
+
+const SLOTS_SIZE: usize = UAT_NUM_CTX * size_of::<SlotTTBS>();
+
+// We need at least page 0 (ttb0)
+const PAGETABLES_SIZE: usize = UAT_PGSZ;
+
+/// Inner data for a Vm instance. This is reference-counted by the outer Vm object.
+struct VmInner {
+    dev: driver::AsahiDevRef,
+    is_kernel: bool,
+    min_va: usize,
+    max_va: usize,
+    page_table: AppleUAT<Uat>,
+    mm: mm::Allocator<(), MappingInner>,
+    uat_inner: Arc<UatInner>,
+    active_users: usize,
+    binding: Option<slotalloc::Guard<SlotInner>>,
+    bind_token: Option<slotalloc::SlotToken>,
+    id: u64,
+}
+
+impl VmInner {
+    /// Returns the slot index, if this VM is bound.
+    fn slot(&self) -> Option<u32> {
+        if self.is_kernel {
+            // The GFX ASC does not care about the ASID. Pick an arbitrary one.
+            // TODO: This needs to be a persistently reserved ASID once we integrate
+            // with the ARM64 kernel ASID machinery to avoid overlap.
+            Some(0)
+        } else {
+            // We don't check whether we lost the slot, which could cause unnecessary
+            // invalidations against another Vm. However, this situation should be very
+            // rare (e.g. a Vm lost its slot, which means 63 other Vms bound in the
+            // interim, and then it gets killed / drops its mappings without doing any
+            // final rendering). Anything doing active maps/unmaps is probably also
+            // rendering and therefore likely bound.
+            self.bind_token
+                .as_ref()
+                .map(|token| (token.last_slot() + UAT_USER_CTX_START as u32))
+        }
+    }
+
+    /// Returns the translation table base for this Vm
+    fn ttb(&self) -> u64 {
+        self.page_table.cfg().ttbr
+    }
+
+    /// Map an IOVA to the shifted address the underlying io_pgtable uses.
+    fn map_iova(&self, iova: usize, size: usize) -> Result<usize> {
+        if iova < self.min_va || (iova + size - 1) > self.max_va {
+            Err(EINVAL)
+        } else if self.is_kernel {
+            Ok(iova - self.min_va)
+        } else {
+            Ok(iova)
+        }
+    }
+
+    /// Map a contiguous range of virtual->physical pages.
+    fn map_pages(
+        &mut self,
+        mut iova: usize,
+        mut paddr: usize,
+        pgsize: usize,
+        pgcount: usize,
+        prot: u32,
+    ) -> Result<usize> {
+        let mut left = pgcount;
+        while left > 0 {
+            let mapped_iova = self.map_iova(iova, pgsize * left)?;
+            let mapped = self
+                .page_table
+                .map_pages(mapped_iova, paddr, pgsize, left, prot)?;
+            assert!(mapped <= left * pgsize);
+
+            left -= mapped / pgsize;
+            paddr += mapped;
+            iova += mapped;
+        }
+        Ok(pgcount * pgsize)
+    }
+
+    /// Unmap a contiguous range of pages.
+    fn unmap_pages(&mut self, mut iova: usize, pgsize: usize, pgcount: usize) -> Result<usize> {
+        let mut left = pgcount;
+        while left > 0 {
+            let mapped_iova = self.map_iova(iova, pgsize * left)?;
+            let unmapped = self.page_table.unmap_pages(mapped_iova, pgsize, left);
+            assert!(unmapped <= left * pgsize);
+
+            left -= unmapped / pgsize;
+            iova += unmapped;
+        }
+
+        Ok(pgcount * pgsize)
+    }
+
+    /// Map an `mm::Node` representing an mapping in VA space.
+    fn map_node(&mut self, node: &mm::Node<(), MappingInner>, prot: u32) -> Result {
+        let mut iova = node.start() as usize;
+        let sgt = node.sgt.as_ref().ok_or(EINVAL)?;
+
+        for range in sgt.iter() {
+            let addr = range.dma_address();
+            let len = range.dma_len();
+
+            if (addr | len | iova) & UAT_PGMSK != 0 {
+                dev_err!(
+                    self.dev,
+                    "MMU: Mapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
+                    addr,
+                    len,
+                    iova
+                );
+                return Err(EINVAL);
+            }
+
+            mod_dev_dbg!(
+                self.dev,
+                "MMU: map: {:#x}:{:#x} -> {:#x}\n",
+                addr,
+                len,
+                iova
+            );
+
+            self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, prot)?;
+
+            iova += len;
+        }
+        Ok(())
+    }
+}
+
+/// Shared reference to a virtual memory address space ([`Vm`]).
+#[derive(Clone)]
+pub(crate) struct Vm {
+    id: u64,
+    file_id: u64,
+    inner: Arc<Mutex<VmInner>>,
+}
+no_debug!(Vm);
+
+/// Slot data for a [`Vm`] slot (nothing, we only care about the indices).
+pub(crate) struct SlotInner();
+
+impl slotalloc::SlotItem for SlotInner {
+    type Data = ();
+}
+
+/// Represents a single user of a binding of a [`Vm`] to a slot.
+///
+/// The number of users is counted, and the slot will be freed when it drops to 0.
+#[derive(Debug)]
+pub(crate) struct VmBind(Vm, u32);
+
+impl VmBind {
+    /// Returns the slot that this `Vm` is bound to.
+    pub(crate) fn slot(&self) -> u32 {
+        self.1
+    }
+}
+
+impl Drop for VmBind {
+    fn drop(&mut self) {
+        let mut inner = self.0.inner.lock();
+
+        assert_ne!(inner.active_users, 0);
+        inner.active_users -= 1;
+        mod_pr_debug!("MMU: slot {} active users {}\n", self.1, inner.active_users);
+        if inner.active_users == 0 {
+            inner.binding = None;
+        }
+    }
+}
+
+impl Clone for VmBind {
+    fn clone(&self) -> VmBind {
+        let mut inner = self.0.inner.lock();
+
+        inner.active_users += 1;
+        mod_pr_debug!("MMU: slot {} active users {}\n", self.1, inner.active_users);
+        VmBind(self.0.clone(), self.1)
+    }
+}
+
+/// Inner data required for an object mapping into a [`Vm`].
+pub(crate) struct MappingInner {
+    owner: Arc<Mutex<VmInner>>,
+    uat_inner: Arc<UatInner>,
+    prot: u32,
+    mapped_size: usize,
+    sgt: Option<gem::SGTable>,
+}
+
+/// An object mapping into a [`Vm`], which reserves the address range from use by other mappings.
+pub(crate) struct Mapping(mm::Node<(), MappingInner>);
+
+impl Mapping {
+    /// Returns the IOVA base of this mapping
+    pub(crate) fn iova(&self) -> usize {
+        self.0.start() as usize
+    }
+
+    /// Returns the size of this mapping in bytes
+    pub(crate) fn size(&self) -> usize {
+        self.0.mapped_size
+    }
+
+    /// Remap a cached mapping as uncached, then synchronously flush that range of VAs from the
+    /// coprocessor cache. This is required to safely unmap cached/private mappings.
+    fn remap_uncached_and_flush(&mut self) {
+        let mut owner = self.0.owner.lock();
+        mod_dev_dbg!(
+            owner.dev,
+            "MMU: remap as uncached {:#x}:{:#x}\n",
+            self.iova(),
+            self.size()
+        );
+
+        // The IOMMU API does not allow us to remap things in-place...
+        // just do an unmap and map again for now.
+        // Do not try to unmap guard page (-1)
+        if owner
+            .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT)
+            .is_err()
+        {
+            dev_err!(
+                owner.dev,
+                "MMU: unmap for remap {:#x}:{:#x} failed\n",
+                self.iova(),
+                self.size()
+            );
+        }
+
+        let prot = self.0.prot | prot::CACHE;
+        if owner.map_node(&self.0, prot).is_err() {
+            dev_err!(
+                owner.dev,
+                "MMU: remap {:#x}:{:#x} failed\n",
+                self.iova(),
+                self.size()
+            );
+        }
+
+        // If we don't have (and have never had) a VM slot, just return
+        let slot = match owner.slot() {
+            None => return,
+            Some(slot) => slot,
+        };
+
+        let flush_slot = if owner.is_kernel {
+            // If this is a kernel mapping, always flush on index 64
+            UAT_NUM_CTX as u32
+        } else {
+            // Otherwise, check if this slot is the active one, otherwise return
+            // Also check that we actually own this slot
+            let ttb = owner.ttb() | TTBR_VALID | (slot as u64) << TTBR_ASID_SHIFT;
+
+            let uat_inner = self.0.uat_inner.lock();
+            uat_inner.handoff().lock();
+            let cur_slot = uat_inner.handoff().current_slot();
+            let ttb_cur = uat_inner.ttbs()[slot as usize].ttb0.load(Ordering::Relaxed);
+            uat_inner.handoff().unlock();
+            if cur_slot == Some(slot) && ttb_cur == ttb {
+                slot
+            } else {
+                return;
+            }
+        };
+
+        // FIXME: There is a race here, though it'll probably never happen in practice.
+        // In theory, it's possible for the ASC to finish using our slot, whatever command
+        // it was processing to complete, the slot to be lost to another context, and the ASC
+        // to begin using it again with a different page table, thus faulting when it gets a
+        // flush request here. In practice, the chance of this happening is probably vanishingly
+        // small, as all 62 other slots would have to be recycled or in use before that slot can
+        // be reused, and the ASC using user contexts at all is very rare.
+
+        // Still, the locking around UAT/Handoff/TTBs should probably be redesigned to better
+        // model the interactions with the firmware and avoid these races.
+        // Possibly TTB changes should be tied to slot locks:
+
+        // Flush:
+        //  - Can early check handoff here (no need to lock).
+        //      If user slot and it doesn't match the active ASC slot,
+        //      we can elide the flush as the ASC guarantees it flushes
+        //      TLBs/caches when it switches context. We just need a
+        //      barrier to ensure ordering.
+        //  - Lock TTB slot
+        //      - If user ctx:
+        //          - Lock handoff AP-side
+        //              - Lock handoff dekker
+        //                  - Check TTB & handoff cur ctx
+        //      - Perform flush if necessary
+        //          - This implies taking the fwring lock
+        //
+        // TTB change:
+        //  - lock TTB slot
+        //      - lock handoff AP-side
+        //          - lock handoff dekker
+        //              change TTB
+
+        // Lock this flush slot, and write the range to it
+        let flush = self.0.uat_inner.lock_flush(flush_slot);
+        let pages = self.size() >> UAT_PGBIT;
+        flush.begin_flush(self.iova() as u64, self.size() as u64);
+        if pages >= 0x10000 {
+            dev_err!(owner.dev, "MMU: Flush too big ({:#x} pages))\n", pages);
+        }
+
+        let cmd = fw::channels::FwCtlMsg {
+            addr: fw::types::U64(self.iova() as u64),
+            unk_8: 0,
+            slot: flush_slot,
+            page_count: pages as u16,
+            unk_12: 2, // ?
+        };
+
+        // Tell the firmware to do a cache flush
+        if let Err(e) = owner.dev.data().gpu.fwctl(cmd) {
+            dev_err!(
+                owner.dev,
+                "MMU: ASC cache flush {:#x}:{:#x} failed (err: {:?})\n",
+                self.iova(),
+                self.size(),
+                e
+            );
+        }
+
+        // Finish the flush
+        flush.end_flush();
+
+        // Slot is unlocked here
+    }
+}
+
+impl Drop for Mapping {
+    fn drop(&mut self) {
+        // This is the main unmap function for UAT mappings.
+        // The sequence of operations here is finicky, due to the interaction
+        // between cached GFX ASC mappings and the page tables. These mappings
+        // always have to be flushed from the cache before being unmapped.
+
+        // For uncached mappings, just unmapping and flushing the TLB is sufficient.
+
+        // For cached mappings, this is the required sequence:
+        // 1. Remap it as uncached
+        // 2. Flush the TLB range
+        // 3. If kernel VA mapping OR user VA mapping and handoff.current_slot() == slot:
+        //    a. Take a lock for this slot
+        //    b. Write the flush range to the right context slot in handoff area
+        //    c. Issue a cache invalidation request via FwCtl queue
+        //    d. Poll for completion via queue
+        //    e. Check for completion flag in the handoff area
+        //    f. Drop the lock
+        // 4. Unmap
+        // 5. Flush the TLB range again
+
+        // prot::CACHE means "cache coherent" which means *uncached* here.
+        if self.0.prot & prot::CACHE == 0 {
+            self.remap_uncached_and_flush();
+        }
+
+        let mut owner = self.0.owner.lock();
+        mod_dev_dbg!(
+            owner.dev,
+            "MMU: unmap {:#x}:{:#x}\n",
+            self.iova(),
+            self.size()
+        );
+
+        if owner
+            .unmap_pages(self.iova(), UAT_PGSZ, self.size() >> UAT_PGBIT)
+            .is_err()
+        {
+            dev_err!(
+                owner.dev,
+                "MMU: unmap {:#x}:{:#x} failed\n",
+                self.iova(),
+                self.size()
+            );
+        }
+
+        if let Some(asid) = owner.slot() {
+            mem::tlbi_range(asid as u8, self.iova(), self.size());
+            mod_dev_dbg!(
+                owner.dev,
+                "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n",
+                asid,
+                self.iova(),
+                self.size()
+            );
+            mem::sync();
+        }
+    }
+}
+
+/// Shared UAT global data structures
+struct UatShared {
+    kernel_ttb1: u64,
+    map_kernel_to_user: bool,
+    handoff_rgn: UatRegion,
+    ttbs_rgn: UatRegion,
+}
+
+impl UatShared {
+    /// Returns the handoff region area
+    fn handoff(&self) -> &Handoff {
+        // SAFETY: pointer is non-null per the type invariant
+        unsafe { (self.handoff_rgn.map.as_ptr() as *mut Handoff).as_ref() }.unwrap()
+    }
+
+    /// Returns the TTBAT area
+    fn ttbs(&self) -> &[SlotTTBS; UAT_NUM_CTX] {
+        // SAFETY: pointer is non-null per the type invariant
+        unsafe { (self.ttbs_rgn.map.as_ptr() as *mut [SlotTTBS; UAT_NUM_CTX]).as_ref() }.unwrap()
+    }
+}
+
+// SAFETY: Nothing here is unsafe to send across threads.
+unsafe impl Send for UatShared {}
+
+/// Inner data for the top-level UAT instance.
+#[pin_data]
+struct UatInner {
+    #[pin]
+    shared: Mutex<UatShared>,
+    #[pin]
+    handoff_flush: [Mutex<HandoffFlush>; UAT_NUM_CTX + 1],
+}
+
+impl UatInner {
+    /// Take the lock on the shared data and return the guard.
+    fn lock(&self) -> Guard<'_, UatShared, MutexBackend> {
+        self.shared.lock()
+    }
+
+    /// Take a lock on a handoff flush slot and return the guard.
+    fn lock_flush(&self, slot: u32) -> Guard<'_, HandoffFlush, MutexBackend> {
+        self.handoff_flush[slot as usize].lock()
+    }
+}
+
+/// Top-level UAT manager object
+pub(crate) struct Uat {
+    dev: driver::AsahiDevRef,
+    cfg: &'static hw::HwConfig,
+    pagetables_rgn: UatRegion,
+
+    inner: Arc<UatInner>,
+    slots: slotalloc::SlotAllocator<SlotInner>,
+
+    kernel_vm: Vm,
+    kernel_lower_vm: Vm,
+}
+
+impl Drop for UatRegion {
+    fn drop(&mut self) {
+        // SAFETY: the pointer is valid by the type invariant
+        unsafe { bindings::memunmap(self.map.as_ptr()) };
+    }
+}
+
+impl Handoff {
+    /// Lock the handoff region from firmware access
+    fn lock(&self) {
+        self.lock_ap.store(1, Ordering::Relaxed);
+        fence(Ordering::SeqCst);
+
+        while self.lock_fw.load(Ordering::Relaxed) != 0 {
+            if self.turn.load(Ordering::Relaxed) != 0 {
+                self.lock_ap.store(0, Ordering::Relaxed);
+                while self.turn.load(Ordering::Relaxed) != 0 {}
+                self.lock_ap.store(1, Ordering::Relaxed);
+                fence(Ordering::SeqCst);
+            }
+        }
+        fence(Ordering::Acquire);
+    }
+
+    /// Unlock the handoff region, allowing firmware access
+    fn unlock(&self) {
+        self.turn.store(1, Ordering::Relaxed);
+        self.lock_ap.store(0, Ordering::Release);
+    }
+
+    /// Returns the current Vm slot mapped by the firmware for lower/unprivileged access, if any.
+    fn current_slot(&self) -> Option<u32> {
+        let slot = self.cur_slot.load(Ordering::Relaxed);
+        if slot == 0 || slot == u32::MAX {
+            None
+        } else {
+            Some(slot)
+        }
+    }
+
+    /// Initialize the handoff region
+    fn init(&self) -> Result {
+        self.magic_ap.store(PPL_MAGIC, Ordering::Relaxed);
+        self.cur_slot.store(0, Ordering::Relaxed);
+        self.unk3.store(0, Ordering::Relaxed);
+        fence(Ordering::SeqCst);
+
+        let start = clock::KernelTime::now();
+        const TIMEOUT: Duration = Duration::from_millis(1000);
+
+        self.lock();
+        while start.elapsed() < TIMEOUT {
+            if self.magic_fw.load(Ordering::Relaxed) == PPL_MAGIC {
+                break;
+            } else {
+                self.unlock();
+                delay::coarse_sleep(Duration::from_millis(10));
+                self.lock();
+            }
+        }
+
+        if self.magic_fw.load(Ordering::Relaxed) != PPL_MAGIC {
+            self.unlock();
+            pr_err!("Handoff: Failed to initialize (firmware not running?)\n");
+            return Err(EIO);
+        }
+
+        self.unlock();
+
+        for i in 0..=UAT_NUM_CTX {
+            self.flush[i].state.store(0, Ordering::Relaxed);
+            self.flush[i].addr.store(0, Ordering::Relaxed);
+            self.flush[i].size.store(0, Ordering::Relaxed);
+        }
+        fence(Ordering::SeqCst);
+        Ok(())
+    }
+}
+
+/// Represents a single flush info slot in the handoff region.
+///
+/// # Invariants
+/// The pointer is valid and there is no aliasing HandoffFlush instance.
+struct HandoffFlush(*const FlushInfo);
+
+// SAFETY: These pointers are safe to send across threads.
+unsafe impl Send for HandoffFlush {}
+
+impl HandoffFlush {
+    /// Set up a flush operation for the coprocessor
+    fn begin_flush(&self, start: u64, size: u64) {
+        let flush = unsafe { self.0.as_ref().unwrap() };
+
+        let state = flush.state.load(Ordering::Relaxed);
+        if state != 0 {
+            pr_err!("Handoff: expected flush state 0, got {}\n", state);
+        }
+        flush.addr.store(start, Ordering::Relaxed);
+        flush.size.store(size, Ordering::Relaxed);
+        flush.state.store(1, Ordering::Relaxed);
+    }
+
+    /// Complete a flush operation for the coprocessor
+    fn end_flush(&self) {
+        let flush = unsafe { self.0.as_ref().unwrap() };
+        let state = flush.state.load(Ordering::Relaxed);
+        if state != 2 {
+            pr_err!("Handoff: expected flush state 2, got {}\n", state);
+        }
+        flush.state.store(0, Ordering::Relaxed);
+    }
+}
+
+// We do not implement FlushOps, since we flush manually in this module after
+// page table operations. Just provide dummy implementations.
+impl io_pgtable::FlushOps for Uat {
+    type Data = ();
+
+    fn tlb_flush_all(_data: <Self::Data as ForeignOwnable>::Borrowed<'_>) {}
+    fn tlb_flush_walk(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _iova: usize,
+        _size: usize,
+        _granule: usize,
+    ) {
+    }
+    fn tlb_add_page(
+        _data: <Self::Data as ForeignOwnable>::Borrowed<'_>,
+        _iova: usize,
+        _granule: usize,
+    ) {
+    }
+}
+
+impl Vm {
+    /// Create a new virtual memory address space
+    fn new(
+        dev: &driver::AsahiDevice,
+        uat_inner: Arc<UatInner>,
+        cfg: &'static hw::HwConfig,
+        is_kernel: bool,
+        id: u64,
+        file_id: u64,
+    ) -> Result<Vm> {
+        let page_table = AppleUAT::new(
+            dev,
+            io_pgtable::Config {
+                pgsize_bitmap: UAT_PGSZ,
+                ias: if is_kernel { UAT_IAS_KERN } else { UAT_IAS },
+                oas: cfg.uat_oas,
+                coherent_walk: true,
+                quirks: 0,
+            },
+            (),
+        )?;
+        let min_va = if is_kernel {
+            IOVA_KERN_BASE
+        } else {
+            IOVA_USER_BASE
+        };
+        let max_va = if is_kernel {
+            IOVA_KERN_TOP
+        } else {
+            IOVA_USER_TOP
+        };
+
+        let mm = mm::Allocator::new(min_va as u64, (max_va - min_va + 1) as u64, ())?;
+
+        Ok(Vm {
+            id,
+            file_id,
+            inner: Arc::pin_init(Mutex::new_named(
+                VmInner {
+                    dev: dev.into(),
+                    min_va,
+                    max_va,
+                    is_kernel,
+                    page_table,
+                    mm,
+                    uat_inner,
+                    binding: None,
+                    bind_token: None,
+                    active_users: 0,
+                    id,
+                },
+                c_str!("VmInner"),
+            ))?,
+        })
+    }
+
+    /// Get the translation table base for this Vm
+    fn ttb(&self) -> u64 {
+        self.inner.lock().ttb()
+    }
+
+    /// Map a GEM object (using its `SGTable`) into this Vm at a free address in a given range.
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn map_in_range(
+        &self,
+        size: usize,
+        sgt: gem::SGTable,
+        alignment: u64,
+        start: u64,
+        end: u64,
+        prot: u32,
+        guard: bool,
+    ) -> Result<Mapping> {
+        let mut inner = self.inner.lock();
+
+        let uat_inner = inner.uat_inner.clone();
+        let node = inner.mm.insert_node_in_range(
+            MappingInner {
+                owner: self.inner.clone(),
+                uat_inner,
+                prot,
+                sgt: Some(sgt),
+                mapped_size: size,
+            },
+            (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page
+            alignment,
+            0,
+            start,
+            end,
+            mm::InsertMode::Best,
+        )?;
+
+        inner.map_node(&node, prot)?;
+        Ok(Mapping(node))
+    }
+
+    /// Map a GEM object (using its `SGTable`) into this Vm at a specific address.
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn map_at(
+        &self,
+        addr: u64,
+        size: usize,
+        sgt: gem::SGTable,
+        prot: u32,
+        guard: bool,
+    ) -> Result<Mapping> {
+        let mut inner = self.inner.lock();
+
+        let uat_inner = inner.uat_inner.clone();
+        let node = inner.mm.reserve_node(
+            MappingInner {
+                owner: self.inner.clone(),
+                uat_inner,
+                prot,
+                sgt: Some(sgt),
+                mapped_size: size,
+            },
+            addr,
+            (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page
+            0,
+        )?;
+
+        inner.map_node(&node, prot)?;
+        Ok(Mapping(node))
+    }
+
+    /// Add a direct MMIO mapping to this Vm at a free address.
+    pub(crate) fn map_io(&self, phys: usize, size: usize, prot: u32) -> Result<Mapping> {
+        let mut inner = self.inner.lock();
+
+        let uat_inner = inner.uat_inner.clone();
+        let node = inner.mm.insert_node_in_range(
+            MappingInner {
+                owner: self.inner.clone(),
+                uat_inner,
+                prot,
+                sgt: None,
+                mapped_size: size,
+            },
+            (size + UAT_PGSZ) as u64, // Add guard page
+            UAT_PGSZ as u64,
+            0,
+            IOVA_KERN_MMIO_BASE as u64,
+            IOVA_KERN_MMIO_TOP as u64,
+            mm::InsertMode::Best,
+        )?;
+
+        let iova = node.start() as usize;
+
+        if (phys | size | iova) & UAT_PGMSK != 0 {
+            dev_err!(
+                inner.dev,
+                "MMU: Mapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
+                phys,
+                size,
+                iova
+            );
+            return Err(EINVAL);
+        }
+
+        dev_info!(
+            inner.dev,
+            "MMU: IO map: {:#x}:{:#x} -> {:#x}\n",
+            phys,
+            size,
+            iova
+        );
+
+        inner.map_pages(iova, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
+
+        Ok(Mapping(node))
+    }
+
+    /// Returns the unique ID of this Vm
+    pub(crate) fn id(&self) -> u64 {
+        self.id
+    }
+
+    /// Returns the unique File ID of the owner of this Vm
+    pub(crate) fn file_id(&self) -> u64 {
+        self.file_id
+    }
+}
+
+impl Drop for VmInner {
+    fn drop(&mut self) {
+        assert_eq!(self.active_users, 0);
+
+        mod_pr_debug!(
+            "VmInner::Drop [{}]: bind_token={:?}\n",
+            self.id,
+            self.bind_token
+        );
+
+        // Make sure this VM is not mapped to a TTB if it was
+        if let Some(token) = self.bind_token.take() {
+            let idx = (token.last_slot() as usize) + UAT_USER_CTX_START;
+            let ttb = self.ttb() | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT;
+
+            let uat_inner = self.uat_inner.lock();
+            uat_inner.handoff().lock();
+            let handoff_cur = uat_inner.handoff().current_slot();
+            let ttb_cur = uat_inner.ttbs()[idx].ttb0.load(Ordering::SeqCst);
+            let inval = ttb_cur == ttb;
+            if inval {
+                if handoff_cur == Some(idx as u32) {
+                    pr_err!(
+                        "VmInner::drop owning slot {}, but it is currently in use by the ASC?\n",
+                        idx
+                    );
+                }
+                uat_inner.ttbs()[idx].ttb0.store(0, Ordering::SeqCst);
+                uat_inner.ttbs()[idx].ttb1.store(0, Ordering::SeqCst);
+            }
+            uat_inner.handoff().unlock();
+            core::mem::drop(uat_inner);
+
+            // In principle we dropped all the Mappings already, but we might as
+            // well play it safe and invalidate the whole ASID.
+            if inval {
+                mod_pr_debug!(
+                    "VmInner::Drop [{}]: need inval for ASID {:#x}\n",
+                    self.id,
+                    idx
+                );
+                mem::tlbi_asid(idx as u8);
+                mem::sync();
+            }
+        }
+    }
+}
+
+impl Uat {
+    /// Map a bootloader-preallocated memory region
+    fn map_region(
+        dev: &dyn device::RawDevice,
+        name: &CStr,
+        size: usize,
+        cached: bool,
+    ) -> Result<UatRegion> {
+        let rdev = dev.raw_device();
+
+        let mut res = core::mem::MaybeUninit::<bindings::resource>::uninit();
+
+        let res = unsafe {
+            let idx = bindings::of_property_match_string(
+                (*rdev).of_node,
+                c_str!("memory-region-names").as_char_ptr(),
+                name.as_char_ptr(),
+            );
+            to_result(idx)?;
+
+            let np = bindings::of_parse_phandle(
+                (*rdev).of_node,
+                c_str!("memory-region").as_char_ptr(),
+                idx,
+            );
+            if np.is_null() {
+                dev_err!(dev, "Missing {} region\n", name);
+                return Err(EINVAL);
+            }
+            let ret = bindings::of_address_to_resource(np, 0, res.as_mut_ptr());
+            #[cfg(CONFIG_OF_DYNAMIC)]
+            bindings::of_node_put(np);
+
+            if ret < 0 {
+                dev_err!(dev, "Failed to get {} region\n", name);
+                to_result(ret)?
+            }
+
+            res.assume_init()
+        };
+
+        let rgn_size: usize = unsafe { bindings::resource_size(&res) } as usize;
+
+        if size > rgn_size {
+            dev_err!(
+                dev,
+                "Region {} is too small (expected {}, got {})\n",
+                name,
+                size,
+                rgn_size
+            );
+            return Err(ENOMEM);
+        }
+
+        let flags = if cached {
+            bindings::MEMREMAP_WB
+        } else {
+            bindings::MEMREMAP_WC
+        };
+        let map = unsafe { bindings::memremap(res.start, rgn_size, flags.into()) };
+        let map = NonNull::new(map);
+
+        match map {
+            None => {
+                dev_err!(dev, "Failed to remap {} region\n", name);
+                Err(ENOMEM)
+            }
+            Some(map) => Ok(UatRegion {
+                base: res.start,
+                map,
+            }),
+        }
+    }
+
+    /// Returns a view into the root kernel (upper half) page table
+    fn kpt0(&self) -> &[Pte; UAT_NPTE] {
+        // SAFETY: pointer is non-null per the type invariant
+        unsafe { (self.pagetables_rgn.map.as_ptr() as *mut [Pte; UAT_NPTE]).as_ref() }.unwrap()
+    }
+
+    /// Returns a reference to the global kernel (upper half) `Vm`
+    pub(crate) fn kernel_vm(&self) -> &Vm {
+        &self.kernel_vm
+    }
+
+    /// Returns a reference to the local kernel (lower half) `Vm`
+    pub(crate) fn kernel_lower_vm(&self) -> &Vm {
+        &self.kernel_lower_vm
+    }
+
+    /// Returns the base physical address of the TTBAT region.
+    pub(crate) fn ttb_base(&self) -> u64 {
+        let inner = self.inner.lock();
+
+        inner.ttbs_rgn.base
+    }
+
+    /// Binds a `Vm` to a slot, preferring the last used one.
+    pub(crate) fn bind(&self, vm: &Vm) -> Result<VmBind> {
+        let mut inner = vm.inner.lock();
+
+        if inner.binding.is_none() {
+            assert_eq!(inner.active_users, 0);
+
+            let slot = self.slots.get(inner.bind_token)?;
+            if slot.changed() {
+                mod_pr_debug!("Vm Bind [{}]: bind_token={:?}\n", vm.id, slot.token(),);
+                let idx = (slot.slot() as usize) + UAT_USER_CTX_START;
+                let ttb = inner.ttb() | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT;
+
+                let uat_inner = self.inner.lock();
+
+                let ttb1 = if uat_inner.map_kernel_to_user {
+                    uat_inner.kernel_ttb1 | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT
+                } else {
+                    0
+                };
+
+                let ttbs = uat_inner.ttbs();
+                uat_inner.handoff().lock();
+                if uat_inner.handoff().current_slot() == Some(idx as u32) {
+                    pr_err!(
+                        "Vm::bind to slot {}, but it is currently in use by the ASC?\n",
+                        idx
+                    );
+                }
+                ttbs[idx].ttb0.store(ttb, Ordering::Relaxed);
+                ttbs[idx].ttb1.store(ttb1, Ordering::Relaxed);
+                uat_inner.handoff().unlock();
+                core::mem::drop(uat_inner);
+
+                // Make sure all TLB entries from the previous owner of this ASID are gone
+                mem::tlbi_asid(idx as u8);
+                mem::sync();
+            }
+
+            inner.bind_token = Some(slot.token());
+            inner.binding = Some(slot);
+        }
+
+        inner.active_users += 1;
+
+        let slot = inner.binding.as_ref().unwrap().slot() + UAT_USER_CTX_START as u32;
+        mod_pr_debug!("MMU: slot {} active users {}\n", slot, inner.active_users);
+        Ok(VmBind(vm.clone(), slot))
+    }
+
+    /// Creates a new `Vm` linked to this UAT.
+    pub(crate) fn new_vm(&self, id: u64, file_id: u64) -> Result<Vm> {
+        Vm::new(&self.dev, self.inner.clone(), self.cfg, false, id, file_id)
+    }
+
+    /// Creates the reference-counted inner data for a new `Uat` instance.
+    #[inline(never)]
+    fn make_inner(dev: &driver::AsahiDevice) -> Result<Arc<UatInner>> {
+        let handoff_rgn = Self::map_region(dev, c_str!("handoff"), HANDOFF_SIZE, false)?;
+        let ttbs_rgn = Self::map_region(dev, c_str!("ttbs"), SLOTS_SIZE, false)?;
+
+        let handoff = unsafe { &(handoff_rgn.map.as_ptr() as *mut Handoff).as_ref().unwrap() };
+
+        dev_info!(dev, "MMU: Initializing kernel page table\n");
+
+        Arc::pin_init(
+            try_pin_init!(UatInner {
+                handoff_flush <- init::pin_init_array_from_fn(|i| {
+                    Mutex::new_named(HandoffFlush(&handoff.flush[i]), c_str!("handoff_flush"))
+                }),
+                shared <- Mutex::new_named(
+                    UatShared {
+                        kernel_ttb1: 0,
+                        map_kernel_to_user: false,
+                        handoff_rgn,
+                        ttbs_rgn,
+                    },
+                    c_str!("uat_shared")
+                ),
+            }),
+            GFP_KERNEL,
+        )
+    }
+
+    /// Creates a new `Uat` instance given the relevant hardware config.
+    #[inline(never)]
+    pub(crate) fn new(
+        dev: &driver::AsahiDevice,
+        cfg: &'static hw::HwConfig,
+        map_kernel_to_user: bool,
+    ) -> Result<Self> {
+        dev_info!(dev, "MMU: Initializing...\n");
+
+        let inner = Self::make_inner(dev)?;
+
+        let pagetables_rgn = Self::map_region(dev, c_str!("pagetables"), PAGETABLES_SIZE, true)?;
+
+        dev_info!(dev, "MMU: Creating kernel page tables\n");
+        let kernel_lower_vm = Vm::new(dev, inner.clone(), cfg, false, 1, 0)?;
+        let kernel_vm = Vm::new(dev, inner.clone(), cfg, true, 0, 0)?;
+
+        dev_info!(dev, "MMU: Kernel page tables created\n");
+
+        let ttb0 = kernel_lower_vm.ttb();
+        let ttb1 = kernel_vm.ttb();
+
+        let uat = Self {
+            dev: dev.into(),
+            cfg,
+            pagetables_rgn,
+            kernel_vm,
+            kernel_lower_vm,
+            inner,
+            slots: slotalloc::SlotAllocator::new(
+                UAT_USER_CTX as u32,
+                (),
+                |_inner, _slot| SlotInner(),
+                c_str!("Uat::SlotAllocator"),
+                static_lock_class!(),
+                static_lock_class!(),
+            )?,
+        };
+
+        let mut inner = uat.inner.lock();
+
+        inner.map_kernel_to_user = map_kernel_to_user;
+        inner.kernel_ttb1 = uat.pagetables_rgn.base;
+
+        inner.handoff().init()?;
+
+        dev_info!(dev, "MMU: Initializing TTBs\n");
+
+        inner.handoff().lock();
+
+        let ttbs = inner.ttbs();
+
+        ttbs[0].ttb0.store(ttb0 | TTBR_VALID, Ordering::Relaxed);
+        ttbs[0]
+            .ttb1
+            .store(uat.pagetables_rgn.base | TTBR_VALID, Ordering::Relaxed);
+
+        for ctx in &ttbs[1..] {
+            ctx.ttb0.store(0, Ordering::Relaxed);
+            ctx.ttb1.store(0, Ordering::Relaxed);
+        }
+
+        inner.handoff().unlock();
+
+        core::mem::drop(inner);
+
+        uat.kpt0()[2].store(ttb1 | PTE_TABLE, Ordering::Relaxed);
+
+        dev_info!(dev, "MMU: initialized\n");
+
+        Ok(uat)
+    }
+}
+
+impl Drop for Uat {
+    fn drop(&mut self) {
+        // Unmap what we mapped
+        self.kpt0()[2].store(0, Ordering::Relaxed);
+
+        // Make sure we flush the TLBs
+        fence(Ordering::SeqCst);
+        mem::tlbi_all();
+        mem::sync();
+    }
+}
diff --git a/drivers/gpu/drm/asahi/object.rs b/drivers/gpu/drm/asahi/object.rs
new file mode 100644
index 00000000000000..3e099f42b4c89a
--- /dev/null
+++ b/drivers/gpu/drm/asahi/object.rs
@@ -0,0 +1,711 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Asahi GPU object model
+//!
+//! The AGX GPU includes a coprocessor that uses a large number of shared memory structures to
+//! communicate with the driver. These structures contain GPU VA pointers to each other, which are
+//! directly dereferenced by the firmware and are expected to always be valid for the usage
+//! lifetime of the containing struct (which is an implicit contract, not explicitly managed).
+//! Any faults cause an unrecoverable firmware crash, requiring a full system reboot.
+//!
+//! In order to manage this complexity safely, we implement a GPU object model using Rust's type
+//! system to enforce GPU object lifetime relationships. GPU objects represent an allocated piece
+//! of memory of a given type, mapped to the GPU (and usually also the CPU). On the CPU side,
+//! these objects are associated with a pure Rust structure that contains the objects it depends
+//! on (or references to them). This allows us to map Rust lifetimes into the GPU object model
+//! system. Then, GPU VA pointers also inherit those lifetimes, which means the Rust borrow checker
+//! can ensure that all pointers are assigned an address that is guaranteed to outlive the GPU
+//! object it points to.
+//!
+//! Since the firmware object model does have self-referencing pointers (and there is of course no
+//! underlying revocability mechanism to make it safe), we must have an escape hatch. GPU pointers
+//! can be weak pointers, which do not enforce lifetimes. In those cases, it is the user's
+//! responsibility to ensure that lifetime requirements are met.
+//!
+//! In other words, the model is necessarily leaky and there is no way to fully map Rust safety to
+//! GPU firmware object safety. The goal of the model is to make it easy to model the lifetimes of
+//! GPU objects and have the compiler help in avoiding mistakes, rather than to guarantee safety
+//! 100% of the time as would be the case for CPU-side Rust code.
+
+// TODO: There is a fundamental soundness issue with sharing memory with the GPU (that even affects
+// C code too). Since the GPU is free to mutate that memory at any time, normal reference invariants
+// cannot be enforced on the CPU side. For example, the compiler could perform an optimization that
+// assumes that a given memory location does not change between two reads, and causes UB otherwise,
+// and then the GPU could mutate that memory out from under the CPU.
+//
+// For cases where we *expect* this to happen, we use atomic types, which avoid this issue. However,
+// doing so for every single field of every type is a non-starter. Right now, there seems to be no
+// good solution for this that does not come with significant performance or ergonomics downsides.
+//
+// In *practice* we are almost always only writing GPU memory, and only reading from atomics, so the
+// chances of this actually triggering UB (e.g. a security issue that can be triggered from the GPU
+// side) due to a compiler optimization are very slim.
+//
+// Further discussion: https://github.com/rust-lang/unsafe-code-guidelines/issues/152
+
+use kernel::{error::code::*, prelude::*};
+
+use alloc::boxed::Box;
+use core::fmt;
+use core::fmt::Debug;
+use core::fmt::Formatter;
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+use core::num::NonZeroU64;
+use core::ops::{Deref, DerefMut, Index, IndexMut};
+use core::{mem, ptr, slice};
+
+use crate::alloc::Allocation;
+use crate::debug::*;
+use crate::fw::types::Zeroable;
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Object;
+
+/// A GPU-side strong pointer, which is a 64-bit non-zero VA with an associated lifetime.
+///
+/// In rare cases these pointers are not aligned, so this is `packed(1)`.
+#[repr(C, packed(1))]
+pub(crate) struct GpuPointer<'a, T: ?Sized>(NonZeroU64, PhantomData<&'a T>);
+
+impl<'a, T: ?Sized> GpuPointer<'a, T> {
+    /// Logical OR the pointer with an arbitrary `u64`. This is used when GPU struct fields contain
+    /// misc flag fields in the upper bits. The lifetime is retained. This is GPU-unsafe in
+    /// principle, but we assert that only non-implemented address bits are touched, which is safe
+    /// for pointers used by the GPU (not by firmware).
+    pub(crate) fn or(&self, other: u64) -> GpuPointer<'a, T> {
+        // This will fail for kernel-half pointers, which should not be ORed.
+        assert_eq!(self.0.get() & other, 0);
+        // Assert that we only touch the high bits.
+        assert_eq!(other & 0xffffffffff, 0);
+        GpuPointer(self.0 | other, PhantomData)
+    }
+
+    /// Add an arbitrary offset to the pointer. This is not safe (from the GPU perspective), and
+    /// should only be used via the `inner_ptr` macro to get pointers to inner fields, hence we mark
+    /// it `unsafe` to discourage direct use.
+    // NOTE: The third argument is a type inference hack.
+    pub(crate) unsafe fn offset<U>(&self, off: usize, _: *const U) -> GpuPointer<'a, U> {
+        GpuPointer::<'a, U>(
+            NonZeroU64::new(self.0.get() + (off as u64)).unwrap(),
+            PhantomData,
+        )
+    }
+}
+
+impl<'a, T: ?Sized> Debug for GpuPointer<'a, T> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let val = self.0;
+        f.write_fmt(format_args!("{:#x} ({})", val, core::any::type_name::<T>()))
+    }
+}
+
+impl<'a, T: ?Sized> From<GpuPointer<'a, T>> for u64 {
+    fn from(value: GpuPointer<'a, T>) -> Self {
+        value.0.get()
+    }
+}
+
+/// Take a pointer to a sub-field within a structure pointed to by a GpuPointer, keeping the
+/// lifetime.
+#[macro_export]
+macro_rules! inner_ptr {
+    ($gpuva:expr, $($f:tt)*) => ({
+        // This mirrors kernel::offset_of(), except we use type inference to avoid having to know
+        // the type of the pointer explicitly.
+        fn uninit_from<'a, T: GpuStruct>(_: GpuPointer<'a, T>) -> core::mem::MaybeUninit<T::Raw<'static>> {
+            core::mem::MaybeUninit::uninit()
+        }
+        let tmp = uninit_from($gpuva);
+        let outer = tmp.as_ptr();
+        // SAFETY: The pointer is valid and aligned, just not initialised; `addr_of` ensures that
+        // we don't actually read from `outer` (which would be UB) nor create an intermediate
+        // reference.
+        let p: *const _ = unsafe { core::ptr::addr_of!((*outer).$($f)*) };
+        let inner = p as *const u8;
+        // SAFETY: The two pointers are within the same allocation block.
+        let off = unsafe { inner.offset_from(outer as *const u8) };
+        // SAFETY: The resulting pointer is guaranteed to point to valid memory within the outer
+        // object.
+        unsafe { $gpuva.offset(off.try_into().unwrap(), p) }
+    })
+}
+
+/// A GPU-side weak pointer, which is a 64-bit non-zero VA with no lifetime.
+///
+/// In rare cases these pointers are not aligned, so this is `packed(1)`.
+#[repr(C, packed(1))]
+pub(crate) struct GpuWeakPointer<T: ?Sized>(NonZeroU64, PhantomData<*const T>);
+
+/// SAFETY: GPU weak pointers are always safe to share between threads.
+unsafe impl<T: ?Sized> Send for GpuWeakPointer<T> {}
+unsafe impl<T: ?Sized> Sync for GpuWeakPointer<T> {}
+
+// Weak pointers can be copied/cloned regardless of their target type.
+impl<T: ?Sized> Copy for GpuWeakPointer<T> {}
+
+impl<T: ?Sized> Clone for GpuWeakPointer<T> {
+    fn clone(&self) -> Self {
+        *self
+    }
+}
+
+impl<T: ?Sized> GpuWeakPointer<T> {
+    /// Add an arbitrary offset to the pointer. This is not safe (from the GPU perspective), and
+    /// should only be used via the `inner_ptr` macro to get pointers to inner fields, hence we mark
+    /// it `unsafe` to discourage direct use.
+    // NOTE: The third argument is a type inference hack.
+    pub(crate) unsafe fn offset<U>(&self, off: usize, _: *const U) -> GpuWeakPointer<U> {
+        GpuWeakPointer::<U>(
+            NonZeroU64::new(self.0.get() + (off as u64)).unwrap(),
+            PhantomData,
+        )
+    }
+
+    /// Upgrade a weak pointer into a strong pointer. This is not considered safe from the GPU
+    /// perspective.
+    pub(crate) unsafe fn upgrade<'a>(&self) -> GpuPointer<'a, T> {
+        GpuPointer(self.0, PhantomData)
+    }
+}
+
+impl<T: ?Sized> From<GpuWeakPointer<T>> for u64 {
+    fn from(value: GpuWeakPointer<T>) -> Self {
+        value.0.get()
+    }
+}
+
+impl<T: ?Sized> Debug for GpuWeakPointer<T> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        let val = self.0;
+        f.write_fmt(format_args!("{:#x} ({})", val, core::any::type_name::<T>()))
+    }
+}
+
+/// Take a pointer to a sub-field within a structure pointed to by a GpuWeakPointer.
+#[macro_export]
+macro_rules! inner_weak_ptr {
+    ($gpuva:expr, $($f:tt)*) => ({
+        // See inner_ptr()
+        fn uninit_from<T: GpuStruct>(_: GpuWeakPointer<T>) -> core::mem::MaybeUninit<T::Raw<'static>> {
+            core::mem::MaybeUninit::uninit()
+        }
+        let tmp = uninit_from($gpuva);
+        let outer = tmp.as_ptr();
+        // SAFETY: The pointer is valid and aligned, just not initialised; `addr_of` ensures that
+        // we don't actually read from `outer` (which would be UB) nor create an intermediate
+        // reference.
+        let p: *const _ = unsafe { core::ptr::addr_of!((*outer).$($f)*) };
+        let inner = p as *const u8;
+        // SAFETY: The two pointers are within the same allocation block.
+        let off = unsafe { inner.offset_from(outer as *const u8) };
+        // SAFETY: The resulting pointer is guaranteed to point to valid memory within the outer
+        // object.
+        unsafe { $gpuva.offset(off.try_into().unwrap(), p) }
+    })
+}
+
+/// Types that implement this trait represent a GPU structure from the CPU side.
+///
+/// The `Raw` type represents the actual raw structure definition on the GPU side.
+///
+/// Types implementing [`GpuStruct`] must have fields owning any objects (or strong references
+/// to them) that GPU pointers in the `Raw` structure point to. This mechanism is used to enforce
+/// lifetimes.
+pub(crate) trait GpuStruct: 'static {
+    /// The type of the GPU-side structure definition representing the firmware struct layout.
+    type Raw<'a>;
+}
+
+/// An instance of a GPU object in memory.
+///
+/// # Invariants
+/// `raw` must point to a valid mapping of the `T::Raw` type associated with the `alloc` allocation.
+/// `gpu_ptr` must be the GPU address of the same object.
+pub(crate) struct GpuObject<T: GpuStruct, U: Allocation<T>> {
+    raw: *mut T::Raw<'static>,
+    alloc: U,
+    gpu_ptr: GpuWeakPointer<T>,
+    inner: Box<T>,
+}
+
+impl<T: GpuStruct, U: Allocation<T>> GpuObject<T, U> {
+    /// Create a new GpuObject given an allocator and the inner data (a type implementing
+    /// GpuStruct).
+    ///
+    /// The caller passes a closure that constructs the `T::Raw` type given a reference to the
+    /// `GpuStruct`. This is the mechanism used to enforce lifetimes.
+    pub(crate) fn new(
+        alloc: U,
+        inner: T,
+        callback: impl for<'a> FnOnce(&'a T) -> T::Raw<'a>,
+    ) -> Result<Self> {
+        let size = mem::size_of::<T::Raw<'static>>();
+        if size > 0x1000 {
+            dev_crit!(
+                alloc.device(),
+                "Allocating {} of size {:#x}, with new, please use new_boxed!\n",
+                core::any::type_name::<T>(),
+                size
+            );
+        }
+        if alloc.size() < size {
+            return Err(ENOMEM);
+        }
+        let gpu_ptr =
+            GpuWeakPointer::<T>(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData);
+        mod_dev_dbg!(
+            alloc.device(),
+            "Allocating {} @ {:#x}\n",
+            core::any::type_name::<T>(),
+            alloc.gpu_ptr()
+        );
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T::Raw<'static>;
+        let mut raw = callback(&inner);
+        // SAFETY: `p` is guaranteed to be valid per the Allocation invariant, and the type is
+        // identical to the type of `raw` other than the lifetime.
+        unsafe { p.copy_from(&mut raw as *mut _ as *mut u8 as *mut _, 1) };
+        mem::forget(raw);
+        Ok(Self {
+            raw: p,
+            gpu_ptr,
+            alloc,
+            inner: Box::new(inner, GFP_KERNEL)?,
+        })
+    }
+
+    /// Create a new GpuObject given an allocator and the boxed inner data (a type implementing
+    /// GpuStruct).
+    ///
+    /// The caller passes a closure that initializes the `T::Raw` type given a reference to the
+    /// `GpuStruct` and a `MaybeUninit<T::Raw>`. This is intended to be used with the place!()
+    /// macro to avoid constructing the whole `T::Raw` object on the stack.
+    pub(crate) fn new_boxed(
+        alloc: U,
+        inner: Box<T>,
+        callback: impl for<'a> FnOnce(
+            &'a T,
+            &'a mut MaybeUninit<T::Raw<'a>>,
+        ) -> Result<&'a mut T::Raw<'a>>,
+    ) -> Result<Self> {
+        if alloc.size() < mem::size_of::<T::Raw<'static>>() {
+            return Err(ENOMEM);
+        }
+        let gpu_ptr =
+            GpuWeakPointer::<T>(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData);
+        mod_dev_dbg!(
+            alloc.device(),
+            "Allocating {} @ {:#x}\n",
+            core::any::type_name::<T>(),
+            alloc.gpu_ptr()
+        );
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut MaybeUninit<T::Raw<'_>>;
+        // SAFETY: `p` is guaranteed to be valid per the Allocation invariant.
+        let raw = callback(&inner, unsafe { &mut *p })?;
+        if p as *mut T::Raw<'_> != raw as *mut _ {
+            dev_err!(
+                alloc.device(),
+                "Allocation callback returned a mismatched reference ({})\n",
+                core::any::type_name::<T>(),
+            );
+            return Err(EINVAL);
+        }
+        Ok(Self {
+            raw: p as *mut u8 as *mut T::Raw<'static>,
+            gpu_ptr,
+            alloc,
+            inner,
+        })
+    }
+
+    /// Create a new GpuObject given an allocator and the inner data (a type implementing
+    /// GpuStruct).
+    ///
+    /// The caller passes a closure that initializes the `T::Raw` type given a reference to the
+    /// `GpuStruct` and a `MaybeUninit<T::Raw>`. This is intended to be used with the place!()
+    /// macro to avoid constructing the whole `T::Raw` object on the stack.
+    pub(crate) fn new_inplace(
+        alloc: U,
+        inner: T,
+        callback: impl for<'a> FnOnce(
+            &'a T,
+            &'a mut MaybeUninit<T::Raw<'a>>,
+        ) -> Result<&'a mut T::Raw<'a>>,
+    ) -> Result<Self> {
+        GpuObject::<T, U>::new_boxed(alloc, Box::new(inner, GFP_KERNEL)?, callback)
+    }
+
+    /// Create a new GpuObject given an allocator and the boxed inner data (a type implementing
+    /// GpuStruct).
+    ///
+    /// The caller passes a closure that initializes the `T::Raw` type given a reference to the
+    /// `GpuStruct` and a `MaybeUninit<T::Raw>`. This is intended to be used with the place!()
+    /// macro to avoid constructing the whole `T::Raw` object on the stack.
+    pub(crate) fn new_init_prealloc<'a, I: Init<T, E>, R: PinInit<T::Raw<'a>, F>, E, F>(
+        alloc: U,
+        inner_init: impl FnOnce(GpuWeakPointer<T>) -> I,
+        raw_init: impl FnOnce(&'a T, GpuWeakPointer<T>) -> R,
+    ) -> Result<Self>
+    where
+        kernel::error::Error: core::convert::From<E>,
+        kernel::error::Error: core::convert::From<F>,
+    {
+        if alloc.size() < mem::size_of::<T::Raw<'static>>() {
+            return Err(ENOMEM);
+        }
+        let gpu_ptr =
+            GpuWeakPointer::<T>(NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?, PhantomData);
+        mod_dev_dbg!(
+            alloc.device(),
+            "Allocating {} @ {:#x}\n",
+            core::any::type_name::<T>(),
+            alloc.gpu_ptr()
+        );
+        let inner = inner_init(gpu_ptr);
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T::Raw<'_>;
+        let ret = Self {
+            raw: p as *mut u8 as *mut T::Raw<'static>,
+            gpu_ptr,
+            alloc,
+            inner: Box::init(inner, GFP_KERNEL)?,
+        };
+        let q = &*ret.inner as *const T;
+        // SAFETY: `p` is guaranteed to be valid per the Allocation invariant.
+        unsafe { raw_init(&*q, gpu_ptr).__pinned_init(p) }?;
+        Ok(ret)
+    }
+
+    /// Returns the GPU VA of this object (as a raw [`NonZeroU64`])
+    pub(crate) fn gpu_va(&self) -> NonZeroU64 {
+        self.gpu_ptr.0
+    }
+
+    /// Returns a strong GPU pointer to this object, with a lifetime.
+    pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, T> {
+        GpuPointer(self.gpu_ptr.0, PhantomData)
+    }
+
+    /// Returns a weak GPU pointer to this object, with no lifetime.
+    pub(crate) fn weak_pointer(&self) -> GpuWeakPointer<T> {
+        GpuWeakPointer(self.gpu_ptr.0, PhantomData)
+    }
+
+    /// Perform a mutation to the inner `Raw` data given a user-supplied callback.
+    ///
+    /// The callback gets a mutable reference to the `GpuStruct` type.
+    pub(crate) fn with_mut<RetVal>(
+        &mut self,
+        callback: impl for<'a> FnOnce(&'a mut <T as GpuStruct>::Raw<'a>, &'a mut T) -> RetVal,
+    ) -> RetVal {
+        // SAFETY: `self.raw` is valid per the type invariant, and the second half is just
+        // converting lifetimes.
+        unsafe { callback(&mut *self.raw, &mut *(&mut *self.inner as *mut _)) }
+    }
+
+    /// Access the inner `Raw` data given a user-supplied callback.
+    ///
+    /// The callback gets a reference to the `GpuStruct` type.
+    pub(crate) fn with<RetVal>(
+        &self,
+        callback: impl for<'a> FnOnce(&'a <T as GpuStruct>::Raw<'a>, &'a T) -> RetVal,
+    ) -> RetVal {
+        // SAFETY: `self.raw` is valid per the type invariant, and the second half is just
+        // converting lifetimes.
+        unsafe { callback(&*self.raw, &*(&*self.inner as *const _)) }
+    }
+}
+
+impl<T: GpuStruct, U: Allocation<T>> Deref for GpuObject<T, U> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.inner
+    }
+}
+
+impl<T: GpuStruct, U: Allocation<T>> DerefMut for GpuObject<T, U> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        &mut self.inner
+    }
+}
+
+impl<T: GpuStruct + Debug, U: Allocation<T>> Debug for GpuObject<T, U>
+where
+    <T as GpuStruct>::Raw<'static>: Debug,
+{
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct(core::any::type_name::<T>())
+            // SAFETY: `self.raw` is valid per the type invariant.
+            .field("raw", &format_args!("{:#X?}", unsafe { &*self.raw }))
+            .field("inner", &format_args!("{:#X?}", &self.inner))
+            .field("alloc", &format_args!("{:?}", &self.alloc))
+            .finish()
+    }
+}
+
+impl<T: GpuStruct + Default, U: Allocation<T>> GpuObject<T, U>
+where
+    for<'a> <T as GpuStruct>::Raw<'a>: Default + Zeroable,
+{
+    /// Create a new GpuObject with default data. `T` must implement `Default` and `T::Raw` must
+    /// implement `Zeroable`, since the GPU-side memory is initialized by zeroing.
+    pub(crate) fn new_default(alloc: U) -> Result<Self> {
+        GpuObject::<T, U>::new_inplace(alloc, Default::default(), |_inner, raw| {
+            // SAFETY: `raw` is valid here, and `T::Raw` implements `Zeroable`.
+            Ok(unsafe {
+                ptr::write_bytes(raw, 0, 1);
+                (*raw).assume_init_mut()
+            })
+        })
+    }
+}
+
+impl<T: GpuStruct, U: Allocation<T>> Drop for GpuObject<T, U> {
+    fn drop(&mut self) {
+        mod_dev_dbg!(
+            self.alloc.device(),
+            "Dropping {} @ {:?}\n",
+            core::any::type_name::<T>(),
+            self.gpu_pointer()
+        );
+    }
+}
+
+// SAFETY: GpuObjects are Send as long as the GpuStruct itself is Send
+unsafe impl<T: GpuStruct + Send, U: Allocation<T>> Send for GpuObject<T, U> {}
+// SAFETY: GpuObjects are Send as long as the GpuStruct itself is Send
+unsafe impl<T: GpuStruct + Sync, U: Allocation<T>> Sync for GpuObject<T, U> {}
+
+/// Trait used to erase the type of a GpuObject, used when we need to keep a list of heterogenous
+/// objects around.
+pub(crate) trait OpaqueGpuObject: Send + Sync {
+    fn gpu_va(&self) -> NonZeroU64;
+}
+
+impl<T: GpuStruct + Sync + Send, U: Allocation<T>> OpaqueGpuObject for GpuObject<T, U> {
+    fn gpu_va(&self) -> NonZeroU64 {
+        Self::gpu_va(self)
+    }
+}
+
+/// An array of raw GPU objects that is only accessible to the GPU (no CPU-side mapping required).
+///
+/// This must necessarily be uninitialized as far as the GPU is concerned, so it cannot be used
+/// when initialization is required.
+///
+/// # Invariants
+///
+/// `alloc` is valid and at least as large as `len` times the size of one `T`.
+/// `gpu_ptr` is valid and points to the allocation start.
+pub(crate) struct GpuOnlyArray<T, U: Allocation<T>> {
+    len: usize,
+    alloc: U,
+    gpu_ptr: NonZeroU64,
+    _p: PhantomData<T>,
+}
+
+impl<T, U: Allocation<T>> GpuOnlyArray<T, U> {
+    /// Allocate a new GPU-only array with the given length.
+    pub(crate) fn new(alloc: U, count: usize) -> Result<GpuOnlyArray<T, U>> {
+        let bytes = count * mem::size_of::<T>();
+        let gpu_ptr = NonZeroU64::new(alloc.gpu_ptr()).ok_or(EINVAL)?;
+        if alloc.size() < bytes {
+            return Err(ENOMEM);
+        }
+        Ok(Self {
+            len: count,
+            alloc,
+            gpu_ptr,
+            _p: PhantomData,
+        })
+    }
+
+    /// Returns the GPU VA of this arraw (as a raw [`NonZeroU64`])
+    pub(crate) fn gpu_va(&self) -> NonZeroU64 {
+        self.gpu_ptr
+    }
+
+    /// Returns a strong GPU pointer to this array, with a lifetime.
+    pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, &'_ [T]> {
+        GpuPointer(self.gpu_ptr, PhantomData)
+    }
+
+    /// Returns a weak GPU pointer to this array, with no lifetime.
+    pub(crate) fn weak_pointer(&self) -> GpuWeakPointer<[T]> {
+        GpuWeakPointer(self.gpu_ptr, PhantomData)
+    }
+
+    /// Returns a pointer to an offset within the array (as a subslice).
+    pub(crate) fn gpu_offset_pointer(&self, offset: usize) -> GpuPointer<'_, &'_ [T]> {
+        if offset > self.len {
+            panic!("Index {} out of bounds (len: {})", offset, self.len);
+        }
+        GpuPointer(
+            NonZeroU64::new(self.gpu_ptr.get() + (offset * mem::size_of::<T>()) as u64).unwrap(),
+            PhantomData,
+        )
+    }
+
+    /* Not used yet
+    /// Returns a weak pointer to an offset within the array (as a subslice).
+    pub(crate) fn weak_offset_pointer(&self, offset: usize) -> GpuWeakPointer<[T]> {
+        if offset > self.len {
+            panic!("Index {} out of bounds (len: {})", offset, self.len);
+        }
+        GpuWeakPointer(
+            NonZeroU64::new(self.gpu_ptr.get() + (offset * mem::size_of::<T>()) as u64).unwrap(),
+            PhantomData,
+        )
+    }
+
+    /// Returns a pointer to an element within the array.
+    pub(crate) fn gpu_item_pointer(&self, index: usize) -> GpuPointer<'_, &'_ T> {
+        if index >= self.len {
+            panic!("Index {} out of bounds (len: {})", index, self.len);
+        }
+        GpuPointer(
+            NonZeroU64::new(self.gpu_ptr.get() + (index * mem::size_of::<T>()) as u64).unwrap(),
+            PhantomData,
+        )
+    }
+    */
+
+    /// Returns a weak pointer to an element within the array.
+    pub(crate) fn weak_item_pointer(&self, index: usize) -> GpuWeakPointer<T> {
+        if index >= self.len {
+            panic!("Index {} out of bounds (len: {})", index, self.len);
+        }
+        GpuWeakPointer(
+            NonZeroU64::new(self.gpu_ptr.get() + (index * mem::size_of::<T>()) as u64).unwrap(),
+            PhantomData,
+        )
+    }
+
+    /// Returns the length of the array.
+    pub(crate) fn len(&self) -> usize {
+        self.len
+    }
+}
+
+impl<T: Debug, U: Allocation<T>> Debug for GpuOnlyArray<T, U> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct(core::any::type_name::<T>())
+            .field("len", &format_args!("{:#X?}", self.len()))
+            .finish()
+    }
+}
+
+impl<T, U: Allocation<T>> Drop for GpuOnlyArray<T, U> {
+    fn drop(&mut self) {
+        mod_dev_dbg!(
+            self.alloc.device(),
+            "Dropping {} @ {:?}\n",
+            core::any::type_name::<T>(),
+            self.gpu_pointer()
+        );
+    }
+}
+
+/// An array of raw GPU objects that is also CPU-accessible.
+///
+/// # Invariants
+///
+/// `raw` is valid and points to the CPU-side view of the array (which must have one).
+pub(crate) struct GpuArray<T, U: Allocation<T>> {
+    raw: *mut T,
+    array: GpuOnlyArray<T, U>,
+}
+
+/* Not used yet
+impl<T: Copy, U: Allocation<T>> GpuArray<T, U> {
+    /// Allocate a new GPU array, copying the contents from a slice.
+    pub(crate) fn new(alloc: U, data: &[T]) -> Result<GpuArray<T, U>> {
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr();
+        let inner = GpuOnlyArray::new(alloc, data.len())?;
+        // SAFETY: `p` is valid per the Allocation type invariant, and GpuOnlyArray guarantees
+        // that its size is at least as large as `data.len()`.
+        unsafe { ptr::copy(data.as_ptr(), p, data.len()) };
+        Ok(Self {
+            raw: p,
+            array: inner,
+        })
+    }
+}
+*/
+
+impl<T: Default, U: Allocation<T>> GpuArray<T, U> {
+    /// Allocate a new GPU array, initializing each element to its default.
+    pub(crate) fn empty(alloc: U, count: usize) -> Result<GpuArray<T, U>> {
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T;
+        let inner = GpuOnlyArray::new(alloc, count)?;
+        let mut pi = p;
+        for _i in 0..count {
+            // SAFETY: `pi` is valid per the Allocation type invariant, and GpuOnlyArray guarantees
+            // that it can never iterate beyond the buffer length.
+            unsafe {
+                pi.write(Default::default());
+                pi = pi.add(1);
+            }
+        }
+        Ok(Self {
+            raw: p,
+            array: inner,
+        })
+    }
+}
+
+impl<T, U: Allocation<T>> GpuArray<T, U> {
+    /// Get a slice view of the array contents.
+    pub(crate) fn as_slice(&self) -> &[T] {
+        // SAFETY: self.raw / self.len are valid per the type invariant
+        unsafe { slice::from_raw_parts(self.raw, self.len) }
+    }
+
+    /// Get a mutable slice view of the array contents.
+    pub(crate) fn as_mut_slice(&mut self) -> &mut [T] {
+        // SAFETY: self.raw / self.len are valid per the type invariant
+        unsafe { slice::from_raw_parts_mut(self.raw, self.len) }
+    }
+}
+
+impl<T, U: Allocation<T>> Deref for GpuArray<T, U> {
+    type Target = GpuOnlyArray<T, U>;
+
+    fn deref(&self) -> &GpuOnlyArray<T, U> {
+        &self.array
+    }
+}
+
+impl<T, U: Allocation<T>> Index<usize> for GpuArray<T, U> {
+    type Output = T;
+
+    fn index(&self, index: usize) -> &T {
+        if index >= self.len {
+            panic!("Index {} out of bounds (len: {})", index, self.len);
+        }
+        // SAFETY: This is bounds checked above
+        unsafe { &*(self.raw.add(index)) }
+    }
+}
+
+impl<T, U: Allocation<T>> IndexMut<usize> for GpuArray<T, U> {
+    fn index_mut(&mut self, index: usize) -> &mut T {
+        if index >= self.len {
+            panic!("Index {} out of bounds (len: {})", index, self.len);
+        }
+        // SAFETY: This is bounds checked above
+        unsafe { &mut *(self.raw.add(index)) }
+    }
+}
+
+// SAFETY: GpuArray are Send as long as the contained type itself is Send
+unsafe impl<T: Send, U: Allocation<T>> Send for GpuArray<T, U> {}
+// SAFETY: GpuArray are Sync as long as the contained type itself is Sync
+unsafe impl<T: Sync, U: Allocation<T>> Sync for GpuArray<T, U> {}
+
+impl<T: Debug, U: Allocation<T>> Debug for GpuArray<T, U> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
+        f.debug_struct(core::any::type_name::<T>())
+            .field("array", &format_args!("{:#X?}", self.as_slice()))
+            .finish()
+    }
+}
diff --git a/drivers/gpu/drm/asahi/queue/common.rs b/drivers/gpu/drm/asahi/queue/common.rs
new file mode 100644
index 00000000000000..d46f0b0fe93da8
--- /dev/null
+++ b/drivers/gpu/drm/asahi/queue/common.rs
@@ -0,0 +1,58 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Common queue functionality.
+//!
+//! Shared helpers used by the submission logic for multiple command types.
+
+use crate::fw::microseq;
+use crate::fw::types::*;
+
+use kernel::io_buffer::IoBufferReader;
+use kernel::prelude::*;
+use kernel::uapi;
+use kernel::user_ptr::UserSlicePtr;
+
+use core::mem::MaybeUninit;
+
+pub(super) fn build_attachments(pointer: u64, count: u32) -> Result<microseq::Attachments> {
+    if count as usize > microseq::MAX_ATTACHMENTS {
+        return Err(EINVAL);
+    }
+
+    const STRIDE: usize = core::mem::size_of::<uapi::drm_asahi_attachment>();
+    let size = STRIDE * count as usize;
+
+    // SAFETY: We only read this once, so there are no TOCTOU issues.
+    let mut reader = unsafe { UserSlicePtr::new(pointer as usize as *mut _, size).reader() };
+
+    let mut attachments: microseq::Attachments = Default::default();
+
+    for i in 0..count {
+        let mut att: MaybeUninit<uapi::drm_asahi_attachment> = MaybeUninit::uninit();
+
+        // SAFETY: The size of `att` is STRIDE
+        unsafe { reader.read_raw(att.as_mut_ptr() as *mut u8, STRIDE)? };
+
+        // SAFETY: All bit patterns in the struct are valid
+        let att = unsafe { att.assume_init() };
+
+        if att.flags != 0 {
+            return Err(EINVAL);
+        }
+        if att.order < 1 || att.order > 6 {
+            return Err(EINVAL);
+        }
+
+        let cache_lines = (att.size + 127) >> 7;
+        attachments.list[i as usize] = microseq::Attachment {
+            address: U64(att.pointer),
+            size: cache_lines.try_into()?,
+            unk_c: 0x17,
+            unk_e: att.order as u16,
+        };
+
+        attachments.count += 1;
+    }
+
+    Ok(attachments)
+}
diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
new file mode 100644
index 00000000000000..f53e64efd4f4e5
--- /dev/null
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -0,0 +1,413 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![allow(clippy::unusual_byte_groupings)]
+
+//! Compute work queue.
+//!
+//! A compute queue consists of one underlying WorkQueue.
+//! This module is in charge of creating all of the firmware structures required to submit compute
+//! work to the GPU, based on the userspace command buffer.
+
+use super::common;
+use crate::alloc::Allocator;
+use crate::debug::*;
+use crate::fw::types::*;
+use crate::gpu::GpuManager;
+use crate::{fw, gpu, microseq};
+use crate::{inner_ptr, inner_weak_ptr};
+use core::mem::MaybeUninit;
+use core::sync::atomic::Ordering;
+use kernel::dma_fence::RawDmaFence;
+use kernel::drm::sched::Job;
+use kernel::io_buffer::IoBufferReader;
+use kernel::prelude::*;
+use kernel::sync::Arc;
+use kernel::uapi;
+use kernel::user_ptr::UserSlicePtr;
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Compute;
+
+#[versions(AGX)]
+impl super::Queue::ver {
+    /// Submit work to a compute queue.
+    pub(super) fn submit_compute(
+        &self,
+        job: &mut Job<super::QueueJob::ver>,
+        cmd: &uapi::drm_asahi_command,
+        result_writer: Option<super::ResultWriter>,
+        id: u64,
+        flush_stamps: bool,
+    ) -> Result {
+        if cmd.cmd_type != uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE {
+            return Err(EINVAL);
+        }
+
+        let dev = self.dev.data();
+        let gpu = match dev.gpu.as_any().downcast_ref::<gpu::GpuManager::ver>() {
+            Some(gpu) => gpu,
+            None => {
+                dev_crit!(self.dev, "GpuManager mismatched with Queue!\n");
+                return Err(EIO);
+            }
+        };
+
+        let mut alloc = gpu.alloc();
+        let kalloc = &mut *alloc;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Compute!\n", id);
+
+        let mut cmdbuf_reader = unsafe {
+            UserSlicePtr::new(
+                cmd.cmd_buffer as usize as *mut _,
+                core::mem::size_of::<uapi::drm_asahi_cmd_compute>(),
+            )
+            .reader()
+        };
+
+        let mut cmdbuf: MaybeUninit<uapi::drm_asahi_cmd_compute> = MaybeUninit::uninit();
+        unsafe {
+            cmdbuf_reader.read_raw(
+                cmdbuf.as_mut_ptr() as *mut u8,
+                core::mem::size_of::<uapi::drm_asahi_cmd_compute>(),
+            )?;
+        }
+        let cmdbuf = unsafe { cmdbuf.assume_init() };
+
+        if cmdbuf.flags != 0 {
+            return Err(EINVAL);
+        }
+
+        // This sequence number increases per new client/VM? assigned to some slot,
+        // but it's unclear *which* slot...
+        let slot_client_seq: u8 = (self.id & 0xff) as u8;
+
+        let vm_bind = job.vm_bind.clone();
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] VM slot = {}\n",
+            id,
+            vm_bind.slot()
+        );
+
+        let notifier = self.notifier.clone();
+
+        let fence = job.fence.clone();
+        let comp_job = job.get_comp()?;
+        let ev_comp = comp_job.event_info();
+
+        let preempt2_off = gpu.get_cfg().compute_preempt1_size;
+        let preempt3_off = preempt2_off + 8;
+        let preempt4_off = preempt3_off + 8;
+        let preempt5_off = preempt4_off + 8;
+        let preempt_size = preempt5_off + 8;
+
+        let preempt_buf = self.ualloc.lock().array_empty(preempt_size)?;
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Event #{} {:#x?} -> {:#x?}\n",
+            id,
+            ev_comp.slot,
+            ev_comp.value,
+            ev_comp.value.next(),
+        );
+
+        let timestamps = Arc::new(
+            kalloc.shared.new_default::<fw::job::JobTimestamps>()?,
+            GFP_KERNEL,
+        )?;
+
+        let uuid = cmdbuf.cmd_id;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] UUID = {:#x?}\n", id, uuid);
+
+        // TODO: check
+        #[ver(V >= V13_0B4)]
+        let count = self.counter.fetch_add(1, Ordering::Relaxed);
+
+        let comp = GpuObject::new_init_prealloc(
+            kalloc.gpu_ro.alloc_object()?,
+            |ptr: GpuWeakPointer<fw::compute::RunCompute::ver>| {
+                let has_result = result_writer.is_some();
+                let notifier = notifier.clone();
+                let vm_bind = vm_bind.clone();
+                try_init!(fw::compute::RunCompute::ver {
+                    preempt_buf: preempt_buf,
+                    micro_seq: {
+                        let mut builder = microseq::Builder::new();
+
+                        let stats = gpu.initdata.runtime_pointers.stats.comp.weak_pointer();
+
+                        let start_comp = builder.add(microseq::StartCompute::ver {
+                            header: microseq::op::StartCompute::HEADER,
+                            unk_pointer: inner_weak_ptr!(ptr, unk_pointee),
+                            #[ver(G < G14X)]
+                            job_params1: Some(inner_weak_ptr!(ptr, job_params1)),
+                            #[ver(G >= G14X)]
+                            job_params1: None,
+                            #[ver(G >= G14X)]
+                            registers: inner_weak_ptr!(ptr, registers),
+                            stats,
+                            work_queue: ev_comp.info_ptr,
+                            vm_slot: vm_bind.slot(),
+                            unk_28: 0x1,
+                            event_generation: self.id as u32,
+                            event_seq: U64(ev_comp.event_seq),
+                            unk_38: 0x0,
+                            job_params2: inner_weak_ptr!(ptr, job_params2),
+                            unk_44: 0x0,
+                            uuid,
+                            attachments: common::build_attachments(
+                                cmdbuf.attachments,
+                                cmdbuf.attachment_count,
+                            )?,
+                            padding: Default::default(),
+                            #[ver(V >= V13_0B4)]
+                            unk_flag: inner_weak_ptr!(ptr, unk_flag),
+                            #[ver(V >= V13_0B4)]
+                            counter: U64(count),
+                            #[ver(V >= V13_0B4)]
+                            notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf),
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(true),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, start_ts),
+                                work_queue: ev_comp.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        #[ver(G < G14X)]
+                        builder.add(microseq::WaitForIdle {
+                            header: microseq::op::WaitForIdle::new(microseq::Pipe::Compute),
+                        })?;
+                        #[ver(G >= G14X)]
+                        builder.add(microseq::WaitForIdle2 {
+                            header: microseq::op::WaitForIdle2::HEADER,
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(false),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, end_ts),
+                                work_queue: ev_comp.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        let off = builder.offset_to(start_comp);
+                        builder.add(microseq::FinalizeCompute::ver {
+                            header: microseq::op::FinalizeCompute::HEADER,
+                            stats,
+                            work_queue: ev_comp.info_ptr,
+                            vm_slot: vm_bind.slot(),
+                            #[ver(V < V13_0B4)]
+                            unk_18: 0,
+                            job_params2: inner_weak_ptr!(ptr, job_params2),
+                            unk_24: 0,
+                            uuid,
+                            fw_stamp: ev_comp.fw_stamp_pointer,
+                            stamp_value: ev_comp.value.next(),
+                            unk_38: 0,
+                            unk_3c: 0,
+                            unk_40: 0,
+                            unk_44: 0,
+                            unk_48: 0,
+                            unk_4c: 0,
+                            unk_50: 0,
+                            unk_54: 0,
+                            unk_58: 0,
+                            #[ver(G == G14 && V < V13_0B4)]
+                            unk_5c_g14: U64(0),
+                            restart_branch_offset: off,
+                            has_attachments: (cmdbuf.attachment_count > 0) as u32,
+                            #[ver(V >= V13_0B4)]
+                            unk_64: Default::default(),
+                            #[ver(V >= V13_0B4)]
+                            unk_flag: inner_weak_ptr!(ptr, unk_flag),
+                            #[ver(V >= V13_0B4)]
+                            unk_79: Default::default(),
+                        })?;
+
+                        builder.add(microseq::RetireStamp {
+                            header: microseq::op::RetireStamp::HEADER,
+                        })?;
+                        builder.build(&mut kalloc.private)?
+                    },
+                    notifier,
+                    vm_bind,
+                    timestamps,
+                })
+            },
+            |inner, _ptr| {
+                let vm_slot = vm_bind.slot();
+                try_init!(fw::compute::raw::RunCompute::ver {
+                    tag: fw::workqueue::CommandType::RunCompute,
+                    #[ver(V >= V13_0B4)]
+                    counter: U64(count),
+                    unk_4: 0,
+                    vm_slot,
+                    notifier: inner.notifier.gpu_pointer(),
+                    unk_pointee: Default::default(),
+                    #[ver(G < G14X)]
+                    __pad0: Default::default(),
+                    #[ver(G < G14X)]
+                    job_params1 <- try_init!(fw::compute::raw::JobParameters1 {
+                        preempt_buf1: inner.preempt_buf.gpu_pointer(),
+                        encoder: U64(cmdbuf.encoder_ptr),
+                        // buf2-5 Only if internal program is used
+                        preempt_buf2: inner.preempt_buf.gpu_offset_pointer(preempt2_off),
+                        preempt_buf3: inner.preempt_buf.gpu_offset_pointer(preempt3_off),
+                        preempt_buf4: inner.preempt_buf.gpu_offset_pointer(preempt4_off),
+                        preempt_buf5: inner.preempt_buf.gpu_offset_pointer(preempt5_off),
+                        pipeline_base: U64(0x11_00000000),
+                        unk_38: U64(0x8c60),
+                        helper_program: cmdbuf.helper_program, // Internal program addr | 1
+                        unk_44: 0,
+                        helper_arg: U64(cmdbuf.helper_arg), // Only if internal program used
+                        unk_50: cmdbuf.buffer_descriptor_size, // 0x40 if internal program used
+                        unk_54: 0,
+                        unk_58: 1,
+                        unk_5c: 0,
+                        iogpu_unk_40: cmdbuf.iogpu_unk_40, // 0x1c if internal program used
+                        __pad: Default::default(),
+                    }),
+                    #[ver(G >= G14X)]
+                    registers: fw::job::raw::RegisterArray::new(
+                        inner_weak_ptr!(_ptr, registers.registers),
+                        |r| {
+                            r.add(0x1a510, inner.preempt_buf.gpu_pointer().into());
+                            r.add(0x1a420, cmdbuf.encoder_ptr);
+                            // buf2-5 Only if internal program is used
+                            r.add(0x1a4d0, inner.preempt_buf.gpu_offset_pointer(preempt2_off).into());
+                            r.add(0x1a4d8, inner.preempt_buf.gpu_offset_pointer(preempt3_off).into());
+                            r.add(0x1a4e0, inner.preempt_buf.gpu_offset_pointer(preempt4_off).into());
+                            r.add(0x1a4e8, inner.preempt_buf.gpu_offset_pointer(preempt5_off).into());
+                            r.add(0x10071, 0x1100000000); // USC_EXEC_BASE_CP
+                            r.add(0x11841, cmdbuf.helper_program.into());
+                            r.add(0x11849, cmdbuf.helper_arg);
+                            r.add(0x11f81, cmdbuf.buffer_descriptor_size.into());
+                            r.add(0x1a440, 0x24201);
+                            r.add(0x12091, cmdbuf.iogpu_unk_40.into());
+                            /*
+                            r.add(0x10201, 0x100); // Some kind of counter?? Does this matter?
+                            r.add(0x10428, 0x100); // Some kind of counter?? Does this matter?
+                            */
+                        }
+                    ),
+                    __pad1: Default::default(),
+                    microsequence: inner.micro_seq.gpu_pointer(),
+                    microsequence_size: inner.micro_seq.len() as u32,
+                    job_params2 <- try_init!(fw::compute::raw::JobParameters2::ver {
+                        #[ver(V >= V13_0B4)]
+                        unk_0_0: 0,
+                        unk_0: Default::default(),
+                        preempt_buf1: inner.preempt_buf.gpu_pointer(),
+                        encoder_end: U64(cmdbuf.encoder_end),
+                        unk_34: Default::default(),
+                        #[ver(G < G14X)]
+                        unk_g14x: 0,
+                        #[ver(G >= G14X)]
+                        unk_g14x: 0x24201,
+                        unk_58: 0,
+                        #[ver(V < V13_0B4)]
+                        unk_5c: 0,
+                    }),
+                    encoder_params <- try_init!(fw::job::raw::EncoderParams {
+                        unk_8: 0x0,     // fixed
+                        sync_grow: 0x0, // check!
+                        unk_10: 0x0,    // fixed
+                        encoder_id: cmdbuf.encoder_id,
+                        unk_18: 0x0, // fixed
+                        unk_mask: cmdbuf.unk_mask,
+                        sampler_array: U64(0),
+                        sampler_count: 0,
+                        sampler_max: 0,
+                    }),
+                    meta <- try_init!(fw::job::raw::JobMeta {
+                        unk_0: 0,
+                        unk_2: 0,
+                        // TODO: make separate flag
+                        no_preemption: ((cmdbuf.helper_program & 1) == 0) as u8,
+                        stamp: ev_comp.stamp_pointer,
+                        fw_stamp: ev_comp.fw_stamp_pointer,
+                        stamp_value: ev_comp.value.next(),
+                        stamp_slot: ev_comp.slot,
+                        evctl_index: 0, // fixed
+                        flush_stamps: flush_stamps as u32,
+                        uuid,
+                        event_seq: ev_comp.event_seq as u32,
+                    }),
+                    cur_ts: U64(0),
+                    start_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), start)),
+                    end_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), end)),
+                    unk_2c0: 0,
+                    unk_2c4: 0,
+                    unk_2c8: 0,
+                    unk_2cc: 0,
+                    client_sequence: slot_client_seq,
+                    pad_2d1: Default::default(),
+                    unk_2d4: 0,
+                    unk_2d8: 0,
+                    #[ver(V >= V13_0B4)]
+                    unk_ts: U64(0),
+                    #[ver(V >= V13_0B4)]
+                    unk_2e1: Default::default(),
+                    #[ver(V >= V13_0B4)]
+                    unk_flag: U32(0),
+                    #[ver(V >= V13_0B4)]
+                    unk_pad: Default::default(),
+                })
+            },
+        )?;
+
+        core::mem::drop(alloc);
+
+        fence.add_command();
+        comp_job.add_cb(comp, vm_bind.slot(), move |cmd, error| {
+            if let Some(err) = error {
+                fence.set_error(err.into())
+            }
+            if let Some(mut rw) = result_writer {
+                let mut result: uapi::drm_asahi_result_compute = Default::default();
+
+                cmd.timestamps.with(|raw, _inner| {
+                    result.ts_start = raw.start.load(Ordering::Relaxed);
+                    result.ts_end = raw.end.load(Ordering::Relaxed);
+                });
+
+                if let Some(err) = error {
+                    result.info = err.into();
+                } else {
+                    result.info.status = uapi::drm_asahi_status_DRM_ASAHI_STATUS_COMPLETE;
+                }
+
+                rw.write(result);
+            }
+
+            fence.command_complete();
+        })?;
+
+        notifier.threshold.with(|raw, _inner| {
+            raw.increment();
+        });
+
+        comp_job.next_seq();
+
+        Ok(())
+    }
+}
diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
new file mode 100644
index 00000000000000..1793b20b08f7f4
--- /dev/null
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -0,0 +1,775 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Submission queue management
+//!
+//! This module implements the userspace view of submission queues and the logic to map userspace
+//! submissions to firmware queues.
+
+use kernel::dma_fence::*;
+use kernel::prelude::*;
+use kernel::{
+    alloc::vec_ext::VecExt,
+    c_str, dma_fence,
+    drm::gem::shmem::VMap,
+    drm::sched,
+    macros::versions,
+    sync::{Arc, Mutex},
+    uapi,
+};
+
+use crate::alloc::Allocator;
+use crate::debug::*;
+use crate::driver::{AsahiDevRef, AsahiDevice};
+use crate::fw::types::*;
+use crate::gpu::GpuManager;
+use crate::inner_weak_ptr;
+use crate::{alloc, buffer, channel, event, file, fw, gem, gpu, mmu, workqueue};
+
+use core::sync::atomic::{AtomicU64, Ordering};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Queue;
+
+const WQ_SIZE: u32 = 0x500;
+
+mod common;
+mod compute;
+mod render;
+
+/// Trait implemented by all versioned queues.
+pub(crate) trait Queue: Send + Sync {
+    fn submit(
+        &mut self,
+        id: u64,
+        in_syncs: Vec<file::SyncItem>,
+        out_syncs: Vec<file::SyncItem>,
+        result_buf: Option<gem::ObjectRef>,
+        commands: Vec<uapi::drm_asahi_command>,
+    ) -> Result;
+}
+
+#[versions(AGX)]
+struct SubQueue {
+    wq: Arc<workqueue::WorkQueue::ver>,
+}
+
+#[versions(AGX)]
+impl SubQueue::ver {
+    fn new_job(&mut self, fence: dma_fence::Fence) -> SubQueueJob::ver {
+        SubQueueJob::ver {
+            wq: self.wq.clone(),
+            fence: Some(fence),
+            job: None,
+        }
+    }
+}
+
+#[versions(AGX)]
+struct SubQueueJob {
+    wq: Arc<workqueue::WorkQueue::ver>,
+    job: Option<workqueue::Job::ver>,
+    fence: Option<dma_fence::Fence>,
+}
+
+#[versions(AGX)]
+impl SubQueueJob::ver {
+    fn get(&mut self) -> Result<&mut workqueue::Job::ver> {
+        if self.job.is_none() {
+            mod_pr_debug!("SubQueueJob: Creating {:?} job\n", self.wq.pipe_type());
+            self.job
+                .replace(self.wq.new_job(self.fence.take().unwrap())?);
+        }
+        Ok(self.job.as_mut().expect("expected a Job"))
+    }
+
+    fn commit(&mut self) -> Result {
+        match self.job.as_mut() {
+            Some(job) => job.commit(),
+            None => Ok(()),
+        }
+    }
+
+    fn can_submit(&self) -> Option<Fence> {
+        self.job.as_ref().and_then(|job| job.can_submit())
+    }
+}
+
+#[versions(AGX)]
+pub(crate) struct Queue {
+    dev: AsahiDevRef,
+    _sched: sched::Scheduler<QueueJob::ver>,
+    entity: sched::Entity<QueueJob::ver>,
+    vm: mmu::Vm,
+    ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+    q_vtx: Option<SubQueue::ver>,
+    q_frag: Option<SubQueue::ver>,
+    q_comp: Option<SubQueue::ver>,
+    buffer: Option<buffer::Buffer::ver>,
+    gpu_context: Arc<workqueue::GpuContext>,
+    notifier_list: Arc<GpuObject<fw::event::NotifierList>>,
+    notifier: Arc<GpuObject<fw::event::Notifier::ver>>,
+    id: u64,
+    fence_ctx: FenceContexts,
+    #[ver(V >= V13_0B4)]
+    counter: AtomicU64,
+}
+
+#[versions(AGX)]
+#[derive(Default)]
+pub(crate) struct JobFence {
+    id: u64,
+    pending: AtomicU64,
+}
+
+#[versions(AGX)]
+impl JobFence::ver {
+    fn add_command(self: &FenceObject<Self>) {
+        self.pending.fetch_add(1, Ordering::Relaxed);
+    }
+
+    fn command_complete(self: &FenceObject<Self>) {
+        let remain = self.pending.fetch_sub(1, Ordering::Relaxed) - 1;
+        mod_pr_debug!(
+            "JobFence[{}]: Command complete (remain: {})\n",
+            self.id,
+            remain
+        );
+        if remain == 0 {
+            mod_pr_debug!("JobFence[{}]: Signaling\n", self.id);
+            if self.signal().is_err() {
+                pr_err!("JobFence[{}]: Fence signal failed\n", self.id);
+            }
+        }
+    }
+}
+
+#[versions(AGX)]
+#[vtable]
+impl dma_fence::FenceOps for JobFence::ver {
+    const USE_64BIT_SEQNO: bool = true;
+
+    fn get_driver_name<'a>(self: &'a FenceObject<Self>) -> &'a CStr {
+        c_str!("asahi")
+    }
+    fn get_timeline_name<'a>(self: &'a FenceObject<Self>) -> &'a CStr {
+        c_str!("queue")
+    }
+}
+
+#[versions(AGX)]
+pub(crate) struct QueueJob {
+    dev: AsahiDevRef,
+    vm_bind: mmu::VmBind,
+    op_guard: Option<gpu::OpGuard>,
+    sj_vtx: Option<SubQueueJob::ver>,
+    sj_frag: Option<SubQueueJob::ver>,
+    sj_comp: Option<SubQueueJob::ver>,
+    fence: UserFence<JobFence::ver>,
+    did_run: bool,
+    id: u64,
+}
+
+#[versions(AGX)]
+impl QueueJob::ver {
+    fn get_vtx(&mut self) -> Result<&mut workqueue::Job::ver> {
+        self.sj_vtx.as_mut().ok_or(EINVAL)?.get()
+    }
+    fn get_frag(&mut self) -> Result<&mut workqueue::Job::ver> {
+        self.sj_frag.as_mut().ok_or(EINVAL)?.get()
+    }
+    fn get_comp(&mut self) -> Result<&mut workqueue::Job::ver> {
+        self.sj_comp.as_mut().ok_or(EINVAL)?.get()
+    }
+
+    fn commit(&mut self) -> Result {
+        mod_dev_dbg!(self.dev, "QueueJob: Committing\n");
+
+        self.sj_vtx.as_mut().map(|a| a.commit()).unwrap_or(Ok(()))?;
+        self.sj_frag
+            .as_mut()
+            .map(|a| a.commit())
+            .unwrap_or(Ok(()))?;
+        self.sj_comp.as_mut().map(|a| a.commit()).unwrap_or(Ok(()))
+    }
+}
+
+#[versions(AGX)]
+impl sched::JobImpl for QueueJob::ver {
+    fn prepare(job: &mut sched::Job<Self>) -> Option<Fence> {
+        mod_dev_dbg!(job.dev, "QueueJob {}: Checking runnability\n", job.id);
+
+        if let Some(sj) = job.sj_vtx.as_ref() {
+            if let Some(fence) = sj.can_submit() {
+                dev_info!(
+                    job.dev,
+                    "QueueJob {}: Blocking due to vertex queue full\n",
+                    job.id
+                );
+                return Some(fence);
+            }
+        }
+        if let Some(sj) = job.sj_frag.as_ref() {
+            if let Some(fence) = sj.can_submit() {
+                dev_info!(
+                    job.dev,
+                    "QueueJob {}: Blocking due to fragment queue full\n",
+                    job.id
+                );
+                return Some(fence);
+            }
+        }
+        if let Some(sj) = job.sj_comp.as_ref() {
+            if let Some(fence) = sj.can_submit() {
+                dev_info!(
+                    job.dev,
+                    "QueueJob {}: Blocking due to compute queue full\n",
+                    job.id
+                );
+                return Some(fence);
+            }
+        }
+        None
+    }
+
+    #[allow(unused_assignments)]
+    fn run(job: &mut sched::Job<Self>) -> Result<Option<dma_fence::Fence>> {
+        mod_dev_dbg!(job.dev, "QueueJob {}: Running Job\n", job.id);
+
+        let dev = job.dev.data();
+        let gpu = match dev
+            .gpu
+            .clone()
+            .arc_as_any()
+            .downcast::<gpu::GpuManager::ver>()
+        {
+            Ok(gpu) => gpu,
+            Err(_) => {
+                dev_crit!(job.dev, "GpuManager mismatched with QueueJob!\n");
+                return Err(EIO);
+            }
+        };
+
+        if job.op_guard.is_none() {
+            job.op_guard = Some(gpu.start_op()?);
+        }
+
+        // First submit all the commands for each queue. This can fail.
+
+        let mut frag_job = None;
+        let mut frag_sub = None;
+        if let Some(sj) = job.sj_frag.as_mut() {
+            frag_job = sj.job.take();
+            if let Some(wqjob) = frag_job.as_mut() {
+                mod_dev_dbg!(job.dev, "QueueJob {}: Submit fragment\n", job.id);
+                frag_sub = Some(wqjob.submit()?);
+            }
+        }
+
+        let mut vtx_job = None;
+        let mut vtx_sub = None;
+        if let Some(sj) = job.sj_vtx.as_mut() {
+            vtx_job = sj.job.take();
+            if let Some(wqjob) = vtx_job.as_mut() {
+                mod_dev_dbg!(job.dev, "QueueJob {}: Submit vertex\n", job.id);
+                vtx_sub = Some(wqjob.submit()?);
+            }
+        }
+
+        let mut comp_job = None;
+        let mut comp_sub = None;
+        if let Some(sj) = job.sj_comp.as_mut() {
+            comp_job = sj.job.take();
+            if let Some(wqjob) = comp_job.as_mut() {
+                mod_dev_dbg!(job.dev, "QueueJob {}: Submit compute\n", job.id);
+                comp_sub = Some(wqjob.submit()?);
+            }
+        }
+
+        // Now we fully commit to running the job
+        mod_dev_dbg!(job.dev, "QueueJob {}: Run fragment\n", job.id);
+        frag_sub.map(|a| gpu.run_job(a)).transpose()?;
+
+        mod_dev_dbg!(job.dev, "QueueJob {}: Run vertex\n", job.id);
+        vtx_sub.map(|a| gpu.run_job(a)).transpose()?;
+
+        mod_dev_dbg!(job.dev, "QueueJob {}: Run compute\n", job.id);
+        comp_sub.map(|a| gpu.run_job(a)).transpose()?;
+
+        mod_dev_dbg!(job.dev, "QueueJob {}: Drop compute job\n", job.id);
+        core::mem::drop(comp_job);
+        mod_dev_dbg!(job.dev, "QueueJob {}: Drop vertex job\n", job.id);
+        core::mem::drop(vtx_job);
+        mod_dev_dbg!(job.dev, "QueueJob {}: Drop fragment job\n", job.id);
+        core::mem::drop(frag_job);
+
+        job.did_run = true;
+
+        Ok(Some(Fence::from_fence(&job.fence)))
+    }
+
+    fn timed_out(job: &mut sched::Job<Self>) -> sched::Status {
+        // FIXME: Handle timeouts properly
+        dev_err!(
+            job.dev,
+            "QueueJob {}: Job timed out on the DRM scheduler, things will probably break (ran: {})\n",
+            job.id, job.did_run
+        );
+        sched::Status::NoDevice
+    }
+}
+
+#[versions(AGX)]
+impl Drop for QueueJob::ver {
+    fn drop(&mut self) {
+        mod_dev_dbg!(self.dev, "QueueJob {}: Dropping\n", self.id);
+    }
+}
+
+struct ResultWriter {
+    vmap: VMap<gem::DriverObject>,
+    offset: usize,
+    len: usize,
+}
+
+impl ResultWriter {
+    fn write<T>(&mut self, mut value: T) {
+        let p: *mut u8 = &mut value as *mut _ as *mut u8;
+        // SAFETY: We know `p` points to a type T of that size, and UAPI types must have
+        // no padding and all bit patterns valid.
+        let slice = unsafe { core::slice::from_raw_parts_mut(p, core::mem::size_of::<T>()) };
+        let len = slice.len().min(self.len);
+        self.vmap.as_mut_slice()[self.offset..self.offset + len].copy_from_slice(&slice[..len]);
+    }
+}
+
+static QUEUE_NAME: &CStr = c_str!("asahi_fence");
+static QUEUE_CLASS_KEY: kernel::sync::LockClassKey = kernel::static_lock_class!();
+
+#[versions(AGX)]
+impl Queue::ver {
+    /// Create a new user queue.
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new(
+        dev: &AsahiDevice,
+        vm: mmu::Vm,
+        alloc: &mut gpu::KernelAllocators,
+        ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
+        ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
+        event_manager: Arc<event::EventManager>,
+        mgr: &buffer::BufferManager::ver,
+        id: u64,
+        priority: u32,
+        caps: u32,
+    ) -> Result<Queue::ver> {
+        mod_dev_dbg!(dev, "[Queue {}] Creating queue\n", id);
+
+        let data = dev.data();
+
+        let mut notifier_list = alloc.private.new_default::<fw::event::NotifierList>()?;
+
+        let self_ptr = notifier_list.weak_pointer();
+        notifier_list.with_mut(|raw, _inner| {
+            raw.list_head.next = Some(inner_weak_ptr!(self_ptr, list_head));
+        });
+
+        let threshold = alloc.shared.new_default::<fw::event::Threshold>()?;
+
+        let notifier: Arc<GpuObject<fw::event::Notifier::ver>> = Arc::new(
+            alloc.private.new_init(
+                /*try_*/ init!(fw::event::Notifier::ver { threshold }),
+                |inner, _p| {
+                    try_init!(fw::event::raw::Notifier::ver {
+                        threshold: inner.threshold.gpu_pointer(),
+                        generation: AtomicU32::new(id as u32),
+                        cur_count: AtomicU32::new(0),
+                        unk_10: AtomicU32::new(0x50),
+                        state: Default::default()
+                    })
+                },
+            )?,
+            GFP_KERNEL,
+        )?;
+
+        let sched = sched::Scheduler::new(dev, 3, WQ_SIZE, 0, 100000, c_str!("asahi_sched"))?;
+        // Priorities are handled by the AGX scheduler, there is no meaning within a
+        // per-queue scheduler.
+        let entity = sched::Entity::new(&sched, sched::Priority::Normal)?;
+
+        let buffer = if caps & uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_RENDER != 0 {
+            Some(buffer::Buffer::ver::new(
+                &*data.gpu,
+                alloc,
+                ualloc.clone(),
+                ualloc_priv,
+                mgr,
+            )?)
+        } else {
+            None
+        };
+
+        let mut ret = Queue::ver {
+            dev: dev.into(),
+            _sched: sched,
+            entity,
+            vm,
+            ualloc,
+            q_vtx: None,
+            q_frag: None,
+            q_comp: None,
+            gpu_context: Arc::try_new(workqueue::GpuContext::new(
+                dev,
+                alloc,
+                buffer.as_ref().map(|b| b.any_ref()),
+            )?)?,
+            buffer,
+            notifier_list: Arc::try_new(notifier_list)?,
+            notifier,
+            id,
+            fence_ctx: FenceContexts::new(1, QUEUE_NAME, QUEUE_CLASS_KEY)?,
+            #[ver(V >= V13_0B4)]
+            counter: AtomicU64::new(0),
+        };
+
+        // Rendering structures
+        if caps & uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_RENDER != 0 {
+            let tvb_blocks = {
+                let lock = crate::THIS_MODULE.kernel_param_lock();
+                *crate::initial_tvb_size.read(&lock)
+            };
+
+            ret.buffer.as_ref().unwrap().ensure_blocks(tvb_blocks)?;
+
+            ret.q_vtx = Some(SubQueue::ver {
+                wq: workqueue::WorkQueue::ver::new(
+                    dev,
+                    alloc,
+                    event_manager.clone(),
+                    ret.gpu_context.clone(),
+                    ret.notifier_list.clone(),
+                    channel::PipeType::Vertex,
+                    id,
+                    priority,
+                    WQ_SIZE,
+                )?,
+            });
+        }
+
+        // Rendering & blit structures
+        if caps
+            & (uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_RENDER
+                | uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_BLIT)
+            != 0
+        {
+            ret.q_frag = Some(SubQueue::ver {
+                wq: workqueue::WorkQueue::ver::new(
+                    dev,
+                    alloc,
+                    event_manager.clone(),
+                    ret.gpu_context.clone(),
+                    ret.notifier_list.clone(),
+                    channel::PipeType::Fragment,
+                    id,
+                    priority,
+                    WQ_SIZE,
+                )?,
+            });
+        }
+
+        // Compute structures
+        if caps & uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_COMPUTE != 0 {
+            ret.q_comp = Some(SubQueue::ver {
+                wq: workqueue::WorkQueue::ver::new(
+                    dev,
+                    alloc,
+                    event_manager,
+                    ret.gpu_context.clone(),
+                    ret.notifier_list.clone(),
+                    channel::PipeType::Compute,
+                    id,
+                    priority,
+                    WQ_SIZE,
+                )?,
+            });
+        }
+
+        mod_dev_dbg!(dev, "[Queue {}] Queue created\n", id);
+        Ok(ret)
+    }
+}
+
+const SQ_RENDER: usize = uapi::drm_asahi_subqueue_DRM_ASAHI_SUBQUEUE_RENDER as usize;
+const SQ_COMPUTE: usize = uapi::drm_asahi_subqueue_DRM_ASAHI_SUBQUEUE_COMPUTE as usize;
+const SQ_COUNT: usize = uapi::drm_asahi_subqueue_DRM_ASAHI_SUBQUEUE_COUNT as usize;
+
+#[versions(AGX)]
+impl Queue for Queue::ver {
+    fn submit(
+        &mut self,
+        id: u64,
+        in_syncs: Vec<file::SyncItem>,
+        out_syncs: Vec<file::SyncItem>,
+        result_buf: Option<gem::ObjectRef>,
+        commands: Vec<uapi::drm_asahi_command>,
+    ) -> Result {
+        let dev = self.dev.data();
+        let gpu = match dev
+            .gpu
+            .clone()
+            .arc_as_any()
+            .downcast::<gpu::GpuManager::ver>()
+        {
+            Ok(gpu) => gpu,
+            Err(_) => {
+                dev_crit!(self.dev, "GpuManager mismatched with JobImpl!\n");
+                return Err(EIO);
+            }
+        };
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Submit job\n", id);
+
+        if gpu.is_crashed() {
+            dev_err!(
+                self.dev,
+                "[Submission {}] GPU is crashed, cannot submit\n",
+                id
+            );
+            return Err(ENODEV);
+        }
+
+        // Empty submissions are not legal
+        if commands.is_empty() {
+            return Err(EINVAL);
+        }
+
+        let op_guard = if !in_syncs.is_empty() {
+            Some(gpu.start_op()?)
+        } else {
+            None
+        };
+
+        let mut events: [Vec<Option<workqueue::QueueEventInfo::ver>>; SQ_COUNT] =
+            Default::default();
+
+        events[SQ_RENDER].push(
+            self.q_frag.as_ref().and_then(|a| a.wq.event_info()),
+            GFP_KERNEL,
+        )?;
+        events[SQ_COMPUTE].push(
+            self.q_comp.as_ref().and_then(|a| a.wq.event_info()),
+            GFP_KERNEL,
+        )?;
+
+        let vm_bind = gpu.bind_vm(&self.vm)?;
+        let vm_slot = vm_bind.slot();
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Creating job\n", id);
+
+        // FIXME: I think this can violate the fence seqno ordering contract.
+        // If we have e.g. a render submission with no barriers and then a compute submission
+        // with no barriers, it's possible for the compute submission to complete first, and
+        // therefore its fence. Maybe we should have separate fence contexts for render
+        // and compute, and then do a ? (Vert+frag should be fine since there is no vert
+        // without frag, and frag always serializes.)
+        let fence: UserFence<JobFence::ver> = self
+            .fence_ctx
+            .new_fence::<JobFence::ver>(
+                0,
+                JobFence::ver {
+                    id,
+                    pending: Default::default(),
+                },
+            )?
+            .into();
+
+        let mut job = self.entity.new_job(
+            1,
+            QueueJob::ver {
+                dev: self.dev.clone(),
+                vm_bind,
+                op_guard,
+                sj_vtx: self
+                    .q_vtx
+                    .as_mut()
+                    .map(|a| a.new_job(Fence::from_fence(&fence))),
+                sj_frag: self
+                    .q_frag
+                    .as_mut()
+                    .map(|a| a.new_job(Fence::from_fence(&fence))),
+                sj_comp: self
+                    .q_comp
+                    .as_mut()
+                    .map(|a| a.new_job(Fence::from_fence(&fence))),
+                fence,
+                did_run: false,
+                id,
+            },
+        )?;
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Adding {} in_syncs\n",
+            id,
+            in_syncs.len()
+        );
+        for sync in in_syncs {
+            if let Some(fence) = sync.fence {
+                job.add_dependency(fence)?;
+            }
+        }
+
+        let mut last_render = None;
+        let mut last_compute = None;
+
+        for (i, cmd) in commands.iter().enumerate() {
+            match cmd.cmd_type {
+                uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => last_render = Some(i),
+                uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => last_compute = Some(i),
+                _ => return Err(EINVAL),
+            }
+        }
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Submitting {} commands\n",
+            id,
+            commands.len()
+        );
+        for (i, cmd) in commands.into_iter().enumerate() {
+            for (queue_idx, index) in cmd.barriers.iter().enumerate() {
+                if *index == uapi::DRM_ASAHI_BARRIER_NONE as u32 {
+                    continue;
+                }
+                if let Some(event) = events[queue_idx].get(*index as usize).ok_or(EINVAL)? {
+                    let mut alloc = gpu.alloc();
+                    let queue_job = match cmd.cmd_type {
+                        uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => job.get_vtx()?,
+                        uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => job.get_comp()?,
+                        _ => return Err(EINVAL),
+                    };
+                    mod_dev_dbg!(self.dev, "[Submission {}] Create Explicit Barrier\n", id);
+                    let barrier = alloc.private.new_init(
+                        kernel::init::zeroed::<fw::workqueue::Barrier>(),
+                        |_inner, _p| {
+                            let queue_job = &queue_job;
+                            try_init!(fw::workqueue::raw::Barrier {
+                                tag: fw::workqueue::CommandType::Barrier,
+                                wait_stamp: event.fw_stamp_pointer,
+                                wait_value: event.value,
+                                wait_slot: event.slot,
+                                stamp_self: queue_job.event_info().value.next(),
+                                uuid: 0xffffbbbb,
+                                barrier_type: 0,
+                                padding: Default::default(),
+                            })
+                        },
+                    )?;
+                    mod_dev_dbg!(self.dev, "[Submission {}] Add Explicit Barrier\n", id);
+                    queue_job.add(barrier, vm_slot)?;
+                } else {
+                    assert!(*index == 0);
+                }
+            }
+
+            let result_writer = match result_buf.as_ref() {
+                None => {
+                    if cmd.result_offset != 0 || cmd.result_size != 0 {
+                        return Err(EINVAL);
+                    }
+                    None
+                }
+                Some(buf) => {
+                    if cmd.result_size != 0 {
+                        if cmd
+                            .result_offset
+                            .checked_add(cmd.result_size)
+                            .ok_or(EINVAL)?
+                            > buf.size() as u64
+                        {
+                            return Err(EINVAL);
+                        }
+                        Some(ResultWriter {
+                            vmap: buf.gem.vmap()?,
+                            offset: cmd.result_offset.try_into()?,
+                            len: cmd.result_size.try_into()?,
+                        })
+                    } else {
+                        None
+                    }
+                }
+            };
+
+            match cmd.cmd_type {
+                uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => {
+                    self.submit_render(
+                        &mut job,
+                        &cmd,
+                        result_writer,
+                        id,
+                        last_render.unwrap() == i,
+                    )?;
+                    events[SQ_RENDER].push(
+                        Some(
+                            job.sj_frag
+                                .as_ref()
+                                .expect("No frag queue?")
+                                .job
+                                .as_ref()
+                                .expect("No frag job?")
+                                .event_info(),
+                        ),
+                        GFP_KERNEL,
+                    )?;
+                }
+                uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => {
+                    self.submit_compute(
+                        &mut job,
+                        &cmd,
+                        result_writer,
+                        id,
+                        last_compute.unwrap() == i,
+                    )?;
+                    events[SQ_COMPUTE].push(
+                        Some(
+                            job.sj_comp
+                                .as_ref()
+                                .expect("No comp queue?")
+                                .job
+                                .as_ref()
+                                .expect("No comp job?")
+                                .event_info(),
+                        ),
+                        GFP_KERNEL,
+                    )?;
+                }
+                _ => return Err(EINVAL),
+            }
+        }
+
+        mod_dev_dbg!(self.dev, "Queue: Committing job\n");
+        job.commit()?;
+
+        mod_dev_dbg!(self.dev, "Queue: Arming job\n");
+        let job = job.arm();
+        let out_fence = job.fences().finished();
+        mod_dev_dbg!(self.dev, "Queue: Pushing job\n");
+        job.push();
+
+        mod_dev_dbg!(self.dev, "Queue: Adding {} out_syncs\n", out_syncs.len());
+        for mut sync in out_syncs {
+            if let Some(chain) = sync.chain_fence.take() {
+                sync.syncobj
+                    .add_point(chain, &out_fence, sync.timeline_value);
+            } else {
+                sync.syncobj.replace_fence(Some(&out_fence));
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[versions(AGX)]
+impl Drop for Queue::ver {
+    fn drop(&mut self) {
+        mod_dev_dbg!(self.dev, "[Queue {}] Dropping queue\n", self.id);
+    }
+}
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
new file mode 100644
index 00000000000000..a2cc8f848f5445
--- /dev/null
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -0,0 +1,1424 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+#![allow(clippy::unusual_byte_groupings)]
+
+//! Render work queue.
+//!
+//! A render queue consists of two underlying WorkQueues, one for vertex and one for fragment work.
+//! This module is in charge of creating all of the firmware structures required to submit 3D
+//! rendering work to the GPU, based on the userspace command buffer.
+
+use super::common;
+use crate::alloc::Allocator;
+use crate::debug::*;
+use crate::fw::types::*;
+use crate::gpu::GpuManager;
+use crate::util::*;
+use crate::workqueue::WorkError;
+use crate::{buffer, fw, gpu, microseq, workqueue};
+use crate::{inner_ptr, inner_weak_ptr};
+use core::mem::MaybeUninit;
+use core::sync::atomic::Ordering;
+use kernel::dma_fence::RawDmaFence;
+use kernel::drm::sched::Job;
+use kernel::io_buffer::IoBufferReader;
+use kernel::new_mutex;
+use kernel::prelude::*;
+use kernel::sync::Arc;
+use kernel::uapi;
+use kernel::user_ptr::UserSlicePtr;
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::Render;
+
+/// Tiling/Vertex control bit to disable using more than one GPU cluster. This results in decreased
+/// throughput but also less latency, which is probably desirable for light vertex loads where the
+/// overhead of clustering/merging would exceed the time it takes to just run the job on one
+/// cluster.
+const TILECTL_DISABLE_CLUSTERING: u32 = 1u32 << 0;
+
+struct RenderResult {
+    result: uapi::drm_asahi_result_render,
+    vtx_complete: bool,
+    frag_complete: bool,
+    vtx_error: Option<workqueue::WorkError>,
+    frag_error: Option<workqueue::WorkError>,
+    writer: super::ResultWriter,
+}
+
+impl RenderResult {
+    fn commit(&mut self) {
+        if !self.vtx_complete || !self.frag_complete {
+            return;
+        }
+
+        let mut error = self.vtx_error.take();
+        if let Some(frag_error) = self.frag_error.take() {
+            if error.is_none() || error == Some(WorkError::Killed) {
+                error = Some(frag_error);
+            }
+        }
+
+        if let Some(err) = error {
+            self.result.info = err.into();
+        } else {
+            self.result.info.status = uapi::drm_asahi_status_DRM_ASAHI_STATUS_COMPLETE;
+        }
+
+        self.writer.write(self.result);
+    }
+}
+
+#[versions(AGX)]
+impl super::Queue::ver {
+    /// Get the appropriate tiling parameters for a given userspace command buffer.
+    fn get_tiling_params(
+        cmdbuf: &uapi::drm_asahi_cmd_render,
+        num_clusters: u32,
+    ) -> Result<buffer::TileInfo> {
+        let width: u32 = cmdbuf.fb_width;
+        let height: u32 = cmdbuf.fb_height;
+        let layers: u32 = cmdbuf.layers;
+
+        if width > 65536 || height > 65536 {
+            return Err(EINVAL);
+        }
+
+        if layers == 0 || layers > 2048 {
+            return Err(EINVAL);
+        }
+
+        let tile_width = 32u32;
+        let tile_height = 32u32;
+
+        let utile_width = cmdbuf.utile_width;
+        let utile_height = cmdbuf.utile_height;
+
+        match (utile_width, utile_height) {
+            (32, 32) | (32, 16) | (16, 16) => (),
+            _ => return Err(EINVAL),
+        };
+
+        let utiles_per_tile_x = tile_width / utile_width;
+        let utiles_per_tile_y = tile_height / utile_height;
+
+        let utiles_per_tile = utiles_per_tile_x * utiles_per_tile_y;
+
+        let tiles_x = (width + tile_width - 1) / tile_width;
+        let tiles_y = (height + tile_height - 1) / tile_height;
+        let tiles = tiles_x * tiles_y;
+
+        let mtiles_x = 4u32;
+        let mtiles_y = 4u32;
+        let mtiles = mtiles_x * mtiles_y;
+
+        let tiles_per_mtile_x = align(div_ceil(tiles_x, mtiles_x), 4);
+        let tiles_per_mtile_y = align(div_ceil(tiles_y, mtiles_y), 4);
+        let tiles_per_mtile = tiles_per_mtile_x * tiles_per_mtile_y;
+
+        let mtile_x1 = tiles_per_mtile_x;
+        let mtile_x2 = 2 * tiles_per_mtile_x;
+        let mtile_x3 = 3 * tiles_per_mtile_x;
+
+        let mtile_y1 = tiles_per_mtile_y;
+        let mtile_y2 = 2 * tiles_per_mtile_y;
+        let mtile_y3 = 3 * tiles_per_mtile_y;
+
+        let rgn_entry_size = 5;
+        // Macrotile stride in 32-bit words
+        let rgn_size = align(rgn_entry_size * tiles_per_mtile * utiles_per_tile, 4) / 4;
+        let tilemap_size = (4 * rgn_size * mtiles * layers) as usize;
+
+        let tpc_entry_size = 8;
+        // TPC stride in 32-bit words
+        let tpc_mtile_stride = tpc_entry_size * utiles_per_tile * tiles_per_mtile / 4;
+        let tpc_size = (num_clusters * (4 * tpc_mtile_stride * mtiles) * layers) as usize;
+
+        // No idea where this comes from, but it fits what macOS does...
+        // GUESS: Number of 32K heap blocks to fit a 5-byte region header/pointer per tile?
+        // That would make a ton of sense...
+        // TODO: Layers? Why the sample count factor here?
+        let meta1_blocks = if num_clusters > 1 {
+            div_ceil(
+                align(tiles_x, 2) * align(tiles_y, 4) * utiles_per_tile,
+                0x1980,
+            )
+        } else {
+            0
+        };
+
+        let mut min_tvb_blocks = align(div_ceil(tiles_x * tiles_y, 128), 8);
+
+        if num_clusters > 1 {
+            min_tvb_blocks = min_tvb_blocks.max(7 + 2 * layers);
+        }
+
+        Ok(buffer::TileInfo {
+            tiles_x,
+            tiles_y,
+            tiles,
+            utile_width,
+            utile_height,
+            //mtiles_x,
+            //mtiles_y,
+            tiles_per_mtile_x,
+            tiles_per_mtile_y,
+            //tiles_per_mtile,
+            utiles_per_mtile_x: tiles_per_mtile_x * utiles_per_tile_x,
+            utiles_per_mtile_y: tiles_per_mtile_y * utiles_per_tile_y,
+            //utiles_per_mtile: tiles_per_mtile * utiles_per_tile,
+            tilemap_size,
+            tpc_size,
+            meta1_blocks,
+            min_tvb_blocks: min_tvb_blocks as usize,
+            params: fw::vertex::raw::TilingParameters {
+                rgn_size,
+                unk_4: 0x88,
+                ppp_ctrl: cmdbuf.ppp_ctrl,
+                x_max: (width - 1) as u16,
+                y_max: (height - 1) as u16,
+                te_screen: ((tiles_y - 1) << 12) | (tiles_x - 1),
+                te_mtile1: mtile_x3 | (mtile_x2 << 9) | (mtile_x1 << 18),
+                te_mtile2: mtile_y3 | (mtile_y2 << 9) | (mtile_y1 << 18),
+                tiles_per_mtile,
+                tpc_stride: tpc_mtile_stride,
+                unk_24: 0x100,
+                unk_28: if layers > 1 {
+                    0xe000 | (layers - 1)
+                } else {
+                    0x8000
+                },
+                __pad: Default::default(),
+            },
+        })
+    }
+
+    /// Submit work to a render queue.
+    pub(super) fn submit_render(
+        &self,
+        job: &mut Job<super::QueueJob::ver>,
+        cmd: &uapi::drm_asahi_command,
+        result_writer: Option<super::ResultWriter>,
+        id: u64,
+        flush_stamps: bool,
+    ) -> Result {
+        if cmd.cmd_type != uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER {
+            return Err(EINVAL);
+        }
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Render!\n", id);
+
+        let mut cmdbuf_reader = unsafe {
+            UserSlicePtr::new(
+                cmd.cmd_buffer as usize as *mut _,
+                core::mem::size_of::<uapi::drm_asahi_cmd_render>(),
+            )
+            .reader()
+        };
+
+        let mut cmdbuf: MaybeUninit<uapi::drm_asahi_cmd_render> = MaybeUninit::uninit();
+        unsafe {
+            cmdbuf_reader.read_raw(
+                cmdbuf.as_mut_ptr() as *mut u8,
+                core::mem::size_of::<uapi::drm_asahi_cmd_render>(),
+            )?;
+        }
+        let cmdbuf = unsafe { cmdbuf.assume_init() };
+
+        if cmdbuf.flags
+            & !(uapi::ASAHI_RENDER_NO_CLEAR_PIPELINE_TEXTURES
+                | uapi::ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S
+                | uapi::ASAHI_RENDER_SYNC_TVB_GROWTH
+                | uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES
+                | uapi::ASAHI_RENDER_NO_VERTEX_CLUSTERING
+                | uapi::ASAHI_RENDER_MSAA_ZS) as u64
+            != 0
+        {
+            return Err(EINVAL);
+        }
+
+        if cmdbuf.fb_width == 0
+            || cmdbuf.fb_height == 0
+            || cmdbuf.fb_width > 16384
+            || cmdbuf.fb_height > 16384
+        {
+            mod_dev_dbg!(
+                self.dev,
+                "[Submission {}] Invalid dimensions {}x{}\n",
+                id,
+                cmdbuf.fb_width,
+                cmdbuf.fb_height
+            );
+            return Err(EINVAL);
+        }
+
+        let dev = self.dev.data();
+        let gpu = match dev.gpu.as_any().downcast_ref::<gpu::GpuManager::ver>() {
+            Some(gpu) => gpu,
+            None => {
+                dev_crit!(self.dev, "GpuManager mismatched with Queue!\n");
+                return Err(EIO);
+            }
+        };
+
+        let nclusters = gpu.get_dyncfg().id.num_clusters;
+
+        // Can be set to false to disable clustering (for simpler jobs), but then the
+        // core masks below should be adjusted to cover a single rolling cluster.
+        let mut clustering = nclusters > 1;
+
+        if debug_enabled(debug::DebugFlags::DisableClustering)
+            || cmdbuf.flags & uapi::ASAHI_RENDER_NO_VERTEX_CLUSTERING as u64 != 0
+        {
+            clustering = false;
+        }
+
+        #[ver(G != G14)]
+        let tiling_control = {
+            let render_cfg = gpu.get_cfg().render;
+            let mut tiling_control = render_cfg.tiling_control;
+
+            if !clustering {
+                tiling_control |= TILECTL_DISABLE_CLUSTERING;
+            }
+            tiling_control
+        };
+
+        let mut alloc = gpu.alloc();
+        let kalloc = &mut *alloc;
+
+        // This sequence number increases per new client/VM? assigned to some slot,
+        // but it's unclear *which* slot...
+        let slot_client_seq: u8 = (self.id & 0xff) as u8;
+
+        let tile_info = Self::get_tiling_params(&cmdbuf, if clustering { nclusters } else { 1 })?;
+
+        let buffer = self.buffer.as_ref().ok_or(EINVAL)?;
+
+        let notifier = self.notifier.clone();
+
+        let tvb_autogrown = buffer.auto_grow()?;
+        if tvb_autogrown {
+            let new_size = buffer.block_count() as usize;
+            cls_dev_dbg!(
+                TVBStats,
+                &self.dev,
+                "[Submission {}] TVB grew to {} bytes ({} blocks) due to overflows\n",
+                id,
+                new_size * buffer::BLOCK_SIZE,
+                new_size,
+            );
+        }
+
+        let tvb_grown = buffer.ensure_blocks(tile_info.min_tvb_blocks)?;
+        if tvb_grown {
+            cls_dev_dbg!(
+                TVBStats,
+                &self.dev,
+                "[Submission {}] TVB grew to {} bytes ({} blocks) due to dimensions ({}x{})\n",
+                id,
+                tile_info.min_tvb_blocks * buffer::BLOCK_SIZE,
+                tile_info.min_tvb_blocks,
+                cmdbuf.fb_width,
+                cmdbuf.fb_height
+            );
+        }
+
+        let scene = Arc::new(buffer.new_scene(kalloc, &tile_info)?, GFP_KERNEL)?;
+
+        let vm_bind = job.vm_bind.clone();
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] VM slot = {}\n",
+            id,
+            vm_bind.slot()
+        );
+
+        let ev_vtx = job.get_vtx()?.event_info();
+        let ev_frag = job.get_frag()?.event_info();
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Vert event #{} -> {:#x?}\n",
+            id,
+            ev_vtx.slot,
+            ev_vtx.value.next(),
+        );
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Frag event #{} -> {:#x?}\n",
+            id,
+            ev_frag.slot,
+            ev_frag.value.next(),
+        );
+
+        let uuid_3d = cmdbuf.cmd_3d_id;
+        let uuid_ta = cmdbuf.cmd_ta_id;
+
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Vert UUID = {:#x?}\n",
+            id,
+            uuid_ta
+        );
+        mod_dev_dbg!(
+            self.dev,
+            "[Submission {}] Frag UUID = {:#x?}\n",
+            id,
+            uuid_3d
+        );
+
+        let fence = job.fence.clone();
+        let frag_job = job.get_frag()?;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Create Barrier\n", id);
+        let barrier = kalloc.private.new_init(
+            kernel::init::zeroed::<fw::workqueue::Barrier>(),
+            |_inner, _p| {
+                try_init!(fw::workqueue::raw::Barrier {
+                    tag: fw::workqueue::CommandType::Barrier,
+                    wait_stamp: ev_vtx.fw_stamp_pointer,
+                    wait_value: ev_vtx.value.next(),
+                    wait_slot: ev_vtx.slot,
+                    stamp_self: ev_frag.value.next(),
+                    uuid: uuid_3d,
+                    barrier_type: 0,
+                    padding: Default::default(),
+                })
+            },
+        )?;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Add Barrier\n", id);
+        frag_job.add(barrier, vm_bind.slot())?;
+
+        let timestamps = Arc::new(
+            kalloc.shared.new_default::<fw::job::RenderTimestamps>()?,
+            GFP_KERNEL,
+        )?;
+
+        let unk1 = false;
+
+        let mut tile_config: u64 = 0;
+        if !unk1 {
+            tile_config |= 0x280;
+        }
+        if cmdbuf.layers > 1 {
+            tile_config |= 1;
+        }
+        if cmdbuf.flags & uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES as u64 != 0 {
+            tile_config |= 0x10000;
+        }
+
+        let mut utile_config =
+            ((tile_info.utile_width / 16) << 12) | ((tile_info.utile_height / 16) << 14);
+        utile_config |= match cmdbuf.samples {
+            1 => 0,
+            2 => 1,
+            4 => 2,
+            _ => return Err(EINVAL),
+        };
+
+        #[ver(G >= G14X)]
+        let frg_tilecfg = 0x0000000_00036011
+            | (((tile_info.tiles_x - 1) as u64) << 44)
+            | (((tile_info.tiles_y - 1) as u64) << 53)
+            | (if unk1 { 0 } else { 0x20_00000000 })
+            | ((utile_config as u64 & 0xf000) << 28);
+
+        let frag_result = result_writer
+            .map(|writer| {
+                let mut result = RenderResult {
+                    result: Default::default(),
+                    vtx_complete: false,
+                    frag_complete: false,
+                    vtx_error: None,
+                    frag_error: None,
+                    writer,
+                };
+
+                if tvb_autogrown {
+                    result.result.flags |= uapi::DRM_ASAHI_RESULT_RENDER_TVB_GROW_OVF as u64;
+                }
+                if tvb_grown {
+                    result.result.flags |= uapi::DRM_ASAHI_RESULT_RENDER_TVB_GROW_MIN as u64;
+                }
+                result.result.tvb_size_bytes = buffer.size() as u64;
+
+                Arc::pin_init(new_mutex!(result, "render result"), GFP_KERNEL)
+            })
+            .transpose()?;
+
+        let vtx_result = frag_result.clone();
+
+        // TODO: check
+        #[ver(V >= V13_0B4)]
+        let count_frag = self.counter.fetch_add(2, Ordering::Relaxed);
+        #[ver(V >= V13_0B4)]
+        let count_vtx = count_frag + 1;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Create Frag\n", id);
+        let frag = GpuObject::new_init_prealloc(
+            kalloc.gpu_ro.alloc_object()?,
+            |ptr: GpuWeakPointer<fw::fragment::RunFragment::ver>| {
+                let has_result = frag_result.is_some();
+                let scene = scene.clone();
+                let notifier = notifier.clone();
+                let vm_bind = vm_bind.clone();
+                let timestamps = timestamps.clone();
+                let private = &mut kalloc.private;
+                try_init!(fw::fragment::RunFragment::ver {
+                    micro_seq: {
+                        let mut builder = microseq::Builder::new();
+
+                        let stats = inner_weak_ptr!(
+                            gpu.initdata.runtime_pointers.stats.frag.weak_pointer(),
+                            stats
+                        );
+
+                        let start_frag = builder.add(microseq::StartFragment::ver {
+                            header: microseq::op::StartFragment::HEADER,
+                            #[ver(G < G14X)]
+                            job_params2: Some(inner_weak_ptr!(ptr, job_params2)),
+                            #[ver(G < G14X)]
+                            job_params1: Some(inner_weak_ptr!(ptr, job_params1)),
+                            #[ver(G >= G14X)]
+                            job_params1: None,
+                            #[ver(G >= G14X)]
+                            job_params2: None,
+                            #[ver(G >= G14X)]
+                            registers: inner_weak_ptr!(ptr, registers),
+                            scene: scene.gpu_pointer(),
+                            stats,
+                            busy_flag: inner_weak_ptr!(ptr, busy_flag),
+                            tvb_overflow_count: inner_weak_ptr!(ptr, tvb_overflow_count),
+                            unk_pointer: inner_weak_ptr!(ptr, unk_pointee),
+                            work_queue: ev_frag.info_ptr,
+                            work_item: ptr,
+                            vm_slot: vm_bind.slot(),
+                            unk_50: 0x1, // fixed
+                            event_generation: self.id as u32,
+                            buffer_slot: scene.slot(),
+                            sync_grow: (cmdbuf.flags & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
+                                != 0) as u32,
+                            event_seq: U64(ev_frag.event_seq),
+                            unk_68: 0,
+                            unk_758_flag: inner_weak_ptr!(ptr, unk_758_flag),
+                            unk_job_buf: inner_weak_ptr!(ptr, unk_buf_0),
+                            #[ver(V >= V13_3)]
+                            unk_7c_0: U64(0),
+                            unk_7c: 0,
+                            unk_80: 0,
+                            unk_84: unk1.into(),
+                            uuid: uuid_3d,
+                            attachments: common::build_attachments(
+                                cmdbuf.fragment_attachments,
+                                cmdbuf.fragment_attachment_count,
+                            )?,
+                            padding: 0,
+                            #[ver(V >= V13_0B4)]
+                            counter: U64(count_frag),
+                            #[ver(V >= V13_0B4)]
+                            notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf),
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(true),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, start_ts),
+                                work_queue: ev_frag.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid: uuid_3d,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        #[ver(G < G14X)]
+                        builder.add(microseq::WaitForIdle {
+                            header: microseq::op::WaitForIdle::new(microseq::Pipe::Fragment),
+                        })?;
+                        #[ver(G >= G14X)]
+                        builder.add(microseq::WaitForIdle2 {
+                            header: microseq::op::WaitForIdle2::HEADER,
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(false),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, end_ts),
+                                work_queue: ev_frag.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid: uuid_3d,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        let off = builder.offset_to(start_frag);
+                        builder.add(microseq::FinalizeFragment::ver {
+                            header: microseq::op::FinalizeFragment::HEADER,
+                            uuid: uuid_3d,
+                            unk_8: 0,
+                            fw_stamp: ev_frag.fw_stamp_pointer,
+                            stamp_value: ev_frag.value.next(),
+                            unk_18: 0,
+                            scene: scene.weak_pointer(),
+                            buffer: scene.weak_buffer_pointer(),
+                            unk_2c: U64(1),
+                            stats,
+                            unk_pointer: inner_weak_ptr!(ptr, unk_pointee),
+                            busy_flag: inner_weak_ptr!(ptr, busy_flag),
+                            work_queue: ev_frag.info_ptr,
+                            work_item: ptr,
+                            vm_slot: vm_bind.slot(),
+                            unk_60: 0,
+                            unk_758_flag: inner_weak_ptr!(ptr, unk_758_flag),
+                            #[ver(V >= V13_3)]
+                            unk_6c_0: U64(0),
+                            unk_6c: U64(0),
+                            unk_74: U64(0),
+                            unk_7c: U64(0),
+                            unk_84: U64(0),
+                            unk_8c: U64(0),
+                            #[ver(G == G14 && V < V13_0B4)]
+                            unk_8c_g14: U64(0),
+                            restart_branch_offset: off,
+                            has_attachments: (cmdbuf.fragment_attachment_count > 0) as u32,
+                            #[ver(V >= V13_0B4)]
+                            unk_9c: Default::default(),
+                        })?;
+
+                        builder.add(microseq::RetireStamp {
+                            header: microseq::op::RetireStamp::HEADER,
+                        })?;
+
+                        builder.build(private)?
+                    },
+                    notifier,
+                    scene,
+                    vm_bind,
+                    aux_fb: self.ualloc.lock().array_empty(0x8000)?,
+                    timestamps,
+                })
+            },
+            |inner, _ptr| {
+                let vm_slot = vm_bind.slot();
+                let aux_fb_info = fw::fragment::raw::AuxFBInfo::ver {
+                    iogpu_unk_214: cmdbuf.iogpu_unk_214,
+                    unk2: 0,
+                    width: cmdbuf.fb_width,
+                    height: cmdbuf.fb_height,
+                    #[ver(V >= V13_0B4)]
+                    unk3: U64(0x100000),
+                };
+
+                try_init!(fw::fragment::raw::RunFragment::ver {
+                    tag: fw::workqueue::CommandType::RunFragment,
+                    #[ver(V >= V13_0B4)]
+                    counter: U64(count_frag),
+                    vm_slot,
+                    unk_8: 0,
+                    microsequence: inner.micro_seq.gpu_pointer(),
+                    microsequence_size: inner.micro_seq.len() as u32,
+                    notifier: inner.notifier.gpu_pointer(),
+                    buffer: inner.scene.buffer_pointer(),
+                    scene: inner.scene.gpu_pointer(),
+                    unk_buffer_buf: inner.scene.kernel_buffer_pointer(),
+                    tvb_tilemap: inner.scene.tvb_tilemap_pointer(),
+                    ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl),
+                    samples: cmdbuf.samples,
+                    tiles_per_mtile_y: tile_info.tiles_per_mtile_y as u16,
+                    tiles_per_mtile_x: tile_info.tiles_per_mtile_x as u16,
+                    unk_50: U64(0),
+                    unk_58: U64(0),
+                    merge_upper_x: F32::from_bits(cmdbuf.merge_upper_x),
+                    merge_upper_y: F32::from_bits(cmdbuf.merge_upper_y),
+                    unk_68: U64(0),
+                    tile_count: U64(tile_info.tiles as u64),
+                    #[ver(G < G14X)]
+                    job_params1 <- try_init!(fw::fragment::raw::JobParameters1::ver {
+                        utile_config,
+                        unk_4: 0,
+                        clear_pipeline: fw::fragment::raw::ClearPipelineBinding {
+                            pipeline_bind: U64(cmdbuf.load_pipeline_bind as u64),
+                            address: U64(cmdbuf.load_pipeline as u64),
+                        },
+                        ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl),
+                        scissor_array: U64(cmdbuf.scissor_array),
+                        depth_bias_array: U64(cmdbuf.depth_bias_array),
+                        aux_fb_info,
+                        depth_dimensions: U64(cmdbuf.depth_dimensions as u64),
+                        visibility_result_buffer: U64(cmdbuf.visibility_result_buffer),
+                        zls_ctrl: U64(cmdbuf.zls_ctrl),
+                        #[ver(G >= G14)]
+                        unk_58_g14_0: U64(0x4040404),
+                        #[ver(G >= G14)]
+                        unk_58_g14_8: U64(0),
+                        depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
+                        depth_buffer_ptr2: U64(cmdbuf.depth_buffer_store),
+                        stencil_buffer_ptr1: U64(cmdbuf.stencil_buffer_load),
+                        stencil_buffer_ptr2: U64(cmdbuf.stencil_buffer_store),
+                        #[ver(G >= G14)]
+                        unk_68_g14_0: Default::default(),
+                        unk_78: Default::default(),
+                        depth_meta_buffer_ptr1: U64(cmdbuf.depth_meta_buffer_load),
+                        unk_a0: Default::default(),
+                        depth_meta_buffer_ptr2: U64(cmdbuf.depth_meta_buffer_store),
+                        unk_b0: Default::default(),
+                        stencil_meta_buffer_ptr1: U64(cmdbuf.stencil_meta_buffer_load),
+                        unk_c0: Default::default(),
+                        stencil_meta_buffer_ptr2: U64(cmdbuf.stencil_meta_buffer_store),
+                        unk_d0: Default::default(),
+                        tvb_tilemap: inner.scene.tvb_tilemap_pointer(),
+                        tvb_heapmeta: inner.scene.tvb_heapmeta_pointer(),
+                        mtile_stride_dwords: U64((4 * tile_info.params.rgn_size as u64) << 24),
+                        tvb_heapmeta_2: inner.scene.tvb_heapmeta_pointer(),
+                        tile_config: U64(tile_config),
+                        aux_fb: inner.aux_fb.gpu_pointer(),
+                        unk_108: Default::default(),
+                        pipeline_base: U64(0x11_00000000),
+                        unk_140: U64(0x8c60),
+                        unk_148: U64(0x0),
+                        unk_150: U64(0x0),
+                        unk_158: U64(0x1c),
+                        unk_160: U64(0),
+                        __pad: Default::default(),
+                        #[ver(V < V13_0B4)]
+                        __pad1: Default::default(),
+                    }),
+                    #[ver(G < G14X)]
+                    job_params2 <- try_init!(fw::fragment::raw::JobParameters2 {
+                        store_pipeline_bind: cmdbuf.store_pipeline_bind,
+                        store_pipeline_addr: cmdbuf.store_pipeline,
+                        unk_8: 0x0,
+                        unk_c: 0x0,
+                        merge_upper_x: F32::from_bits(cmdbuf.merge_upper_x),
+                        merge_upper_y: F32::from_bits(cmdbuf.merge_upper_y),
+                        unk_18: U64(0x0),
+                        utiles_per_mtile_y: tile_info.utiles_per_mtile_y as u16,
+                        utiles_per_mtile_x: tile_info.utiles_per_mtile_x as u16,
+                        unk_24: 0x0,
+                        tile_counts: ((tile_info.tiles_y - 1) << 12) | (tile_info.tiles_x - 1),
+                        tib_blocks: cmdbuf.tib_blocks,
+                        isp_bgobjdepth: cmdbuf.isp_bgobjdepth,
+                        // TODO: does this flag need to be exposed to userspace?
+                        isp_bgobjvals: cmdbuf.isp_bgobjvals | 0x400,
+                        unk_38: 0,
+                        unk_3c: 1,
+                        unk_40: 0,
+                        __pad: Default::default(),
+                    }),
+                    #[ver(G >= G14X)]
+                    registers: fw::job::raw::RegisterArray::new(
+                        inner_weak_ptr!(_ptr, registers.registers),
+                        |r| {
+                            r.add(0x1739, 1);
+                            r.add(0x10009, utile_config.into());
+                            r.add(0x15379, cmdbuf.store_pipeline_bind.into());
+                            r.add(0x15381, cmdbuf.store_pipeline.into());
+                            r.add(0x15369, cmdbuf.load_pipeline_bind.into());
+                            r.add(0x15371, cmdbuf.load_pipeline.into());
+                            r.add(0x15131, cmdbuf.merge_upper_x.into());
+                            r.add(0x15139, cmdbuf.merge_upper_y.into());
+                            r.add(0x100a1, 0);
+                            r.add(0x15069, 0);
+                            r.add(0x15071, 0); // pointer
+                            r.add(0x16058, 0);
+                            r.add(0x10019, cmdbuf.ppp_multisamplectl);
+                            let isp_mtile_size = (tile_info.utiles_per_mtile_y
+                                | (tile_info.utiles_per_mtile_x << 16))
+                                .into();
+                            r.add(0x100b1, isp_mtile_size); // ISP_MTILE_SIZE
+                            r.add(0x16030, isp_mtile_size); // ISP_MTILE_SIZE
+                            r.add(
+                                0x100d9,
+                                (((tile_info.tiles_y - 1) << 12) | (tile_info.tiles_x - 1)).into(),
+                            ); // TE_SCREEN
+                            r.add(0x16098, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x15109, cmdbuf.scissor_array); // ISP_SCISSOR_BASE
+                            r.add(0x15101, cmdbuf.depth_bias_array); // ISP_DBIAS_BASE
+                            r.add(0x15021, cmdbuf.iogpu_unk_214.into()); // aux_fb_info.unk_1
+                            r.add(
+                                0x15211,
+                                ((cmdbuf.fb_height as u64) << 32) | cmdbuf.fb_width as u64,
+                            ); // aux_fb_info.{width, heigh
+                            r.add(0x15049, aux_fb_info.unk3); // s2.aux_fb_info.unk3
+                            r.add(0x10051, cmdbuf.tib_blocks.into()); // s1.unk_2c
+                            r.add(0x15321, cmdbuf.depth_dimensions.into()); // ISP_ZLS_PIXELS
+                            r.add(0x15301, cmdbuf.isp_bgobjdepth.into()); // ISP_BGOBJDEPTH
+                            r.add(0x15309, cmdbuf.isp_bgobjvals.into() | 0x400); // ISP_BGOBJVALS
+                            r.add(0x15311, cmdbuf.visibility_result_buffer); // ISP_OCLQRY_BASE
+                            r.add(0x15319, cmdbuf.zls_ctrl); // ISP_ZLSCTL
+                            r.add(0x15349, 0x4040404); // s2.unk_58_g14_0
+                            r.add(0x15351, 0); // s2.unk_58_g14_8
+                            r.add(0x15329, cmdbuf.depth_buffer_load); // ISP_ZLOAD_BASE
+                            r.add(0x15331, cmdbuf.depth_buffer_store); // ISP_ZSTORE_BASE
+                            r.add(0x15339, cmdbuf.stencil_buffer_load); // ISP_STENCIL_LOAD_BASE
+                            r.add(0x15341, cmdbuf.stencil_buffer_store); // ISP_STENCIL_STORE_BASE
+                            r.add(0x15231, 0);
+                            r.add(0x15221, 0);
+                            r.add(0x15239, 0);
+                            r.add(0x15229, 0);
+                            r.add(0x15401, 0);
+                            r.add(0x15421, 0);
+                            r.add(0x15409, 0);
+                            r.add(0x15429, 0);
+                            r.add(0x153c1, cmdbuf.depth_meta_buffer_load);
+                            r.add(0x15411, 0);
+                            r.add(0x153c9, cmdbuf.depth_meta_buffer_store);
+                            r.add(0x15431, 0);
+                            r.add(0x153d1, cmdbuf.stencil_meta_buffer_load);
+                            r.add(0x15419, 0);
+                            r.add(0x153d9, cmdbuf.stencil_meta_buffer_store);
+                            r.add(0x15439, 0);
+                            r.add(0x16429, inner.scene.tvb_tilemap_pointer().into());
+                            r.add(0x16060, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x16431, (4 * tile_info.params.rgn_size as u64) << 24); // ISP_RGN?
+                            r.add(0x10039, tile_config); // tile_config ISP_CTL?
+                            r.add(0x16451, 0x0); // ISP_RENDER_ORIGIN
+                            r.add(0x11821, 0x0); // some shader?
+                            r.add(0x11829, 0);
+                            r.add(0x11f79, 0);
+                            r.add(0x15359, 0);
+                            r.add(0x10069, 0x11_00000000); // USC_EXEC_BASE_ISP
+                            r.add(0x16020, 0);
+                            r.add(0x16461, inner.aux_fb.gpu_pointer().into());
+                            r.add(0x16090, inner.aux_fb.gpu_pointer().into());
+                            r.add(0x120a1, 0x1c);
+                            r.add(0x160a8, 0);
+                            r.add(0x16068, frg_tilecfg);
+                            r.add(0x160b8, 0x0);
+                            /*
+                            r.add(0x10201, 0x100); // Some kind of counter?? Does this matter?
+                            r.add(0x10428, 0x100); // Some kind of counter?? Does this matter?
+                            r.add(0x1c838, 1);  // ?
+                            r.add(0x1ca28, 0x1502960f00); // ??
+                            r.add(0x1731, 0x1); // ??
+                            */
+                        }
+                    ),
+                    job_params3 <- try_init!(fw::fragment::raw::JobParameters3::ver {
+                        depth_bias_array: fw::fragment::raw::ArrayAddr {
+                            ptr: U64(cmdbuf.depth_bias_array),
+                            unk_padding: U64(0),
+                        },
+                        scissor_array: fw::fragment::raw::ArrayAddr {
+                            ptr: U64(cmdbuf.scissor_array),
+                            unk_padding: U64(0),
+                        },
+                        visibility_result_buffer: U64(cmdbuf.visibility_result_buffer),
+                        unk_118: U64(0x0),
+                        unk_120: Default::default(),
+                        unk_reload_pipeline: fw::fragment::raw::ClearPipelineBinding {
+                            pipeline_bind: U64(cmdbuf.partial_reload_pipeline_bind as u64),
+                            address: U64(cmdbuf.partial_reload_pipeline as u64),
+                        },
+                        unk_258: U64(0),
+                        unk_260: U64(0),
+                        unk_268: U64(0),
+                        unk_270: U64(0),
+                        reload_pipeline: fw::fragment::raw::ClearPipelineBinding {
+                            pipeline_bind: U64(cmdbuf.partial_reload_pipeline_bind as u64),
+                            address: U64(cmdbuf.partial_reload_pipeline as u64),
+                        },
+                        zls_ctrl: U64(cmdbuf.zls_ctrl),
+                        #[ver(G >= G14X)]
+                        unk_290: U64(0x4040404),
+                        #[ver(G < G14X)]
+                        unk_290: U64(0x0),
+                        depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
+                        unk_2a0: U64(0x0),
+                        unk_2a8: U64(0x0),
+                        depth_buffer_ptr2: U64(cmdbuf.depth_buffer_store),
+                        depth_buffer_ptr3: U64(cmdbuf.depth_buffer_partial),
+                        depth_meta_buffer_ptr3: U64(cmdbuf.depth_meta_buffer_partial),
+                        stencil_buffer_ptr1: U64(cmdbuf.stencil_buffer_load),
+                        unk_2d0: U64(0x0),
+                        unk_2d8: U64(0x0),
+                        stencil_buffer_ptr2: U64(cmdbuf.stencil_buffer_store),
+                        stencil_buffer_ptr3: U64(cmdbuf.stencil_buffer_partial),
+                        stencil_meta_buffer_ptr3: U64(cmdbuf.stencil_meta_buffer_partial),
+                        unk_2f8: Default::default(),
+                        tib_blocks: cmdbuf.tib_blocks,
+                        unk_30c: 0x0,
+                        aux_fb_info,
+                        tile_config: U64(tile_config),
+                        unk_328_padding: Default::default(),
+                        unk_partial_store_pipeline: fw::fragment::raw::StorePipelineBinding::new(
+                            cmdbuf.partial_store_pipeline_bind,
+                            cmdbuf.partial_store_pipeline
+                        ),
+                        partial_store_pipeline: fw::fragment::raw::StorePipelineBinding::new(
+                            cmdbuf.partial_store_pipeline_bind,
+                            cmdbuf.partial_store_pipeline
+                        ),
+                        isp_bgobjdepth: cmdbuf.isp_bgobjdepth,
+                        isp_bgobjvals: cmdbuf.isp_bgobjvals,
+                        sample_size: cmdbuf.sample_size,
+                        unk_37c: 0x0,
+                        unk_380: U64(0x0),
+                        unk_388: U64(0x0),
+                        #[ver(V >= V13_0B4)]
+                        unk_390_0: U64(0x0),
+                        depth_dimensions: U64(cmdbuf.depth_dimensions as u64),
+                    }),
+                    unk_758_flag: 0,
+                    unk_75c_flag: 0,
+                    unk_buf: Default::default(),
+                    busy_flag: 0,
+                    tvb_overflow_count: 0,
+                    unk_878: 0,
+                    encoder_params <- try_init!(fw::job::raw::EncoderParams {
+                        unk_8: (cmdbuf.flags & uapi::ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S as u64
+                            != 0) as u32,
+                        sync_grow: (cmdbuf.flags & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
+                                    != 0) as u32,
+                        unk_10: 0x0, // fixed
+                        encoder_id: cmdbuf.encoder_id,
+                        unk_18: 0x0, // fixed
+                        unk_mask: 0xffffffff,
+                        sampler_array: U64(0),
+                        sampler_count: 0,
+                        sampler_max: 0,
+                    }),
+                    process_empty_tiles: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES as u64
+                        != 0) as u32,
+                    no_clear_pipeline_textures: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_NO_CLEAR_PIPELINE_TEXTURES as u64
+                        != 0) as u32,
+                    msaa_zs: (cmdbuf.flags & uapi::ASAHI_RENDER_MSAA_ZS as u64 != 0) as u32,
+                    unk_pointee: 0,
+                    #[ver(V >= V13_3)]
+                    unk_v13_3: 0,
+                    meta <- try_init!(fw::job::raw::JobMeta {
+                        unk_0: 0,
+                        unk_2: 0,
+                        no_preemption: 0,
+                        stamp: ev_frag.stamp_pointer,
+                        fw_stamp: ev_frag.fw_stamp_pointer,
+                        stamp_value: ev_frag.value.next(),
+                        stamp_slot: ev_frag.slot,
+                        evctl_index: 0, // fixed
+                        flush_stamps: flush_stamps as u32,
+                        uuid: uuid_3d,
+                        event_seq: ev_frag.event_seq as u32,
+                    }),
+                    unk_after_meta: unk1.into(),
+                    unk_buf_0: U64(0),
+                    unk_buf_8: U64(0),
+                    #[ver(G < G14X)]
+                    unk_buf_10: U64(1),
+                    #[ver(G >= G14X)]
+                    unk_buf_10: U64(0),
+                    cur_ts: U64(0),
+                    start_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), frag.start)),
+                    end_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), frag.end)),
+                    unk_914: 0,
+                    unk_918: U64(0),
+                    unk_920: 0,
+                    client_sequence: slot_client_seq,
+                    pad_925: Default::default(),
+                    unk_928: 0,
+                    unk_92c: 0,
+                    #[ver(V >= V13_0B4)]
+                    unk_ts: U64(0),
+                    #[ver(V >= V13_0B4)]
+                    unk_92d_8: Default::default(),
+                })
+            },
+        )?;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Add Frag\n", id);
+        fence.add_command();
+
+        frag_job.add_cb(frag, vm_bind.slot(), move |cmd, error| {
+            if let Some(err) = error {
+                fence.set_error(err.into());
+            }
+            if let Some(mut res) = frag_result.as_ref().map(|a| a.lock()) {
+                cmd.timestamps.with(|raw, _inner| {
+                    res.result.fragment_ts_start = raw.frag.start.load(Ordering::Relaxed);
+                    res.result.fragment_ts_end = raw.frag.end.load(Ordering::Relaxed);
+                });
+                cmd.with(|raw, _inner| {
+                    res.result.num_tvb_overflows = raw.tvb_overflow_count;
+                });
+                res.frag_error = error;
+                res.frag_complete = true;
+                res.commit();
+            }
+            fence.command_complete();
+        })?;
+
+        let fence = job.fence.clone();
+        let vtx_job = job.get_vtx()?;
+
+        if scene.rebind() || tvb_grown || tvb_autogrown {
+            mod_dev_dbg!(self.dev, "[Submission {}] Create Bind Buffer\n", id);
+            let bind_buffer = kalloc.private.new_init(
+                {
+                    let scene = scene.clone();
+                    try_init!(fw::buffer::InitBuffer::ver { scene })
+                },
+                |inner, _ptr| {
+                    let vm_slot = vm_bind.slot();
+                    try_init!(fw::buffer::raw::InitBuffer::ver {
+                        tag: fw::workqueue::CommandType::InitBuffer,
+                        vm_slot,
+                        buffer_slot: inner.scene.slot(),
+                        unk_c: 0,
+                        block_count: buffer.block_count(),
+                        buffer: inner.scene.buffer_pointer(),
+                        stamp_value: ev_vtx.value.next(),
+                    })
+                },
+            )?;
+
+            mod_dev_dbg!(self.dev, "[Submission {}] Add Bind Buffer\n", id);
+            vtx_job.add(bind_buffer, vm_bind.slot())?;
+        }
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Create Vertex\n", id);
+        let vtx = GpuObject::new_init_prealloc(
+            kalloc.gpu_ro.alloc_object()?,
+            |ptr: GpuWeakPointer<fw::vertex::RunVertex::ver>| {
+                let has_result = vtx_result.is_some();
+                let scene = scene.clone();
+                let vm_bind = vm_bind.clone();
+                let timestamps = timestamps.clone();
+                let private = &mut kalloc.private;
+                try_init!(fw::vertex::RunVertex::ver {
+                    micro_seq: {
+                        let mut builder = microseq::Builder::new();
+
+                        let stats = inner_weak_ptr!(
+                            gpu.initdata.runtime_pointers.stats.vtx.weak_pointer(),
+                            stats
+                        );
+
+                        let start_vtx = builder.add(microseq::StartVertex::ver {
+                            header: microseq::op::StartVertex::HEADER,
+                            #[ver(G < G14X)]
+                            tiling_params: Some(inner_weak_ptr!(ptr, tiling_params)),
+                            #[ver(G < G14X)]
+                            job_params1: Some(inner_weak_ptr!(ptr, job_params1)),
+                            #[ver(G >= G14X)]
+                            tiling_params: None,
+                            #[ver(G >= G14X)]
+                            job_params1: None,
+                            #[ver(G >= G14X)]
+                            registers: inner_weak_ptr!(ptr, registers),
+                            buffer: scene.weak_buffer_pointer(),
+                            scene: scene.weak_pointer(),
+                            stats,
+                            work_queue: ev_vtx.info_ptr,
+                            vm_slot: vm_bind.slot(),
+                            unk_38: 1, // fixed
+                            event_generation: self.id as u32,
+                            buffer_slot: scene.slot(),
+                            unk_44: 0,
+                            event_seq: U64(ev_vtx.event_seq),
+                            unk_50: 0,
+                            unk_pointer: inner_weak_ptr!(ptr, unk_pointee),
+                            unk_job_buf: inner_weak_ptr!(ptr, unk_buf_0),
+                            unk_64: 0x0, // fixed
+                            unk_68: unk1.into(),
+                            uuid: uuid_ta,
+                            attachments: common::build_attachments(
+                                cmdbuf.vertex_attachments,
+                                cmdbuf.vertex_attachment_count,
+                            )?,
+                            padding: 0,
+                            #[ver(V >= V13_0B4)]
+                            counter: U64(count_vtx),
+                            #[ver(V >= V13_0B4)]
+                            notifier_buf: inner_weak_ptr!(notifier.weak_pointer(), state.unk_buf),
+                            #[ver(V < V13_0B4)]
+                            unk_178: 0x0, // padding?
+                            #[ver(V >= V13_0B4)]
+                            unk_178: (!clustering) as u32,
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(true),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, start_ts),
+                                work_queue: ev_vtx.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid: uuid_ta,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        #[ver(G < G14X)]
+                        builder.add(microseq::WaitForIdle {
+                            header: microseq::op::WaitForIdle::new(microseq::Pipe::Vertex),
+                        })?;
+                        #[ver(G >= G14X)]
+                        builder.add(microseq::WaitForIdle2 {
+                            header: microseq::op::WaitForIdle2::HEADER,
+                        })?;
+
+                        if has_result {
+                            builder.add(microseq::Timestamp::ver {
+                                header: microseq::op::Timestamp::new(false),
+                                cur_ts: inner_weak_ptr!(ptr, cur_ts),
+                                start_ts: inner_weak_ptr!(ptr, start_ts),
+                                update_ts: inner_weak_ptr!(ptr, end_ts),
+                                work_queue: ev_vtx.info_ptr,
+                                unk_24: U64(0),
+                                #[ver(V >= V13_0B4)]
+                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                uuid: uuid_ta,
+                                unk_30_padding: 0,
+                            })?;
+                        }
+
+                        let off = builder.offset_to(start_vtx);
+                        builder.add(microseq::FinalizeVertex::ver {
+                            header: microseq::op::FinalizeVertex::HEADER,
+                            scene: scene.weak_pointer(),
+                            buffer: scene.weak_buffer_pointer(),
+                            stats,
+                            work_queue: ev_vtx.info_ptr,
+                            vm_slot: vm_bind.slot(),
+                            unk_28: 0x0, // fixed
+                            unk_pointer: inner_weak_ptr!(ptr, unk_pointee),
+                            unk_34: 0x0, // fixed
+                            uuid: uuid_ta,
+                            fw_stamp: ev_vtx.fw_stamp_pointer,
+                            stamp_value: ev_vtx.value.next(),
+                            unk_48: U64(0x0), // fixed
+                            unk_50: 0x0,      // fixed
+                            unk_54: 0x0,      // fixed
+                            unk_58: U64(0x0), // fixed
+                            unk_60: 0x0,      // fixed
+                            unk_64: 0x0,      // fixed
+                            unk_68: 0x0,      // fixed
+                            #[ver(G >= G14 && V < V13_0B4)]
+                            unk_68_g14: U64(0),
+                            restart_branch_offset: off,
+                            has_attachments: (cmdbuf.vertex_attachment_count > 0) as u32,
+                            #[ver(V >= V13_0B4)]
+                            unk_74: Default::default(), // Ventura
+                        })?;
+
+                        builder.add(microseq::RetireStamp {
+                            header: microseq::op::RetireStamp::HEADER,
+                        })?;
+                        builder.build(private)?
+                    },
+                    notifier,
+                    scene,
+                    vm_bind,
+                    timestamps,
+                })
+            },
+            |inner, _ptr| {
+                let vm_slot = vm_bind.slot();
+                #[ver(G < G14)]
+                let core_masks = gpu.core_masks_packed();
+
+                try_init!(fw::vertex::raw::RunVertex::ver {
+                    tag: fw::workqueue::CommandType::RunVertex,
+                    #[ver(V >= V13_0B4)]
+                    counter: U64(count_vtx),
+                    vm_slot,
+                    unk_8: 0,
+                    notifier: inner.notifier.gpu_pointer(),
+                    buffer_slot: inner.scene.slot(),
+                    unk_1c: 0,
+                    buffer: inner.scene.buffer_pointer(),
+                    scene: inner.scene.gpu_pointer(),
+                    unk_buffer_buf: inner.scene.kernel_buffer_pointer(),
+                    unk_34: 0,
+                    #[ver(G < G14X)]
+                    job_params1 <- try_init!(fw::vertex::raw::JobParameters1::ver {
+                        unk_0: U64(if unk1 { 0 } else { 0x200 }), // sometimes 0
+                        unk_8: f32!(1e-20),                       // fixed
+                        unk_c: f32!(1e-20),                       // fixed
+                        tvb_tilemap: inner.scene.tvb_tilemap_pointer(),
+                        #[ver(G < G14)]
+                        tvb_cluster_tilemaps: inner.scene.cluster_tilemaps_pointer(),
+                        tpc: inner.scene.tpc_pointer(),
+                        tvb_heapmeta: inner.scene.tvb_heapmeta_pointer().or(0x8000_0000_0000_0000),
+                        iogpu_unk_54: 0x6b0003, // fixed
+                        iogpu_unk_55: 0x3a0012, // fixed
+                        iogpu_unk_56: U64(0x1), // fixed
+                        #[ver(G < G14)]
+                        tvb_cluster_meta1: inner
+                            .scene
+                            .meta_1_pointer()
+                            .map(|x| x.or((tile_info.meta1_blocks as u64) << 50)),
+                        utile_config,
+                        unk_4c: 0,
+                        ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), // fixed
+                        tvb_heapmeta_2: inner.scene.tvb_heapmeta_pointer(),
+                        #[ver(G < G14)]
+                        unk_60: U64(0x0), // fixed
+                        #[ver(G < G14)]
+                        core_mask: Array::new([
+                            *core_masks.first().unwrap_or(&0),
+                            *core_masks.get(1).unwrap_or(&0),
+                        ]),
+                        preempt_buf1: inner.scene.preempt_buf_1_pointer(),
+                        preempt_buf2: inner.scene.preempt_buf_2_pointer(),
+                        unk_80: U64(0x1), // fixed
+                        preempt_buf3: inner.scene.preempt_buf_3_pointer().or(0x4_0000_0000_0000), // check
+                        encoder_addr: U64(cmdbuf.encoder_ptr),
+                        #[ver(G < G14)]
+                        tvb_cluster_meta2: inner.scene.meta_2_pointer(),
+                        #[ver(G < G14)]
+                        tvb_cluster_meta3: inner.scene.meta_3_pointer(),
+                        #[ver(G < G14)]
+                        tiling_control,
+                        #[ver(G < G14)]
+                        unk_ac: 0, // fixed
+                        unk_b0: Default::default(), // fixed
+                        pipeline_base: U64(0x11_00000000),
+                        #[ver(G < G14)]
+                        tvb_cluster_meta4: inner
+                            .scene
+                            .meta_4_pointer()
+                            .map(|x| x.or(0x3000_0000_0000_0000)),
+                        #[ver(G < G14)]
+                        unk_f0: U64(0x1c + align(tile_info.meta1_blocks, 4) as u64),
+                        unk_f8: U64(0x8c60),     // fixed
+                        unk_100: Default::default(),      // fixed
+                        unk_118: 0x1c, // fixed
+                        __pad: Default::default(),
+                    }),
+                    #[ver(G < G14X)]
+                    tiling_params: tile_info.params,
+                    #[ver(G >= G14X)]
+                    registers: fw::job::raw::RegisterArray::new(
+                        inner_weak_ptr!(_ptr, registers.registers),
+                        |r| {
+                            r.add(0x10141, if unk1 { 0 } else { 0x200 }); // s2.unk_0
+                            r.add(0x1c039, inner.scene.tvb_tilemap_pointer().into());
+                            r.add(0x1c9c8, inner.scene.tvb_tilemap_pointer().into());
+
+                            let cl_tilemaps_ptr = inner
+                                .scene
+                                .cluster_tilemaps_pointer()
+                                .map_or(0, |a| a.into());
+                            r.add(0x1c041, cl_tilemaps_ptr);
+                            r.add(0x1c9d0, cl_tilemaps_ptr);
+                            r.add(0x1c0a1, inner.scene.tpc_pointer().into()); // TE_TPC_ADDR
+
+                            let tvb_heapmeta_ptr = inner
+                                .scene
+                                .tvb_heapmeta_pointer()
+                                .or(0x8000_0000_0000_0000)
+                                .into();
+                            r.add(0x1c031, tvb_heapmeta_ptr);
+                            r.add(0x1c9c0, tvb_heapmeta_ptr);
+                            r.add(0x1c051, 0x3a0012006b0003); // iogpu_unk_54/55
+                            r.add(0x1c061, 1); // iogpu_unk_56
+                            r.add(0x10149, utile_config.into()); // s2.unk_48 utile_config
+                            r.add(0x10139, cmdbuf.ppp_multisamplectl); // PPP_MULTISAMPLECTL
+                            r.add(0x10111, inner.scene.preempt_buf_1_pointer().into());
+                            r.add(0x1c9b0, inner.scene.preempt_buf_1_pointer().into());
+                            r.add(0x10119, inner.scene.preempt_buf_2_pointer().into());
+                            r.add(0x1c9b8, inner.scene.preempt_buf_2_pointer().into());
+                            r.add(0x1c958, 1); // s2.unk_80
+                            r.add(
+                                0x1c950,
+                                inner
+                                    .scene
+                                    .preempt_buf_3_pointer()
+                                    .or(0x4_0000_0000_0000)
+                                    .into(),
+                            );
+                            r.add(0x1c930, 0); // VCE related addr, lsb to enable
+                            r.add(0x1c880, cmdbuf.encoder_ptr); // VDM_CTRL_STREAM_BASE
+                            r.add(0x1c898, 0x0); // if lsb set, faults in UL1C0, possibly missing addr.
+                            r.add(
+                                0x1c948,
+                                inner.scene.meta_2_pointer().map_or(0, |a| a.into()),
+                            ); // tvb_cluster_meta2
+                            r.add(
+                                0x1c888,
+                                inner.scene.meta_3_pointer().map_or(0, |a| a.into()),
+                            ); // tvb_cluster_meta3
+                            r.add(0x1c890, tiling_control.into()); // tvb_tiling_control
+                            r.add(0x1c918, 4);
+                            r.add(0x1c079, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x1c9d8, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x1c089, 0);
+                            r.add(0x1c9e0, 0);
+                            let cl_meta_4_pointer =
+                                inner.scene.meta_4_pointer().map_or(0, |a| a.into());
+                            r.add(0x16c41, cl_meta_4_pointer); // tvb_cluster_meta4
+                            r.add(0x1ca40, cl_meta_4_pointer); // tvb_cluster_meta4
+                            r.add(0x1c9a8, 0x1c); // + meta1_blocks? min_free_tvb_pages?
+                            r.add(
+                                0x1c920,
+                                inner.scene.meta_1_pointer().map_or(0, |a| a.into()),
+                            ); // ??? | meta1_blocks?
+                            r.add(0x10151, 0);
+                            r.add(0x1c199, 0);
+                            r.add(0x1c1a1, 0);
+                            r.add(0x1c1a9, 0); // 0x10151 bit 1 enables
+                            r.add(0x1c1b1, 0);
+                            r.add(0x1c1b9, 0);
+                            r.add(0x10061, 0x11_00000000); // USC_EXEC_BASE_TA
+                            r.add(0x11801, 0); // some shader?
+                            r.add(0x11809, 0); // maybe arg?
+                            r.add(0x11f71, 0);
+                            r.add(0x1c0b1, tile_info.params.rgn_size.into()); // TE_PSG
+                            r.add(0x1c850, tile_info.params.rgn_size.into());
+                            r.add(0x10131, tile_info.params.unk_4.into());
+                            r.add(0x10121, tile_info.params.ppp_ctrl.into()); // PPP_CTRL
+                            r.add(
+                                0x10129,
+                                tile_info.params.x_max as u64
+                                    | ((tile_info.params.y_max as u64) << 16),
+                            ); // PPP_SCREEN
+                            r.add(0x101b9, tile_info.params.te_screen.into()); // TE_SCREEN
+                            r.add(0x1c069, tile_info.params.te_mtile1.into()); // TE_MTILE1
+                            r.add(0x1c071, tile_info.params.te_mtile2.into()); // TE_MTILE2
+                            r.add(0x1c081, tile_info.params.tiles_per_mtile.into()); // TE_MTILE
+                            r.add(0x1c0a9, tile_info.params.tpc_stride.into()); // TE_TPC
+                            r.add(0x10171, tile_info.params.unk_24.into());
+                            r.add(0x10169, tile_info.params.unk_28.into()); // TA_RENDER_TARGET_MAX
+                            r.add(0x12099, 0x1c);
+                            r.add(0x1c9e8, 0);
+                            /*
+                            r.add(0x10209, 0x100); // Some kind of counter?? Does this matter?
+                            r.add(0x1c9f0, 0x100); // Some kind of counter?? Does this matter?
+                            r.add(0x1c830, 1); // ?
+                            r.add(0x1ca30, 0x1502960e60); // ?
+                            r.add(0x16c39, 0x1502960e60); // ?
+                            r.add(0x1c910, 0xa0000b011d); // ?
+                            r.add(0x1c8e0, 0xff); // cluster mask
+                            r.add(0x1c8e8, 0); // ?
+                            */
+                        }
+                    ),
+                    tpc: inner.scene.tpc_pointer(),
+                    tpc_size: U64(tile_info.tpc_size as u64),
+                    microsequence: inner.micro_seq.gpu_pointer(),
+                    microsequence_size: inner.micro_seq.len() as u32,
+                    fragment_stamp_slot: ev_frag.slot,
+                    fragment_stamp_value: ev_frag.value.next(),
+                    unk_pointee: 0,
+                    unk_pad: 0,
+                    job_params2 <- try_init!(fw::vertex::raw::JobParameters2 {
+                        unk_480: Default::default(), // fixed
+                        unk_498: U64(0x0),           // fixed
+                        unk_4a0: 0x0,                // fixed
+                        preempt_buf1: inner.scene.preempt_buf_1_pointer(),
+                        unk_4ac: 0x0,      // fixed
+                        unk_4b0: U64(0x0), // fixed
+                        unk_4b8: 0x0,      // fixed
+                        unk_4bc: U64(0x0), // fixed
+                        unk_4c4_padding: Default::default(),
+                        unk_50c: 0x0,      // fixed
+                        unk_510: U64(0x0), // fixed
+                        unk_518: U64(0x0), // fixed
+                        unk_520: U64(0x0), // fixed
+                    }),
+                    encoder_params <- try_init!(fw::job::raw::EncoderParams {
+                        unk_8: 0x0,     // fixed
+                        sync_grow: 0x0, // fixed
+                        unk_10: 0x0,    // fixed
+                        encoder_id: cmdbuf.encoder_id,
+                        unk_18: 0x0, // fixed
+                        unk_mask: 0xffffffff,
+                        sampler_array: U64(0),
+                        sampler_count: 0,
+                        sampler_max: 0,
+                    }),
+                    unk_55c: 0,
+                    unk_560: 0,
+                    sync_grow: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
+                        != 0) as u32,
+                    unk_568: 0,
+                    unk_56c: 0,
+                    meta <- try_init!(fw::job::raw::JobMeta {
+                        unk_0: 0,
+                        unk_2: 0,
+                        no_preemption: 0,
+                        stamp: ev_vtx.stamp_pointer,
+                        fw_stamp: ev_vtx.fw_stamp_pointer,
+                        stamp_value: ev_vtx.value.next(),
+                        stamp_slot: ev_vtx.slot,
+                        evctl_index: 0, // fixed
+                        flush_stamps: flush_stamps as u32,
+                        uuid: uuid_ta,
+                        event_seq: ev_vtx.event_seq as u32,
+                    }),
+                    unk_after_meta: unk1.into(),
+                    unk_buf_0: U64(0),
+                    unk_buf_8: U64(0),
+                    unk_buf_10: U64(0),
+                    cur_ts: U64(0),
+                    start_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), vtx.start)),
+                    end_ts: Some(inner_ptr!(inner.timestamps.gpu_pointer(), vtx.end)),
+                    unk_5c4: 0,
+                    unk_5c8: 0,
+                    unk_5cc: 0,
+                    unk_5d0: 0,
+                    client_sequence: slot_client_seq,
+                    pad_5d5: Default::default(),
+                    unk_5d8: 0,
+                    unk_5dc: 0,
+                    #[ver(V >= V13_0B4)]
+                    unk_ts: U64(0),
+                    #[ver(V >= V13_0B4)]
+                    unk_5dd_8: Default::default(),
+                })
+            },
+        )?;
+
+        core::mem::drop(alloc);
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Add Vertex\n", id);
+        fence.add_command();
+        vtx_job.add_cb(vtx, vm_bind.slot(), move |cmd, error| {
+            if let Some(err) = error {
+                fence.set_error(err.into())
+            }
+            if let Some(mut res) = vtx_result.as_ref().map(|a| a.lock()) {
+                cmd.timestamps.with(|raw, _inner| {
+                    res.result.vertex_ts_start = raw.vtx.start.load(Ordering::Relaxed);
+                    res.result.vertex_ts_end = raw.vtx.end.load(Ordering::Relaxed);
+                });
+                res.result.tvb_usage_bytes = cmd.scene.used_bytes() as u64;
+                if cmd.scene.overflowed() {
+                    res.result.flags |= uapi::DRM_ASAHI_RESULT_RENDER_TVB_OVERFLOWED as u64;
+                }
+                res.vtx_error = error;
+                res.vtx_complete = true;
+                res.commit();
+            }
+            fence.command_complete();
+        })?;
+
+        mod_dev_dbg!(self.dev, "[Submission {}] Increment counters\n", id);
+        self.notifier.threshold.with(|raw, _inner| {
+            raw.increment();
+            raw.increment();
+        });
+
+        // TODO: handle rollbacks, move to job submit?
+        buffer.increment();
+
+        job.get_vtx()?.next_seq();
+        job.get_frag()?.next_seq();
+
+        Ok(())
+    }
+}
diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs
new file mode 100644
index 00000000000000..5a92e026c2a53e
--- /dev/null
+++ b/drivers/gpu/drm/asahi/regs.rs
@@ -0,0 +1,475 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU MMIO register abstraction
+//!
+//! Since the vast majority of the interactions with the GPU are brokered through the firmware,
+//! there is very little need to interact directly with GPU MMIO register. This module abstracts
+//! the few operations that require that, mainly reading the MMU fault status, reading GPU ID
+//! information, and starting the GPU firmware coprocessor.
+
+use crate::hw;
+use kernel::{
+    alloc::{flags::*, vec_ext::VecExt},
+    device,
+    io_mem::IoMem,
+    platform,
+    prelude::*,
+    types::ARef,
+};
+
+/// Size of the ASC control MMIO region.
+pub(crate) const ASC_CTL_SIZE: usize = 0x4000;
+
+/// Size of the SGX MMIO region.
+pub(crate) const SGX_SIZE: usize = 0x1000000;
+
+const CPU_CONTROL: usize = 0x44;
+const CPU_RUN: u32 = 0x1 << 4; // BIT(4)
+
+const FAULT_INFO: usize = 0x17030;
+
+const ID_VERSION: usize = 0xd04000;
+const ID_UNK08: usize = 0xd04008;
+const ID_COUNTS_1: usize = 0xd04010;
+const ID_COUNTS_2: usize = 0xd04014;
+const ID_UNK18: usize = 0xd04018;
+const ID_CLUSTERS: usize = 0xd0401c;
+
+const CORE_MASK_0: usize = 0xd01500;
+const CORE_MASK_1: usize = 0xd01514;
+
+const CORE_MASKS_G14X: usize = 0xe01500;
+const FAULT_INFO_G14X: usize = 0xd8c0;
+const FAULT_ADDR_G14X: usize = 0xd8c8;
+
+/// Enum representing the unit that caused an MMU fault.
+#[allow(non_camel_case_types)]
+#[allow(clippy::upper_case_acronyms)]
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum FaultUnit {
+    /// Decompress / pixel fetch
+    DCMP(u8),
+    /// USC L1 Cache (device loads/stores)
+    UL1C(u8),
+    /// Compress / pixel store
+    CMP(u8),
+    GSL1(u8),
+    IAP(u8),
+    VCE(u8),
+    /// Tiling Engine
+    TE(u8),
+    RAS(u8),
+    /// Vertex Data Master
+    VDM(u8),
+    PPP(u8),
+    /// ISP Parameter Fetch
+    IPF(u8),
+    IPF_CPF(u8),
+    VF(u8),
+    VF_CPF(u8),
+    /// Depth/Stencil load/store
+    ZLS(u8),
+
+    /// Parameter Management
+    dPM,
+    /// Compute Data Master
+    dCDM_KS(u8),
+    dIPP,
+    dIPP_CS,
+    // Vertex Data Master
+    dVDM_CSD,
+    dVDM_SSD,
+    dVDM_ILF,
+    dVDM_ILD,
+    dRDE(u8),
+    FC,
+    GSL2,
+
+    /// Graphics L2 Cache Control?
+    GL2CC_META(u8),
+    GL2CC_MB,
+
+    /// Parameter Management
+    gPM_SP(u8),
+    /// Vertex Data Master - CSD
+    gVDM_CSD_SP(u8),
+    gVDM_SSD_SP(u8),
+    gVDM_ILF_SP(u8),
+    gVDM_TFP_SP(u8),
+    gVDM_MMB_SP(u8),
+    /// Compute Data Master
+    gCDM_CS_KS0_SP(u8),
+    gCDM_CS_KS1_SP(u8),
+    gCDM_CS_KS2_SP(u8),
+    gCDM_KS0_SP(u8),
+    gCDM_KS1_SP(u8),
+    gCDM_KS2_SP(u8),
+    gIPP_SP(u8),
+    gIPP_CS_SP(u8),
+    gRDE0_SP(u8),
+    gRDE1_SP(u8),
+
+    gCDM_CS,
+    gCDM_ID,
+    gCDM_CSR,
+    gCDM_CSW,
+    gCDM_CTXR,
+    gCDM_CTXW,
+    gIPP,
+    gIPP_CS,
+    gKSM_RCE,
+
+    Unknown(u8),
+}
+
+/// Reason for an MMU fault.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) enum FaultReason {
+    Unmapped,
+    AfFault,
+    WriteOnly,
+    ReadOnly,
+    NoAccess,
+    Unknown(u8),
+}
+
+/// Collection of information about an MMU fault.
+#[derive(Copy, Clone, Debug, Eq, PartialEq)]
+pub(crate) struct FaultInfo {
+    pub(crate) address: u64,
+    pub(crate) sideband: u8,
+    pub(crate) vm_slot: u32,
+    pub(crate) unit_code: u8,
+    pub(crate) unit: FaultUnit,
+    pub(crate) level: u8,
+    pub(crate) unk_5: u8,
+    pub(crate) read: bool,
+    pub(crate) reason: FaultReason,
+}
+
+/// Device resources for this GPU instance.
+pub(crate) struct Resources {
+    dev: ARef<device::Device>,
+    asc: IoMem<ASC_CTL_SIZE>,
+    sgx: IoMem<SGX_SIZE>,
+}
+
+impl Resources {
+    /// Map the required resources given our platform device.
+    pub(crate) fn new(pdev: &mut platform::Device) -> Result<Resources> {
+        // TODO: add device abstraction to ioremap by name
+        let asc_res = unsafe { pdev.ioremap_resource(0)? };
+        let sgx_res = unsafe { pdev.ioremap_resource(1)? };
+
+        Ok(Resources {
+            // SAFETY: This device does DMA via the UAT IOMMU.
+            dev: device::Device::from_dev(pdev),
+            asc: asc_res,
+            sgx: sgx_res,
+        })
+    }
+
+    fn sgx_read32(&self, off: usize) -> u32 {
+        self.sgx.readl_relaxed(off)
+    }
+
+    /* Not yet used
+    fn sgx_write32(&self, off: usize, val: u32) {
+        self.sgx.writel_relaxed(val, off)
+    }
+    */
+
+    fn sgx_read64(&self, off: usize) -> u64 {
+        self.sgx.readq_relaxed(off)
+    }
+
+    /* Not yet used
+    fn sgx_write64(&self, off: usize, val: u64) {
+        self.sgx.writeq_relaxed(val, off)
+    }
+    */
+
+    /// Initialize the MMIO registers for the GPU.
+    pub(crate) fn init_mmio(&self) -> Result {
+        // Nothing to do for now...
+
+        Ok(())
+    }
+
+    /// Start the ASC coprocessor CPU.
+    pub(crate) fn start_cpu(&self) -> Result {
+        let val = self.asc.readl_relaxed(CPU_CONTROL);
+
+        self.asc.writel_relaxed(val | CPU_RUN, CPU_CONTROL);
+
+        Ok(())
+    }
+
+    /// Get the GPU identification info from registers.
+    ///
+    /// See [`hw::GpuIdConfig`] for the result.
+    pub(crate) fn get_gpu_id(&self) -> Result<hw::GpuIdConfig> {
+        let id_version = self.sgx_read32(ID_VERSION);
+        let id_unk08 = self.sgx_read32(ID_UNK08);
+        let id_counts_1 = self.sgx_read32(ID_COUNTS_1);
+        let id_counts_2 = self.sgx_read32(ID_COUNTS_2);
+        let id_unk18 = self.sgx_read32(ID_UNK18);
+        let id_clusters = self.sgx_read32(ID_CLUSTERS);
+
+        dev_info!(
+            self.dev,
+            "GPU ID registers: {:#x} {:#x} {:#x} {:#x} {:#x} {:#x}\n",
+            id_version,
+            id_unk08,
+            id_counts_1,
+            id_counts_2,
+            id_unk18,
+            id_clusters
+        );
+
+        let gpu_gen = (id_version >> 24) & 0xff;
+
+        let mut core_mask_regs = Vec::new();
+
+        let num_clusters = match gpu_gen {
+            4 | 5 => {
+                // G13 | G14G
+                core_mask_regs.push(self.sgx_read32(CORE_MASK_0), GFP_KERNEL)?;
+                core_mask_regs.push(self.sgx_read32(CORE_MASK_1), GFP_KERNEL)?;
+                (id_clusters >> 12) & 0xff
+            }
+            6 => {
+                // G14X
+                core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X), GFP_KERNEL)?;
+                core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X + 4), GFP_KERNEL)?;
+                core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X + 8), GFP_KERNEL)?;
+                (id_counts_1 >> 8) & 0xff
+            }
+            a => {
+                dev_err!(self.dev, "Unknown GPU generation {}\n", a);
+                return Err(ENODEV);
+            }
+        };
+
+        let mut core_masks_packed = Vec::new();
+        core_masks_packed.extend_from_slice(&core_mask_regs, GFP_KERNEL)?;
+
+        dev_info!(self.dev, "Core masks: {:#x?}\n", core_masks_packed);
+
+        let num_cores = id_counts_1 & 0xff;
+
+        if num_cores > 32 {
+            dev_err!(
+                self.dev,
+                "Too many cores per cluster ({} > 32)\n",
+                num_cores
+            );
+            return Err(ENODEV);
+        }
+
+        if num_cores * num_clusters > (core_mask_regs.len() * 32) as u32 {
+            dev_err!(
+                self.dev,
+                "Too many total cores ({} x {} > {})\n",
+                num_clusters,
+                num_cores,
+                core_mask_regs.len() * 32
+            );
+            return Err(ENODEV);
+        }
+
+        let mut core_masks = Vec::new();
+        let mut total_active_cores: u32 = 0;
+
+        let max_core_mask = ((1u64 << num_cores) - 1) as u32;
+        for _ in 0..num_clusters {
+            let mask = core_mask_regs[0] & max_core_mask;
+            core_masks.push(mask, GFP_KERNEL)?;
+            for i in 0..core_mask_regs.len() {
+                core_mask_regs[i] >>= num_cores;
+                if i < (core_mask_regs.len() - 1) {
+                    core_mask_regs[i] |= core_mask_regs[i + 1] << (32 - num_cores);
+                }
+            }
+            total_active_cores += mask.count_ones();
+        }
+
+        if core_mask_regs.iter().any(|a| *a != 0) {
+            dev_err!(self.dev, "Leftover core mask: {:#x?}\n", core_mask_regs);
+            return Err(EIO);
+        }
+
+        let (gpu_rev, gpu_rev_id) = match (id_version >> 8) & 0xff {
+            0x00 => (hw::GpuRevision::A0, hw::GpuRevisionID::A0),
+            0x01 => (hw::GpuRevision::A1, hw::GpuRevisionID::A1),
+            0x10 => (hw::GpuRevision::B0, hw::GpuRevisionID::B0),
+            0x11 => (hw::GpuRevision::B1, hw::GpuRevisionID::B1),
+            0x20 => (hw::GpuRevision::C0, hw::GpuRevisionID::C0),
+            0x21 => (hw::GpuRevision::C1, hw::GpuRevisionID::C1),
+            a => {
+                dev_err!(self.dev, "Unknown GPU revision {}\n", a);
+                return Err(ENODEV);
+            }
+        };
+
+        Ok(hw::GpuIdConfig {
+            gpu_gen: match (id_version >> 24) & 0xff {
+                4 => hw::GpuGen::G13,
+                5 => hw::GpuGen::G14,
+                6 => hw::GpuGen::G14, // G14X has a separate ID
+                a => {
+                    dev_err!(self.dev, "Unknown GPU generation {}\n", a);
+                    return Err(ENODEV);
+                }
+            },
+            gpu_variant: match (id_version >> 16) & 0xff {
+                1 => hw::GpuVariant::P, // Guess
+                2 => hw::GpuVariant::G,
+                3 => hw::GpuVariant::S,
+                4 => {
+                    if num_clusters > 4 {
+                        hw::GpuVariant::D
+                    } else {
+                        hw::GpuVariant::C
+                    }
+                }
+                a => {
+                    dev_err!(self.dev, "Unknown GPU variant {}\n", a);
+                    return Err(ENODEV);
+                }
+            },
+            gpu_rev,
+            gpu_rev_id,
+            max_dies: (id_clusters >> 20) & 0xf,
+            num_clusters,
+            num_cores,
+            num_frags: num_cores, // Used to be id_counts_1[15:8] but does not work for G14X
+            num_gps: (id_counts_2 >> 16) & 0xff,
+            total_active_cores,
+            core_masks,
+            core_masks_packed,
+        })
+    }
+
+    /// Get the fault information from the MMU status register, if one occurred.
+    pub(crate) fn get_fault_info(&self, cfg: &'static hw::HwConfig) -> Option<FaultInfo> {
+        let g14x = cfg.gpu_core as u32 >= hw::GpuCore::G14S as u32;
+
+        let fault_info = if g14x {
+            self.sgx_read64(FAULT_INFO_G14X)
+        } else {
+            self.sgx_read64(FAULT_INFO)
+        };
+
+        if fault_info & 1 == 0 {
+            return None;
+        }
+
+        let fault_addr = if g14x {
+            self.sgx_read64(FAULT_ADDR_G14X)
+        } else {
+            fault_info >> 30
+        };
+
+        let unit_code = ((fault_info >> 9) & 0xff) as u8;
+        let unit = match unit_code {
+            0x00..=0x9f => match unit_code & 0xf {
+                0x0 => FaultUnit::DCMP(unit_code >> 4),
+                0x1 => FaultUnit::UL1C(unit_code >> 4),
+                0x2 => FaultUnit::CMP(unit_code >> 4),
+                0x3 => FaultUnit::GSL1(unit_code >> 4),
+                0x4 => FaultUnit::IAP(unit_code >> 4),
+                0x5 => FaultUnit::VCE(unit_code >> 4),
+                0x6 => FaultUnit::TE(unit_code >> 4),
+                0x7 => FaultUnit::RAS(unit_code >> 4),
+                0x8 => FaultUnit::VDM(unit_code >> 4),
+                0x9 => FaultUnit::PPP(unit_code >> 4),
+                0xa => FaultUnit::IPF(unit_code >> 4),
+                0xb => FaultUnit::IPF_CPF(unit_code >> 4),
+                0xc => FaultUnit::VF(unit_code >> 4),
+                0xd => FaultUnit::VF_CPF(unit_code >> 4),
+                0xe => FaultUnit::ZLS(unit_code >> 4),
+                _ => FaultUnit::Unknown(unit_code),
+            },
+            0xa1 => FaultUnit::dPM,
+            0xa2 => FaultUnit::dCDM_KS(0),
+            0xa3 => FaultUnit::dCDM_KS(1),
+            0xa4 => FaultUnit::dCDM_KS(2),
+            0xa5 => FaultUnit::dIPP,
+            0xa6 => FaultUnit::dIPP_CS,
+            0xa7 => FaultUnit::dVDM_CSD,
+            0xa8 => FaultUnit::dVDM_SSD,
+            0xa9 => FaultUnit::dVDM_ILF,
+            0xaa => FaultUnit::dVDM_ILD,
+            0xab => FaultUnit::dRDE(0),
+            0xac => FaultUnit::dRDE(1),
+            0xad => FaultUnit::FC,
+            0xae => FaultUnit::GSL2,
+            0xb0..=0xb7 => FaultUnit::GL2CC_META(unit_code & 0xf),
+            0xb8 => FaultUnit::GL2CC_MB,
+            0xd0..=0xdf if g14x => match unit_code & 0xf {
+                0x0 => FaultUnit::gCDM_CS,
+                0x1 => FaultUnit::gCDM_ID,
+                0x2 => FaultUnit::gCDM_CSR,
+                0x3 => FaultUnit::gCDM_CSW,
+                0x4 => FaultUnit::gCDM_CTXR,
+                0x5 => FaultUnit::gCDM_CTXW,
+                0x6 => FaultUnit::gIPP,
+                0x7 => FaultUnit::gIPP_CS,
+                0x8 => FaultUnit::gKSM_RCE,
+                _ => FaultUnit::Unknown(unit_code),
+            },
+            0xe0..=0xff if g14x => match unit_code & 0xf {
+                0x0 => FaultUnit::gPM_SP((unit_code >> 4) & 1),
+                0x1 => FaultUnit::gVDM_CSD_SP((unit_code >> 4) & 1),
+                0x2 => FaultUnit::gVDM_SSD_SP((unit_code >> 4) & 1),
+                0x3 => FaultUnit::gVDM_ILF_SP((unit_code >> 4) & 1),
+                0x4 => FaultUnit::gVDM_TFP_SP((unit_code >> 4) & 1),
+                0x5 => FaultUnit::gVDM_MMB_SP((unit_code >> 4) & 1),
+                0x6 => FaultUnit::gRDE0_SP((unit_code >> 4) & 1),
+                _ => FaultUnit::Unknown(unit_code),
+            },
+            0xe0..=0xff if !g14x => match unit_code & 0xf {
+                0x0 => FaultUnit::gPM_SP((unit_code >> 4) & 1),
+                0x1 => FaultUnit::gVDM_CSD_SP((unit_code >> 4) & 1),
+                0x2 => FaultUnit::gVDM_SSD_SP((unit_code >> 4) & 1),
+                0x3 => FaultUnit::gVDM_ILF_SP((unit_code >> 4) & 1),
+                0x4 => FaultUnit::gVDM_TFP_SP((unit_code >> 4) & 1),
+                0x5 => FaultUnit::gVDM_MMB_SP((unit_code >> 4) & 1),
+                0x6 => FaultUnit::gCDM_CS_KS0_SP((unit_code >> 4) & 1),
+                0x7 => FaultUnit::gCDM_CS_KS1_SP((unit_code >> 4) & 1),
+                0x8 => FaultUnit::gCDM_CS_KS2_SP((unit_code >> 4) & 1),
+                0x9 => FaultUnit::gCDM_KS0_SP((unit_code >> 4) & 1),
+                0xa => FaultUnit::gCDM_KS1_SP((unit_code >> 4) & 1),
+                0xb => FaultUnit::gCDM_KS2_SP((unit_code >> 4) & 1),
+                0xc => FaultUnit::gIPP_SP((unit_code >> 4) & 1),
+                0xd => FaultUnit::gIPP_CS_SP((unit_code >> 4) & 1),
+                0xe => FaultUnit::gRDE0_SP((unit_code >> 4) & 1),
+                0xf => FaultUnit::gRDE1_SP((unit_code >> 4) & 1),
+                _ => FaultUnit::Unknown(unit_code),
+            },
+            _ => FaultUnit::Unknown(unit_code),
+        };
+
+        let reason = match (fault_info >> 1) & 0x7 {
+            0 => FaultReason::Unmapped,
+            1 => FaultReason::AfFault,
+            2 => FaultReason::WriteOnly,
+            3 => FaultReason::ReadOnly,
+            4 => FaultReason::NoAccess,
+            a => FaultReason::Unknown(a as u8),
+        };
+
+        Some(FaultInfo {
+            address: fault_addr << 6,
+            sideband: ((fault_info >> 23) & 0x7f) as u8,
+            vm_slot: ((fault_info >> 17) & 0x3f) as u32,
+            unit_code,
+            unit,
+            level: ((fault_info >> 7) & 3) as u8,
+            unk_5: ((fault_info >> 5) & 3) as u8,
+            read: (fault_info & (1 << 4)) != 0,
+            reason,
+        })
+    }
+}
diff --git a/drivers/gpu/drm/asahi/slotalloc.rs b/drivers/gpu/drm/asahi/slotalloc.rs
new file mode 100644
index 00000000000000..4cbbda073584a7
--- /dev/null
+++ b/drivers/gpu/drm/asahi/slotalloc.rs
@@ -0,0 +1,295 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Generic slot allocator
+//!
+//! This is a simple allocator to manage fixed-size pools of GPU resources that are transiently
+//! required during command execution. Each item resides in a "slot" at a given index. Users borrow
+//! and return free items from the available pool.
+//!
+//! Allocations are "sticky", and return a token that callers can use to request the same slot
+//! again later. This allows slots to be lazily invalidated, so that multiple uses by the same user
+//! avoid any actual cleanup work.
+//!
+//! The allocation policy is currently a simple LRU mechanism, doing a full linear scan over the
+//! slots when no token was previously provided. This is probably good enough, since in the absence
+//! of serious system contention most allocation requests will be immediately fulfilled from the
+//! previous slot without doing an LRU scan.
+
+use core::ops::{Deref, DerefMut};
+use kernel::{
+    alloc::{flags::*, vec_ext::VecExt},
+    error::{code::*, Result},
+    prelude::*,
+    str::CStr,
+    sync::{Arc, CondVar, LockClassKey, Mutex},
+};
+
+/// Trait representing a single item within a slot.
+pub(crate) trait SlotItem {
+    /// Arbitrary user data associated with the SlotAllocator.
+    type Data;
+
+    /// Called eagerly when this item is released back into the available pool.
+    fn release(&mut self, _data: &mut Self::Data, _slot: u32) {}
+}
+
+/// Trivial implementation for users which do not require any slot data nor any allocator data.
+impl SlotItem for () {
+    type Data = ();
+}
+
+/// Represents a current or previous allocation of an item from a slot. Users keep `SlotToken`s
+/// around across allocations to request that, if possible, the same slot be reused.
+#[derive(Copy, Clone, Debug)]
+pub(crate) struct SlotToken {
+    time: u64,
+    slot: u32,
+}
+
+impl SlotToken {
+    /// Returns the slot index that this token represents a past assignment to.
+    pub(crate) fn last_slot(&self) -> u32 {
+        self.slot
+    }
+}
+
+/// A guard representing active ownership of a slot.
+pub(crate) struct Guard<T: SlotItem> {
+    item: Option<T>,
+    changed: bool,
+    token: SlotToken,
+    alloc: Arc<SlotAllocatorOuter<T>>,
+}
+
+impl<T: SlotItem> Guard<T> {
+    /// Returns the active slot owned by this `Guard`.
+    pub(crate) fn slot(&self) -> u32 {
+        self.token.slot
+    }
+
+    /// Returns `true` if the slot changed since the last allocation (or no `SlotToken` was
+    /// provided), or `false` if the previously allocated slot was successfully re-acquired with
+    /// no other users in the interim.
+    pub(crate) fn changed(&self) -> bool {
+        self.changed
+    }
+
+    /// Returns a `SlotToken` that can be used to re-request the same slot at a later time, after
+    /// this `Guard` is dropped.
+    pub(crate) fn token(&self) -> SlotToken {
+        self.token
+    }
+}
+
+impl<T: SlotItem> Deref for Guard<T> {
+    type Target = T;
+
+    fn deref(&self) -> &Self::Target {
+        self.item.as_ref().expect("SlotItem Guard lost our item!")
+    }
+}
+
+impl<T: SlotItem> DerefMut for Guard<T> {
+    fn deref_mut(&mut self) -> &mut Self::Target {
+        self.item.as_mut().expect("SlotItem Guard lost our item!")
+    }
+}
+
+/// A slot item that is currently free.
+struct Entry<T: SlotItem> {
+    item: T,
+    get_time: u64,
+    drop_time: u64,
+}
+
+/// Inner data for the `SlotAllocator`, protected by a `Mutex`.
+struct SlotAllocatorInner<T: SlotItem> {
+    data: T::Data,
+    slots: Vec<Option<Entry<T>>>,
+    get_count: u64,
+    drop_count: u64,
+}
+
+/// A single slot allocator instance.
+#[pin_data]
+struct SlotAllocatorOuter<T: SlotItem> {
+    #[pin]
+    inner: Mutex<SlotAllocatorInner<T>>,
+    #[pin]
+    cond: CondVar,
+}
+
+/// A shared reference to a slot allocator instance.
+pub(crate) struct SlotAllocator<T: SlotItem>(Arc<SlotAllocatorOuter<T>>);
+
+impl<T: SlotItem> SlotAllocator<T> {
+    /// Creates a new `SlotAllocator`, with a fixed number of slots and arbitrary associated data.
+    ///
+    /// The caller provides a constructor callback which takes a reference to the `T::Data` and
+    /// creates a single slot. This is called during construction to create all the initial
+    /// items, which then live the lifetime of the `SlotAllocator`.
+    pub(crate) fn new(
+        num_slots: u32,
+        mut data: T::Data,
+        mut constructor: impl FnMut(&mut T::Data, u32) -> T,
+        name: &'static CStr,
+        lock_key1: LockClassKey,
+        lock_key2: LockClassKey,
+    ) -> Result<SlotAllocator<T>> {
+        let mut slots = Vec::with_capacity(num_slots as usize, GFP_KERNEL)?;
+
+        for i in 0..num_slots {
+            slots
+                .try_push(Some(Entry {
+                    item: constructor(&mut data, i),
+                    get_time: 0,
+                    drop_time: 0,
+                }))
+                .expect("try_push() failed after reservation");
+        }
+
+        let inner = SlotAllocatorInner {
+            data,
+            slots,
+            get_count: 0,
+            drop_count: 0,
+        };
+
+        let alloc = Arc::pin_init(
+            pin_init!(SlotAllocatorOuter {
+                // SAFETY: `mutex_init!` is called below.
+                inner <- Mutex::new_with_key(inner, name, lock_key1),
+                // SAFETY: `condvar_init!` is called below.
+                cond <- CondVar::new(name, lock_key2),
+            }),
+            GFP_KERNEL,
+        )?;
+
+        Ok(SlotAllocator(alloc))
+    }
+
+    /// Calls a callback on the inner data associated with this allocator, taking the lock.
+    pub(crate) fn with_inner<RetVal>(&self, cb: impl FnOnce(&mut T::Data) -> RetVal) -> RetVal {
+        let mut inner = self.0.inner.lock();
+        cb(&mut inner.data)
+    }
+
+    /// Gets a fresh slot, optionally reusing a previous allocation if a `SlotToken` is provided.
+    ///
+    /// Blocks if no slots are free.
+    pub(crate) fn get(&self, token: Option<SlotToken>) -> Result<Guard<T>> {
+        self.get_inner(token, |_a, _b| Ok(()))
+    }
+
+    /// Gets a fresh slot, optionally reusing a previous allocation if a `SlotToken` is provided.
+    ///
+    /// Blocks if no slots are free.
+    ///
+    /// This version allows the caller to pass in a callback that gets a mutable reference to the
+    /// user data for the allocator and the freshly acquired slot, which is called before the
+    /// allocator lock is released. This can be used to perform bookkeeping associated with
+    /// specific slots (such as tracking their current owner).
+    pub(crate) fn get_inner(
+        &self,
+        token: Option<SlotToken>,
+        cb: impl FnOnce(&mut T::Data, &mut Guard<T>) -> Result<()>,
+    ) -> Result<Guard<T>> {
+        let mut inner = self.0.inner.lock();
+
+        if let Some(token) = token {
+            let slot = &mut inner.slots[token.slot as usize];
+            if slot.is_some() {
+                let count = slot.as_ref().unwrap().get_time;
+                if count == token.time {
+                    let mut guard = Guard {
+                        item: Some(slot.take().unwrap().item),
+                        token,
+                        changed: false,
+                        alloc: self.0.clone(),
+                    };
+                    cb(&mut inner.data, &mut guard)?;
+                    return Ok(guard);
+                }
+            }
+        }
+
+        let mut first = true;
+        let slot = loop {
+            let mut oldest_time = u64::MAX;
+            let mut oldest_slot = 0u32;
+
+            for (i, slot) in inner.slots.iter().enumerate() {
+                if let Some(slot) = slot.as_ref() {
+                    if slot.drop_time < oldest_time {
+                        oldest_slot = i as u32;
+                        oldest_time = slot.drop_time;
+                    }
+                }
+            }
+
+            if oldest_time == u64::MAX {
+                if first {
+                    pr_warn!(
+                        "{}: out of slots, blocking\n",
+                        core::any::type_name::<Self>()
+                    );
+                }
+                first = false;
+                if self.0.cond.wait_interruptible(&mut inner) {
+                    return Err(ERESTARTSYS);
+                }
+            } else {
+                break oldest_slot;
+            }
+        };
+
+        inner.get_count += 1;
+
+        let item = inner.slots[slot as usize]
+            .take()
+            .expect("Someone stole our slot?")
+            .item;
+
+        let mut guard = Guard {
+            item: Some(item),
+            changed: true,
+            token: SlotToken {
+                time: inner.get_count,
+                slot,
+            },
+            alloc: self.0.clone(),
+        };
+
+        cb(&mut inner.data, &mut guard)?;
+        Ok(guard)
+    }
+}
+
+impl<T: SlotItem> Clone for SlotAllocator<T> {
+    fn clone(&self) -> Self {
+        SlotAllocator(self.0.clone())
+    }
+}
+
+impl<T: SlotItem> Drop for Guard<T> {
+    fn drop(&mut self) {
+        let mut inner = self.alloc.inner.lock();
+        if inner.slots[self.token.slot as usize].is_some() {
+            pr_crit!(
+                "{}: tried to return an item into a full slot ({})\n",
+                core::any::type_name::<Self>(),
+                self.token.slot
+            );
+        } else {
+            inner.drop_count += 1;
+            let mut item = self.item.take().expect("Guard lost its item");
+            item.release(&mut inner.data, self.token.slot);
+            inner.slots[self.token.slot as usize] = Some(Entry {
+                item,
+                get_time: self.token.time,
+                drop_time: inner.drop_count,
+            });
+            self.alloc.cond.notify_one();
+        }
+    }
+}
diff --git a/drivers/gpu/drm/asahi/util.rs b/drivers/gpu/drm/asahi/util.rs
new file mode 100644
index 00000000000000..8d1a37f17cd887
--- /dev/null
+++ b/drivers/gpu/drm/asahi/util.rs
@@ -0,0 +1,44 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! Miscellaneous utility functions
+
+use core::ops::{Add, BitAnd, Div, Not, Sub};
+
+/// Aligns an integer type to a power of two.
+pub(crate) fn align<T>(a: T, b: T) -> T
+where
+    T: Copy
+        + Default
+        + BitAnd<Output = T>
+        + Not<Output = T>
+        + Add<Output = T>
+        + Sub<Output = T>
+        + Div<Output = T>
+        + core::cmp::PartialEq,
+{
+    let def: T = Default::default();
+    #[allow(clippy::eq_op)]
+    let one: T = !def / !def;
+
+    assert!((b & (b - one)) == def);
+
+    (a + b - one) & !(b - one)
+}
+
+/// Integer division rounding up.
+pub(crate) fn div_ceil<T>(a: T, b: T) -> T
+where
+    T: Copy
+        + Default
+        + BitAnd<Output = T>
+        + Not<Output = T>
+        + Add<Output = T>
+        + Sub<Output = T>
+        + Div<Output = T>,
+{
+    let def: T = Default::default();
+    #[allow(clippy::eq_op)]
+    let one: T = !def / !def;
+
+    (a + b - one) / b
+}
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
new file mode 100644
index 00000000000000..453350cc958665
--- /dev/null
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -0,0 +1,905 @@
+// SPDX-License-Identifier: GPL-2.0-only OR MIT
+
+//! GPU command execution queues
+//!
+//! The AGX GPU firmware schedules GPU work commands out of work queues, which are ring buffers of
+//! pointers to work commands. There can be an arbitrary number of work queues. Work queues have an
+//! associated type (vertex, fragment, or compute) and may only contain generic commands or commands
+//! specific to that type.
+//!
+//! This module manages queueing work commands into a work queue and submitting them for execution
+//! by the firmware. An active work queue needs an event to signal completion of its work, which is
+//! owned by what we call a batch. This event then notifies the work queue when work is completed,
+//! and that triggers freeing of all resources associated with that work. An idle work queue gives
+//! up its associated event.
+
+use crate::debug::*;
+use crate::fw::channels::PipeType;
+use crate::fw::types::*;
+use crate::fw::workqueue::*;
+use crate::no_debug;
+use crate::object::OpaqueGpuObject;
+use crate::regs::FaultReason;
+use crate::{channel, driver, event, fw, gpu, object, regs};
+use core::num::NonZeroU64;
+use core::sync::atomic::Ordering;
+use kernel::{
+    alloc::{box_ext::BoxExt, flags::*, vec_ext::VecExt},
+    c_str, dma_fence,
+    error::code::*,
+    prelude::*,
+    sync::{
+        lock::{mutex::MutexBackend, Guard},
+        Arc, Mutex,
+    },
+    uapi,
+};
+
+const DEBUG_CLASS: DebugFlags = DebugFlags::WorkQueue;
+
+const MAX_JOB_SLOTS: u32 = 127;
+
+/// An enum of possible errors that might cause a piece of work to fail execution.
+#[derive(Copy, Clone, Debug, PartialEq, Eq)]
+pub(crate) enum WorkError {
+    /// GPU timeout (command execution took too long).
+    Timeout,
+    /// GPU MMU fault (invalid access).
+    Fault(regs::FaultInfo),
+    /// Work failed due to an error caused by other concurrent GPU work.
+    Killed,
+    /// The GPU crashed.
+    NoDevice,
+    /// Unknown reason.
+    Unknown,
+}
+
+impl From<WorkError> for uapi::drm_asahi_result_info {
+    fn from(err: WorkError) -> Self {
+        match err {
+            WorkError::Fault(info) => Self {
+                status: uapi::drm_asahi_status_DRM_ASAHI_STATUS_FAULT,
+                fault_type: match info.reason {
+                    FaultReason::Unmapped => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_UNMAPPED,
+                    FaultReason::AfFault => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_AF_FAULT,
+                    FaultReason::WriteOnly => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_WRITE_ONLY,
+                    FaultReason::ReadOnly => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_READ_ONLY,
+                    FaultReason::NoAccess => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_NO_ACCESS,
+                    FaultReason::Unknown(_) => uapi::drm_asahi_fault_DRM_ASAHI_FAULT_UNKNOWN,
+                },
+                unit: info.unit_code.into(),
+                sideband: info.sideband.into(),
+                level: info.level,
+                extra: info.unk_5.into(),
+                is_read: info.read as u8,
+                pad: 0,
+                address: info.address,
+            },
+            a => Self {
+                status: match a {
+                    WorkError::Timeout => uapi::drm_asahi_status_DRM_ASAHI_STATUS_TIMEOUT,
+                    WorkError::Killed => uapi::drm_asahi_status_DRM_ASAHI_STATUS_KILLED,
+                    WorkError::NoDevice => uapi::drm_asahi_status_DRM_ASAHI_STATUS_NO_DEVICE,
+                    _ => uapi::drm_asahi_status_DRM_ASAHI_STATUS_UNKNOWN_ERROR,
+                },
+                ..Default::default()
+            },
+        }
+    }
+}
+
+impl From<WorkError> for kernel::error::Error {
+    fn from(err: WorkError) -> Self {
+        match err {
+            WorkError::Timeout => ETIMEDOUT,
+            // Not EFAULT because that's for userspace faults
+            WorkError::Fault(_) => EIO,
+            WorkError::Unknown => ENODATA,
+            WorkError::Killed => ECANCELED,
+            WorkError::NoDevice => ENODEV,
+        }
+    }
+}
+
+/// A GPU context tracking structure, which must be explicitly invalidated when dropped.
+pub(crate) struct GpuContext {
+    dev: driver::AsahiDevRef,
+    data: Option<Box<GpuObject<fw::workqueue::GpuContextData>>>,
+}
+no_debug!(GpuContext);
+
+impl GpuContext {
+    /// Allocate a new GPU context.
+    pub(crate) fn new(
+        dev: &driver::AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+        buffer: Option<Arc<dyn core::any::Any + Send + Sync>>,
+    ) -> Result<GpuContext> {
+        Ok(GpuContext {
+            dev: dev.into(),
+            data: Some(Box::new(
+                alloc.shared.new_object(
+                    fw::workqueue::GpuContextData { _buffer: buffer },
+                    |_inner| Default::default(),
+                )?,
+                GFP_KERNEL,
+            )?),
+        })
+    }
+
+    /// Returns the GPU pointer to the inner GPU context data structure.
+    pub(crate) fn gpu_pointer(&self) -> GpuPointer<'_, fw::workqueue::GpuContextData> {
+        self.data.as_ref().unwrap().gpu_pointer()
+    }
+}
+
+impl Drop for GpuContext {
+    fn drop(&mut self) {
+        mod_dev_dbg!(self.dev, "GpuContext: Freeing GPU context\n");
+        let dev = self.dev.data();
+        let data = self.data.take().unwrap();
+        dev.gpu.free_context(data);
+    }
+}
+
+struct SubmittedWork<O, C>
+where
+    O: OpaqueGpuObject,
+    C: FnOnce(&mut O, Option<WorkError>) + Send + Sync + 'static,
+{
+    object: O,
+    value: EventValue,
+    error: Option<WorkError>,
+    wptr: u32,
+    vm_slot: u32,
+    callback: Option<C>,
+    fence: dma_fence::Fence,
+}
+
+pub(crate) trait GenSubmittedWork: Send + Sync {
+    fn gpu_va(&self) -> NonZeroU64;
+    fn value(&self) -> event::EventValue;
+    fn wptr(&self) -> u32;
+    fn set_wptr(&mut self, wptr: u32);
+    fn mark_error(&mut self, error: WorkError);
+    fn complete(&mut self);
+    fn get_fence(&self) -> dma_fence::Fence;
+}
+
+impl<O: OpaqueGpuObject, C: FnOnce(&mut O, Option<WorkError>) + Send + Sync> GenSubmittedWork
+    for SubmittedWork<O, C>
+{
+    fn gpu_va(&self) -> NonZeroU64 {
+        self.object.gpu_va()
+    }
+
+    fn value(&self) -> event::EventValue {
+        self.value
+    }
+
+    fn wptr(&self) -> u32 {
+        self.wptr
+    }
+
+    fn set_wptr(&mut self, wptr: u32) {
+        self.wptr = wptr;
+    }
+
+    fn complete(&mut self) {
+        if let Some(cb) = self.callback.take() {
+            cb(&mut self.object, self.error);
+        }
+    }
+
+    fn mark_error(&mut self, error: WorkError) {
+        mod_pr_debug!("WorkQueue: Command at value {:#x?} failed\n", self.value);
+        self.error = Some(match error {
+            WorkError::Fault(info) if info.vm_slot != self.vm_slot => WorkError::Killed,
+            err => err,
+        });
+    }
+
+    fn get_fence(&self) -> dma_fence::Fence {
+        self.fence.clone()
+    }
+}
+
+/// Inner data for managing a single work queue.
+#[versions(AGX)]
+struct WorkQueueInner {
+    dev: driver::AsahiDevRef,
+    event_manager: Arc<event::EventManager>,
+    info: GpuObject<QueueInfo::ver>,
+    new: bool,
+    pipe_type: PipeType,
+    size: u32,
+    wptr: u32,
+    pending: Vec<Box<dyn GenSubmittedWork>>,
+    last_token: Option<event::Token>,
+    pending_jobs: usize,
+    last_submitted: Option<event::EventValue>,
+    last_completed: Option<event::EventValue>,
+    event: Option<(event::Event, event::EventValue)>,
+    priority: u32,
+    commit_seq: u64,
+    submit_seq: u64,
+    event_seq: u64,
+}
+
+/// An instance of a work queue.
+#[versions(AGX)]
+#[pin_data]
+pub(crate) struct WorkQueue {
+    info_pointer: GpuWeakPointer<QueueInfo::ver>,
+    #[pin]
+    inner: Mutex<WorkQueueInner::ver>,
+}
+
+#[versions(AGX)]
+impl WorkQueueInner::ver {
+    /// Return the GPU done pointer, representing how many work items have been completed by the
+    /// GPU.
+    fn doneptr(&self) -> u32 {
+        self.info
+            .state
+            .with(|raw, _inner| raw.gpu_doneptr.load(Ordering::Acquire))
+    }
+}
+
+#[versions(AGX)]
+#[derive(Copy, Clone)]
+pub(crate) struct QueueEventInfo {
+    pub(crate) stamp_pointer: GpuWeakPointer<Stamp>,
+    pub(crate) fw_stamp_pointer: GpuWeakPointer<FwStamp>,
+    pub(crate) slot: u32,
+    pub(crate) value: event::EventValue,
+    pub(crate) cmd_seq: u64,
+    pub(crate) event_seq: u64,
+    pub(crate) info_ptr: GpuWeakPointer<QueueInfo::ver>,
+}
+
+#[versions(AGX)]
+pub(crate) struct Job {
+    wq: Arc<WorkQueue::ver>,
+    event_info: QueueEventInfo::ver,
+    start_value: EventValue,
+    pending: Vec<Box<dyn GenSubmittedWork>>,
+    committed: bool,
+    submitted: bool,
+    event_count: usize,
+    fence: dma_fence::Fence,
+}
+
+#[versions(AGX)]
+pub(crate) struct JobSubmission<'a> {
+    inner: Option<Guard<'a, WorkQueueInner::ver, MutexBackend>>,
+    wptr: u32,
+    event_count: usize,
+    command_count: usize,
+}
+
+#[versions(AGX)]
+impl Job::ver {
+    pub(crate) fn event_info(&self) -> QueueEventInfo::ver {
+        let mut info = self.event_info;
+        info.cmd_seq += self.pending.len() as u64;
+        info.event_seq += self.event_count as u64;
+
+        info
+    }
+
+    pub(crate) fn next_seq(&mut self) {
+        self.event_count += 1;
+        self.event_info.value.increment();
+    }
+
+    pub(crate) fn add<O: object::OpaqueGpuObject + 'static>(
+        &mut self,
+        command: O,
+        vm_slot: u32,
+    ) -> Result {
+        self.add_cb(command, vm_slot, |_, _| {})
+    }
+
+    pub(crate) fn add_cb<O: object::OpaqueGpuObject + 'static>(
+        &mut self,
+        command: O,
+        vm_slot: u32,
+        callback: impl FnOnce(&mut O, Option<WorkError>) + Sync + Send + 'static,
+    ) -> Result {
+        if self.committed {
+            pr_err!("WorkQueue: Tried to mutate committed Job\n");
+            return Err(EINVAL);
+        }
+
+        self.pending.push(
+            Box::new(
+                SubmittedWork::<_, _> {
+                    object: command,
+                    value: self.event_info.value.next(),
+                    error: None,
+                    callback: Some(callback),
+                    wptr: 0,
+                    vm_slot,
+                    fence: self.fence.clone(),
+                },
+                GFP_KERNEL,
+            )?,
+            GFP_KERNEL,
+        )?;
+
+        Ok(())
+    }
+
+    pub(crate) fn commit(&mut self) -> Result {
+        if self.committed {
+            pr_err!("WorkQueue: Tried to commit committed Job\n");
+            return Err(EINVAL);
+        }
+
+        if self.pending.is_empty() {
+            pr_err!("WorkQueue: Job::commit() with no commands\n");
+            return Err(EINVAL);
+        }
+
+        let mut inner = self.wq.inner.lock();
+
+        let ev = inner.event.as_mut().expect("WorkQueue: Job lost its event");
+
+        if ev.1 != self.start_value {
+            pr_err!(
+                "WorkQueue: Job::commit() out of order (event slot {} {:?} != {:?}\n",
+                ev.0.slot(),
+                ev.1,
+                self.start_value
+            );
+            return Err(EINVAL);
+        }
+
+        ev.1 = self.event_info.value;
+        inner.commit_seq += self.pending.len() as u64;
+        inner.event_seq += self.event_count as u64;
+        self.committed = true;
+
+        Ok(())
+    }
+
+    pub(crate) fn can_submit(&self) -> Option<dma_fence::Fence> {
+        let inner = self.wq.inner.lock();
+        if inner.free_slots() > self.event_count && inner.free_space() > self.pending.len() {
+            None
+        } else if let Some(work) = inner.pending.first() {
+            Some(work.get_fence())
+        } else {
+            pr_err!("WorkQueue: Cannot submit, but queue is empty?\n");
+            None
+        }
+    }
+
+    pub(crate) fn submit(&mut self) -> Result<JobSubmission::ver<'_>> {
+        if !self.committed {
+            pr_err!("WorkQueue: Tried to submit uncommitted Job\n");
+            return Err(EINVAL);
+        }
+
+        if self.submitted {
+            pr_err!("WorkQueue: Tried to submit Job twice\n");
+            return Err(EINVAL);
+        }
+
+        if self.pending.is_empty() {
+            pr_err!("WorkQueue: Job::submit() with no commands\n");
+            return Err(EINVAL);
+        }
+
+        let mut inner = self.wq.inner.lock();
+
+        if inner.submit_seq != self.event_info.cmd_seq {
+            pr_err!(
+                "WorkQueue: Job::submit() out of order (submit_seq {} != {})\n",
+                inner.submit_seq,
+                self.event_info.cmd_seq
+            );
+            return Err(EINVAL);
+        }
+
+        if inner.commit_seq < (self.event_info.cmd_seq + self.pending.len() as u64) {
+            pr_err!(
+                "WorkQueue: Job::submit() out of order (commit_seq {} != {})\n",
+                inner.commit_seq,
+                (self.event_info.cmd_seq + self.pending.len() as u64)
+            );
+            return Err(EINVAL);
+        }
+
+        let mut wptr = inner.wptr;
+        let command_count = self.pending.len();
+
+        if inner.free_space() <= command_count {
+            pr_err!("WorkQueue: Job does not fit in ring buffer\n");
+            return Err(EBUSY);
+        }
+
+        inner.pending.reserve(command_count, GFP_KERNEL)?;
+
+        inner.last_submitted = inner.event.as_ref().map(|e| e.1);
+
+        for mut command in self.pending.drain(..) {
+            command.set_wptr(wptr);
+
+            let next_wptr = (wptr + 1) % inner.size;
+            assert!(inner.doneptr() != next_wptr);
+            inner.info.ring[wptr as usize] = command.gpu_va().get();
+            wptr = next_wptr;
+
+            // Cannot fail, since we did a reserve(1) above
+            inner
+                .pending
+                .push(command, GFP_KERNEL)
+                .expect("push() failed after reserve()");
+        }
+
+        self.submitted = true;
+
+        Ok(JobSubmission::ver {
+            inner: Some(inner),
+            wptr,
+            command_count,
+            event_count: self.event_count,
+        })
+    }
+}
+
+#[versions(AGX)]
+impl<'a> JobSubmission::ver<'a> {
+    pub(crate) fn run(mut self, channel: &mut channel::PipeChannel::ver) {
+        let command_count = self.command_count;
+        let mut inner = self.inner.take().expect("No inner?");
+        let wptr = self.wptr;
+        core::mem::forget(self);
+
+        inner
+            .info
+            .state
+            .with(|raw, _inner| raw.cpu_wptr.store(wptr, Ordering::Release));
+
+        inner.wptr = wptr;
+
+        let event = inner.event.as_mut().expect("JobSubmission lost its event");
+
+        let event_slot = event.0.slot();
+
+        let msg = fw::channels::RunWorkQueueMsg::ver {
+            pipe_type: inner.pipe_type,
+            work_queue: Some(inner.info.weak_pointer()),
+            wptr: inner.wptr,
+            event_slot,
+            is_new: inner.new,
+            __pad: Default::default(),
+        };
+        channel.send(&msg);
+        inner.new = false;
+
+        inner.submit_seq += command_count as u64;
+    }
+
+    pub(crate) fn pipe_type(&self) -> PipeType {
+        self.inner.as_ref().expect("No inner?").pipe_type
+    }
+
+    pub(crate) fn priority(&self) -> u32 {
+        self.inner.as_ref().expect("No inner?").priority
+    }
+}
+
+#[versions(AGX)]
+impl Drop for Job::ver {
+    fn drop(&mut self) {
+        mod_pr_debug!("WorkQueue: Dropping Job\n");
+        let mut inner = self.wq.inner.lock();
+
+        if self.committed && !self.submitted {
+            let pipe_type = inner.pipe_type;
+            let event = inner.event.as_mut().expect("Job lost its event");
+            mod_pr_debug!(
+                "WorkQueue({:?}): Roll back {} events (slot {} val {:#x?}) and {} commands\n",
+                pipe_type,
+                self.event_count,
+                event.0.slot(),
+                event.1,
+                self.pending.len()
+            );
+            event.1.sub(self.event_count as u32);
+            inner.commit_seq -= self.pending.len() as u64;
+            inner.event_seq -= self.event_count as u64;
+        }
+
+        inner.pending_jobs -= 1;
+
+        if inner.pending.is_empty() && inner.pending_jobs == 0 {
+            mod_pr_debug!("WorkQueue({:?}): Dropping event\n", inner.pipe_type);
+            inner.event = None;
+            inner.last_submitted = None;
+            inner.last_completed = None;
+        }
+        mod_pr_debug!("WorkQueue({:?}): Dropped Job\n", inner.pipe_type);
+    }
+}
+
+#[versions(AGX)]
+impl<'a> Drop for JobSubmission::ver<'a> {
+    fn drop(&mut self) {
+        let inner = self.inner.as_mut().expect("No inner?");
+        mod_pr_debug!("WorkQueue({:?}): Dropping JobSubmission\n", inner.pipe_type);
+
+        let new_len = inner.pending.len() - self.command_count;
+        inner.pending.truncate(new_len);
+
+        let pipe_type = inner.pipe_type;
+        let event = inner.event.as_mut().expect("JobSubmission lost its event");
+        mod_pr_debug!(
+            "WorkQueue({:?}): Roll back {} events (slot {} val {:#x?}) and {} commands\n",
+            pipe_type,
+            self.event_count,
+            event.0.slot(),
+            event.1,
+            self.command_count
+        );
+        event.1.sub(self.event_count as u32);
+        inner.commit_seq -= self.command_count as u64;
+        inner.event_seq -= self.event_count as u64;
+        mod_pr_debug!("WorkQueue({:?}): Dropped JobSubmission\n", inner.pipe_type);
+    }
+}
+
+#[versions(AGX)]
+impl WorkQueueInner::ver {
+    /// Return the number of free entries in the workqueue
+    pub(crate) fn free_space(&self) -> usize {
+        self.size as usize - self.pending.len() - 1
+    }
+
+    pub(crate) fn free_slots(&self) -> usize {
+        let busy_slots = if let Some(ls) = self.last_submitted {
+            let lc = self
+                .last_completed
+                .expect("last_submitted but not completed?");
+            ls.delta(&lc)
+        } else {
+            0
+        };
+
+        ((MAX_JOB_SLOTS as i32) - busy_slots).max(0) as usize
+    }
+}
+
+#[versions(AGX)]
+impl WorkQueue::ver {
+    /// Create a new WorkQueue of a given type and priority.
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn new(
+        dev: &driver::AsahiDevice,
+        alloc: &mut gpu::KernelAllocators,
+        event_manager: Arc<event::EventManager>,
+        gpu_context: Arc<GpuContext>,
+        notifier_list: Arc<GpuObject<fw::event::NotifierList>>,
+        pipe_type: PipeType,
+        id: u64,
+        priority: u32,
+        size: u32,
+    ) -> Result<Arc<WorkQueue::ver>> {
+        let gpu_buf = alloc.private.array_empty(0x2c18)?;
+        let shared = &mut alloc.shared;
+        let inner = WorkQueueInner::ver {
+            dev: dev.into(),
+            event_manager,
+            info: alloc.private.new_init(
+                try_init!(QueueInfo::ver {
+                    state: {
+                        let mut s = shared.new_default::<RingState>()?;
+                        s.with_mut(|raw, _inner| {
+                            raw.rb_size = size;
+                        });
+                        s
+                    },
+                    ring: shared.array_empty(size as usize)?,
+                    gpu_buf,
+                    notifier_list: notifier_list,
+                    gpu_context: gpu_context,
+                }),
+                |inner, _p| {
+                    try_init!(raw::QueueInfo::ver {
+                        state: inner.state.gpu_pointer(),
+                        ring: inner.ring.gpu_pointer(),
+                        notifier_list: inner.notifier_list.gpu_pointer(),
+                        gpu_buf: inner.gpu_buf.gpu_pointer(),
+                        gpu_rptr1: Default::default(),
+                        gpu_rptr2: Default::default(),
+                        gpu_rptr3: Default::default(),
+                        event_id: AtomicI32::new(-1),
+                        priority: *raw::PRIORITY.get(priority as usize).ok_or(EINVAL)?,
+                        unk_4c: -1,
+                        uuid: id as u32,
+                        unk_54: -1,
+                        unk_58: Default::default(),
+                        busy: Default::default(),
+                        __pad: Default::default(),
+                        unk_84_state: Default::default(),
+                        unk_88: 0,
+                        unk_8c: 0,
+                        unk_90: 0,
+                        unk_94: 0,
+                        pending: Default::default(),
+                        unk_9c: 0,
+                        #[ver(V >= V13_2 && G < G14X)]
+                        unk_a0_0: 0,
+                        gpu_context: inner.gpu_context.gpu_pointer(),
+                        unk_a8: Default::default(),
+                        #[ver(V >= V13_2 && G < G14X)]
+                        unk_b0: 0,
+                    })
+                },
+            )?,
+            new: true,
+            pipe_type,
+            size,
+            wptr: 0,
+            pending: Vec::new(),
+            last_token: None,
+            event: None,
+            priority,
+            pending_jobs: 0,
+            commit_seq: 0,
+            submit_seq: 0,
+            event_seq: 0,
+            last_completed: None,
+            last_submitted: None,
+        };
+
+        let info_pointer = inner.info.weak_pointer();
+
+        let mutex_init = match pipe_type {
+            PipeType::Vertex => Mutex::new_named(inner, c_str!("WorkQueue::inner (Vertex)")),
+            PipeType::Fragment => Mutex::new_named(inner, c_str!("WorkQueue::inner (Fragment)")),
+            PipeType::Compute => Mutex::new_named(inner, c_str!("WorkQueue::inner (Compute)")),
+        };
+
+        Arc::pin_init(pin_init!(Self {
+            info_pointer,
+            inner <- mutex_init,
+        }))
+    }
+
+    pub(crate) fn event_info(&self) -> Option<QueueEventInfo::ver> {
+        let inner = self.inner.lock();
+
+        inner.event.as_ref().map(|ev| QueueEventInfo::ver {
+            stamp_pointer: ev.0.stamp_pointer(),
+            fw_stamp_pointer: ev.0.fw_stamp_pointer(),
+            slot: ev.0.slot(),
+            value: ev.1,
+            cmd_seq: inner.commit_seq,
+            event_seq: inner.event_seq,
+            info_ptr: self.info_pointer,
+        })
+    }
+
+    pub(crate) fn new_job(self: &Arc<Self>, fence: dma_fence::Fence) -> Result<Job::ver> {
+        let mut inner = self.inner.lock();
+
+        if inner.event.is_none() {
+            mod_pr_debug!("WorkQueue({:?}): Grabbing event\n", inner.pipe_type);
+            let event = inner.event_manager.get(inner.last_token, self.clone())?;
+            let cur = event.current();
+            inner.last_token = Some(event.token());
+            mod_pr_debug!(
+                "WorkQueue({:?}): Grabbed event slot {}: {:#x?}\n",
+                inner.pipe_type,
+                event.slot(),
+                cur
+            );
+            inner.event = Some((event, cur));
+            inner.last_submitted = Some(cur);
+            inner.last_completed = Some(cur);
+        }
+
+        inner.pending_jobs += 1;
+
+        let ev = &inner.event.as_ref().unwrap();
+
+        mod_pr_debug!("WorkQueue({:?}): New job\n", inner.pipe_type);
+        Ok(Job::ver {
+            wq: self.clone(),
+            event_info: QueueEventInfo::ver {
+                stamp_pointer: ev.0.stamp_pointer(),
+                fw_stamp_pointer: ev.0.fw_stamp_pointer(),
+                slot: ev.0.slot(),
+                value: ev.1,
+                cmd_seq: inner.commit_seq,
+                event_seq: inner.event_seq,
+                info_ptr: self.info_pointer,
+            },
+            start_value: ev.1,
+            pending: Vec::new(),
+            event_count: 0,
+            committed: false,
+            submitted: false,
+            fence,
+        })
+    }
+
+    pub(crate) fn pipe_type(&self) -> PipeType {
+        self.inner.lock().pipe_type
+    }
+}
+
+/// Trait used to erase the version-specific type of WorkQueues, to avoid leaking
+/// version-specificity into the event module.
+pub(crate) trait WorkQueue {
+    fn signal(&self) -> bool;
+    fn mark_error(&self, value: event::EventValue, error: WorkError);
+    fn fail_all(&self, error: WorkError);
+}
+
+#[versions(AGX)]
+impl WorkQueue for WorkQueue::ver {
+    /// Signal a workqueue that some work was completed.
+    ///
+    /// This will check the event stamp value to find out exactly how many commands were processed.
+    fn signal(&self) -> bool {
+        let mut inner = self.inner.lock();
+        let event = inner.event.as_ref();
+        let value = match event {
+            None => {
+                pr_err!("WorkQueue: signal() called but no event?\n");
+                return true;
+            }
+            Some(event) => event.0.current(),
+        };
+
+        inner.last_completed = Some(value);
+
+        mod_pr_debug!(
+            "WorkQueue({:?}): Signaling event {:?} value {:#x?}\n",
+            inner.pipe_type,
+            inner.last_token,
+            value
+        );
+
+        let mut completed_commands: usize = 0;
+
+        for cmd in inner.pending.iter() {
+            if cmd.value() <= value {
+                mod_pr_debug!(
+                    "WorkQueue({:?}): Command at value {:#x?} complete\n",
+                    inner.pipe_type,
+                    cmd.value()
+                );
+                completed_commands += 1;
+            } else {
+                break;
+            }
+        }
+
+        if completed_commands == 0 {
+            return inner.pending.is_empty();
+        }
+
+        let mut completed = Vec::new();
+
+        if completed.reserve(completed_commands, GFP_KERNEL).is_err() {
+            pr_crit!(
+                "WorkQueue({:?}): Failed to allocate space for {} completed commands\n",
+                inner.pipe_type,
+                completed_commands
+            );
+        }
+
+        let pipe_type = inner.pipe_type;
+
+        for cmd in inner.pending.drain(..completed_commands) {
+            if completed.push(cmd, GFP_KERNEL).is_err() {
+                pr_crit!(
+                    "WorkQueue({:?}): Failed to signal a completed command\n",
+                    pipe_type,
+                );
+            }
+        }
+
+        mod_pr_debug!(
+            "WorkQueue({:?}): Completed {} commands\n",
+            inner.pipe_type,
+            completed_commands
+        );
+
+        if let Some(i) = completed.last() {
+            inner
+                .info
+                .state
+                .with(|raw, _inner| raw.cpu_freeptr.store(i.wptr(), Ordering::Release));
+        }
+
+        let empty = inner.pending.is_empty();
+        if empty && inner.pending_jobs == 0 {
+            inner.event = None;
+            inner.last_submitted = None;
+            inner.last_completed = None;
+        }
+
+        let dev = inner.dev.clone();
+        core::mem::drop(inner);
+
+        for cmd in completed.iter_mut() {
+            cmd.complete();
+        }
+
+        let gpu = &dev.data().gpu;
+        gpu.add_completed_work(completed);
+
+        empty
+    }
+
+    /// Mark this queue's work up to a certain stamp value as having failed.
+    fn mark_error(&self, value: event::EventValue, error: WorkError) {
+        // If anything is marked completed, we can consider it successful
+        // at this point, even if we didn't get the signal event yet.
+        self.signal();
+
+        let mut inner = self.inner.lock();
+
+        if inner.event.is_none() {
+            pr_err!("WorkQueue: signal_fault() called but no event?\n");
+            return;
+        }
+
+        mod_pr_debug!(
+            "WorkQueue({:?}): Signaling fault for event {:?} at value {:#x?}\n",
+            inner.pipe_type,
+            inner.last_token,
+            value
+        );
+
+        for cmd in inner.pending.iter_mut() {
+            if cmd.value() <= value {
+                cmd.mark_error(error);
+            } else {
+                break;
+            }
+        }
+    }
+
+    /// Mark all of this queue's work as having failed, and complete it.
+    fn fail_all(&self, error: WorkError) {
+        // If anything is marked completed, we can consider it successful
+        // at this point, even if we didn't get the signal event yet.
+        self.signal();
+
+        let mut inner = self.inner.lock();
+
+        if inner.event.is_none() {
+            pr_err!("WorkQueue: fail_all() called but no event?\n");
+            return;
+        }
+
+        mod_pr_debug!(
+            "WorkQueue({:?}): Failing all jobs {:?}\n",
+            inner.pipe_type,
+            error
+        );
+
+        let mut cmds = Vec::new();
+
+        core::mem::swap(&mut inner.pending, &mut cmds);
+
+        if inner.pending_jobs == 0 {
+            inner.event = None;
+        }
+
+        core::mem::drop(inner);
+
+        for mut cmd in cmds {
+            cmd.mark_error(error);
+            cmd.complete();
+        }
+    }
+}
diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index 1a9e99fcd6eba2..8f227dfe79458c 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -114,6 +114,7 @@ impl<T, B: Backend> Lock<T, B> {
     }
 
     /// Constructs a new lock initialiser taking an initialiser/
+    #[track_caller]
     pub fn pin_init<E>(t: impl PinInit<T, E>) -> impl PinInit<Self, E>
     where
         E: core::convert::From<core::convert::Infallible>,
@@ -123,7 +124,6 @@ impl<T, B: Backend> Lock<T, B> {
     }
 
     /// Constructs a new lock initialiser.
-    #[allow(clippy::new_ret_no_self)]
     #[track_caller]
     pub fn new_named(t: T, name: &'static CStr) -> impl PinInit<Self> {
         let (key, _) = caller_lock_class();
@@ -131,6 +131,7 @@ impl<T, B: Backend> Lock<T, B> {
     }
 
     /// Constructs a new lock initialiser taking an initialiser/
+    #[track_caller]
     pub fn pin_init_named<E>(t: impl PinInit<T, E>, name: &'static CStr) -> impl PinInit<Self, E>
     where
         E: core::convert::From<core::convert::Infallible>,
diff --git a/rust/macros/versions.rs b/rust/macros/versions.rs
index 89d43096d35808..65b475453b66ac 100644
--- a/rust/macros/versions.rs
+++ b/rust/macros/versions.rs
@@ -93,14 +93,14 @@ static AGX_VERSIONS: VersionConfig = VersionConfig {
     fields: &["G", "V"],
     enums: &[
         &["G13", "G14", "G14X"],
-        &["V12_3", "V12_4", "V13_0B4", "V13_2", "V13_3"],
+        &["V12_3", "V12_4", "V13_0B4", "V13_2", "V13_3", "V13_5"],
     ],
     versions: &[
         &["G13", "V12_3"],
         &["G14", "V12_4"],
-        &["G13", "V13_3"],
-        &["G14", "V13_3"],
-        &["G14X", "V13_3"],
+        &["G13", "V13_5"],
+        &["G14", "V13_5"],
+        &["G14X", "V13_5"],
     ],
 };
 

From c4806a50cb97c3ac99368d9e63d91456945c485a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 26 Apr 2023 20:05:17 +0900
Subject: [PATCH 0896/1027] DO NOT MERGE: drm/asahi: Add an experimental UAPI
 extension

This lets us play around with unknown stuff from userspace, without
having to commit to adding it to the UAPI (or knowing whether it's safe
to expose at all).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 include/uapi/drm/asahi_drm.h | 63 ++++++++++++++++++++++++++++++++++++
 1 file changed, 63 insertions(+)

diff --git a/include/uapi/drm/asahi_drm.h b/include/uapi/drm/asahi_drm.h
index 6ea316f87ed451..af2e8b5801b53e 100644
--- a/include/uapi/drm/asahi_drm.h
+++ b/include/uapi/drm/asahi_drm.h
@@ -444,6 +444,69 @@ struct drm_asahi_cmd_render {
 	__u32 isp_bgobjvals;
 };
 
+#define ASAHI_RENDER_UNK_UNK1			(1UL << 0)
+#define ASAHI_RENDER_UNK_SET_TILE_CONFIG	(1UL << 1)
+#define ASAHI_RENDER_UNK_SET_UTILE_CONFIG	(1UL << 2)
+#define ASAHI_RENDER_UNK_SET_AUX_FB_UNK		(1UL << 3)
+#define ASAHI_RENDER_UNK_SET_G14_UNK		(1UL << 4)
+
+#define ASAHI_RENDER_UNK_SET_FRG_UNK_140	(1UL << 20)
+#define ASAHI_RENDER_UNK_SET_FRG_UNK_158	(1UL << 21)
+#define ASAHI_RENDER_UNK_SET_FRG_TILECFG	(1UL << 22)
+#define ASAHI_RENDER_UNK_SET_LOAD_BGOBJVALS	(1UL << 23)
+#define ASAHI_RENDER_UNK_SET_FRG_UNK_38		(1UL << 24)
+#define ASAHI_RENDER_UNK_SET_FRG_UNK_3C		(1UL << 25)
+
+#define ASAHI_RENDER_UNK_SET_RELOAD_ZLSCTRL	(1UL << 27)
+#define ASAHI_RENDER_UNK_SET_UNK_BUF_10		(1UL << 28)
+#define ASAHI_RENDER_UNK_SET_FRG_UNK_MASK	(1UL << 29)
+
+#define ASAHI_RENDER_UNK_SET_IOGPU_UNK54	(1UL << 40)
+#define ASAHI_RENDER_UNK_SET_IOGPU_UNK56	(1UL << 41)
+#define ASAHI_RENDER_UNK_SET_TILING_CONTROL	(1UL << 42)
+#define ASAHI_RENDER_UNK_SET_TILING_CONTROL_2	(1UL << 43)
+#define ASAHI_RENDER_UNK_SET_VTX_UNK_F0		(1UL << 44)
+#define ASAHI_RENDER_UNK_SET_VTX_UNK_F8		(1UL << 45)
+#define ASAHI_RENDER_UNK_SET_VTX_UNK_118	(1UL << 46)
+#define ASAHI_RENDER_UNK_SET_VTX_UNK_MASK	(1UL << 47)
+
+#define ASAHI_RENDER_EXT_UNKNOWNS	0xff00
+
+/* XXX: Do not upstream this struct */
+struct drm_asahi_cmd_render_unknowns {
+	/** @type: Type ID of this extension */
+	__u32 type;
+	__u32 pad;
+	/** @next: Pointer to the next extension struct, if any */
+	__u64 next;
+
+	__u64 flags;
+
+	__u64 tile_config;
+	__u64 utile_config;
+
+	__u64 aux_fb_unk;
+	__u64 g14_unk;
+	__u64 frg_unk_140;
+	__u64 frg_unk_158;
+	__u64 frg_tilecfg;
+	__u64 load_bgobjvals;
+	__u64 frg_unk_38;
+	__u64 frg_unk_3c;
+	__u64 reload_zlsctrl;
+	__u64 unk_buf_10;
+	__u64 frg_unk_mask;
+
+	__u64 iogpu_unk54;
+	__u64 iogpu_unk56;
+	__u64 tiling_control;
+	__u64 tiling_control_2;
+	__u64 vtx_unk_f0;
+	__u64 vtx_unk_f8;
+	__u64 vtx_unk_118;
+	__u64 vtx_unk_mask;
+};
+
 /* XXX check */
 #define ASAHI_COMPUTE_NO_PREEMPTION (1UL << 0)
 

From 65627d5e0a05e80ca783b0fb780512dde4cf99f5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 26 Apr 2023 20:19:19 +0900
Subject: [PATCH 0897/1027] drm/asahi: render: Implement unknown value UAPI
 extension

---
 drivers/gpu/drm/asahi/debug.rs        |   1 +
 drivers/gpu/drm/asahi/queue/render.rs | 180 +++++++++++++++++++++-----
 2 files changed, 150 insertions(+), 31 deletions(-)

diff --git a/drivers/gpu/drm/asahi/debug.rs b/drivers/gpu/drm/asahi/debug.rs
index f03a3f991d74a4..4f20d55db0337f 100644
--- a/drivers/gpu/drm/asahi/debug.rs
+++ b/drivers/gpu/drm/asahi/debug.rs
@@ -65,6 +65,7 @@ pub(crate) enum DebugFlags {
     Debug6 = 54,
     Debug7 = 55,
 
+    AllowUnknownOverrides = 62,
     OopsOnGpuCrash = 63,
 }
 
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index a2cc8f848f5445..940ead382822e3 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -250,6 +250,46 @@ impl super::Queue::ver {
             return Err(EINVAL);
         }
 
+        let mut unks: uapi::drm_asahi_cmd_render_unknowns = Default::default();
+
+        let mut ext_ptr = cmdbuf.extensions;
+        while ext_ptr != 0 {
+            let ext_type = u32::from_ne_bytes(
+                unsafe { UserSlicePtr::new(ext_ptr as usize as *mut _, 4) }
+                    .read_all()?
+                    .try_into()
+                    .or(Err(EINVAL))?,
+            );
+
+            match ext_type {
+                uapi::ASAHI_RENDER_EXT_UNKNOWNS => {
+                    if !debug_enabled(debug::DebugFlags::AllowUnknownOverrides) {
+                        return Err(EINVAL);
+                    }
+                    let mut ext_reader = unsafe {
+                        UserSlicePtr::new(
+                            ext_ptr as usize as *mut _,
+                            core::mem::size_of::<uapi::drm_asahi_cmd_render_unknowns>(),
+                        )
+                        .reader()
+                    };
+                    unsafe {
+                        ext_reader.read_raw(
+                            &mut unks as *mut _ as *mut u8,
+                            core::mem::size_of::<uapi::drm_asahi_cmd_render_unknowns>(),
+                        )?;
+                    }
+
+                    ext_ptr = unks.next;
+                }
+                _ => return Err(EINVAL),
+            }
+        }
+
+        if unks.pad != 0 {
+            return Err(EINVAL);
+        }
+
         let dev = self.dev.data();
         let gpu = match dev.gpu.as_any().downcast_ref::<gpu::GpuManager::ver>() {
             Some(gpu) => gpu,
@@ -272,7 +312,7 @@ impl super::Queue::ver {
         }
 
         #[ver(G != G14)]
-        let tiling_control = {
+        let mut tiling_control = {
             let render_cfg = gpu.get_cfg().render;
             let mut tiling_control = render_cfg.tiling_control;
 
@@ -395,7 +435,7 @@ impl super::Queue::ver {
             GFP_KERNEL,
         )?;
 
-        let unk1 = false;
+        let unk1 = unks.flags & uapi::ASAHI_RENDER_UNK_UNK1 as u64 != 0;
 
         let mut tile_config: u64 = 0;
         if !unk1 {
@@ -418,7 +458,7 @@ impl super::Queue::ver {
         };
 
         #[ver(G >= G14X)]
-        let frg_tilecfg = 0x0000000_00036011
+        let mut frg_tilecfg = 0x0000000_00036011
             | (((tile_info.tiles_x - 1) as u64) << 44)
             | (((tile_info.tiles_y - 1) as u64) << 53)
             | (if unk1 { 0 } else { 0x20_00000000 })
@@ -455,6 +495,85 @@ impl super::Queue::ver {
         #[ver(V >= V13_0B4)]
         let count_vtx = count_frag + 1;
 
+        // Unknowns handling
+
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_TILE_CONFIG as u64 != 0 {
+            tile_config = unks.tile_config;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_UTILE_CONFIG as u64 != 0 {
+            utile_config = unks.utile_config as u32;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_AUX_FB_UNK as u64 == 0 {
+            unks.aux_fb_unk = 0x100000;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_G14_UNK as u64 == 0 {
+            unks.g14_unk = 0x4040404;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_140 as u64 == 0 {
+            unks.frg_unk_140 = 0x8c60;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_158 as u64 == 0 {
+            unks.frg_unk_158 = 0x1c;
+        }
+        #[ver(G >= G14X)]
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_TILECFG as u64 != 0 {
+            frg_tilecfg = unks.frg_tilecfg;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_LOAD_BGOBJVALS as u64 == 0 {
+            unks.load_bgobjvals = cmdbuf.isp_bgobjvals.into();
+            #[ver(G < G14X)]
+            unks.load_bgobjvals |= 0x400;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_38 as u64 == 0 {
+            unks.frg_unk_38 = 0;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_3C as u64 == 0 {
+            unks.frg_unk_3c = 1;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_RELOAD_ZLSCTRL as u64 == 0 {
+            unks.reload_zlsctrl = cmdbuf.zls_ctrl;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_UNK_BUF_10 as u64 == 0 {
+            #[ver(G < G14X)]
+            unks.unk_buf_10 = 1;
+            #[ver(G >= G14X)]
+            unks.unk_buf_10 = 0;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_MASK as u64 == 0 {
+            unks.frg_unk_mask = 0xffffffff;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_IOGPU_UNK54 == 0 {
+            unks.iogpu_unk54 = 0x3a0012006b0003;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_IOGPU_UNK56 == 0 {
+            unks.iogpu_unk56 = 1;
+        }
+        #[ver(G != G14)]
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_TILING_CONTROL != 0 {
+            tiling_control = unks.tiling_control as u32;
+        }
+        #[ver(G != G14)]
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_TILING_CONTROL_2 == 0 {
+            #[ver(G < G14X)]
+            unks.tiling_control_2 = 0;
+            #[ver(G >= G14X)]
+            unks.tiling_control_2 = 4;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_VTX_UNK_F0 == 0 {
+            unks.vtx_unk_f0 = 0x1c;
+            #[ver(G < G14X)]
+            unks.vtx_unk_f0 += align(tile_info.meta1_blocks, 4) as u64;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_VTX_UNK_F8 == 0 {
+            unks.vtx_unk_f8 = 0x8c60;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_VTX_UNK_118 == 0 {
+            unks.vtx_unk_118 = 0x1c;
+        }
+        if unks.flags & uapi::ASAHI_RENDER_UNK_SET_VTX_UNK_MASK == 0 {
+            unks.vtx_unk_mask = 0xffffffff;
+        }
+
         mod_dev_dbg!(self.dev, "[Submission {}] Create Frag\n", id);
         let frag = GpuObject::new_init_prealloc(
             kalloc.gpu_ro.alloc_object()?,
@@ -614,7 +733,7 @@ impl super::Queue::ver {
                     width: cmdbuf.fb_width,
                     height: cmdbuf.fb_height,
                     #[ver(V >= V13_0B4)]
-                    unk3: U64(0x100000),
+                    unk3: U64(unks.aux_fb_unk),
                 };
 
                 try_init!(fw::fragment::raw::RunFragment::ver {
@@ -656,7 +775,7 @@ impl super::Queue::ver {
                         visibility_result_buffer: U64(cmdbuf.visibility_result_buffer),
                         zls_ctrl: U64(cmdbuf.zls_ctrl),
                         #[ver(G >= G14)]
-                        unk_58_g14_0: U64(0x4040404),
+                        unk_58_g14_0: U64(unks.g14_unk),
                         #[ver(G >= G14)]
                         unk_58_g14_8: U64(0),
                         depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
@@ -682,10 +801,10 @@ impl super::Queue::ver {
                         aux_fb: inner.aux_fb.gpu_pointer(),
                         unk_108: Default::default(),
                         pipeline_base: U64(0x11_00000000),
-                        unk_140: U64(0x8c60),
+                        unk_140: U64(unks.frg_unk_140),
                         unk_148: U64(0x0),
                         unk_150: U64(0x0),
-                        unk_158: U64(0x1c),
+                        unk_158: U64(unks.frg_unk_158),
                         unk_160: U64(0),
                         __pad: Default::default(),
                         #[ver(V < V13_0B4)]
@@ -707,10 +826,10 @@ impl super::Queue::ver {
                         tib_blocks: cmdbuf.tib_blocks,
                         isp_bgobjdepth: cmdbuf.isp_bgobjdepth,
                         // TODO: does this flag need to be exposed to userspace?
-                        isp_bgobjvals: cmdbuf.isp_bgobjvals | 0x400,
-                        unk_38: 0,
-                        unk_3c: 1,
-                        unk_40: 0,
+                        isp_bgobjvals: unks.load_bgobjvals as u32,
+                        unk_38: unks.frg_unk_38 as u32,
+                        unk_3c: unks.frg_unk_3c as u32,
+                        unk_40: unks.frg_unk_40 as u32,
                         __pad: Default::default(),
                     }),
                     #[ver(G >= G14X)]
@@ -747,14 +866,14 @@ impl super::Queue::ver {
                                 0x15211,
                                 ((cmdbuf.fb_height as u64) << 32) | cmdbuf.fb_width as u64,
                             ); // aux_fb_info.{width, heigh
-                            r.add(0x15049, aux_fb_info.unk3); // s2.aux_fb_info.unk3
+                            r.add(0x15049, unks.aux_fb_unk); // s2.aux_fb_info.unk3
                             r.add(0x10051, cmdbuf.tib_blocks.into()); // s1.unk_2c
                             r.add(0x15321, cmdbuf.depth_dimensions.into()); // ISP_ZLS_PIXELS
                             r.add(0x15301, cmdbuf.isp_bgobjdepth.into()); // ISP_BGOBJDEPTH
-                            r.add(0x15309, cmdbuf.isp_bgobjvals.into() | 0x400); // ISP_BGOBJVALS
+                            r.add(0x15309, unks.load_bgobjvals); // ISP_BGOBJVALS
                             r.add(0x15311, cmdbuf.visibility_result_buffer); // ISP_OCLQRY_BASE
                             r.add(0x15319, cmdbuf.zls_ctrl); // ISP_ZLSCTL
-                            r.add(0x15349, 0x4040404); // s2.unk_58_g14_0
+                            r.add(0x15349, unks.g14_unk); // s2.unk_58_g14_0
                             r.add(0x15351, 0); // s2.unk_58_g14_8
                             r.add(0x15329, cmdbuf.depth_buffer_load); // ISP_ZLOAD_BASE
                             r.add(0x15331, cmdbuf.depth_buffer_store); // ISP_ZSTORE_BASE
@@ -789,7 +908,7 @@ impl super::Queue::ver {
                             r.add(0x16020, 0);
                             r.add(0x16461, inner.aux_fb.gpu_pointer().into());
                             r.add(0x16090, inner.aux_fb.gpu_pointer().into());
-                            r.add(0x120a1, 0x1c);
+                            r.add(0x120a1, unks.frg_unk_158);
                             r.add(0x160a8, 0);
                             r.add(0x16068, frg_tilecfg);
                             r.add(0x160b8, 0x0);
@@ -826,9 +945,9 @@ impl super::Queue::ver {
                             pipeline_bind: U64(cmdbuf.partial_reload_pipeline_bind as u64),
                             address: U64(cmdbuf.partial_reload_pipeline as u64),
                         },
-                        zls_ctrl: U64(cmdbuf.zls_ctrl),
+                        zls_ctrl: U64(unks.reload_zlsctrl),
                         #[ver(G >= G14X)]
-                        unk_290: U64(0x4040404),
+                        unk_290: U64(unks.g14_unk),
                         #[ver(G < G14X)]
                         unk_290: U64(0x0),
                         depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
@@ -881,7 +1000,7 @@ impl super::Queue::ver {
                         unk_10: 0x0, // fixed
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
-                        unk_mask: 0xffffffff,
+                        unk_mask: unks.frg_unk_mask as u32,
                         sampler_array: U64(0),
                         sampler_count: 0,
                         sampler_max: 0,
@@ -1152,9 +1271,8 @@ impl super::Queue::ver {
                         tvb_cluster_tilemaps: inner.scene.cluster_tilemaps_pointer(),
                         tpc: inner.scene.tpc_pointer(),
                         tvb_heapmeta: inner.scene.tvb_heapmeta_pointer().or(0x8000_0000_0000_0000),
-                        iogpu_unk_54: 0x6b0003, // fixed
-                        iogpu_unk_55: 0x3a0012, // fixed
-                        iogpu_unk_56: U64(0x1), // fixed
+                        iogpu_unk_54: U64(unks.iogpu_unk54), // fixed
+                        iogpu_unk_56: U64(unks.iogpu_unk56), // fixed
                         #[ver(G < G14)]
                         tvb_cluster_meta1: inner
                             .scene
@@ -1183,7 +1301,7 @@ impl super::Queue::ver {
                         #[ver(G < G14)]
                         tiling_control,
                         #[ver(G < G14)]
-                        unk_ac: 0, // fixed
+                        unk_ac: unks.tiling_control_2 as u32, // fixed
                         unk_b0: Default::default(), // fixed
                         pipeline_base: U64(0x11_00000000),
                         #[ver(G < G14)]
@@ -1192,10 +1310,10 @@ impl super::Queue::ver {
                             .meta_4_pointer()
                             .map(|x| x.or(0x3000_0000_0000_0000)),
                         #[ver(G < G14)]
-                        unk_f0: U64(0x1c + align(tile_info.meta1_blocks, 4) as u64),
-                        unk_f8: U64(0x8c60),     // fixed
+                        unk_f0: U64(unks.vtx_unk_f0),
+                        unk_f8: U64(unks.vtx_unk_f8),     // fixed
                         unk_100: Default::default(),      // fixed
-                        unk_118: 0x1c, // fixed
+                        unk_118: unks.vtx_unk_118 as u32, // fixed
                         __pad: Default::default(),
                     }),
                     #[ver(G < G14X)]
@@ -1223,8 +1341,8 @@ impl super::Queue::ver {
                                 .into();
                             r.add(0x1c031, tvb_heapmeta_ptr);
                             r.add(0x1c9c0, tvb_heapmeta_ptr);
-                            r.add(0x1c051, 0x3a0012006b0003); // iogpu_unk_54/55
-                            r.add(0x1c061, 1); // iogpu_unk_56
+                            r.add(0x1c051, unks.iogpu_unk54); // iogpu_unk_54/55
+                            r.add(0x1c061, unks.iogpu_unk56); // iogpu_unk_56
                             r.add(0x10149, utile_config.into()); // s2.unk_48 utile_config
                             r.add(0x10139, cmdbuf.ppp_multisamplectl); // PPP_MULTISAMPLECTL
                             r.add(0x10111, inner.scene.preempt_buf_1_pointer().into());
@@ -1252,7 +1370,7 @@ impl super::Queue::ver {
                                 inner.scene.meta_3_pointer().map_or(0, |a| a.into()),
                             ); // tvb_cluster_meta3
                             r.add(0x1c890, tiling_control.into()); // tvb_tiling_control
-                            r.add(0x1c918, 4);
+                            r.add(0x1c918, unks.tiling_control_2);
                             r.add(0x1c079, inner.scene.tvb_heapmeta_pointer().into());
                             r.add(0x1c9d8, inner.scene.tvb_heapmeta_pointer().into());
                             r.add(0x1c089, 0);
@@ -1261,7 +1379,7 @@ impl super::Queue::ver {
                                 inner.scene.meta_4_pointer().map_or(0, |a| a.into());
                             r.add(0x16c41, cl_meta_4_pointer); // tvb_cluster_meta4
                             r.add(0x1ca40, cl_meta_4_pointer); // tvb_cluster_meta4
-                            r.add(0x1c9a8, 0x1c); // + meta1_blocks? min_free_tvb_pages?
+                            r.add(0x1c9a8, unks.vtx_unk_f0); // + meta1_blocks? min_free_tvb_pages?
                             r.add(
                                 0x1c920,
                                 inner.scene.meta_1_pointer().map_or(0, |a| a.into()),
@@ -1292,7 +1410,7 @@ impl super::Queue::ver {
                             r.add(0x1c0a9, tile_info.params.tpc_stride.into()); // TE_TPC
                             r.add(0x10171, tile_info.params.unk_24.into());
                             r.add(0x10169, tile_info.params.unk_28.into()); // TA_RENDER_TARGET_MAX
-                            r.add(0x12099, 0x1c);
+                            r.add(0x12099, unks.vtx_unk_118);
                             r.add(0x1c9e8, 0);
                             /*
                             r.add(0x10209, 0x100); // Some kind of counter?? Does this matter?
@@ -1335,7 +1453,7 @@ impl super::Queue::ver {
                         unk_10: 0x0,    // fixed
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
-                        unk_mask: 0xffffffff,
+                        unk_mask: unks.vtx_unk_mask as u32,
                         sampler_array: U64(0),
                         sampler_count: 0,
                         sampler_max: 0,

From ac61fe767116a3ec70522bb77035d168b49353eb Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 19 Jul 2023 14:10:20 +0900
Subject: [PATCH 0898/1027] drm/asahi: hw,initdata: Initdata fixes for G14S

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/hw/mod.rs   |  1 +
 drivers/gpu/drm/asahi/hw/t600x.rs |  1 +
 drivers/gpu/drm/asahi/hw/t602x.rs | 29 +++++++++++++++++++----------
 drivers/gpu/drm/asahi/hw/t8103.rs |  1 +
 drivers/gpu/drm/asahi/hw/t8112.rs |  1 +
 drivers/gpu/drm/asahi/initdata.rs |  8 +++++++-
 6 files changed, 30 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index 0685f833b41a1d..f943ff688552b3 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -173,6 +173,7 @@ pub(crate) struct HwConfigA {
 #[allow(missing_docs)]
 #[derive(Debug, Copy, Clone)]
 pub(crate) struct HwConfigB {
+    pub(crate) unk_454: u32,
     pub(crate) unk_4e0: u64,
     pub(crate) unk_534: u32,
     pub(crate) unk_ab8: u32,
diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs
index a124ea9c6ec29d..e4bd02b0c215fe 100644
--- a/drivers/gpu/drm/asahi/hw/t600x.rs
+++ b/drivers/gpu/drm/asahi/hw/t600x.rs
@@ -79,6 +79,7 @@ pub(crate) const HWCONFIG_T6002: super::HwConfig = HwConfig {
         unk_e24: 125,
     },
     db: HwConfigB {
+        unk_454: 1,
         unk_4e0: 4,
         unk_534: 1,
         unk_ab8: 0x2084,
diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
index f1c0151eb63f5c..5e2ea9490f7bfb 100644
--- a/drivers/gpu/drm/asahi/hw/t602x.rs
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -6,7 +6,7 @@ use crate::f32;
 
 use super::*;
 
-const fn iomaps(mcc_count: usize) -> [Option<IOMapping>; 24] {
+const fn iomaps(chip_id: u32, mcc_count: usize) -> [Option<IOMapping>; 24] {
     [
         Some(IOMapping::new(0x404d00000, 0x144000, 0x144000, true)), // Fender
         Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),    // AICTimer
@@ -15,18 +15,21 @@ const fn iomaps(mcc_count: usize) -> [Option<IOMapping>; 24] {
         None,                                                        // UVD
         None,                                                        // unused
         None,                                                        // DisplayUnderrunWA
-        Some(IOMapping::new(0x28e478000, 0x4000, 0x4000, false)), // AnalogTempSensorControllerRegs
-        None,                                                     // PMPDoorbell
-        Some(IOMapping::new(0x404e08000, 0x8000, 0x8000, true)),  // MetrologySensorRegs
-        None,                                                     // GMGIFAFRegs
+        Some(match chip_id {
+            0x6020 => IOMapping::new(0x28e460000, 0x4000, 0x4000, false),
+            _ => IOMapping::new(0x28e478000, 0x8000, 0x4000, false),
+        }), // AnalogTempSensorControllerRegs
+        None,                                                        // PMPDoorbell
+        Some(IOMapping::new(0x404e08000, 0x8000, 0x8000, true)),     // MetrologySensorRegs
+        None,                                                        // GMGIFAFRegs
         Some(IOMapping::new(
             0x200000000,
             mcc_count * 0xd8000,
             0xd8000,
             true,
         )), // MCache registers
-        Some(IOMapping::new(0x28e118000, 0x4000, 0x4000, false)), // AICBankedRegisters
-        None,                                                     // PMGRScratch
+        Some(IOMapping::new(0x28e118000, 0x4000, 0x4000, false)),    // AICBankedRegisters
+        None,                                                        // PMGRScratch
         None, // NIA Special agent idle register die 0
         None, // NIA Special agent idle register die 1
         None, // CRE registers
@@ -79,6 +82,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
         unk_e24: 125,
     },
     db: HwConfigB {
+        unk_454: 1,
         unk_4e0: 4,
         unk_534: 0,
         unk_ab8: 0, // Unused
@@ -136,7 +140,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
     fast_sensor_mask: [0x40005000c000d00, 0x40005000c000d00],
     fast_sensor_mask_alt: [0x140015001d001d00, 0x140015001d001d00],
     fast_die0_sensor_present: 0, // Unused
-    io_mappings: &iomaps(16),
+    io_mappings: &iomaps(0x6022, 16),
     sram_base: Some(0x404d60000),
     sram_size: Some(0x20000),
 };
@@ -150,7 +154,7 @@ pub(crate) const HWCONFIG_T6021: super::HwConfig = HwConfig {
     max_num_clusters: 4,
     fast_sensor_mask: [0x40005000c000d00, 0],
     fast_sensor_mask_alt: [0x140015001d001d00, 0],
-    io_mappings: &iomaps(8),
+    io_mappings: &iomaps(0x6021, 8),
     ..HWCONFIG_T6022
 };
 
@@ -159,9 +163,14 @@ pub(crate) const HWCONFIG_T6020: super::HwConfig = HwConfig {
     gpu_variant: GpuVariant::S,
     gpu_core: GpuCore::G14S,
 
+    db: HwConfigB {
+        unk_454: 0,
+        ..HWCONFIG_T6021.db
+    },
+
     max_num_clusters: 2,
     fast_sensor_mask: [0xc000d00, 0],
     fast_sensor_mask_alt: [0x1d001d00, 0],
-    io_mappings: &iomaps(4),
+    io_mappings: &iomaps(0x6020, 4),
     ..HWCONFIG_T6021
 };
diff --git a/drivers/gpu/drm/asahi/hw/t8103.rs b/drivers/gpu/drm/asahi/hw/t8103.rs
index c8d6ae868a3175..e3e2f77ce4ef7e 100644
--- a/drivers/gpu/drm/asahi/hw/t8103.rs
+++ b/drivers/gpu/drm/asahi/hw/t8103.rs
@@ -39,6 +39,7 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
         unk_e24: 112,
     },
     db: HwConfigB {
+        unk_454: 1,
         unk_4e0: 0,
         unk_534: 0,
         unk_ab8: 0x48,
diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs
index 206f61180af8e3..3541857edd01ed 100644
--- a/drivers/gpu/drm/asahi/hw/t8112.rs
+++ b/drivers/gpu/drm/asahi/hw/t8112.rs
@@ -39,6 +39,7 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
         unk_e24: 125,
     },
     db: HwConfigB {
+        unk_454: 1,
         unk_4e0: 4,
         unk_534: 0,
         unk_ab8: 0x2048,
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 0d23208fdbe3ea..ead6cfe341ec32 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -495,7 +495,7 @@ impl<'a> InitDataBuilder::ver<'a> {
                     unkptr_38: U64(0xffffffa0_11800000),
                     // TODO: yuv matrices
                     chip_id: cfg.chip_id,
-                    unk_454: 0x1,
+                    unk_454: cfg.db.unk_454,
                     unk_458: 0x1,
                     unk_460: 0x1,
                     unk_464: 0x1,
@@ -609,6 +609,12 @@ impl<'a> InitDataBuilder::ver<'a> {
                         }
                     }
 
+                    // Special case override for T602x
+                    #[ver(G == G14X)]
+                    if dyncfg.id.gpu_rev_id == hw::GpuRevisionID::B1 {
+                        raw.gpu_rev_id = hw::GpuRevisionID::B0 as u32;
+                    }
+
                     Ok(())
                 },
             )

From 6ad76e944306845f899ac8c014f2dc10dffaf1ca Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 19 Jul 2023 14:30:26 +0900
Subject: [PATCH 0899/1027] drm/asahi: hw: Drop max_dies

This isn't available on t602x, so it was probably a bad guess. Just use
the known die count for the userspace info, from our HW info tables.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs   | 2 +-
 drivers/gpu/drm/asahi/gpu.rs    | 1 -
 drivers/gpu/drm/asahi/hw/mod.rs | 2 --
 drivers/gpu/drm/asahi/regs.rs   | 1 -
 4 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index fbe24b2e646152..dbad7f9afd4b68 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -220,7 +220,7 @@ impl File {
             gpu_revision: gpu.get_dyncfg().id.gpu_rev as u32,
             chip_id: gpu.get_cfg().chip_id,
 
-            num_dies: gpu.get_dyncfg().id.max_dies,
+            num_dies: gpu.get_cfg().num_dies,
             num_clusters_total: gpu.get_dyncfg().id.num_clusters,
             num_cores_per_cluster: gpu.get_dyncfg().id.num_cores,
             num_frags_per_cluster: gpu.get_dyncfg().id.num_frags,
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index b39321c4c26fda..e860a1eb265778 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -714,7 +714,6 @@ impl GpuManager::ver {
             gpu_id.gpu_gen,
             gpu_id.gpu_variant
         );
-        dev_info!(dev, "  Max dies: {}\n", gpu_id.max_dies);
         dev_info!(dev, "  Clusters: {}\n", gpu_id.num_clusters);
         dev_info!(
             dev,
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index f943ff688552b3..5d320d138adab6 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -328,8 +328,6 @@ pub(crate) struct GpuIdConfig {
     pub(crate) gpu_rev: GpuRevision,
     /// GPU silicon revision ID (firmware enum).
     pub(crate) gpu_rev_id: GpuRevisionID,
-    /// Maximum number of dies supported.
-    pub(crate) max_dies: u32,
     /// Total number of GPU clusters.
     pub(crate) num_clusters: u32,
     /// Maximum number of GPU cores per cluster.
diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs
index 5a92e026c2a53e..0251cf0409b710 100644
--- a/drivers/gpu/drm/asahi/regs.rs
+++ b/drivers/gpu/drm/asahi/regs.rs
@@ -340,7 +340,6 @@ impl Resources {
             },
             gpu_rev,
             gpu_rev_id,
-            max_dies: (id_clusters >> 20) & 0xf,
             num_clusters,
             num_cores,
             num_frags: num_cores, // Used to be id_counts_1[15:8] but does not work for G14X

From 7cce39c362a04b62e4f92ec242a8743ec05e71d6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 19 Jul 2023 19:56:28 +0900
Subject: [PATCH 0900/1027] drm/asahi: initdata: Fix ver_info for G13 V13.5

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/initdata.rs | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index ead6cfe341ec32..d2064a5e65d466 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -873,9 +873,7 @@ impl<'a> InitDataBuilder::ver<'a> {
             |inner, _ptr| {
                 let cfg = &self.cfg;
                 try_init!(raw::InitData::ver {
-                    #[ver(V == V13_5 && G == G13)]
-                    ver_info: Array::new([1, 1, 16, 1]), // TODO
-                    #[ver(V == V13_5 && G == G14)]
+                    #[ver(V == V13_5 && G != G14X)]
                     ver_info: Array::new([0x6ba0, 0x1f28, 0x601, 0xb0]),
                     #[ver(V == V13_5 && G == G14X)]
                     ver_info: Array::new([0xb390, 0x70f8, 0x601, 0xb0]),

From ed8c7d8c76cc7a6a68a6c8ed5eaf4567683ef3fd Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 14:59:08 +0900
Subject: [PATCH 0901/1027] drm/asahi: fw/channels: Identify subpipe (?) field
 in tvb ack

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/channels.rs | 3 ++-
 drivers/gpu/drm/asahi/gpu.rs         | 1 +
 2 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index edee8b1b9d5eea..13dc51e30409e9 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -177,7 +177,8 @@ pub(crate) enum DeviceControlMsg {
         buffer_slot: u32,
         vm_slot: u32,
         counter: u32,
-        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x10 }>,
+        subpipe: u32,
+        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x14 }>,
     },
     Unk0e(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk0f(Array<DEVICECONTROL_SZ::ver, u8>),
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index e860a1eb265778..eca4e78bc49f30 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -1281,6 +1281,7 @@ impl GpuManager for GpuManager::ver {
             buffer_slot,
             vm_slot,
             counter,
+            subpipe: 0, // TODO
             __pad: Default::default(),
         };
 

From e5a3ce933535c10abc491adfede3fabc183d6863 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 14:59:39 +0900
Subject: [PATCH 0902/1027] drm/asahi: fw/channels: Document more message types
 a bit

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/channels.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index 13dc51e30409e9..200ea0333f4d7a 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -186,9 +186,9 @@ pub(crate) enum DeviceControlMsg {
     Unk11(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk12(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk13(Array<DEVICECONTROL_SZ::ver, u8>),
-    Unk14(Array<DEVICECONTROL_SZ::ver, u8>),
-    Unk15(Array<DEVICECONTROL_SZ::ver, u8>),
-    Unk16(Array<DEVICECONTROL_SZ::ver, u8>),
+    Unk14(Array<DEVICECONTROL_SZ::ver, u8>), // Init?
+    Unk15(Array<DEVICECONTROL_SZ::ver, u8>), // Enable something
+    Unk16(Array<DEVICECONTROL_SZ::ver, u8>), // Disable something
     #[ver(V >= V13_3)]
     Unk17(Array<DEVICECONTROL_SZ::ver, u8>),
     DestroyContext {
@@ -212,7 +212,7 @@ pub(crate) enum DeviceControlMsg {
         __pad2: Pad<{ DEVICECONTROL_SZ::ver - 0x18 }>,
     },
     Unk18(Array<DEVICECONTROL_SZ::ver, u8>),
-    Initialize(Pad<DEVICECONTROL_SZ::ver>),
+    Initialize(Pad<DEVICECONTROL_SZ::ver>), // Update RegionC
 }
 
 #[versions(AGX)]

From 5b007e8835b7e39265305319ced3a2bc1c1539b2 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 15:00:00 +0900
Subject: [PATCH 0903/1027] drm/asahi: initdata: Fix 13.5 field position for
 G13X

Only affects unk_534 getting shifted over for G13X.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/initdata.rs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index c9df1121847470..4f1e63df2de4d0 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -721,11 +721,12 @@ pub(crate) mod raw {
         pub(crate) unk_528: u32,
         pub(crate) unk_52c: u32,
         pub(crate) unk_530: u32,
-        pub(crate) unk_534: u32,
-        pub(crate) unk_538: u32,
 
         #[ver(V >= V13_0B4)]
-        pub(crate) unk_53c_0: u32,
+        pub(crate) unk_534_0: u32,
+
+        pub(crate) unk_534: u32,
+        pub(crate) unk_538: u32,
 
         pub(crate) num_frags: u32,
         pub(crate) unk_540: u32,

From 992a172dd87bd6dfbc4e24468e0b9d2be9f8d1da Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 15:01:32 +0900
Subject: [PATCH 0904/1027] drm/asahi: initdata: Document more stuff

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/initdata.rs | 6 ++++--
 drivers/gpu/drm/asahi/initdata.rs    | 1 +
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index 4f1e63df2de4d0..95759edfba9a81 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -793,7 +793,7 @@ pub(crate) mod raw {
         pub(crate) pad_b10_0: Array<0x8, u8>,
 
         pub(crate) unk_b10: u32,
-        pub(crate) pad_b14: Pad<0x8>,
+        pub(crate) timer_offset: U64,
         pub(crate) unk_b1c: u32,
         pub(crate) unk_b20: u32,
         pub(crate) unk_b24: u32,
@@ -1130,6 +1130,8 @@ pub(crate) mod raw {
         #[ver((G >= G14X && V < V13_3) || (G <= G14 && V >= V13_3))]
         pub(crate) unk_x_pad: Array<0x4, u8>,
 
+        // bit 0: sets sgx_reg 0x17620
+        // bit 1: sets sgx_reg 0x17630
         pub(crate) fault_control: u32,
         pub(crate) do_init: u32,
         pub(crate) unk_10e88: Array<0x188, u8>,
@@ -1183,7 +1185,7 @@ pub(crate) mod raw {
         #[ver(V >= V13_0B4)]
         pub(crate) unk_118e4_0: u32,
 
-        pub(crate) unk_118e4: u32,
+        pub(crate) cdm_context_store_latency_threshold: u32,
         pub(crate) unk_118e8: u32,
         pub(crate) unk_118ec: Array<0x400, u8>,
         pub(crate) unk_11cec: Array<0x54, u8>,
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index d2064a5e65d466..ddb8889f6559a4 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -538,6 +538,7 @@ impl<'a> InitDataBuilder::ver<'a> {
                     #[ver(V < V13_0B4)]
                     unk_ae4: Array::new([0x0, 0xf, 0x3f, 0x3f]),
                     unk_b10: 0x1,
+                    timer_offset: U64(0),
                     unk_b24: 0x1,
                     unk_b28: 0x1,
                     unk_b2c: 0x1,

From 9dbb67d045ffc5a120c5d99dfbe896496ecfd134 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 15:02:15 +0900
Subject: [PATCH 0905/1027] drm/asahi: fw/microseq: Document bits of
 has_attachments

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/microseq.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/microseq.rs b/drivers/gpu/drm/asahi/fw/microseq.rs
index 70c069fee95e83..69c070dca13e6e 100644
--- a/drivers/gpu/drm/asahi/fw/microseq.rs
+++ b/drivers/gpu/drm/asahi/fw/microseq.rs
@@ -224,7 +224,7 @@ pub(crate) struct FinalizeVertex {
     pub(crate) unk_68_g14: U64,
 
     pub(crate) restart_branch_offset: i32,
-    pub(crate) has_attachments: u32,
+    pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic
 
     #[ver(V >= V13_0B4)]
     pub(crate) unk_74: Array<0x10, u8>,
@@ -310,7 +310,7 @@ pub(crate) struct FinalizeFragment {
     pub(crate) unk_8c_g14: U64,
 
     pub(crate) restart_branch_offset: i32,
-    pub(crate) has_attachments: u32,
+    pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic
 
     #[ver(V >= V13_0B4)]
     pub(crate) unk_9c: Array<0x10, u8>,
@@ -383,7 +383,7 @@ pub(crate) struct FinalizeCompute<'a> {
     pub(crate) unk_5c_g14: U64,
 
     pub(crate) restart_branch_offset: i32,
-    pub(crate) has_attachments: u32,
+    pub(crate) has_attachments: u32, // Check DCMP errors bits 2,3 1=ktrace 2=log 3=panic
 
     #[ver(V >= V13_0B4)]
     pub(crate) unk_64: Array<0xd, u8>,

From 2978ebe820ea7baa6a23105998ab6542bdb05d45 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 15:02:29 +0900
Subject: [PATCH 0906/1027] drm/asahi: hw/t600x: Expand some mappings

Seems this changed in 13.5. Probably doesn't matter but should be safe
to always expand.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/hw/t600x.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs
index e4bd02b0c215fe..2df0c61fb3079f 100644
--- a/drivers/gpu/drm/asahi/hw/t600x.rs
+++ b/drivers/gpu/drm/asahi/hw/t600x.rs
@@ -15,7 +15,7 @@ const fn iomaps(mcc_count: usize, has_die1: bool) -> [Option<IOMapping>; 20] {
         None,                                                      // UVD
         None,                                                      // unused
         None,                                                      // DisplayUnderrunWA
-        Some(IOMapping::new(0x28e494000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
+        Some(IOMapping::new(0x28e494000, 0x8000, 0x4000, false)),  // AnalogTempSensorControllerRegs
         None,                                                      // PMPDoorbell
         Some(IOMapping::new(0x404d80000, 0x8000, 0x8000, true)),   // MetrologySensorRegs
         Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
@@ -37,7 +37,7 @@ const fn iomaps(mcc_count: usize, has_die1: bool) -> [Option<IOMapping>; 20] {
         None,                                                     // CRE registers
         None,                                                     // Streaming codec registers
         Some(IOMapping::new(0x28e3d0000, 0x1000, 0x1000, true)),  // ?
-        Some(IOMapping::new(0x28e3c0000, 0x1000, 0x1000, false)), // ?
+        Some(IOMapping::new(0x28e3c0000, 0x2000, 0x2000, false)), // ?
     ]
 }
 

From 9b17b39b3e10b29e61aab3fb99c866d6bde914e1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 21:15:40 +0900
Subject: [PATCH 0907/1027] drm/asahi: hw/t8112: Make last IOMapping read-only

This matches 13.5 firmware and t60xx.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/hw/t8112.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs
index 3541857edd01ed..3b8167343b7c45 100644
--- a/drivers/gpu/drm/asahi/hw/t8112.rs
+++ b/drivers/gpu/drm/asahi/hw/t8112.rs
@@ -100,7 +100,7 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
         Some(IOMapping::new(0x204e00000, 0x10000, 0x10000, true)), // CRE registers
         Some(IOMapping::new(0x27d050000, 0x4000, 0x4000, true)), // Streaming codec registers
         Some(IOMapping::new(0x23b3d0000, 0x1000, 0x1000, true)), //
-        Some(IOMapping::new(0x23b3c0000, 0x1000, 0x1000, true)), //
+        Some(IOMapping::new(0x23b3c0000, 0x1000, 0x1000, false)), //
     ],
     sram_base: None,
     sram_size: None,

From b1924a7075e0ce17e59581fc45ddb65df6764584 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 2 Aug 2023 22:35:57 +0900
Subject: [PATCH 0908/1027] drm/asahi: render: Correct some values for G14

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 940ead382822e3..21fd904c0adb49 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -507,7 +507,10 @@ impl super::Queue::ver {
             unks.aux_fb_unk = 0x100000;
         }
         if unks.flags & uapi::ASAHI_RENDER_UNK_SET_G14_UNK as u64 == 0 {
+            #[ver(G >= G14)]
             unks.g14_unk = 0x4040404;
+            #[ver(G < G14)]
+            unks.g14_unk = 0;
         }
         if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_140 as u64 == 0 {
             unks.frg_unk_140 = 0x8c60;
@@ -521,7 +524,7 @@ impl super::Queue::ver {
         }
         if unks.flags & uapi::ASAHI_RENDER_UNK_SET_LOAD_BGOBJVALS as u64 == 0 {
             unks.load_bgobjvals = cmdbuf.isp_bgobjvals.into();
-            #[ver(G < G14X)]
+            #[ver(G < G14)]
             unks.load_bgobjvals |= 0x400;
         }
         if unks.flags & uapi::ASAHI_RENDER_UNK_SET_FRG_UNK_38 as u64 == 0 {
@@ -946,10 +949,7 @@ impl super::Queue::ver {
                             address: U64(cmdbuf.partial_reload_pipeline as u64),
                         },
                         zls_ctrl: U64(unks.reload_zlsctrl),
-                        #[ver(G >= G14X)]
                         unk_290: U64(unks.g14_unk),
-                        #[ver(G < G14X)]
-                        unk_290: U64(0x0),
                         depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
                         unk_2a0: U64(0x0),
                         unk_2a8: U64(0x0),

From 3140b11b75b40e7521899ade4c9f2bdf86b45ce8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 3 Aug 2023 17:27:58 +0900
Subject: [PATCH 0909/1027] drm/asahi: initdata: Initialize GpuStatsFrag
 properly

I don't know if this matters, but just in case...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/initdata.rs | 39 ++++++++++++++++++++++------
 drivers/gpu/drm/asahi/fw/microseq.rs |  4 +--
 drivers/gpu/drm/asahi/initdata.rs    | 11 +++++++-
 3 files changed, 43 insertions(+), 11 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index 95759edfba9a81..12e8257b7723db 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -840,13 +840,35 @@ pub(crate) mod raw {
     }
     default_zeroed!(GpuStatsVtx);
 
+    #[versions(AGX)]
     #[derive(Debug)]
     #[repr(C)]
     pub(crate) struct GpuStatsFrag {
         // This changes all the time and we don't use it, let's just make it a big buffer
-        pub(crate) opaque: Array<0x3000, u8>,
+        // except for these two fields which may need init.
+        #[ver(G >= G14X)]
+        pub(crate) unk1_0: Array<0x910, u8>,
+        pub(crate) unk1: Array<0x100, u8>,
+        pub(crate) cur_stamp_id: i32,
+        pub(crate) unk2: Array<0x14, u8>,
+        pub(crate) unk_id: i32,
+        pub(crate) unk3: Array<0x1000, u8>,
+    }
+
+    #[versions(AGX)]
+    impl Default for GpuStatsFrag::ver {
+        fn default() -> Self {
+            Self {
+                #[ver(G >= G14X)]
+                unk1_0: Default::default(),
+                unk1: Default::default(),
+                cur_stamp_id: -1,
+                unk2: Default::default(),
+                unk_id: -1,
+                unk3: Default::default(),
+            }
+        }
     }
-    default_zeroed!(GpuStatsFrag);
 
     #[derive(Debug)]
     #[repr(C)]
@@ -856,14 +878,14 @@ pub(crate) mod raw {
     }
     default_zeroed!(GpuGlobalStatsVtx);
 
-    #[derive(Debug)]
+    #[versions(AGX)]
+    #[derive(Debug, Default)]
     #[repr(C)]
     pub(crate) struct GpuGlobalStatsFrag {
         pub(crate) total_cmds: u32,
         pub(crate) unk_4: u32,
-        pub(crate) stats: GpuStatsFrag,
+        pub(crate) stats: GpuStatsFrag::ver,
     }
-    default_zeroed!(GpuGlobalStatsFrag);
 
     #[derive(Debug)]
     #[repr(C)]
@@ -933,7 +955,7 @@ pub(crate) mod raw {
         pub(crate) unk_160: U64,
         pub(crate) unk_168: U64,
         pub(crate) stats_vtx: GpuPointer<'a, super::GpuGlobalStatsVtx>,
-        pub(crate) stats_frag: GpuPointer<'a, super::GpuGlobalStatsFrag>,
+        pub(crate) stats_frag: GpuPointer<'a, super::GpuGlobalStatsFrag::ver>,
         pub(crate) stats_comp: GpuPointer<'a, super::GpuStatsComp>,
         pub(crate) hwdata_a: GpuPointer<'a, super::HwDataA::ver>,
         pub(crate) unkptr_190: GpuPointer<'a, &'a [u8]>,
@@ -1271,7 +1293,8 @@ where
 
 trivial_gpustruct!(FwStatus);
 trivial_gpustruct!(GpuGlobalStatsVtx);
-trivial_gpustruct!(GpuGlobalStatsFrag);
+#[versions(AGX)]
+trivial_gpustruct!(GpuGlobalStatsFrag::ver);
 trivial_gpustruct!(GpuStatsComp);
 
 #[versions(AGX)]
@@ -1284,7 +1307,7 @@ trivial_gpustruct!(HwDataB::ver);
 #[derive(Debug)]
 pub(crate) struct Stats {
     pub(crate) vtx: GpuObject<GpuGlobalStatsVtx>,
-    pub(crate) frag: GpuObject<GpuGlobalStatsFrag>,
+    pub(crate) frag: GpuObject<GpuGlobalStatsFrag::ver>,
     pub(crate) comp: GpuObject<GpuStatsComp>,
 }
 
diff --git a/drivers/gpu/drm/asahi/fw/microseq.rs b/drivers/gpu/drm/asahi/fw/microseq.rs
index 69c070dca13e6e..2f6555799baf59 100644
--- a/drivers/gpu/drm/asahi/fw/microseq.rs
+++ b/drivers/gpu/drm/asahi/fw/microseq.rs
@@ -243,7 +243,7 @@ pub(crate) struct StartFragment<'a> {
     #[ver(G >= G14X)]
     pub(crate) registers: GpuWeakPointer<job::raw::RegisterArray>,
     pub(crate) scene: GpuPointer<'a, buffer::Scene::ver>,
-    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag>,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag::ver>,
     pub(crate) busy_flag: GpuWeakPointer<u32>,
     pub(crate) tvb_overflow_count: GpuWeakPointer<u32>,
     pub(crate) unk_pointer: GpuWeakPointer<u32>,
@@ -290,7 +290,7 @@ pub(crate) struct FinalizeFragment {
     pub(crate) scene: GpuWeakPointer<buffer::Scene::ver>,
     pub(crate) buffer: GpuWeakPointer<buffer::Info::ver>,
     pub(crate) unk_2c: U64,
-    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag>,
+    pub(crate) stats: GpuWeakPointer<initdata::raw::GpuStatsFrag::ver>,
     pub(crate) unk_pointer: GpuWeakPointer<u32>,
     pub(crate) busy_flag: GpuWeakPointer<u32>,
     pub(crate) work_queue: GpuWeakPointer<workqueue::QueueInfo::ver>,
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index ddb8889f6559a4..ee302721e7e59f 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -764,7 +764,16 @@ impl<'a> InitDataBuilder::ver<'a> {
                         let alloc = &mut *alloc;
                         try_init!(Stats::ver {
                             vtx: alloc.private.new_default::<GpuGlobalStatsVtx>()?,
-                            frag: alloc.private.new_default::<GpuGlobalStatsFrag>()?,
+                            frag: alloc.private.new_init(
+                                init::zeroed::<GpuGlobalStatsFrag::ver>(),
+                                |_inner, _ptr| {
+                                    try_init!(raw::GpuGlobalStatsFrag::ver {
+                                        total_cmds: 0,
+                                        unk_4: 0,
+                                        stats: Default::default(),
+                                    })
+                                }
+                            )?,
                             comp: alloc.private.new_default::<GpuStatsComp>()?,
                         })
                     },

From 46e04946b29b3dfb1fe2c5238dd4f3782471e201 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 3 Aug 2023 18:17:19 +0900
Subject: [PATCH 0910/1027] drm/asahi: slotalloc: Allow initializing empty
 slots

These slots can never be used.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs    |  2 +-
 drivers/gpu/drm/asahi/event.rs     | 10 ++++++----
 drivers/gpu/drm/asahi/mmu.rs       |  2 +-
 drivers/gpu/drm/asahi/slotalloc.rs | 15 +++++++++------
 4 files changed, 17 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index 01c65cc9ba9223..15e834ceb07fcf 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -737,7 +737,7 @@ impl BufferManager::ver {
         Ok(BufferManager::ver(slotalloc::SlotAllocator::new(
             NUM_BUFFERS,
             BufferManagerInner::ver { owners },
-            |_inner, _slot| BufferSlotInner::ver(),
+            |_inner, _slot| Some(BufferSlotInner::ver()),
             c_str!("BufferManager::SlotAllocator"),
             static_lock_class!(),
             static_lock_class!(),
diff --git a/drivers/gpu/drm/asahi/event.rs b/drivers/gpu/drm/asahi/event.rs
index cb4387c483b401..9e17ca0e1d7a26 100644
--- a/drivers/gpu/drm/asahi/event.rs
+++ b/drivers/gpu/drm/asahi/event.rs
@@ -154,10 +154,12 @@ impl EventManager {
             alloc: slotalloc::SlotAllocator::new(
                 NUM_EVENTS,
                 inner,
-                |inner: &mut EventManagerInner, slot| EventInner {
-                    stamp: &inner.stamps[slot as usize].0,
-                    gpu_stamp: inner.stamps.weak_item_pointer(slot as usize),
-                    gpu_fw_stamp: inner.fw_stamps.weak_item_pointer(slot as usize),
+                |inner: &mut EventManagerInner, slot| {
+                    Some(EventInner {
+                        stamp: &inner.stamps[slot as usize].0,
+                        gpu_stamp: inner.stamps.weak_item_pointer(slot as usize),
+                        gpu_fw_stamp: inner.fw_stamps.weak_item_pointer(slot as usize),
+                    })
                 },
                 c_str!("EventManager::SlotAllocator"),
                 static_lock_class!(),
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index e859bb78b01275..a67859ae3d0234 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -1207,7 +1207,7 @@ impl Uat {
             slots: slotalloc::SlotAllocator::new(
                 UAT_USER_CTX as u32,
                 (),
-                |_inner, _slot| SlotInner(),
+                |_inner, _slot| Some(SlotInner()),
                 c_str!("Uat::SlotAllocator"),
                 static_lock_class!(),
                 static_lock_class!(),
diff --git a/drivers/gpu/drm/asahi/slotalloc.rs b/drivers/gpu/drm/asahi/slotalloc.rs
index 4cbbda073584a7..635800cc7fe562 100644
--- a/drivers/gpu/drm/asahi/slotalloc.rs
+++ b/drivers/gpu/drm/asahi/slotalloc.rs
@@ -131,7 +131,7 @@ impl<T: SlotItem> SlotAllocator<T> {
     pub(crate) fn new(
         num_slots: u32,
         mut data: T::Data,
-        mut constructor: impl FnMut(&mut T::Data, u32) -> T,
+        mut constructor: impl FnMut(&mut T::Data, u32) -> Option<T>,
         name: &'static CStr,
         lock_key1: LockClassKey,
         lock_key2: LockClassKey,
@@ -140,11 +140,14 @@ impl<T: SlotItem> SlotAllocator<T> {
 
         for i in 0..num_slots {
             slots
-                .try_push(Some(Entry {
-                    item: constructor(&mut data, i),
-                    get_time: 0,
-                    drop_time: 0,
-                }))
+                .push(
+                    constructor(&mut data, i).map(|item| Entry {
+                        item,
+                        get_time: 0,
+                        drop_time: 0,
+                    }),
+                    GFP_KERNEL,
+                )
                 .expect("try_push() failed after reservation");
         }
 

From 4ba274029bf9a541ca0ef72abe95f1a69e3089b4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 3 Aug 2023 19:20:07 +0900
Subject: [PATCH 0911/1027] drm/asahi: fw.channels: Add static assertions for
 message sizes

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/channels.rs | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index 200ea0333f4d7a..85bfc1cec0a255 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -5,6 +5,7 @@
 use super::types::*;
 use crate::default_zeroed;
 use core::sync::atomic::Ordering;
+use kernel::static_assert;
 
 pub(crate) mod raw {
     use super::*;
@@ -215,6 +216,9 @@ pub(crate) enum DeviceControlMsg {
     Initialize(Pad<DEVICECONTROL_SZ::ver>), // Update RegionC
 }
 
+#[versions(AGX)]
+static_assert!(core::mem::size_of::<DeviceControlMsg::ver>() == 4 + DEVICECONTROL_SZ::ver);
+
 #[versions(AGX)]
 default_zeroed!(DeviceControlMsg::ver);
 
@@ -256,6 +260,8 @@ pub(crate) enum EventMsg {
     }, // Max discriminant: 0x7
 }
 
+static_assert!(core::mem::size_of::<EventMsg>() == 4 + EVENT_SZ);
+
 pub(crate) const EVENT_MAX: u32 = 0x7;
 
 #[derive(Copy, Clone)]
@@ -392,6 +398,9 @@ pub(crate) enum StatsMsg {
     }, // Max discriminant: 0xe
 }
 
+#[versions(AGX)]
+static_assert!(core::mem::size_of::<StatsMsg::ver>() == 4 + STATS_SZ::ver);
+
 #[versions(AGX)]
 pub(crate) const STATS_MAX: u32 = 0xe;
 

From 594cc7036603ab45039def573d3107ba1aa44030 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:06:42 +0900
Subject: [PATCH 0912/1027] drm/asahi: hw.t602x: Fixes for t6022

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/hw/mod.rs   |  4 +---
 drivers/gpu/drm/asahi/hw/t602x.rs | 17 ++++++++++++-----
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index 5d320d138adab6..1c6cd5e57b5484 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -71,9 +71,7 @@ pub(crate) enum GpuCore {
     G14G = 15,
     G14S = 16,
     G14C = 17,
-    // G15M = 18,
-    // G15P_AGX2 = 19,
-    // G15P = 20,
+    G14D = 18, // Split out, unlike G13D
 }
 
 /// GPU revision ID. Note: Part of the firmware ABI.
diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
index 5e2ea9490f7bfb..360284bcf8c4fa 100644
--- a/drivers/gpu/drm/asahi/hw/t602x.rs
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -37,7 +37,12 @@ const fn iomaps(chip_id: u32, mcc_count: usize) -> [Option<IOMapping>; 24] {
         Some(IOMapping::new(0x28e3d0000, 0x4000, 0x4000, true)), // ?
         Some(IOMapping::new(0x28e3c0000, 0x4000, 0x4000, false)), // ?
         Some(IOMapping::new(0x28e3d8000, 0x4000, 0x4000, true)), // ?
-        Some(IOMapping::new(0x404eac000, 0x4000, 0x4000, true)), // ?
+        Some(IOMapping::new(
+            0x404eac000,
+            if mcc_count > 8 { 0x8000 } else { 0x4000 },
+            0x4000,
+            true,
+        )), // ?
         None,
         None,
     ]
@@ -48,7 +53,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
     chip_id: 0x6022,
     gpu_gen: GpuGen::G14,
     gpu_variant: GpuVariant::D,
-    gpu_core: GpuCore::G14C,
+    gpu_core: GpuCore::G14D,
     gpu_feat_compat: 0,
     gpu_feat_incompat: feat::incompat::MANDATORY_ZS_COMPRESSION,
 
@@ -109,7 +114,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
         125, 125, 125, 125, 125, 125, 125, 125, 7500, 125, 125, 125, 125, 125, 125, 125,
     ],
     unk_hws2_0: 700,
-    unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.7, 0.9])),
+    unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.5, 0.9])),
     unk_hws2_24: 6,
     global_unk_54: 4000,
     sram_k: f32!(1.02),
@@ -137,8 +142,9 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
         0, 2, 2, 1, 1, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 2, 90, 75, 1, 1, 1, 1, 90, 75, 1, 1,
     ]),
     has_csafr: true,
-    fast_sensor_mask: [0x40005000c000d00, 0x40005000c000d00],
-    fast_sensor_mask_alt: [0x140015001d001d00, 0x140015001d001d00],
+    fast_sensor_mask: [0x40005000c000d00, 0xd000c0005000400],
+    // Apple typo? Should probably be 0x140015001c001d00
+    fast_sensor_mask_alt: [0x140015001d001d00, 0x1d001c0015001400],
     fast_die0_sensor_present: 0, // Unused
     io_mappings: &iomaps(0x6022, 16),
     sram_base: Some(0x404d60000),
@@ -152,6 +158,7 @@ pub(crate) const HWCONFIG_T6021: super::HwConfig = HwConfig {
 
     num_dies: 1,
     max_num_clusters: 4,
+    unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.7, 0.9])),
     fast_sensor_mask: [0x40005000c000d00, 0],
     fast_sensor_mask_alt: [0x140015001d001d00, 0],
     io_mappings: &iomaps(0x6021, 8),

From 30e39abedab9e5abc7c632c5d95404f2a67c31ce Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:09:15 +0900
Subject: [PATCH 0913/1027] drm/asahi: regs: Fix cluster count for G14D

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/regs.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/regs.rs b/drivers/gpu/drm/asahi/regs.rs
index 0251cf0409b710..3a3e37466e6d9d 100644
--- a/drivers/gpu/drm/asahi/regs.rs
+++ b/drivers/gpu/drm/asahi/regs.rs
@@ -243,7 +243,8 @@ impl Resources {
                 core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X), GFP_KERNEL)?;
                 core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X + 4), GFP_KERNEL)?;
                 core_mask_regs.push(self.sgx_read32(CORE_MASKS_G14X + 8), GFP_KERNEL)?;
-                (id_counts_1 >> 8) & 0xff
+                // Clusters per die * num dies
+                ((id_counts_1 >> 8) & 0xff) * ((id_counts_1 >> 16) & 0xf)
             }
             a => {
                 dev_err!(self.dev, "Unknown GPU generation {}\n", a);

From c63c72d750ee6ca7c61c11e4b45b1063e0648a21 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:09:30 +0900
Subject: [PATCH 0914/1027] drm/asahi: Enable probing for t6022

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/driver.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs
index 0fa8b533651069..d52ea625e9caae 100644
--- a/drivers/gpu/drm/asahi/driver.rs
+++ b/drivers/gpu/drm/asahi/driver.rs
@@ -86,6 +86,7 @@ kernel::define_of_id_table! {ASAHI_ID_TABLE, &'static hw::HwConfig, [
     (of::DeviceId::Compatible(b"apple,agx-t6002"), Some(&hw::t600x::HWCONFIG_T6002)),
     (of::DeviceId::Compatible(b"apple,agx-t6020"), Some(&hw::t602x::HWCONFIG_T6020)),
     (of::DeviceId::Compatible(b"apple,agx-t6021"), Some(&hw::t602x::HWCONFIG_T6021)),
+    (of::DeviceId::Compatible(b"apple,agx-t6022"), Some(&hw::t602x::HWCONFIG_T6022)),
 ]}
 
 /// Platform Driver implementation for `AsahiDriver`.

From 477fc25149c8d5da13352d318218355805980d13 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:18:44 +0900
Subject: [PATCH 0915/1027] drm/asahi: Increase recursion limit

We're running into this again with the init macros...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/asahi.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs
index 5423c856abcea7..52fed0b9bb2b8a 100644
--- a/drivers/gpu/drm/asahi/asahi.rs
+++ b/drivers/gpu/drm/asahi/asahi.rs
@@ -1,5 +1,5 @@
 // SPDX-License-Identifier: GPL-2.0-only OR MIT
-#![recursion_limit = "1024"]
+#![recursion_limit = "2048"]
 
 //! Driver for the Apple AGX GPUs found in Apple Silicon SoCs.
 

From 51cf4d445d0f170a0e797e22e969c686ff64e070 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:19:23 +0900
Subject: [PATCH 0916/1027] drm/asahi: initdata: Fixes for G14D

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/initdata.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index ee302721e7e59f..f57700b7dc7016 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -308,6 +308,8 @@ impl<'a> InitDataBuilder::ver<'a> {
                     unk_alpha_neg: f32!(0.8),
                     unk_alpha: f32!(0.2),
                     fast_die0_sensor_mask: U64(cfg.fast_sensor_mask[0]),
+                    #[ver(G >= G14X)]
+                    fast_die1_sensor_mask: U64(cfg.fast_sensor_mask[1]),
                     fast_die0_release_temp_cc: 100 * pwr.fast_die0_release_temp,
                     unk_87c: cfg.da.unk_87c,
                     unk_880: 0x4,
@@ -400,9 +402,13 @@ impl<'a> InitDataBuilder::ver<'a> {
                         })
                     },
                     fast_die0_sensor_mask_2: U64(cfg.fast_sensor_mask[0]),
+                    #[ver(G >= G14X)]
+                    fast_die1_sensor_mask_2: U64(cfg.fast_sensor_mask[1]),
                     unk_e24: cfg.da.unk_e24,
                     unk_e28: 1,
                     fast_die0_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[0]),
+                    #[ver(G >= G14X)]
+                    fast_die1_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[1]),
                     #[ver(V < V13_0B4)]
                     fast_die0_sensor_present: U64(cfg.fast_die0_sensor_present as u64),
                     unk_163c: 1,

From 5d3929a14deb73b81331635a0be3ae789f0887dd Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 14:39:55 +0900
Subject: [PATCH 0917/1027] drm/asahi: initdata,hw: Identify & set
 idle_off_standby_timer

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/initdata.rs |  2 +-
 drivers/gpu/drm/asahi/hw/mod.rs      | 10 ++++++++--
 drivers/gpu/drm/asahi/hw/t600x.rs    |  2 +-
 drivers/gpu/drm/asahi/hw/t602x.rs    |  2 +-
 drivers/gpu/drm/asahi/hw/t8103.rs    |  2 +-
 drivers/gpu/drm/asahi/hw/t8112.rs    |  2 +-
 drivers/gpu/drm/asahi/initdata.rs    |  2 +-
 7 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index 12e8257b7723db..ca8c9fcdac2f7f 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -1121,7 +1121,7 @@ pub(crate) mod raw {
         pub(crate) hws2: HwDataShared2,
 
         #[ver(V >= V13_0B4)]
-        pub(crate) unk_hws2_0: u32,
+        pub(crate) idle_off_standby_timer: u32,
 
         #[ver(V >= V13_0B4)]
         pub(crate) unk_hws2_4: Array<0x8, F32>,
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index 1c6cd5e57b5484..d7604c3943b2f5 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -270,8 +270,8 @@ pub(crate) struct HwConfig {
     /// HwDataShared3.table.
     pub(crate) shared3_tab: &'static [u32],
 
-    /// Globals.unk_hws2_0.
-    pub(crate) unk_hws2_0: u32,
+    /// Globals.idle_off_standby_timer.
+    pub(crate) idle_off_standby_timer_default: u32,
     /// Globals.unk_hws2_4.
     pub(crate) unk_hws2_4: Option<[F32; 8]>,
     /// Globals.unk_hws2_24.
@@ -410,6 +410,8 @@ pub(crate) struct PwrConfig {
     pub(crate) fw_early_wake_timeout_ms: u32,
     /// Delay from the GPU becoming idle to powerdown
     pub(crate) idle_off_delay_ms: u32,
+    /// Related to the above?
+    pub(crate) idle_off_standby_timer: u32,
     /// Percent?
     pub(crate) perf_boost_ce_step: u32,
     /// Minimum utilization before performance state is increased in %.
@@ -610,6 +612,10 @@ impl PwrConfig {
             fender_idle_off_delay_ms: prop!("apple,fender-idle-off-delay-ms", 40),
             fw_early_wake_timeout_ms: prop!("apple,fw-early-wake-timeout-ms", 5),
             idle_off_delay_ms: prop!("apple,idle-off-delay-ms", 2),
+            idle_off_standby_timer: prop!(
+                "apple,idleoff-standby-timer",
+                cfg.idle_off_standby_timer_default
+            ),
             perf_boost_ce_step: prop!("apple,perf-boost-ce-step", 25),
             perf_boost_min_util: prop!("apple,perf-boost-min-util", 100),
             perf_filter_drop_threshold: prop!("apple,perf-filter-drop-threshold"),
diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs
index 2df0c61fb3079f..2d81ae42fb62f2 100644
--- a/drivers/gpu/drm/asahi/hw/t600x.rs
+++ b/drivers/gpu/drm/asahi/hw/t600x.rs
@@ -96,7 +96,7 @@ pub(crate) const HWCONFIG_T6002: super::HwConfig = HwConfig {
     shared2_curves: None,
     shared3_unk: 0,
     shared3_tab: &[],
-    unk_hws2_0: 0,
+    idle_off_standby_timer_default: 0,
     unk_hws2_4: None,
     unk_hws2_24: 0,
     global_unk_54: 0xffff,
diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
index 360284bcf8c4fa..63b920fc403133 100644
--- a/drivers/gpu/drm/asahi/hw/t602x.rs
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -113,7 +113,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
     shared3_tab: &[
         125, 125, 125, 125, 125, 125, 125, 125, 7500, 125, 125, 125, 125, 125, 125, 125,
     ],
-    unk_hws2_0: 700,
+    idle_off_standby_timer_default: 700,
     unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.5, 0.9])),
     unk_hws2_24: 6,
     global_unk_54: 4000,
diff --git a/drivers/gpu/drm/asahi/hw/t8103.rs b/drivers/gpu/drm/asahi/hw/t8103.rs
index e3e2f77ce4ef7e..50a4bd0a0ff1c5 100644
--- a/drivers/gpu/drm/asahi/hw/t8103.rs
+++ b/drivers/gpu/drm/asahi/hw/t8103.rs
@@ -55,7 +55,7 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
     shared2_curves: None,
     shared3_unk: 0,
     shared3_tab: &[],
-    unk_hws2_0: 0,
+    idle_off_standby_timer_default: 0,
     unk_hws2_4: None,
     unk_hws2_24: 0,
     global_unk_54: 0xffff,
diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs
index 3b8167343b7c45..bc868cafbb4680 100644
--- a/drivers/gpu/drm/asahi/hw/t8112.rs
+++ b/drivers/gpu/drm/asahi/hw/t8112.rs
@@ -66,7 +66,7 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
         10700, 10700, 10700, 10700, 10700, 6000, 1000, 1000, 1000, 10700, 10700, 10700, 10700,
         10700, 10700, 10700,
     ],
-    unk_hws2_0: 0,
+    idle_off_standby_timer_default: 0,
     unk_hws2_4: None,
     unk_hws2_24: 0,
     global_unk_54: 0xffff,
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index f57700b7dc7016..80649c7bafd30f 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -696,7 +696,7 @@ impl<'a> InitDataBuilder::ver<'a> {
                     hws2 <- Self::hw_shared2(cfg, dyncfg),
                     hws3 <- Self::hw_shared3(cfg),
                     #[ver(V >= V13_0B4)]
-                    unk_hws2_0: cfg.unk_hws2_0,
+                    idle_off_standby_timer: pwr.idle_off_standby_timer,
                     #[ver(V >= V13_0B4)]
                     unk_hws2_4: cfg.unk_hws2_4.map(Array::new).unwrap_or_default(),
                     #[ver(V >= V13_0B4)]

From ee2d45c2d137bca3ae5c53b6697a63ec834ac11a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 15:02:35 +0900
Subject: [PATCH 0918/1027] drm/asahi: initdata: Fudge t3 calculation a bit

This gives exact numbers for G14D on the test machine. It looks like
a rounding thing, but it's not clear exactly what the original logic
would be here... I'm guessing the macOS driver uses floats and a
different order of operations and that's why it comes up with a
different result sometimes.

Also fix an incorrectly named variable.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/initdata.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 80649c7bafd30f..22a9bb66f72eb8 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -125,11 +125,11 @@ impl<'a> InitDataBuilder::ver<'a> {
                         continue;
                     }
 
-                    let f_mhz = (ps.freq_hz / 1000) as u64;
+                    let f_khz = (ps.freq_hz / 1000) as u64;
                     let v_max = ps.max_volt_mv() as u64;
 
                     t1.try_push(
-                        (1000000000 * (curve_cfg.t1_coef as u64) / (f_mhz * v_max))
+                        (1000000000 * (curve_cfg.t1_coef as u64) / (f_khz * v_max))
                             .try_into()
                             .unwrap(),
                         GFP_KERNEL,
@@ -137,7 +137,7 @@ impl<'a> InitDataBuilder::ver<'a> {
 
                     for (j, scale) in curve_cfg.t3_scales.iter().enumerate() {
                         t3[j].try_push(
-                            (t3_coef as u64 * 1000000000 * *scale as u64 / (f_mhz * v_max * 6))
+                            (t3_coef as u64 * 1000000100 * *scale as u64 / (f_khz * v_max * 6))
                                 .try_into()
                                 .unwrap(),
                         )?;

From bcd56140456fae8203f0bd8d73fea3d7e5e9fc48 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 18:46:22 +0900
Subject: [PATCH 0919/1027] drm/asahi: gpu,hw: Fix array IOMappings

These arrays aren't consecutive, but rather are supposed to have a
stride and possibly also span multiple dies. This doesn't affect T8xxx,
but I have no idea how this ever worked for T60xx...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/initdata.rs |  4 +-
 drivers/gpu/drm/asahi/gpu.rs         | 77 ++++++++++++++++++++++------
 drivers/gpu/drm/asahi/hw/mod.rs      | 18 +++++--
 drivers/gpu/drm/asahi/hw/t600x.rs    | 44 ++++++++--------
 drivers/gpu/drm/asahi/hw/t602x.rs    | 47 ++++++++---------
 drivers/gpu/drm/asahi/hw/t8103.rs    | 28 +++++-----
 drivers/gpu/drm/asahi/hw/t8112.rs    | 36 ++++++-------
 drivers/gpu/drm/asahi/mmu.rs         | 43 ++++++----------
 8 files changed, 171 insertions(+), 126 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index ca8c9fcdac2f7f..f6d766c5f0480e 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -591,8 +591,8 @@ pub(crate) mod raw {
     pub(crate) struct IOMapping {
         pub(crate) phys_addr: U64,
         pub(crate) virt_addr: U64,
-        pub(crate) size: u32,
-        pub(crate) range_size: u32,
+        pub(crate) total_size: u32,
+        pub(crate) element_size: u32,
         pub(crate) readwrite: U64,
     }
 
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index eca4e78bc49f30..857bc5832abd67 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -91,6 +91,10 @@ const IOVA_KERN_GPU_TOP: u64 = 0xffffffadffffffff;
 const IOVA_KERN_RTKIT_BASE: u64 = 0xffffffae00000000;
 /// GPU/FW shared structure VA range top.
 const IOVA_KERN_RTKIT_TOP: u64 = 0xffffffae0fffffff;
+/// FW MMIO VA range base.
+const IOVA_KERN_MMIO_BASE: u64 = 0xffffffaf00000000;
+/// FW MMIO VA range top.
+const IOVA_KERN_MMIO_TOP: u64 = 0xffffffafffffffff;
 
 /// GPU/FW buffer manager control address (context 0 low)
 pub(crate) const IOVA_KERN_GPU_BUFMGR_LOW: u64 = 0x20_0000_0000;
@@ -202,6 +206,7 @@ pub(crate) struct GpuManager {
     #[pin]
     alloc: Mutex<KernelAllocators>,
     io_mappings: Vec<mmu::Mapping>,
+    next_mmio_iova: u64,
     #[pin]
     rtkit: Mutex<Option<rtkit::RtKit<GpuManager::ver>>>,
     #[pin]
@@ -509,18 +514,19 @@ impl GpuManager::ver {
 
         for (i, map) in cfg.io_mappings.iter().enumerate() {
             if let Some(map) = map.as_ref() {
-                Self::iomap(&mut mgr, i, map)?;
+                Self::iomap(&mut mgr, cfg, i, map)?;
             }
         }
 
         #[ver(V >= V13_0B4)]
         if let Some(base) = cfg.sram_base {
             let size = cfg.sram_size.unwrap() as usize;
+            let iova = mgr.as_mut().alloc_mmio_iova(size);
 
             let mapping =
                 mgr.uat
                     .kernel_vm()
-                    .map_io(base as usize, size, mmu::PROT_FW_SHARED_RW)?;
+                    .map_io(iova, base as usize, size, mmu::PROT_FW_SHARED_RW)?;
 
             mgr.as_mut()
                 .initdata_mut()
@@ -561,6 +567,21 @@ impl GpuManager::ver {
         unsafe { &mut self.get_unchecked_mut().io_mappings }
     }
 
+    /// Allocate an MMIO iova range
+    fn alloc_mmio_iova(self: Pin<&mut Self>, size: usize) -> u64 {
+        // SAFETY: next_mmio_iova does not require structural pinning.
+        let next_ref = unsafe { &mut self.get_unchecked_mut().next_mmio_iova };
+
+        let addr = *next_ref;
+        let next = addr + (size + mmu::UAT_PGSZ) as u64;
+
+        assert!(next - 1 <= IOVA_KERN_MMIO_TOP);
+
+        *next_ref = next;
+
+        addr
+    }
+
     /// Build the entire GPU InitData structure tree and return it as a boxed GpuObject.
     fn make_initdata(
         dev: &AsahiDevice,
@@ -675,6 +696,7 @@ impl GpuManager::ver {
             initdata: *initdata,
             uat: *uat,
             io_mappings: Vec::new(),
+            next_mmio_iova: IOVA_KERN_MMIO_BASE,
             rtkit <- Mutex::new_named(None, c_str!("rtkit")),
             crashed: AtomicBool::new(false),
             event_manager,
@@ -804,21 +826,47 @@ impl GpuManager::ver {
     /// index.
     fn iomap(
         this: &mut Pin<UniqueArc<GpuManager::ver>>,
+        cfg: &'static hw::HwConfig,
         index: usize,
         map: &hw::IOMapping,
     ) -> Result {
+        let dies = if map.per_die {
+            cfg.num_dies as usize
+        } else {
+            1
+        };
+
         let off = map.base & mmu::UAT_PGMSK;
         let base = map.base - off;
         let end = (map.base + map.size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK;
-        let mapping = this.uat.kernel_vm().map_io(
-            base,
-            end - base,
-            if map.writable {
-                mmu::PROT_FW_MMIO_RW
-            } else {
-                mmu::PROT_FW_MMIO_RO
-            },
-        )?;
+        let map_size = end - base;
+
+        // Array mappings must be aligned
+        assert!((off == 0 && map_size == map.size) || (map.count == 1 && !map.per_die));
+        assert!(map.count > 0);
+
+        let iova = this.as_mut().alloc_mmio_iova(map_size * map.count * dies);
+        let mut cur_iova = iova;
+
+        for die in 0..dies {
+            for i in 0..map.count {
+                let phys_off = die * 0x20_0000_0000 + i * map.stride;
+
+                let mapping = this.uat.kernel_vm().map_io(
+                    cur_iova,
+                    base + phys_off,
+                    map_size,
+                    if map.writable {
+                        mmu::PROT_FW_MMIO_RW
+                    } else {
+                        mmu::PROT_FW_MMIO_RO
+                    },
+                )?;
+
+                this.as_mut().io_mappings_mut().push(mapping, GFP_KERNEL)?;
+                cur_iova += map_size as u64;
+            }
+        }
 
         this.as_mut()
             .initdata_mut()
@@ -827,14 +875,13 @@ impl GpuManager::ver {
             .with_mut(|raw, _| {
                 raw.io_mappings[index] = fw::initdata::raw::IOMapping {
                     phys_addr: U64(map.base as u64),
-                    virt_addr: U64((mapping.iova() + off) as u64),
-                    size: map.size as u32,
-                    range_size: map.range_size as u32,
+                    virt_addr: U64(iova + off as u64),
+                    total_size: (map.size * map.count * dies) as u32,
+                    element_size: map.size as u32,
                     readwrite: U64(map.writable as u64),
                 };
             });
 
-        this.as_mut().io_mappings_mut().try_push(mapping)?;
         Ok(())
     }
 
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index d7604c3943b2f5..c6269c57b505c7 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -133,10 +133,14 @@ pub(crate) struct PowerZone {
 pub(crate) struct IOMapping {
     /// Base physical address of the mapping.
     pub(crate) base: usize,
-    /// Size of the mapping.
+    /// Whether this mapping should be replicated to all dies
+    pub(crate) per_die: bool,
+    /// Number of mappings.
+    pub(crate) count: usize,
+    /// Size of one mapping.
     pub(crate) size: usize,
-    /// Range size of the mapping (for arrays?)
-    pub(crate) range_size: usize,
+    /// Stride between mappings.
+    pub(crate) stride: usize,
     /// Whether the mapping should be writable.
     pub(crate) writable: bool,
 }
@@ -145,14 +149,18 @@ impl IOMapping {
     /// Convenience constructor for a new IOMapping.
     pub(crate) const fn new(
         base: usize,
+        per_die: bool,
+        count: usize,
         size: usize,
-        range_size: usize,
+        stride: usize,
         writable: bool,
     ) -> IOMapping {
         IOMapping {
             base,
+            per_die,
+            count,
             size,
-            range_size,
+            stride,
             writable,
         }
     }
diff --git a/drivers/gpu/drm/asahi/hw/t600x.rs b/drivers/gpu/drm/asahi/hw/t600x.rs
index 2d81ae42fb62f2..962e065587136d 100644
--- a/drivers/gpu/drm/asahi/hw/t600x.rs
+++ b/drivers/gpu/drm/asahi/hw/t600x.rs
@@ -8,36 +8,38 @@ use super::*;
 
 const fn iomaps(mcc_count: usize, has_die1: bool) -> [Option<IOMapping>; 20] {
     [
-        Some(IOMapping::new(0x404d00000, 0x1c000, 0x1c000, true)), // Fender
-        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
-        Some(IOMapping::new(0x28e104000, 0x4000, 0x4000, true)),   // AICSWInt
-        Some(IOMapping::new(0x404000000, 0x20000, 0x20000, true)), // RGX
-        None,                                                      // UVD
-        None,                                                      // unused
-        None,                                                      // DisplayUnderrunWA
-        Some(IOMapping::new(0x28e494000, 0x8000, 0x4000, false)),  // AnalogTempSensorControllerRegs
-        None,                                                      // PMPDoorbell
-        Some(IOMapping::new(0x404d80000, 0x8000, 0x8000, true)),   // MetrologySensorRegs
-        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
+        Some(IOMapping::new(0x404d00000, false, 1, 0x1c000, 0, true)), // Fender
+        Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer
+        Some(IOMapping::new(0x28e104000, false, 1, 0x4000, 0, true)),  // AICSWInt
+        Some(IOMapping::new(0x404000000, false, 1, 0x20000, 0, true)), // RGX
+        None,                                                          // UVD
+        None,                                                          // unused
+        None,                                                          // DisplayUnderrunWA
+        Some(IOMapping::new(0x28e494000, true, 1, 0x4000, 0, false)), // AnalogTempSensorControllerRegs
+        None,                                                         // PMPDoorbell
+        Some(IOMapping::new(0x404d80000, false, 1, 0x8000, 0, true)), // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)), // GMGIFAFRegs
         Some(IOMapping::new(
             0x200000000,
-            mcc_count * 0xd8000,
+            true,
+            mcc_count,
             0xd8000,
+            0x1000000,
             true,
         )), // MCache registers
-        None,                                                      // AICBankedRegisters
-        None,                                                      // PMGRScratch
-        Some(IOMapping::new(0x2643c4000, 0x1000, 0x1000, true)), // NIA Special agent idle register die 0
+        None,                                                         // AICBankedRegisters
+        None,                                                         // PMGRScratch
+        Some(IOMapping::new(0x2643c4000, false, 1, 0x1000, 0, true)), // NIA Special agent idle register die 0
         if has_die1 {
             // NIA Special agent idle register die 1
-            Some(IOMapping::new(0x22643c4000, 0x1000, 0x1000, true))
+            Some(IOMapping::new(0x22643c4000, false, 1, 0x1000, 0, true))
         } else {
             None
         },
-        None,                                                     // CRE registers
-        None,                                                     // Streaming codec registers
-        Some(IOMapping::new(0x28e3d0000, 0x1000, 0x1000, true)),  // ?
-        Some(IOMapping::new(0x28e3c0000, 0x2000, 0x2000, false)), // ?
+        None,                                                          // CRE registers
+        None,                                                          // Streaming codec registers
+        Some(IOMapping::new(0x28e3d0000, false, 1, 0x1000, 0, true)),  // ?
+        Some(IOMapping::new(0x28e3c0000, false, 1, 0x2000, 0, false)), // ?
     ]
 }
 
@@ -128,7 +130,7 @@ pub(crate) const HWCONFIG_T6002: super::HwConfig = HwConfig {
     fast_sensor_mask: [0x8080808080808080, 0],
     fast_sensor_mask_alt: [0x9090909090909090, 0],
     fast_die0_sensor_present: 0xff,
-    io_mappings: &iomaps(16, true),
+    io_mappings: &iomaps(8, true),
     sram_base: None,
     sram_size: None,
 };
diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
index 63b920fc403133..8872d215f924ba 100644
--- a/drivers/gpu/drm/asahi/hw/t602x.rs
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -8,41 +8,38 @@ use super::*;
 
 const fn iomaps(chip_id: u32, mcc_count: usize) -> [Option<IOMapping>; 24] {
     [
-        Some(IOMapping::new(0x404d00000, 0x144000, 0x144000, true)), // Fender
-        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),    // AICTimer
-        Some(IOMapping::new(0x28e106000, 0x4000, 0x4000, true)),     // AICSWInt
-        Some(IOMapping::new(0x404000000, 0x20000, 0x20000, true)),   // RGX
-        None,                                                        // UVD
-        None,                                                        // unused
-        None,                                                        // DisplayUnderrunWA
+        Some(IOMapping::new(0x404d00000, false, 1, 0x144000, 0, true)), // Fender
+        Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)),  // AICTimer
+        Some(IOMapping::new(0x28e106000, false, 1, 0x4000, 0, true)),   // AICSWInt
+        Some(IOMapping::new(0x404000000, false, 1, 0x20000, 0, true)),  // RGX
+        None,                                                           // UVD
+        None,                                                           // unused
+        None,                                                           // DisplayUnderrunWA
         Some(match chip_id {
-            0x6020 => IOMapping::new(0x28e460000, 0x4000, 0x4000, false),
-            _ => IOMapping::new(0x28e478000, 0x8000, 0x4000, false),
+            0x6020 => IOMapping::new(0x28e460000, true, 1, 0x4000, 0, false),
+            _ => IOMapping::new(0x28e478000, true, 1, 0x4000, 0, false),
         }), // AnalogTempSensorControllerRegs
-        None,                                                        // PMPDoorbell
-        Some(IOMapping::new(0x404e08000, 0x8000, 0x8000, true)),     // MetrologySensorRegs
-        None,                                                        // GMGIFAFRegs
+        None,                                                           // PMPDoorbell
+        Some(IOMapping::new(0x404e08000, false, 1, 0x8000, 0, true)),   // MetrologySensorRegs
+        None,                                                           // GMGIFAFRegs
         Some(IOMapping::new(
             0x200000000,
-            mcc_count * 0xd8000,
+            true,
+            mcc_count,
             0xd8000,
+            0x1000000,
             true,
         )), // MCache registers
-        Some(IOMapping::new(0x28e118000, 0x4000, 0x4000, false)),    // AICBankedRegisters
-        None,                                                        // PMGRScratch
+        Some(IOMapping::new(0x28e118000, false, 1, 0x4000, 0, false)),  // AICBankedRegisters
+        None,                                                           // PMGRScratch
         None, // NIA Special agent idle register die 0
         None, // NIA Special agent idle register die 1
         None, // CRE registers
         None, // Streaming codec registers
-        Some(IOMapping::new(0x28e3d0000, 0x4000, 0x4000, true)), // ?
-        Some(IOMapping::new(0x28e3c0000, 0x4000, 0x4000, false)), // ?
-        Some(IOMapping::new(0x28e3d8000, 0x4000, 0x4000, true)), // ?
-        Some(IOMapping::new(
-            0x404eac000,
-            if mcc_count > 8 { 0x8000 } else { 0x4000 },
-            0x4000,
-            true,
-        )), // ?
+        Some(IOMapping::new(0x28e3d0000, false, 1, 0x4000, 0, true)), // ?
+        Some(IOMapping::new(0x28e3c0000, false, 1, 0x4000, 0, false)), // ?
+        Some(IOMapping::new(0x28e3d8000, false, 1, 0x4000, 0, true)), // ?
+        Some(IOMapping::new(0x404eac000, true, 1, 0x4000, 0, true)), // ?
         None,
         None,
     ]
@@ -146,7 +143,7 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
     // Apple typo? Should probably be 0x140015001c001d00
     fast_sensor_mask_alt: [0x140015001d001d00, 0x1d001c0015001400],
     fast_die0_sensor_present: 0, // Unused
-    io_mappings: &iomaps(0x6022, 16),
+    io_mappings: &iomaps(0x6022, 8),
     sram_base: Some(0x404d60000),
     sram_size: Some(0x20000),
 };
diff --git a/drivers/gpu/drm/asahi/hw/t8103.rs b/drivers/gpu/drm/asahi/hw/t8103.rs
index 50a4bd0a0ff1c5..7b88c7374afb7d 100644
--- a/drivers/gpu/drm/asahi/hw/t8103.rs
+++ b/drivers/gpu/drm/asahi/hw/t8103.rs
@@ -68,20 +68,20 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
     fast_sensor_mask_alt: [0x12, 0],
     fast_die0_sensor_present: 0x01,
     io_mappings: &[
-        Some(IOMapping::new(0x204d00000, 0x1c000, 0x1c000, true)), // Fender
-        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
-        Some(IOMapping::new(0x23b104000, 0x4000, 0x4000, true)),   // AICSWInt
-        Some(IOMapping::new(0x204000000, 0x20000, 0x20000, true)), // RGX
-        None,                                                      // UVD
-        None,                                                      // unused
-        None,                                                      // DisplayUnderrunWA
-        Some(IOMapping::new(0x23b2e8000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
-        Some(IOMapping::new(0x23bc00000, 0x1000, 0x1000, true)),   // PMPDoorbell
-        Some(IOMapping::new(0x204d80000, 0x5000, 0x5000, true)),   // MetrologySensorRegs
-        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
-        Some(IOMapping::new(0x200000000, 0xd6400, 0xd6400, true)), // MCache registers
-        None,                                                      // AICBankedRegisters
-        Some(IOMapping::new(0x23b738000, 0x1000, 0x1000, true)),   // PMGRScratch
+        Some(IOMapping::new(0x204d00000, false, 1, 0x1c000, 0, true)), // Fender
+        Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer
+        Some(IOMapping::new(0x23b104000, false, 1, 0x4000, 0, true)),  // AICSWInt
+        Some(IOMapping::new(0x204000000, false, 1, 0x20000, 0, true)), // RGX
+        None,                                                          // UVD
+        None,                                                          // unused
+        None,                                                          // DisplayUnderrunWA
+        Some(IOMapping::new(0x23b2e8000, false, 1, 0x1000, 0, false)), // AnalogTempSensorControllerRegs
+        Some(IOMapping::new(0x23bc00000, false, 1, 0x1000, 0, true)),  // PMPDoorbell
+        Some(IOMapping::new(0x204d80000, false, 1, 0x5000, 0, true)),  // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)),  // GMGIFAFRegs
+        Some(IOMapping::new(0x200000000, false, 1, 0xd6400, 0, true)), // MCache registers
+        None,                                                          // AICBankedRegisters
+        Some(IOMapping::new(0x23b738000, false, 1, 0x1000, 0, true)),  // PMGRScratch
         None, // NIA Special agent idle register die 0
         None, // NIA Special agent idle register die 1
         None, // CRE registers
diff --git a/drivers/gpu/drm/asahi/hw/t8112.rs b/drivers/gpu/drm/asahi/hw/t8112.rs
index bc868cafbb4680..012c5422721912 100644
--- a/drivers/gpu/drm/asahi/hw/t8112.rs
+++ b/drivers/gpu/drm/asahi/hw/t8112.rs
@@ -81,26 +81,26 @@ pub(crate) const HWCONFIG: super::HwConfig = HwConfig {
     fast_sensor_mask_alt: [0x6800, 0],
     fast_die0_sensor_present: 0x02,
     io_mappings: &[
-        Some(IOMapping::new(0x204d00000, 0x14000, 0x14000, true)), // Fender
-        Some(IOMapping::new(0x20e100000, 0x4000, 0x4000, false)),  // AICTimer
-        Some(IOMapping::new(0x23b0c4000, 0x4000, 0x4000, true)),   // AICSWInt
-        Some(IOMapping::new(0x204000000, 0x20000, 0x20000, true)), // RGX
-        None,                                                      // UVD
-        None,                                                      // unused
-        None,                                                      // DisplayUnderrunWA
-        Some(IOMapping::new(0x23b2c0000, 0x1000, 0x1000, false)),  // AnalogTempSensorControllerRegs
-        None,                                                      // PMPDoorbell
-        Some(IOMapping::new(0x204d80000, 0x8000, 0x8000, true)),   // MetrologySensorRegs
-        Some(IOMapping::new(0x204d61000, 0x1000, 0x1000, true)),   // GMGIFAFRegs
-        Some(IOMapping::new(0x200000000, 0xd6400, 0xd6400, true)), // MCache registers
-        None,                                                      // AICBankedRegisters
-        None,                                                      // PMGRScratch
+        Some(IOMapping::new(0x204d00000, false, 1, 0x14000, 0, true)), // Fender
+        Some(IOMapping::new(0x20e100000, false, 1, 0x4000, 0, false)), // AICTimer
+        Some(IOMapping::new(0x23b0c4000, false, 1, 0x4000, 0, true)),  // AICSWInt
+        Some(IOMapping::new(0x204000000, false, 1, 0x20000, 0, true)), // RGX
+        None,                                                          // UVD
+        None,                                                          // unused
+        None,                                                          // DisplayUnderrunWA
+        Some(IOMapping::new(0x23b2c0000, false, 1, 0x1000, 0, false)), // AnalogTempSensorControllerRegs
+        None,                                                          // PMPDoorbell
+        Some(IOMapping::new(0x204d80000, false, 1, 0x8000, 0, true)),  // MetrologySensorRegs
+        Some(IOMapping::new(0x204d61000, false, 1, 0x1000, 0, true)),  // GMGIFAFRegs
+        Some(IOMapping::new(0x200000000, false, 1, 0xd6400, 0, true)), // MCache registers
+        None,                                                          // AICBankedRegisters
+        None,                                                          // PMGRScratch
         None, // NIA Special agent idle register die 0
         None, // NIA Special agent idle register die 1
-        Some(IOMapping::new(0x204e00000, 0x10000, 0x10000, true)), // CRE registers
-        Some(IOMapping::new(0x27d050000, 0x4000, 0x4000, true)), // Streaming codec registers
-        Some(IOMapping::new(0x23b3d0000, 0x1000, 0x1000, true)), //
-        Some(IOMapping::new(0x23b3c0000, 0x1000, 0x1000, false)), //
+        Some(IOMapping::new(0x204e00000, false, 1, 0x10000, 0, true)), // CRE registers
+        Some(IOMapping::new(0x27d050000, false, 1, 0x4000, 0, true)), // Streaming codec registers
+        Some(IOMapping::new(0x23b3d0000, false, 1, 0x1000, 0, true)), //
+        Some(IOMapping::new(0x23b3c0000, false, 1, 0x1000, 0, false)), //
     ],
     sram_base: None,
     sram_size: None,
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index a67859ae3d0234..22407748f27f4b 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -76,10 +76,6 @@ const IOVA_KERN_BASE: usize = 0xffffffa000000000;
 /// Driver-managed kernel top VA
 const IOVA_KERN_TOP: usize = 0xffffffafffffffff;
 
-/// Range reserved for MMIO maps
-const IOVA_KERN_MMIO_BASE: usize = 0xffffffaf00000000;
-const IOVA_KERN_MMIO_TOP: usize = 0xffffffafffffffff;
-
 const TTBR_VALID: u64 = 0x1; // BIT(0)
 const TTBR_ASID_SHIFT: usize = 48;
 
@@ -889,29 +885,10 @@ impl Vm {
     }
 
     /// Add a direct MMIO mapping to this Vm at a free address.
-    pub(crate) fn map_io(&self, phys: usize, size: usize, prot: u32) -> Result<Mapping> {
+    pub(crate) fn map_io(&self, iova: u64, phys: usize, size: usize, prot: u32) -> Result<Mapping> {
         let mut inner = self.inner.lock();
 
-        let uat_inner = inner.uat_inner.clone();
-        let node = inner.mm.insert_node_in_range(
-            MappingInner {
-                owner: self.inner.clone(),
-                uat_inner,
-                prot,
-                sgt: None,
-                mapped_size: size,
-            },
-            (size + UAT_PGSZ) as u64, // Add guard page
-            UAT_PGSZ as u64,
-            0,
-            IOVA_KERN_MMIO_BASE as u64,
-            IOVA_KERN_MMIO_TOP as u64,
-            mm::InsertMode::Best,
-        )?;
-
-        let iova = node.start() as usize;
-
-        if (phys | size | iova) & UAT_PGMSK != 0 {
+        if (iova as usize | phys | size) & UAT_PGMSK != 0 {
             dev_err!(
                 inner.dev,
                 "MMU: Mapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
@@ -930,7 +907,21 @@ impl Vm {
             iova
         );
 
-        inner.map_pages(iova, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
+        let uat_inner = inner.uat_inner.clone();
+        let node = inner.mm.reserve_node(
+            MappingInner {
+                owner: self.inner.clone(),
+                uat_inner,
+                prot,
+                sgt: None,
+                mapped_size: size,
+            },
+            iova,
+            size as u64,
+            0,
+        )?;
+
+        inner.map_pages(iova as usize, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
 
         Ok(Mapping(node))
     }

From bb51a54c4e78f3da25fb919bdf6be5df971911fc Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 9 Aug 2023 19:50:00 +0900
Subject: [PATCH 0920/1027] drm/asahi: hw/t602x: Increase meta/preempt sizes
 for G14D

meta2,meta3 confirmed, meta4 conservative guess (it's small anyway). Not
sure yet if max_splits also needs to be doubled.

Compute preempt just conservatively doubled. Since that one is a bit
larger, use the smaller size for <= G14C.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/hw/t602x.rs | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/hw/t602x.rs b/drivers/gpu/drm/asahi/hw/t602x.rs
index 8872d215f924ba..23efa413f85f6e 100644
--- a/drivers/gpu/drm/asahi/hw/t602x.rs
+++ b/drivers/gpu/drm/asahi/hw/t602x.rs
@@ -65,12 +65,12 @@ pub(crate) const HWCONFIG_T6022: super::HwConfig = HwConfig {
     preempt1_size: 0x540,
     preempt2_size: 0x280,
     preempt3_size: 0x40,
-    compute_preempt1_size: 0x25980, // CHECK for T6022
+    compute_preempt1_size: 0x25980 * 2, // Conservative guess
     clustering: Some(HwClusteringConfig {
         meta1_blocksize: 0x44,
-        meta2_size: 0xc0 * 8,
-        meta3_size: 0x280 * 8,
-        meta4_size: 0x10 * 64,
+        meta2_size: 0xc0 * 16,
+        meta3_size: 0x280 * 16,
+        meta4_size: 0x10 * 128,
         max_splits: 64,
     }),
 
@@ -155,6 +155,7 @@ pub(crate) const HWCONFIG_T6021: super::HwConfig = HwConfig {
 
     num_dies: 1,
     max_num_clusters: 4,
+    compute_preempt1_size: 0x25980,
     unk_hws2_4: Some(f32!([1.0, 0.8, 0.2, 0.9, 0.1, 0.25, 0.7, 0.9])),
     fast_sensor_mask: [0x40005000c000d00, 0],
     fast_sensor_mask_alt: [0x140015001d001d00, 0],

From ac2971da0c83f5cd42c366001eec16b0c3004016 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 13:19:09 +0900
Subject: [PATCH 0921/1027] drm/asahi: Expose firmware version to userspace

Might be useful for something.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs   |  6 ++++++
 drivers/gpu/drm/asahi/gpu.rs    | 19 +++++++++++++------
 drivers/gpu/drm/asahi/hw/mod.rs |  2 ++
 3 files changed, 21 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index dbad7f9afd4b68..d24daba60cd169 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -247,12 +247,18 @@ impl File {
 
             result_render_size: core::mem::size_of::<uapi::drm_asahi_result_render>() as u32,
             result_compute_size: core::mem::size_of::<uapi::drm_asahi_result_compute>() as u32,
+
+            firmware_version: [0; 4],
         };
 
         for (i, mask) in gpu.get_dyncfg().id.core_masks.iter().enumerate() {
             *(params.core_masks.get_mut(i).ok_or(EIO)?) = (*mask).into();
         }
 
+        for i in 0..3 {
+            params.firmware_version[i] = *gpu.get_dyncfg().firmware_version.get(i).unwrap_or(&0);
+        }
+
         let size = core::mem::size_of::<uapi::drm_asahi_params_global>().min(data.size.try_into()?);
 
         // SAFETY: We only write to this userptr once, so there are no TOCTOU issues.
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 857bc5832abd67..e9200be2477249 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -18,6 +18,7 @@ use core::time::Duration;
 use kernel::{
     c_str,
     delay::coarse_sleep,
+    device::RawDevice,
     error::code::*,
     macros::versions,
     prelude::*,
@@ -810,16 +811,22 @@ impl GpuManager::ver {
             return Err(EIO);
         }
 
-        Ok(Box::try_new(hw::DynConfig {
-            pwr: pwr_cfg,
-            uat_ttb_base: uat.ttb_base(),
-            id: gpu_id,
-        })?)
+        let node = dev.of_node().ok_or(EIO)?;
+
+        Ok(Box::new(
+            hw::DynConfig {
+                pwr: pwr_cfg,
+                uat_ttb_base: uat.ttb_base(),
+                id: gpu_id,
+                firmware_version: node.get_property(c_str!("apple,firmware-version"))?,
+            },
+            GFP_KERNEL,
+        )?)
     }
 
     /// Create the global GPU event manager, and return an `Arc<>` to it.
     fn make_event_manager(alloc: &mut KernelAllocators) -> Result<Arc<event::EventManager>> {
-        Ok(Arc::try_new(event::EventManager::new(alloc)?)?)
+        Ok(Arc::new(event::EventManager::new(alloc)?, GFP_KERNEL)?)
     }
 
     /// Create a new MMIO mapping and add it to the mappings list in initdata at the specified
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index c6269c57b505c7..0d28299da764c1 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -321,6 +321,8 @@ pub(crate) struct DynConfig {
     pub(crate) id: GpuIdConfig,
     /// Power calibration configuration for this specific chip/device.
     pub(crate) pwr: PwrConfig,
+    /// Firmware version.
+    pub(crate) firmware_version: Vec<u32>,
 }
 
 /// Specific GPU ID configuration fetched from SGX MMIO registers.

From 07cdc666b7f9c586d2b339366ac545ec5fa0b8c9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:22:57 +0900
Subject: [PATCH 0922/1027] drm/asahi: alloc: Support tagging array allocs

It's hard to tell what a given array buffer is just from the type, so
add support for explicitly adding a u32 tag. This can help us
differentiate between allocs in the debug codepaths or when dumping
memory.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs | 28 +++++++++++++++++++++-------
 1 file changed, 21 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index f042cd7ebcbee0..808e8a62352634 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -112,7 +112,7 @@ impl<T, U: RawAllocation> Debug for GenericAlloc<T, U> {
 #[repr(C)]
 struct AllocDebugData {
     state: u32,
-    _pad: u32,
+    tag: u32,
     size: u64,
     base_gpuva: u64,
     obj_gpuva: u64,
@@ -120,9 +120,9 @@ struct AllocDebugData {
 }
 
 /// Magic flag indicating a live allocation.
-const STATE_LIVE: u32 = 0x4556494c;
+const STATE_LIVE: u32 = u32::from_le_bytes(*b"LIVE");
 /// Magic flag indicating a freed allocation.
-const STATE_DEAD: u32 = 0x44414544;
+const STATE_DEAD: u32 = u32::from_le_bytes(*b"DEAD");
 
 /// Marker byte to identify when firmware/GPU write beyond the end of an allocation.
 const GUARD_MARKER: u8 = 0x93;
@@ -292,6 +292,7 @@ pub(crate) trait Allocator {
         &mut self,
         size: usize,
         align: usize,
+        tag: Option<u32>,
     ) -> Result<GenericAlloc<T, Self::Raw>> {
         let padding = if debug_enabled(DebugFlags::DetectOverflows) {
             size
@@ -309,7 +310,7 @@ pub(crate) trait Allocator {
 
                 let mut debug = AllocDebugData {
                     state: STATE_LIVE,
-                    _pad: 0,
+                    tag: tag.unwrap_or(0),
                     size: size as u64,
                     base_gpuva: alloc.gpu_ptr(),
                     obj_gpuva: alloc.gpu_ptr() + debug_offset as u64,
@@ -376,7 +377,7 @@ pub(crate) trait Allocator {
         let size = mem::size_of::<T::Raw<'static>>();
         let align = mem::align_of::<T::Raw<'static>>();
 
-        self.alloc_generic(size, align)
+        self.alloc_generic(size, align, None)
     }
 
     /// Allocate an empty `GpuArray` of a given type and length.
@@ -387,7 +388,20 @@ pub(crate) trait Allocator {
         let size = mem::size_of::<T>() * count;
         let align = mem::align_of::<T>();
 
-        let alloc = self.alloc_generic(size, align)?;
+        let alloc = self.alloc_generic(size, align, None)?;
+        GpuArray::<T, GenericAlloc<T, Self::Raw>>::empty(alloc, count)
+    }
+
+    /// Allocate an empty `GpuArray` of a given type and length.
+    fn array_empty_tagged<T: Sized + Default>(
+        &mut self,
+        count: usize,
+        tag: &[u8; 4],
+    ) -> Result<GpuArray<T, GenericAlloc<T, Self::Raw>>> {
+        let size = mem::size_of::<T>() * count;
+        let align = mem::align_of::<T>();
+
+        let alloc = self.alloc_generic(size, align, Some(u32::from_le_bytes(*tag)))?;
         GpuArray::<T, GenericAlloc<T, Self::Raw>>::empty(alloc, count)
     }
 
@@ -399,7 +413,7 @@ pub(crate) trait Allocator {
         let size = mem::size_of::<T>() * count;
         let align = mem::align_of::<T>();
 
-        let alloc = self.alloc_generic(size, align)?;
+        let alloc = self.alloc_generic(size, align, None)?;
         GpuOnlyArray::<T, GenericAlloc<T, Self::Raw>>::new(alloc, count)
     }
 }

From c63b1abcc6f1c07cc7b14670a4da2804160487f9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:25:55 +0900
Subject: [PATCH 0923/1027] drm/asahi: alloc: Use tag as the guard marker

To more easily debug GPU/FW-side overreads, use the alloc tag to fill
the padding instead of using a constant.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs | 44 +++++++++++++++++++++++-----------
 1 file changed, 30 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index 808e8a62352634..073ca63f51ee1f 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -78,6 +78,8 @@ pub(crate) struct GenericAlloc<T, U: RawAllocation> {
     alloc_size: usize,
     debug_offset: usize,
     padding: usize,
+    tag: u32,
+    pad_word: u32,
     _p: PhantomData<T>,
 }
 
@@ -125,7 +127,7 @@ const STATE_LIVE: u32 = u32::from_le_bytes(*b"LIVE");
 const STATE_DEAD: u32 = u32::from_le_bytes(*b"DEAD");
 
 /// Marker byte to identify when firmware/GPU write beyond the end of an allocation.
-const GUARD_MARKER: u8 = 0x93;
+const GUARD_MARKER: u32 = 0x93939393;
 
 impl<T, U: RawAllocation> Drop for GenericAlloc<T, U> {
     fn drop(&mut self) {
@@ -151,20 +153,26 @@ impl<T, U: RawAllocation> Drop for GenericAlloc<T, U> {
                         self.padding,
                     )
                 };
-                if let Some(first_err) = guard.iter().position(|&r| r != GUARD_MARKER) {
-                    let last_err = guard
-                        .iter()
-                        .rev()
-                        .position(|&r| r != GUARD_MARKER)
-                        .unwrap_or(0);
+                let mut first_err = None;
+                let mut last_err = 0;
+                for (i, p) in guard.iter().enumerate() {
+                    if *p != (self.pad_word >> (8 * (i & 3))) as u8 {
+                        if first_err.is_none() {
+                            first_err = Some(i);
+                        }
+                        last_err = i;
+                    }
+                }
+                if let Some(start) = first_err {
                     dev_warn!(
                         self.device(),
-                        "Allocator: Corruption after object of type {} at {:#x}:{:#x} + {:#x}..={:#x}\n",
+                        "Allocator: Corruption after object of type {}/{:#x} at {:#x}:{:#x} + {:#x}..={:#x}\n",
                         core::any::type_name::<T>(),
+                        self.tag,
                         self.gpu_ptr(),
                         self.size(),
-                        first_err,
-                        self.padding - last_err - 1
+                        start,
+                        last_err,
                     );
                 }
             }
@@ -336,6 +344,8 @@ pub(crate) trait Allocator {
                     alloc,
                     alloc_size: size,
                     debug_offset,
+                    tag: tag.unwrap_or(0),
+                    pad_word: tag.unwrap_or(GUARD_MARKER) | 0x81818181,
                     padding,
                     _p: PhantomData,
                 }
@@ -344,6 +354,8 @@ pub(crate) trait Allocator {
                     alloc: self.alloc(size + padding, align)?,
                     alloc_size: size,
                     debug_offset: 0,
+                    tag: tag.unwrap_or(0),
+                    pad_word: tag.unwrap_or(GUARD_MARKER) | 0x81818181,
                     padding,
                     _p: PhantomData,
                 }
@@ -357,10 +369,14 @@ pub(crate) trait Allocator {
 
         if padding != 0 {
             if let Some(p) = ret.ptr() {
-                unsafe {
-                    (p.as_ptr() as *mut u8)
-                        .add(ret.size())
-                        .write_bytes(GUARD_MARKER, padding);
+                let guard = unsafe {
+                    core::slice::from_raw_parts_mut(
+                        (p.as_ptr() as *mut u8).add(ret.size()),
+                        padding,
+                    )
+                };
+                for (i, p) in guard.iter_mut().enumerate() {
+                    *p = (ret.pad_word >> (8 * (i & 3))) as u8;
                 }
             }
         }

From cd8ed2dfbe74a60951d87f0834fca86c96a8ee14 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:29:52 +0900
Subject: [PATCH 0924/1027] drm/asahi: buffer: Add tags to all the buffers

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs | 53 +++++++++++++++++++--------------
 1 file changed, 31 insertions(+), 22 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index 15e834ceb07fcf..3ade6e3416f987 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -336,8 +336,10 @@ impl Buffer::ver {
                 try_init!(buffer::Info::ver {
                     block_ctl: shared.new_default::<buffer::BlockControl>()?,
                     counter: shared.new_default::<buffer::Counter>()?,
-                    page_list: ualloc_priv.lock().array_empty(max_pages)?,
-                    block_list: ualloc_priv.lock().array_empty(max_blocks * 2)?,
+                    page_list: ualloc_priv.lock().array_empty_tagged(max_pages, b"PLST")?,
+                    block_list: ualloc_priv
+                        .lock()
+                        .array_empty_tagged(max_blocks * 2, b"BLST")?,
                 })
             },
             |inner, _p| {
@@ -380,7 +382,7 @@ impl Buffer::ver {
         )?;
 
         // Technically similar to Scene below, let's play it safe.
-        let kernel_buffer = alloc.shared.array_empty(0x40)?;
+        let kernel_buffer = alloc.shared.array_empty_tagged(0x40, b"KBUF")?;
         let stats = alloc
             .shared
             .new_object(Default::default(), |_inner| buffer::raw::Stats {
@@ -538,20 +540,26 @@ impl Buffer::ver {
         // On M1 Ultra, it can grow and usually doesn't exceed 64 entries.
         // macOS allocates a whole 64K * 0x80 for this, so let's go with
         // that to be safe...
-        let user_buffer = inner.ualloc.lock().array_empty(if inner.num_clusters > 1 {
-            0x10080
-        } else {
-            0x80
-        })?;
-
-        let tvb_heapmeta = inner.ualloc.lock().array_empty(0x200)?;
-        let tvb_tilemap = inner.ualloc.lock().array_empty(tilemap_size)?;
+        let user_buffer = inner.ualloc.lock().array_empty_tagged(
+            if inner.num_clusters > 1 {
+                0x10080
+            } else {
+                0x80
+            },
+            b"UBUF",
+        )?;
 
-        mod_pr_debug!("Buffer: Allocating misc buffers\n");
-        let preempt_buf = inner
+        let tvb_heapmeta = inner.ualloc.lock().array_empty_tagged(0x200, b"HMTA")?;
+        let tvb_tilemap = inner
             .ualloc
             .lock()
-            .array_empty(inner.preempt1_size + inner.preempt2_size + inner.preempt3_size)?;
+            .array_empty_tagged(tilemap_size, b"TMAP")?;
+
+        mod_pr_debug!("Buffer: Allocating misc buffers\n");
+        let preempt_buf = inner.ualloc.lock().array_empty_tagged(
+            inner.preempt1_size + inner.preempt2_size + inner.preempt3_size,
+            b"PRMT",
+        )?;
 
         let tpc = match inner.tpc.as_ref() {
             Some(buf) if buf.len() >= tpc_size => buf.clone(),
@@ -560,11 +568,12 @@ impl Buffer::ver {
                 // priv seems to work and might be faster?
                 // Needs to be FW-writable anyway, so ualloc
                 // won't work.
-                let buf = Arc::try_new(
-                    inner
-                        .ualloc_priv
-                        .lock()
-                        .array_empty((tpc_size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK)?,
+                let buf = Arc::new(
+                    inner.ualloc_priv.lock().array_empty_tagged(
+                        (tpc_size + mmu::UAT_PGMSK) & !mmu::UAT_PGMSK,
+                        b"TPC ",
+                    )?,
+                    GFP_KERNEL,
                 )?;
                 inner.tpc = Some(buf.clone());
                 buf
@@ -591,8 +600,8 @@ impl Buffer::ver {
             let tilemaps = inner
                 .ualloc
                 .lock()
-                .array_empty(cfg.max_splits * tilemap_size)?;
-            let meta = inner.ualloc.lock().array_empty(meta_size)?;
+                .array_empty_tagged(cfg.max_splits * tilemap_size, b"CTMP")?;
+            let meta = inner.ualloc.lock().array_empty_tagged(meta_size, b"CMTA")?;
             Some(buffer::ClusterBuffers { tilemaps, meta })
         } else {
             None
@@ -620,7 +629,7 @@ impl Buffer::ver {
                 clustering: clustering,
                 preempt_buf: preempt_buf,
                 #[ver(G >= G14X)]
-                control_word: _gpu.array_empty(1)?,
+                control_word: _gpu.array_empty_tagged(1, b"CWRD")?,
             }),
             |inner, _p| {
                 try_init!(buffer::raw::Scene::ver {

From a2e8e7febc351c5bd845d17a1678ca6e2d027943 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:43:19 +0900
Subject: [PATCH 0925/1027] drm/asahi: initdata: Tag more arrays

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/initdata.rs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 22a9bb66f72eb8..31cb7070289ee0 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -785,13 +785,13 @@ impl<'a> InitDataBuilder::ver<'a> {
                     },
 
                     hwdata_a: hwa,
-                    unkptr_190: alloc.private.array_empty(0x80)?,
-                    unkptr_198: alloc.private.array_empty(0xc0)?,
+                    unkptr_190: alloc.private.array_empty_tagged(0x80, b"I190")?,
+                    unkptr_198: alloc.private.array_empty_tagged(0xc0, b"I198")?,
                     hwdata_b: hwb,
 
-                    unkptr_1b8: alloc.private.array_empty(0x1000)?,
-                    unkptr_1c0: alloc.private.array_empty(0x300)?,
-                    unkptr_1c8: alloc.private.array_empty(0x1000)?,
+                    unkptr_1b8: alloc.private.array_empty_tagged(0x1000, b"I1B8")?,
+                    unkptr_1c0: alloc.private.array_empty_tagged(0x300, b"I1C0")?,
+                    unkptr_1c8: alloc.private.array_empty_tagged(0x1000, b"I1C8")?,
 
                     buffer_mgr_ctl,
                 })
@@ -881,7 +881,7 @@ impl<'a> InitDataBuilder::ver<'a> {
 
         let obj = self.alloc.private.new_init(
             try_init!(InitData::ver {
-                unk_buf: shared_ro.array_empty(0x4000)?,
+                unk_buf: shared_ro.array_empty_tagged(0x4000, b"IDTA")?,
                 runtime_pointers,
                 globals,
                 fw_status,

From 8e785675f46b666efdc9f7bf1baaefbfa6373dbf Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:43:30 +0900
Subject: [PATCH 0926/1027] drm/asahi: queue/compute: Tag preempt buf

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/compute.rs | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index f53e64efd4f4e5..52f7e40628142d 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -101,7 +101,10 @@ impl super::Queue::ver {
         let preempt5_off = preempt4_off + 8;
         let preempt_size = preempt5_off + 8;
 
-        let preempt_buf = self.ualloc.lock().array_empty(preempt_size)?;
+        let preempt_buf = self
+            .ualloc
+            .lock()
+            .array_empty_tagged(preempt_size, b"CPMT")?;
 
         mod_dev_dbg!(
             self.dev,

From 61551a5f67c54767dbf14a47207baa66b9440ed4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:43:40 +0900
Subject: [PATCH 0927/1027] drm/asahi: render: Tag AuxFB

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 21fd904c0adb49..6a14f8c384060e 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -724,7 +724,7 @@ impl super::Queue::ver {
                     notifier,
                     scene,
                     vm_bind,
-                    aux_fb: self.ualloc.lock().array_empty(0x8000)?,
+                    aux_fb: self.ualloc.lock().array_empty_tagged(0x8000, b"AXFB")?,
                     timestamps,
                 })
             },

From 128c79c56cbf120873c7fbedeeb0c7c78f01897e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 14:43:48 +0900
Subject: [PATCH 0928/1027] drm/asahi: workqueue: Tag GPU buf

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 453350cc958665..4415dac068d449 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -588,7 +588,7 @@ impl WorkQueue::ver {
         priority: u32,
         size: u32,
     ) -> Result<Arc<WorkQueue::ver>> {
-        let gpu_buf = alloc.private.array_empty(0x2c18)?;
+        let gpu_buf = alloc.private.array_empty_tagged(0x2c18, b"GPBF")?;
         let shared = &mut alloc.shared;
         let inner = WorkQueueInner::ver {
             dev: dev.into(),

From da241613cbcc53f212d91c6d66c11b4a43c489b3 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 15:47:59 +0900
Subject: [PATCH 0929/1027] drm/asahi: buffer,render: Identify and provide
 layer meta buf

It looks like one of the "heapmeta" pointers is actually a layer
metadata pointer, that macOS just allocates contiguously with the
tilemap headers and heap meta buffers. Size seems to always be 0x100.

Let's allocate it after the heapmeta, which will make debugging easier.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs       | 12 +++++++++++-
 drivers/gpu/drm/asahi/fw/fragment.rs  |  4 ++--
 drivers/gpu/drm/asahi/fw/vertex.rs    |  2 +-
 drivers/gpu/drm/asahi/queue/render.rs | 13 +++++++------
 4 files changed, 21 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index 3ade6e3416f987..6c26b5e6d653a1 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -91,6 +91,8 @@ pub(crate) struct TileInfo {
     pub(crate) tpc_size: usize,
     /// Number of blocks in the clustering meta buffer (for clustering).
     pub(crate) meta1_blocks: u32,
+    /// Layering metadata size.
+    pub(crate) layermeta_size: usize,
     /// Minimum number of TVB blocks for this render.
     pub(crate) min_tvb_blocks: usize,
     /// Tiling parameter structure passed to firmware.
@@ -163,6 +165,11 @@ impl Scene::ver {
         self.object.tvb_heapmeta.gpu_pointer()
     }
 
+    /// Returns the GPU pointer to the layer metadata buffer.
+    pub(crate) fn tvb_layermeta_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
+        self.object.tvb_heapmeta.gpu_offset_pointer(0x200)
+    }
+
     /// Returns the GPU pointer to the top-level TVB tilemap buffer.
     pub(crate) fn tvb_tilemap_pointer(&self) -> GpuPointer<'_, &'_ [u8]> {
         self.object.tvb_tilemap.gpu_pointer()
@@ -549,7 +556,10 @@ impl Buffer::ver {
             b"UBUF",
         )?;
 
-        let tvb_heapmeta = inner.ualloc.lock().array_empty_tagged(0x200, b"HMTA")?;
+        let tvb_heapmeta = inner
+            .ualloc
+            .lock()
+            .array_empty_tagged(0x200 + tile_info.layermeta_size, b"HMTA")?;
         let tvb_tilemap = inner
             .ualloc
             .lock()
diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs
index 078c7cfed9c0f0..67a21d4f9418ab 100644
--- a/drivers/gpu/drm/asahi/fw/fragment.rs
+++ b/drivers/gpu/drm/asahi/fw/fragment.rs
@@ -97,9 +97,9 @@ pub(crate) mod raw {
         pub(crate) stencil_meta_buffer_ptr2: U64,
         pub(crate) unk_d0: U64,
         pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>,
-        pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>,
         pub(crate) mtile_stride_dwords: U64,
-        pub(crate) tvb_heapmeta_2: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tvb_heapmeta: GpuPointer<'a, &'a [u8]>,
         pub(crate) tile_config: U64,
         pub(crate) aux_fb: GpuPointer<'a, &'a [u8]>,
         pub(crate) unk_108: Array<0x6, U64>,
diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
index 2a94f7e5ffdd89..e5065b9ecc933e 100644
--- a/drivers/gpu/drm/asahi/fw/vertex.rs
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -47,7 +47,7 @@ pub(crate) mod raw {
         pub(crate) utile_config: u32,
         pub(crate) unk_4c: u32,
         pub(crate) ppp_multisamplectl: U64,
-        pub(crate) tvb_heapmeta_2: GpuPointer<'a, &'a [u8]>,
+        pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>,
         #[ver(G < G14)]
         pub(crate) unk_60: U64,
         #[ver(G < G14)]
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 6a14f8c384060e..b7d4d99ea2f7e9 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -168,6 +168,7 @@ impl super::Queue::ver {
             tilemap_size,
             tpc_size,
             meta1_blocks,
+            layermeta_size: if layers > 1 { 0x100 } else { 0 },
             min_tvb_blocks: min_tvb_blocks as usize,
             params: fw::vertex::raw::TilingParameters {
                 rgn_size,
@@ -797,9 +798,9 @@ impl super::Queue::ver {
                         stencil_meta_buffer_ptr2: U64(cmdbuf.stencil_meta_buffer_store),
                         unk_d0: Default::default(),
                         tvb_tilemap: inner.scene.tvb_tilemap_pointer(),
-                        tvb_heapmeta: inner.scene.tvb_heapmeta_pointer(),
+                        tvb_layermeta: inner.scene.tvb_layermeta_pointer(),
                         mtile_stride_dwords: U64((4 * tile_info.params.rgn_size as u64) << 24),
-                        tvb_heapmeta_2: inner.scene.tvb_heapmeta_pointer(),
+                        tvb_heapmeta: inner.scene.tvb_heapmeta_pointer(),
                         tile_config: U64(tile_config),
                         aux_fb: inner.aux_fb.gpu_pointer(),
                         unk_108: Default::default(),
@@ -899,7 +900,7 @@ impl super::Queue::ver {
                             r.add(0x153d9, cmdbuf.stencil_meta_buffer_store);
                             r.add(0x15439, 0);
                             r.add(0x16429, inner.scene.tvb_tilemap_pointer().into());
-                            r.add(0x16060, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x16060, inner.scene.tvb_layermeta_pointer().into());
                             r.add(0x16431, (4 * tile_info.params.rgn_size as u64) << 24); // ISP_RGN?
                             r.add(0x10039, tile_config); // tile_config ISP_CTL?
                             r.add(0x16451, 0x0); // ISP_RENDER_ORIGIN
@@ -1281,7 +1282,7 @@ impl super::Queue::ver {
                         utile_config,
                         unk_4c: 0,
                         ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), // fixed
-                        tvb_heapmeta_2: inner.scene.tvb_heapmeta_pointer(),
+                        tvb_layermeta: inner.scene.tvb_layermeta_pointer(),
                         #[ver(G < G14)]
                         unk_60: U64(0x0), // fixed
                         #[ver(G < G14)]
@@ -1371,8 +1372,8 @@ impl super::Queue::ver {
                             ); // tvb_cluster_meta3
                             r.add(0x1c890, tiling_control.into()); // tvb_tiling_control
                             r.add(0x1c918, unks.tiling_control_2);
-                            r.add(0x1c079, inner.scene.tvb_heapmeta_pointer().into());
-                            r.add(0x1c9d8, inner.scene.tvb_heapmeta_pointer().into());
+                            r.add(0x1c079, inner.scene.tvb_layermeta_pointer().into());
+                            r.add(0x1c9d8, inner.scene.tvb_layermeta_pointer().into());
                             r.add(0x1c089, 0);
                             r.add(0x1c9e0, 0);
                             let cl_meta_4_pointer =

From ab09f5377bbf8bd7bbd759a28f82418575e9ef22 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 15:57:59 +0900
Subject: [PATCH 0930/1027] drm/asahi: compute: Implement bindless samplers

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/compute.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index 52f7e40628142d..a22dcbfad81408 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -337,9 +337,9 @@ impl super::Queue::ver {
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
                         unk_mask: cmdbuf.unk_mask,
-                        sampler_array: U64(0),
-                        sampler_count: 0,
-                        sampler_max: 0,
+                        sampler_array: U64(cmdbuf.sampler_array),
+                        sampler_count: cmdbuf.sampler_count,
+                        sampler_max: cmdbuf.sampler_max,
                     }),
                     meta <- try_init!(fw::job::raw::JobMeta {
                         unk_0: 0,

From 92fb857cc566534a3f36e8ffb55cd774bf358681 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 15:58:09 +0900
Subject: [PATCH 0931/1027] drm/asahi: render: Implement bindless samplers

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index b7d4d99ea2f7e9..34d3f043489b46 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -1002,9 +1002,9 @@ impl super::Queue::ver {
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
                         unk_mask: unks.frg_unk_mask as u32,
-                        sampler_array: U64(0),
-                        sampler_count: 0,
-                        sampler_max: 0,
+                        sampler_array: U64(cmdbuf.fragment_sampler_array),
+                        sampler_count: cmdbuf.fragment_sampler_count,
+                        sampler_max: cmdbuf.fragment_sampler_max,
                     }),
                     process_empty_tiles: (cmdbuf.flags
                         & uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES as u64
@@ -1455,9 +1455,9 @@ impl super::Queue::ver {
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
                         unk_mask: unks.vtx_unk_mask as u32,
-                        sampler_array: U64(0),
-                        sampler_count: 0,
-                        sampler_max: 0,
+                        sampler_array: U64(cmdbuf.vertex_sampler_array),
+                        sampler_count: cmdbuf.vertex_sampler_count,
+                        sampler_max: cmdbuf.vertex_sampler_max,
                     }),
                     unk_55c: 0,
                     unk_560: 0,

From 7a152eae1896b116548a189098498a56a7b9b176 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 18:03:04 +0900
Subject: [PATCH 0932/1027] drm/asahi: fw,queue: Implement helper programs

Also expose no preemption flag (?) separately.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/compute.rs    |  2 +-
 drivers/gpu/drm/asahi/fw/fragment.rs   |  5 +++--
 drivers/gpu/drm/asahi/fw/vertex.rs     |  5 ++++-
 drivers/gpu/drm/asahi/queue/compute.rs |  8 +++++---
 drivers/gpu/drm/asahi/queue/render.rs  | 26 +++++++++++++++++---------
 5 files changed, 30 insertions(+), 16 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/compute.rs b/drivers/gpu/drm/asahi/fw/compute.rs
index 2a616ca349b9bd..e29935d9bc5f35 100644
--- a/drivers/gpu/drm/asahi/fw/compute.rs
+++ b/drivers/gpu/drm/asahi/fw/compute.rs
@@ -24,7 +24,7 @@ pub(crate) mod raw {
         pub(crate) helper_program: u32,
         pub(crate) unk_44: u32,
         pub(crate) helper_arg: U64,
-        pub(crate) unk_50: u32,
+        pub(crate) helper_unk: u32,
         pub(crate) unk_54: u32,
         pub(crate) unk_58: u32,
         pub(crate) unk_5c: u32,
diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs
index 67a21d4f9418ab..ef4fedfc2bdb82 100644
--- a/drivers/gpu/drm/asahi/fw/fragment.rs
+++ b/drivers/gpu/drm/asahi/fw/fragment.rs
@@ -105,8 +105,9 @@ pub(crate) mod raw {
         pub(crate) unk_108: Array<0x6, U64>,
         pub(crate) pipeline_base: U64,
         pub(crate) unk_140: U64,
-        pub(crate) unk_148: U64,
-        pub(crate) unk_150: U64,
+        pub(crate) helper_program: u32,
+        pub(crate) unk_14c: u32,
+        pub(crate) helper_arg: U64,
         pub(crate) unk_158: U64,
         pub(crate) unk_160: U64,
 
diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
index e5065b9ecc933e..a6318898817abf 100644
--- a/drivers/gpu/drm/asahi/fw/vertex.rs
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -72,7 +72,10 @@ pub(crate) mod raw {
         #[ver(G < G14)]
         pub(crate) unk_f0: U64,
         pub(crate) unk_f8: U64,
-        pub(crate) unk_100: Array<3, U64>,
+        pub(crate) helper_program: u32,
+        pub(crate) unk_104: u32,
+        pub(crate) helper_arg: U64,
+        pub(crate) unk_110: U64,
         pub(crate) unk_118: u32,
         #[ver(G >= G14)]
         pub(crate) __pad: Pad<{ 8 * 9 + 0x268 }>,
diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index a22dcbfad81408..44780048126242 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -282,7 +282,7 @@ impl super::Queue::ver {
                         helper_program: cmdbuf.helper_program, // Internal program addr | 1
                         unk_44: 0,
                         helper_arg: U64(cmdbuf.helper_arg), // Only if internal program used
-                        unk_50: cmdbuf.buffer_descriptor_size, // 0x40 if internal program used
+                        helper_unk: cmdbuf.helper_unk, // 0x40 if internal program used
                         unk_54: 0,
                         unk_58: 1,
                         unk_5c: 0,
@@ -303,7 +303,7 @@ impl super::Queue::ver {
                             r.add(0x10071, 0x1100000000); // USC_EXEC_BASE_CP
                             r.add(0x11841, cmdbuf.helper_program.into());
                             r.add(0x11849, cmdbuf.helper_arg);
-                            r.add(0x11f81, cmdbuf.buffer_descriptor_size.into());
+                            r.add(0x11f81, cmdbuf.helper_unk.into());
                             r.add(0x1a440, 0x24201);
                             r.add(0x12091, cmdbuf.iogpu_unk_40.into());
                             /*
@@ -345,7 +345,9 @@ impl super::Queue::ver {
                         unk_0: 0,
                         unk_2: 0,
                         // TODO: make separate flag
-                        no_preemption: ((cmdbuf.helper_program & 1) == 0) as u8,
+                        no_preemption: (cmdbuf.flags
+                        & uapi::ASAHI_COMPUTE_NO_PREEMPTION as u64
+                        != 0) as u8,
                         stamp: ev_comp.stamp_pointer,
                         fw_stamp: ev_comp.fw_stamp_pointer,
                         stamp_value: ev_comp.value.next(),
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 34d3f043489b46..aa2091be588c0f 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -806,8 +806,9 @@ impl super::Queue::ver {
                         unk_108: Default::default(),
                         pipeline_base: U64(0x11_00000000),
                         unk_140: U64(unks.frg_unk_140),
-                        unk_148: U64(0x0),
-                        unk_150: U64(0x0),
+                        helper_program: cmdbuf.fragment_helper_program,
+                        unk_14c: 0,
+                        helper_arg: U64(cmdbuf.fragment_helper_arg),
                         unk_158: U64(unks.frg_unk_158),
                         unk_160: U64(0),
                         __pad: Default::default(),
@@ -904,8 +905,8 @@ impl super::Queue::ver {
                             r.add(0x16431, (4 * tile_info.params.rgn_size as u64) << 24); // ISP_RGN?
                             r.add(0x10039, tile_config); // tile_config ISP_CTL?
                             r.add(0x16451, 0x0); // ISP_RENDER_ORIGIN
-                            r.add(0x11821, 0x0); // some shader?
-                            r.add(0x11829, 0);
+                            r.add(0x11821, cmdbuf.fragment_helper_program.into());
+                            r.add(0x11829, cmdbuf.fragment_helper_arg);
                             r.add(0x11f79, 0);
                             r.add(0x15359, 0);
                             r.add(0x10069, 0x11_00000000); // USC_EXEC_BASE_ISP
@@ -1019,7 +1020,9 @@ impl super::Queue::ver {
                     meta <- try_init!(fw::job::raw::JobMeta {
                         unk_0: 0,
                         unk_2: 0,
-                        no_preemption: 0,
+                        no_preemption: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_NO_PREEMPTION as u64
+                        != 0) as u8,
                         stamp: ev_frag.stamp_pointer,
                         fw_stamp: ev_frag.fw_stamp_pointer,
                         stamp_value: ev_frag.value.next(),
@@ -1313,7 +1316,10 @@ impl super::Queue::ver {
                         #[ver(G < G14)]
                         unk_f0: U64(unks.vtx_unk_f0),
                         unk_f8: U64(unks.vtx_unk_f8),     // fixed
-                        unk_100: Default::default(),      // fixed
+                        helper_program: cmdbuf.vertex_helper_program,
+                        unk_104: 0,
+                        helper_arg: U64(cmdbuf.vertex_helper_arg),
+                        unk_110: Default::default(),      // fixed
                         unk_118: unks.vtx_unk_118 as u32, // fixed
                         __pad: Default::default(),
                     }),
@@ -1392,8 +1398,8 @@ impl super::Queue::ver {
                             r.add(0x1c1b1, 0);
                             r.add(0x1c1b9, 0);
                             r.add(0x10061, 0x11_00000000); // USC_EXEC_BASE_TA
-                            r.add(0x11801, 0); // some shader?
-                            r.add(0x11809, 0); // maybe arg?
+                            r.add(0x11801, cmdbuf.vertex_helper_program.into());
+                            r.add(0x11809, cmdbuf.vertex_helper_arg);
                             r.add(0x11f71, 0);
                             r.add(0x1c0b1, tile_info.params.rgn_size.into()); // TE_PSG
                             r.add(0x1c850, tile_info.params.rgn_size.into());
@@ -1469,7 +1475,9 @@ impl super::Queue::ver {
                     meta <- try_init!(fw::job::raw::JobMeta {
                         unk_0: 0,
                         unk_2: 0,
-                        no_preemption: 0,
+                        no_preemption: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_NO_PREEMPTION as u64
+                        != 0) as u8,
                         stamp: ev_vtx.stamp_pointer,
                         fw_stamp: ev_vtx.fw_stamp_pointer,
                         stamp_value: ev_vtx.value.next(),

From 1368b254e04a95072649c3bcb48b5bdf925e4ef7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 16 Aug 2023 21:00:18 +0900
Subject: [PATCH 0933/1027] drm/asahi: render: Identify and set Z/S strides for
 layered rendering

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/fragment.rs  | 21 ++++++++-------
 drivers/gpu/drm/asahi/queue/render.rs | 37 +++++++++++++++------------
 2 files changed, 32 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs
index ef4fedfc2bdb82..586f3414a02957 100644
--- a/drivers/gpu/drm/asahi/fw/fragment.rs
+++ b/drivers/gpu/drm/asahi/fw/fragment.rs
@@ -87,15 +87,18 @@ pub(crate) mod raw {
         #[ver(G >= G14)]
         pub(crate) unk_68_g14_0: Array<0x20, u8>,
 
-        pub(crate) unk_78: Array<0x4, U64>,
+        pub(crate) depth_buffer_stride1: U64,
+        pub(crate) depth_buffer_stride2: U64,
+        pub(crate) stencil_buffer_stride1: U64,
+        pub(crate) stencil_buffer_stride2: U64,
         pub(crate) depth_meta_buffer_ptr1: U64,
-        pub(crate) unk_a0: U64,
+        pub(crate) depth_meta_buffer_stride1: U64,
         pub(crate) depth_meta_buffer_ptr2: U64,
-        pub(crate) unk_b0: U64,
+        pub(crate) depth_meta_buffer_stride2: U64,
         pub(crate) stencil_meta_buffer_ptr1: U64,
-        pub(crate) unk_c0: U64,
+        pub(crate) stencil_meta_buffer_stride1: U64,
         pub(crate) stencil_meta_buffer_ptr2: U64,
-        pub(crate) unk_d0: U64,
+        pub(crate) stencil_meta_buffer_stride2: U64,
         pub(crate) tvb_tilemap: GpuPointer<'a, &'a [u8]>,
         pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>,
         pub(crate) mtile_stride_dwords: U64,
@@ -160,14 +163,14 @@ pub(crate) mod raw {
         pub(crate) zls_ctrl: U64,
         pub(crate) unk_290: U64,
         pub(crate) depth_buffer_ptr1: U64,
-        pub(crate) unk_2a0: U64,
-        pub(crate) unk_2a8: U64,
+        pub(crate) depth_buffer_stride3: U64,
+        pub(crate) depth_meta_buffer_stride3: U64,
         pub(crate) depth_buffer_ptr2: U64,
         pub(crate) depth_buffer_ptr3: U64,
         pub(crate) depth_meta_buffer_ptr3: U64,
         pub(crate) stencil_buffer_ptr1: U64,
-        pub(crate) unk_2d0: U64,
-        pub(crate) unk_2d8: U64,
+        pub(crate) stencil_buffer_stride3: U64,
+        pub(crate) stencil_meta_buffer_stride3: U64,
         pub(crate) stencil_buffer_ptr2: U64,
         pub(crate) stencil_buffer_ptr3: U64,
         pub(crate) stencil_meta_buffer_ptr3: U64,
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index aa2091be588c0f..404fd51110a7fa 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -788,15 +788,18 @@ impl super::Queue::ver {
                         stencil_buffer_ptr2: U64(cmdbuf.stencil_buffer_store),
                         #[ver(G >= G14)]
                         unk_68_g14_0: Default::default(),
-                        unk_78: Default::default(),
+                        depth_buffer_stride1: U64(cmdbuf.depth_buffer_load_stride),
+                        depth_buffer_stride2: U64(cmdbuf.depth_buffer_store_stride),
+                        stencil_buffer_stride1: U64(cmdbuf.stencil_buffer_load_stride),
+                        stencil_buffer_stride2: U64(cmdbuf.stencil_buffer_store_stride),
                         depth_meta_buffer_ptr1: U64(cmdbuf.depth_meta_buffer_load),
-                        unk_a0: Default::default(),
+                        depth_meta_buffer_stride1: U64(cmdbuf.depth_meta_buffer_load_stride),
                         depth_meta_buffer_ptr2: U64(cmdbuf.depth_meta_buffer_store),
-                        unk_b0: Default::default(),
+                        depth_meta_buffer_stride2: U64(cmdbuf.depth_meta_buffer_store_stride),
                         stencil_meta_buffer_ptr1: U64(cmdbuf.stencil_meta_buffer_load),
-                        unk_c0: Default::default(),
+                        stencil_meta_buffer_stride1: U64(cmdbuf.stencil_meta_buffer_load_stride),
                         stencil_meta_buffer_ptr2: U64(cmdbuf.stencil_meta_buffer_store),
-                        unk_d0: Default::default(),
+                        stencil_meta_buffer_stride2: U64(cmdbuf.stencil_meta_buffer_store_stride),
                         tvb_tilemap: inner.scene.tvb_tilemap_pointer(),
                         tvb_layermeta: inner.scene.tvb_layermeta_pointer(),
                         mtile_stride_dwords: U64((4 * tile_info.params.rgn_size as u64) << 24),
@@ -888,18 +891,18 @@ impl super::Queue::ver {
                             r.add(0x15221, 0);
                             r.add(0x15239, 0);
                             r.add(0x15229, 0);
-                            r.add(0x15401, 0);
-                            r.add(0x15421, 0);
-                            r.add(0x15409, 0);
-                            r.add(0x15429, 0);
+                            r.add(0x15401, cmdbuf.depth_buffer_load_stride);
+                            r.add(0x15421, cmdbuf.depth_buffer_store_stride);
+                            r.add(0x15409, cmdbuf.stencil_buffer_load_stride);
+                            r.add(0x15429, cmdbuf.stencil_buffer_store_stride);
                             r.add(0x153c1, cmdbuf.depth_meta_buffer_load);
-                            r.add(0x15411, 0);
+                            r.add(0x15411, cmdbuf.depth_meta_buffer_load_stride);
                             r.add(0x153c9, cmdbuf.depth_meta_buffer_store);
-                            r.add(0x15431, 0);
+                            r.add(0x15431, cmdbuf.depth_meta_buffer_store_stride);
                             r.add(0x153d1, cmdbuf.stencil_meta_buffer_load);
-                            r.add(0x15419, 0);
+                            r.add(0x15419, cmdbuf.stencil_meta_buffer_load_stride);
                             r.add(0x153d9, cmdbuf.stencil_meta_buffer_store);
-                            r.add(0x15439, 0);
+                            r.add(0x15439, cmdbuf.stencil_meta_buffer_store_stride);
                             r.add(0x16429, inner.scene.tvb_tilemap_pointer().into());
                             r.add(0x16060, inner.scene.tvb_layermeta_pointer().into());
                             r.add(0x16431, (4 * tile_info.params.rgn_size as u64) << 24); // ISP_RGN?
@@ -953,14 +956,14 @@ impl super::Queue::ver {
                         zls_ctrl: U64(unks.reload_zlsctrl),
                         unk_290: U64(unks.g14_unk),
                         depth_buffer_ptr1: U64(cmdbuf.depth_buffer_load),
-                        unk_2a0: U64(0x0),
-                        unk_2a8: U64(0x0),
+                        depth_buffer_stride3: U64(cmdbuf.depth_buffer_partial_stride),
+                        depth_meta_buffer_stride3: U64(cmdbuf.depth_meta_buffer_partial_stride),
                         depth_buffer_ptr2: U64(cmdbuf.depth_buffer_store),
                         depth_buffer_ptr3: U64(cmdbuf.depth_buffer_partial),
                         depth_meta_buffer_ptr3: U64(cmdbuf.depth_meta_buffer_partial),
                         stencil_buffer_ptr1: U64(cmdbuf.stencil_buffer_load),
-                        unk_2d0: U64(0x0),
-                        unk_2d8: U64(0x0),
+                        stencil_buffer_stride3: U64(cmdbuf.stencil_buffer_partial_stride),
+                        stencil_meta_buffer_stride3: U64(cmdbuf.stencil_meta_buffer_partial_stride),
                         stencil_buffer_ptr2: U64(cmdbuf.stencil_buffer_store),
                         stencil_buffer_ptr3: U64(cmdbuf.stencil_buffer_partial),
                         stencil_meta_buffer_ptr3: U64(cmdbuf.stencil_meta_buffer_partial),

From c5237a682cf7d06de5cd2e4dc6b9de212be450ff Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 17 Aug 2023 19:11:25 +0900
Subject: [PATCH 0934/1027] drm/asahi: queue: Quieten some debugs

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/mod.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
index 1793b20b08f7f4..35427f4ae0e306 100644
--- a/drivers/gpu/drm/asahi/queue/mod.rs
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -199,7 +199,7 @@ impl sched::JobImpl for QueueJob::ver {
 
         if let Some(sj) = job.sj_vtx.as_ref() {
             if let Some(fence) = sj.can_submit() {
-                dev_info!(
+                mod_dev_dbg!(
                     job.dev,
                     "QueueJob {}: Blocking due to vertex queue full\n",
                     job.id
@@ -209,7 +209,7 @@ impl sched::JobImpl for QueueJob::ver {
         }
         if let Some(sj) = job.sj_frag.as_ref() {
             if let Some(fence) = sj.can_submit() {
-                dev_info!(
+                mod_dev_dbg!(
                     job.dev,
                     "QueueJob {}: Blocking due to fragment queue full\n",
                     job.id
@@ -219,7 +219,7 @@ impl sched::JobImpl for QueueJob::ver {
         }
         if let Some(sj) = job.sj_comp.as_ref() {
             if let Some(fence) = sj.can_submit() {
-                dev_info!(
+                mod_dev_dbg!(
                     job.dev,
                     "QueueJob {}: Blocking due to compute queue full\n",
                     job.id

From e05dadbb5e07817f09875451a4769446738764b2 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 30 Aug 2023 14:24:47 +0900
Subject: [PATCH 0935/1027] drm/asahi: Add verbose UAPI error reporting

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/debug.rs        |   1 +
 drivers/gpu/drm/asahi/file.rs         | 114 +++++++++++++++++++++++---
 drivers/gpu/drm/asahi/queue/mod.rs    |  54 ++++++++++--
 drivers/gpu/drm/asahi/queue/render.rs |  38 +++++++--
 4 files changed, 179 insertions(+), 28 deletions(-)

diff --git a/drivers/gpu/drm/asahi/debug.rs b/drivers/gpu/drm/asahi/debug.rs
index 4f20d55db0337f..ab8490bd536bcc 100644
--- a/drivers/gpu/drm/asahi/debug.rs
+++ b/drivers/gpu/drm/asahi/debug.rs
@@ -28,6 +28,7 @@ pub(crate) enum DebugFlags {
     Queue = 10,
     Render = 11,
     Compute = 12,
+    Errors = 13,
 
     // 14-15: Misc stats
     MemStats = 14,
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index d24daba60cd169..68a00d6c6b7b39 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -52,12 +52,14 @@ pub(crate) struct SyncItem {
 impl SyncItem {
     fn parse_one(file: &DrmFile, data: uapi::drm_asahi_sync, out: bool) -> Result<SyncItem> {
         if data.extensions != 0 {
+            cls_pr_debug!(Errors, "drm_asahi_sync extension unexpected\n");
             return Err(EINVAL);
         }
 
         match data.sync_type {
             uapi::drm_asahi_sync_type_DRM_ASAHI_SYNC_SYNCOBJ => {
                 if data.timeline_value != 0 {
+                    cls_pr_debug!(Errors, "Non-timeline sync object with a nonzero value\n");
                     return Err(EINVAL);
                 }
                 let syncobj = drm::syncobj::SyncObj::lookup_handle(file, data.handle)?;
@@ -66,7 +68,10 @@ impl SyncItem {
                     fence: if out {
                         None
                     } else {
-                        Some(syncobj.fence_get().ok_or(EINVAL)?)
+                        Some(syncobj.fence_get().ok_or_else(|| {
+                            cls_pr_debug!(Errors, "Failed to get fence from sync object\n");
+                            EINVAL
+                        })?)
                     },
                     syncobj,
                     chain_fence: None,
@@ -80,7 +85,13 @@ impl SyncItem {
                 } else {
                     syncobj
                         .fence_get()
-                        .ok_or(EINVAL)?
+                        .ok_or_else(|| {
+                            cls_pr_debug!(
+                                Errors,
+                                "Failed to get fence from timeline sync object\n"
+                            );
+                            EINVAL
+                        })?
                         .chain_find_seqno(data.timeline_value)?
                 };
 
@@ -95,7 +106,10 @@ impl SyncItem {
                     timeline_value: data.timeline_value,
                 })
             }
-            _ => Err(EINVAL),
+            _ => {
+                cls_pr_debug!(Errors, "Invalid sync type {}\n", data.sync_type);
+                Err(EINVAL)
+            }
         }
     }
 
@@ -201,6 +215,7 @@ impl File {
         let gpu = &device.data().gpu;
 
         if data.extensions != 0 || data.param_group != 0 || data.pad != 0 {
+            cls_pr_debug!(Errors, "get_params: Invalid arguments\n");
             return Err(EINVAL);
         }
 
@@ -278,6 +293,7 @@ impl File {
         file: &DrmFile,
     ) -> Result<u32> {
         if data.extensions != 0 {
+            cls_pr_debug!(Errors, "vm_create: Unexpected extensions\n");
             return Err(EINVAL);
         }
 
@@ -350,6 +366,7 @@ impl File {
         file: &DrmFile,
     ) -> Result<u32> {
         if data.extensions != 0 {
+            cls_pr_debug!(Errors, "vm_destroy: Unexpected extensions\n");
             return Err(EINVAL);
         }
 
@@ -377,6 +394,7 @@ impl File {
             || (data.flags & !(uapi::ASAHI_GEM_WRITEBACK | uapi::ASAHI_GEM_VM_PRIVATE)) != 0
             || (data.flags & uapi::ASAHI_GEM_VM_PRIVATE == 0 && data.vm_id != 0)
         {
+            cls_pr_debug!(Errors, "gem_create: Invalid arguments\n");
             return Err(EINVAL);
         }
 
@@ -424,6 +442,7 @@ impl File {
         );
 
         if data.extensions != 0 || data.flags != 0 {
+            cls_pr_debug!(Errors, "gem_mmap_offset: Unexpected extensions or flags\n");
             return Err(EINVAL);
         }
 
@@ -452,6 +471,7 @@ impl File {
         );
 
         if data.extensions != 0 {
+            cls_pr_debug!(Errors, "gem_bind: Unexpected extensions\n");
             return Err(EINVAL);
         }
 
@@ -461,7 +481,10 @@ impl File {
             uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND_ALL => {
                 Self::do_gem_unbind_all(device, data, file)
             }
-            _ => Err(EINVAL),
+            _ => {
+                cls_pr_debug!(Errors, "gem_bind: Invalid op {}\n", data.op);
+                Err(EINVAL)
+            }
         }
     }
 
@@ -471,20 +494,29 @@ impl File {
         file: &DrmFile,
     ) -> Result<u32> {
         if data.offset != 0 {
+            pr_err!("gem_bind: Offset not supported yet\n");
             return Err(EINVAL); // Not supported yet
         }
 
         if (data.addr | data.range) as usize & mmu::UAT_PGMSK != 0 {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Addr/range not page aligned: {:#x} {:#x}\n",
+                data.addr,
+                data.range
+            );
             return Err(EINVAL); // Must be page aligned
         }
 
         if (data.flags & !(uapi::ASAHI_BIND_READ | uapi::ASAHI_BIND_WRITE)) != 0 {
+            cls_pr_debug!(Errors, "gem_bind: Invalid flags {:#x}\n", data.flags);
             return Err(EINVAL);
         }
 
         let mut bo = gem::lookup_handle(file, data.handle)?;
 
         if data.range != bo.size().try_into()? {
+            pr_err!("gem_bind: Partial maps not supported yet\n");
             return Err(EINVAL); // Not supported yet
         }
 
@@ -493,18 +525,42 @@ impl File {
 
         if (VM_SHADER_START..=VM_SHADER_END).contains(&start) {
             if !(VM_SHADER_START..=VM_SHADER_END).contains(&end) {
+                cls_pr_debug!(
+                    Errors,
+                    "gem_bind: Invalid map range {:#x}..{:#x} (straddles shader range)\n",
+                    start,
+                    end
+                );
                 return Err(EINVAL); // Invalid map range
             }
         } else if (VM_USER_START..=VM_USER_END).contains(&start) {
             if !(VM_USER_START..=VM_USER_END).contains(&end) {
+                cls_pr_debug!(
+                    Errors,
+                    "gem_bind: Invalid map range {:#x}..{:#x} (straddles user range)\n",
+                    start,
+                    end
+                );
                 return Err(EINVAL); // Invalid map range
             }
         } else {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Invalid map range {:#x}..{:#x}\n",
+                start,
+                end
+            );
             return Err(EINVAL); // Invalid map range
         }
 
         // Just in case
         if end >= VM_DRV_GPU_START {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Invalid map range {:#x}..{:#x} (intrudes in kernel range)\n",
+                start,
+                end
+            );
             return Err(EINVAL);
         }
 
@@ -517,6 +573,11 @@ impl File {
         } else if data.flags & uapi::ASAHI_BIND_WRITE != 0 {
             mmu::PROT_GPU_SHARED_WO
         } else {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Must specify read or write (flags: {:#x})\n",
+                data.flags
+            );
             return Err(EINVAL); // Must specify one of ASAHI_BIND_{READ,WRITE}
         };
 
@@ -541,6 +602,7 @@ impl File {
         file: &DrmFile,
     ) -> Result<u32> {
         if data.flags != 0 || data.offset != 0 || data.range != 0 || data.addr != 0 {
+            cls_pr_debug!(Errors, "gem_unbind_all: Invalid arguments\n");
             return Err(EINVAL);
         }
 
@@ -591,6 +653,7 @@ impl File {
                     | uapi::drm_asahi_queue_cap_DRM_ASAHI_QUEUE_CAP_COMPUTE))
                 != 0
         {
+            cls_pr_debug!(Errors, "queue_create: Invalid arguments\n");
             return Err(EINVAL);
         }
 
@@ -625,6 +688,7 @@ impl File {
         file: &DrmFile,
     ) -> Result<u32> {
         if data.extensions != 0 {
+            cls_pr_debug!(Errors, "queue_destroy: Unexpected extensions\n");
             return Err(EINVAL);
         }
 
@@ -646,16 +710,44 @@ impl File {
         data: &mut uapi::drm_asahi_submit,
         file: &DrmFile,
     ) -> Result<u32> {
-        if data.extensions != 0
-            || data.flags != 0
-            || data.in_sync_count > MAX_SYNCS_PER_SUBMISSION
-            || data.out_sync_count > MAX_SYNCS_PER_SUBMISSION
-            || data.command_count > MAX_COMMANDS_PER_SUBMISSION
-        {
+        debug::update_debug_flags();
+
+        if data.extensions != 0 {
+            cls_pr_debug!(Errors, "submit: Unexpected extensions\n");
             return Err(EINVAL);
         }
 
-        debug::update_debug_flags();
+        if data.flags != 0 {
+            cls_pr_debug!(Errors, "submit: Unexpected flags {:#x}\n", data.flags);
+            return Err(EINVAL);
+        }
+        if data.in_sync_count > MAX_SYNCS_PER_SUBMISSION {
+            cls_pr_debug!(
+                Errors,
+                "submit: Too many in syncs: {} > {}\n",
+                data.in_sync_count,
+                MAX_SYNCS_PER_SUBMISSION
+            );
+            return Err(EINVAL);
+        }
+        if data.out_sync_count > MAX_SYNCS_PER_SUBMISSION {
+            cls_pr_debug!(
+                Errors,
+                "submit: Too many out syncs: {} > {}\n",
+                data.out_sync_count,
+                MAX_SYNCS_PER_SUBMISSION
+            );
+            return Err(EINVAL);
+        }
+        if data.command_count > MAX_COMMANDS_PER_SUBMISSION {
+            cls_pr_debug!(
+                Errors,
+                "submit: Too many commands: {} > {}\n",
+                data.command_count,
+                MAX_COMMANDS_PER_SUBMISSION
+            );
+            return Err(EINVAL);
+        }
 
         let gpu = &device.data().gpu;
         gpu.update_globals();
diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
index 35427f4ae0e306..b86dc6aa3324c7 100644
--- a/drivers/gpu/drm/asahi/queue/mod.rs
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -171,13 +171,31 @@ pub(crate) struct QueueJob {
 #[versions(AGX)]
 impl QueueJob::ver {
     fn get_vtx(&mut self) -> Result<&mut workqueue::Job::ver> {
-        self.sj_vtx.as_mut().ok_or(EINVAL)?.get()
+        self.sj_vtx
+            .as_mut()
+            .ok_or_else(|| {
+                cls_pr_debug!(Errors, "No vertex queue\n");
+                EINVAL
+            })?
+            .get()
     }
     fn get_frag(&mut self) -> Result<&mut workqueue::Job::ver> {
-        self.sj_frag.as_mut().ok_or(EINVAL)?.get()
+        self.sj_frag
+            .as_mut()
+            .ok_or_else(|| {
+                cls_pr_debug!(Errors, "No fragment queue\n");
+                EINVAL
+            })?
+            .get()
     }
     fn get_comp(&mut self) -> Result<&mut workqueue::Job::ver> {
-        self.sj_comp.as_mut().ok_or(EINVAL)?.get()
+        self.sj_comp
+            .as_mut()
+            .ok_or_else(|| {
+                cls_pr_debug!(Errors, "No compute queue\n");
+                EINVAL
+            })?
+            .get()
     }
 
     fn commit(&mut self) -> Result {
@@ -537,6 +555,7 @@ impl Queue for Queue::ver {
 
         // Empty submissions are not legal
         if commands.is_empty() {
+            cls_pr_debug!(Errors, "Empty submission\n");
             return Err(EINVAL);
         }
 
@@ -623,7 +642,10 @@ impl Queue for Queue::ver {
             match cmd.cmd_type {
                 uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => last_render = Some(i),
                 uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => last_compute = Some(i),
-                _ => return Err(EINVAL),
+                _ => {
+                    cls_pr_debug!(Errors, "Unknown command type {}\n", cmd.cmd_type);
+                    return Err(EINVAL);
+                }
             }
         }
 
@@ -638,7 +660,10 @@ impl Queue for Queue::ver {
                 if *index == uapi::DRM_ASAHI_BARRIER_NONE as u32 {
                     continue;
                 }
-                if let Some(event) = events[queue_idx].get(*index as usize).ok_or(EINVAL)? {
+                if let Some(event) = events[queue_idx].get(*index as usize).ok_or_else(|| {
+                    cls_pr_debug!(Errors, "Invalid barrier #{}: {}\n", queue_idx, index);
+                    EINVAL
+                })? {
                     let mut alloc = gpu.alloc();
                     let queue_job = match cmd.cmd_type {
                         uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => job.get_vtx()?,
@@ -672,18 +697,29 @@ impl Queue for Queue::ver {
             let result_writer = match result_buf.as_ref() {
                 None => {
                     if cmd.result_offset != 0 || cmd.result_size != 0 {
+                        cls_pr_debug!(Errors, "No result buffer but result requested\n");
                         return Err(EINVAL);
                     }
                     None
                 }
                 Some(buf) => {
                     if cmd.result_size != 0 {
-                        if cmd
+                        let end_offset = cmd
                             .result_offset
                             .checked_add(cmd.result_size)
-                            .ok_or(EINVAL)?
-                            > buf.size() as u64
-                        {
+                            .ok_or_else(|| {
+                                cls_pr_debug!(Errors, "result_offset + result_size overflow\n");
+                                EINVAL
+                            })?;
+                        if end_offset > buf.size() as u64 {
+                            cls_pr_debug!(
+                                Errors,
+                                "Result buffer overflow ({} + {} > {})\n",
+                                cmd.result_offset,
+                                cmd.result_size,
+                                buf.size()
+                            );
+
                             return Err(EINVAL);
                         }
                         Some(ResultWriter {
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 404fd51110a7fa..d757f62dbcbb55 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -79,10 +79,12 @@ impl super::Queue::ver {
         let layers: u32 = cmdbuf.layers;
 
         if width > 65536 || height > 65536 {
+            cls_pr_debug!(Errors, "Framebuffer too large ({} x {})\n", width, height);
             return Err(EINVAL);
         }
 
         if layers == 0 || layers > 2048 {
+            cls_pr_debug!(Errors, "Layer count invalid ({})\n", layers);
             return Err(EINVAL);
         }
 
@@ -94,7 +96,15 @@ impl super::Queue::ver {
 
         match (utile_width, utile_height) {
             (32, 32) | (32, 16) | (16, 16) => (),
-            _ => return Err(EINVAL),
+            _ => {
+                cls_pr_debug!(
+                    Errors,
+                    "uTile size invalid ({} x {})\n",
+                    utile_width,
+                    utile_height
+                );
+                return Err(EINVAL);
+            }
         };
 
         let utiles_per_tile_x = tile_width / utile_width;
@@ -202,6 +212,7 @@ impl super::Queue::ver {
         flush_stamps: bool,
     ) -> Result {
         if cmd.cmd_type != uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER {
+            cls_pr_debug!(Errors, "Not a render command ({})\n", cmd.cmd_type);
             return Err(EINVAL);
         }
 
@@ -233,6 +244,7 @@ impl super::Queue::ver {
                 | uapi::ASAHI_RENDER_MSAA_ZS) as u64
             != 0
         {
+            cls_pr_debug!(Errors, "Invalid flags ({:#x})\n", cmdbuf.flags);
             return Err(EINVAL);
         }
 
@@ -241,10 +253,9 @@ impl super::Queue::ver {
             || cmdbuf.fb_width > 16384
             || cmdbuf.fb_height > 16384
         {
-            mod_dev_dbg!(
-                self.dev,
-                "[Submission {}] Invalid dimensions {}x{}\n",
-                id,
+            cls_pr_debug!(
+                Errors,
+                "Invalid dimensions ({}x{})\n",
                 cmdbuf.fb_width,
                 cmdbuf.fb_height
             );
@@ -265,6 +276,7 @@ impl super::Queue::ver {
             match ext_type {
                 uapi::ASAHI_RENDER_EXT_UNKNOWNS => {
                     if !debug_enabled(debug::DebugFlags::AllowUnknownOverrides) {
+                        cls_pr_debug!(Errors, "Overrides not enabled\n");
                         return Err(EINVAL);
                     }
                     let mut ext_reader = unsafe {
@@ -283,11 +295,15 @@ impl super::Queue::ver {
 
                     ext_ptr = unks.next;
                 }
-                _ => return Err(EINVAL),
+                _ => {
+                    cls_pr_debug!(Errors, "Unknown extension {}\n", ext_type);
+                    return Err(EINVAL);
+                }
             }
         }
 
         if unks.pad != 0 {
+            cls_pr_debug!(Errors, "Nonzero unks.pad: {}\n", unks.pad);
             return Err(EINVAL);
         }
 
@@ -332,7 +348,10 @@ impl super::Queue::ver {
 
         let tile_info = Self::get_tiling_params(&cmdbuf, if clustering { nclusters } else { 1 })?;
 
-        let buffer = self.buffer.as_ref().ok_or(EINVAL)?;
+        let buffer = self.buffer.as_ref().ok_or_else(|| {
+            cls_pr_debug!(Errors, "Failed to get buffer\n");
+            EINVAL
+        })?;
 
         let notifier = self.notifier.clone();
 
@@ -455,7 +474,10 @@ impl super::Queue::ver {
             1 => 0,
             2 => 1,
             4 => 2,
-            _ => return Err(EINVAL),
+            _ => {
+                cls_pr_debug!(Errors, "Invalid sample count {}\n", cmdbuf.samples);
+                return Err(EINVAL);
+            }
         };
 
         #[ver(G >= G14X)]

From 1a08332a8fd94c49e19787496e4aa43b3648da05 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 1 Sep 2023 18:39:22 +0900
Subject: [PATCH 0936/1027] drm/asahi: file: Remove sync limit

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs | 21 +--------------------
 1 file changed, 1 insertion(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 68a00d6c6b7b39..341dd678dc35a6 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -22,7 +22,6 @@ use kernel::{dma_fence, drm, uapi, xarray};
 
 const DEBUG_CLASS: DebugFlags = DebugFlags::File;
 
-const MAX_SYNCS_PER_SUBMISSION: u32 = 64;
 const MAX_COMMANDS_PER_SUBMISSION: u32 = 64;
 pub(crate) const MAX_COMMANDS_IN_FLIGHT: u32 = 1024;
 
@@ -250,7 +249,7 @@ impl File {
             vm_shader_start: VM_SHADER_START,
             vm_shader_end: VM_SHADER_END,
 
-            max_syncs_per_submission: MAX_SYNCS_PER_SUBMISSION,
+            max_syncs_per_submission: 0,
             max_commands_per_submission: MAX_COMMANDS_PER_SUBMISSION,
             max_commands_in_flight: MAX_COMMANDS_IN_FLIGHT,
             max_attachments: crate::microseq::MAX_ATTACHMENTS as u32,
@@ -721,24 +720,6 @@ impl File {
             cls_pr_debug!(Errors, "submit: Unexpected flags {:#x}\n", data.flags);
             return Err(EINVAL);
         }
-        if data.in_sync_count > MAX_SYNCS_PER_SUBMISSION {
-            cls_pr_debug!(
-                Errors,
-                "submit: Too many in syncs: {} > {}\n",
-                data.in_sync_count,
-                MAX_SYNCS_PER_SUBMISSION
-            );
-            return Err(EINVAL);
-        }
-        if data.out_sync_count > MAX_SYNCS_PER_SUBMISSION {
-            cls_pr_debug!(
-                Errors,
-                "submit: Too many out syncs: {} > {}\n",
-                data.out_sync_count,
-                MAX_SYNCS_PER_SUBMISSION
-            );
-            return Err(EINVAL);
-        }
         if data.command_count > MAX_COMMANDS_PER_SUBMISSION {
             cls_pr_debug!(
                 Errors,

From aea66a1ad84e0c59c8b814fdaa8d1809646a1b98 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 1 Sep 2023 18:40:19 +0900
Subject: [PATCH 0937/1027] drm/asahi: render: Remove sync TVB growth support

We decided we can't reasonably support this given fence forward progress
requirements.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index d757f62dbcbb55..4ffd019f411582 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -238,7 +238,6 @@ impl super::Queue::ver {
         if cmdbuf.flags
             & !(uapi::ASAHI_RENDER_NO_CLEAR_PIPELINE_TEXTURES
                 | uapi::ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S
-                | uapi::ASAHI_RENDER_SYNC_TVB_GROWTH
                 | uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES
                 | uapi::ASAHI_RENDER_NO_VERTEX_CLUSTERING
                 | uapi::ASAHI_RENDER_MSAA_ZS) as u64
@@ -642,8 +641,7 @@ impl super::Queue::ver {
                             unk_50: 0x1, // fixed
                             event_generation: self.id as u32,
                             buffer_slot: scene.slot(),
-                            sync_grow: (cmdbuf.flags & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
-                                != 0) as u32,
+                            sync_grow: 0,
                             event_seq: U64(ev_frag.event_seq),
                             unk_68: 0,
                             unk_758_flag: inner_weak_ptr!(ptr, unk_758_flag),
@@ -1022,8 +1020,7 @@ impl super::Queue::ver {
                     encoder_params <- try_init!(fw::job::raw::EncoderParams {
                         unk_8: (cmdbuf.flags & uapi::ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S as u64
                             != 0) as u32,
-                        sync_grow: (cmdbuf.flags & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
-                                    != 0) as u32,
+                        sync_grow: 0,
                         unk_10: 0x0, // fixed
                         encoder_id: cmdbuf.encoder_id,
                         unk_18: 0x0, // fixed
@@ -1492,9 +1489,7 @@ impl super::Queue::ver {
                     }),
                     unk_55c: 0,
                     unk_560: 0,
-                    sync_grow: (cmdbuf.flags
-                        & uapi::ASAHI_RENDER_SYNC_TVB_GROWTH as u64
-                        != 0) as u32,
+                    sync_grow: 0,
                     unk_568: 0,
                     unk_56c: 0,
                     meta <- try_init!(fw::job::raw::JobMeta {

From 1d9c07954ff9084438fde4e522980ed26f6b12f4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 1 Sep 2023 18:43:57 +0900
Subject: [PATCH 0938/1027] drm/asahi: buffer: Complain more loudly about sync
 requests

This codepath is known broken and we have no plans to fix it. However,
it works most of the time, and it can work around bugs in the minimum
TVB size calculation formula. So if that ever happens, we still want it
to fix the problem, but complain loudly so we can fix the calculation.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index 6c26b5e6d653a1..3206525c1bc3a8 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -770,8 +770,8 @@ impl BufferManager::ver {
             .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned())
         {
             Some(owner) => {
-                pr_info!(
-                    "BufferManager: Received synchronous grow request for slot {}, this is not generally expected\n",
+                pr_err!(
+                    "BufferManager: Unexpected grow request for slot {}. This might deadlock. Please report this bug.\n",
                     slot
                 );
                 owner.sync_grow();

From 68366724b645fc21dbf2ba7d633aca33891c14ab Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 Nov 2023 18:16:48 +0900
Subject: [PATCH 0939/1027] drm/asahi: Identify & add render VS spills flag

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/vertex.rs    | 2 +-
 drivers/gpu/drm/asahi/queue/render.rs | 5 ++++-
 2 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
index a6318898817abf..12833488bcbeb6 100644
--- a/drivers/gpu/drm/asahi/fw/vertex.rs
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -141,7 +141,7 @@ pub(crate) mod raw {
         pub(crate) unk_560: u32,
         pub(crate) sync_grow: u32,
         pub(crate) unk_568: u32,
-        pub(crate) unk_56c: u32,
+        pub(crate) spills: u32,
         pub(crate) meta: job::raw::JobMeta,
         pub(crate) unk_after_meta: u32,
         pub(crate) unk_buf_0: U64,
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 4ffd019f411582..52f75dc1c2b354 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -238,6 +238,7 @@ impl super::Queue::ver {
         if cmdbuf.flags
             & !(uapi::ASAHI_RENDER_NO_CLEAR_PIPELINE_TEXTURES
                 | uapi::ASAHI_RENDER_SET_WHEN_RELOADING_Z_OR_S
+                | uapi::ASAHI_RENDER_VERTEX_SPILLS
                 | uapi::ASAHI_RENDER_PROCESS_EMPTY_TILES
                 | uapi::ASAHI_RENDER_NO_VERTEX_CLUSTERING
                 | uapi::ASAHI_RENDER_MSAA_ZS) as u64
@@ -1491,7 +1492,9 @@ impl super::Queue::ver {
                     unk_560: 0,
                     sync_grow: 0,
                     unk_568: 0,
-                    unk_56c: 0,
+                    spills: (cmdbuf.flags
+                        & uapi::ASAHI_RENDER_VERTEX_SPILLS as u64
+                        != 0) as u32,
                     meta <- try_init!(fw::job::raw::JobMeta {
                         unk_0: 0,
                         unk_2: 0,

From 55f3dee9c476420ee0df1fca4855f72e936b29a7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 Nov 2023 17:34:46 +0900
Subject: [PATCH 0940/1027] drm/asahi: Identify and allocate clustered layering
 metadata buf

Turns out multi-cluster machines also need a clustered buffer for
layered rendering.

Fixes layered rendering on G13X with barriers (I guess if you don't
flush memory this stays in some kind of cache and somehow doesn't
matter?).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs       | 24 +++++++++++++++++++-----
 drivers/gpu/drm/asahi/fw/vertex.rs    |  2 +-
 drivers/gpu/drm/asahi/queue/render.rs |  8 +++++---
 3 files changed, 25 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index 3206525c1bc3a8..b1db8e378a38d4 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -111,6 +111,8 @@ pub(crate) struct Scene {
     // Note: these are dead code only on some version variants.
     // It's easier to do this than to propagate the version conditionals everywhere.
     #[allow(dead_code)]
+    meta1_off: usize,
+    #[allow(dead_code)]
     meta2_off: usize,
     #[allow(dead_code)]
     meta3_off: usize,
@@ -208,13 +210,22 @@ impl Scene::ver {
             .map(|c| c.tilemaps.gpu_pointer())
     }
 
+    /// Returns the GPU pointer to the clustering layer metadata buffer, if clustering is enabled.
+    #[allow(dead_code)]
+    pub(crate) fn tvb_cluster_layermeta_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
+        self.object
+            .clustering
+            .as_ref()
+            .map(|c| c.meta.gpu_pointer())
+    }
+
     /// Returns the GPU pointer to the clustering metadata 1 buffer, if clustering is enabled.
     #[allow(dead_code)]
     pub(crate) fn meta_1_pointer(&self) -> Option<GpuPointer<'_, &'_ [u8]>> {
         self.object
             .clustering
             .as_ref()
-            .map(|c| c.meta.gpu_pointer())
+            .map(|c| c.meta.gpu_offset_pointer(self.meta1_off))
     }
 
     /// Returns the GPU pointer to the clustering metadata 2 buffer, if clustering is enabled.
@@ -590,6 +601,7 @@ impl Buffer::ver {
             }
         };
 
+        let mut clmeta_size = 0;
         let mut meta1_size = 0;
         let mut meta2_size = 0;
         let mut meta3_size = 0;
@@ -597,6 +609,7 @@ impl Buffer::ver {
         let clustering = if inner.num_clusters > 1 {
             let cfg = inner.cfg.clustering.as_ref().unwrap();
 
+            clmeta_size = tile_info.layermeta_size * cfg.max_splits;
             // Maybe: (4x4 macro tiles + 1 global page)*n, 32bit each (17*4*n)
             // Unused on t602x?
             meta1_size = align(tile_info.meta1_blocks as usize * cfg.meta1_blocksize, 0x80);
@@ -604,7 +617,7 @@ impl Buffer::ver {
             meta3_size = align(cfg.meta3_size, 0x80);
             let meta4_size = cfg.meta4_size;
 
-            let meta_size = meta1_size + meta2_size + meta3_size + meta4_size;
+            let meta_size = clmeta_size + meta1_size + meta2_size + meta3_size + meta4_size;
 
             mod_pr_debug!("Buffer: Allocating clustering buffers\n");
             let tilemaps = inner
@@ -690,9 +703,10 @@ impl Buffer::ver {
             rebind,
             preempt2_off: inner.preempt1_size,
             preempt3_off: inner.preempt1_size + inner.preempt2_size,
-            meta2_off: meta1_size,
-            meta3_off: meta1_size + meta2_size,
-            meta4_off: meta1_size + meta2_size + meta3_size,
+            meta1_off: clmeta_size,
+            meta2_off: clmeta_size + meta1_size,
+            meta3_off: clmeta_size + meta1_size + meta2_size,
+            meta4_off: clmeta_size + meta1_size + meta2_size + meta3_size,
         })
     }
 
diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
index 12833488bcbeb6..1b2e3d978018ce 100644
--- a/drivers/gpu/drm/asahi/fw/vertex.rs
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -49,7 +49,7 @@ pub(crate) mod raw {
         pub(crate) ppp_multisamplectl: U64,
         pub(crate) tvb_layermeta: GpuPointer<'a, &'a [u8]>,
         #[ver(G < G14)]
-        pub(crate) unk_60: U64,
+        pub(crate) tvb_cluster_layermeta: Option<GpuPointer<'a, &'a [u8]>>,
         #[ver(G < G14)]
         pub(crate) core_mask: Array<2, u32>,
         pub(crate) preempt_buf1: GpuPointer<'a, &'a [u8]>,
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 52f75dc1c2b354..a2b38b2bcfd1f1 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -1310,7 +1310,7 @@ impl super::Queue::ver {
                         ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), // fixed
                         tvb_layermeta: inner.scene.tvb_layermeta_pointer(),
                         #[ver(G < G14)]
-                        unk_60: U64(0x0), // fixed
+                        tvb_cluster_layermeta: inner.scene.tvb_cluster_layermeta_pointer(),
                         #[ver(G < G14)]
                         core_mask: Array::new([
                             *core_masks.first().unwrap_or(&0),
@@ -1403,8 +1403,10 @@ impl super::Queue::ver {
                             r.add(0x1c918, unks.tiling_control_2);
                             r.add(0x1c079, inner.scene.tvb_layermeta_pointer().into());
                             r.add(0x1c9d8, inner.scene.tvb_layermeta_pointer().into());
-                            r.add(0x1c089, 0);
-                            r.add(0x1c9e0, 0);
+                            let cl_layermeta_pointer =
+                                inner.scene.tvb_cluster_layermeta_pointer().map_or(0, |a| a.into());
+                            r.add(0x1c089, cl_layermeta_pointer);
+                            r.add(0x1c9e0, cl_layermeta_pointer);
                             let cl_meta_4_pointer =
                                 inner.scene.meta_4_pointer().map_or(0, |a| a.into());
                             r.add(0x16c41, cl_meta_4_pointer); // tvb_cluster_meta4

From f128e57eefb028d1bdbc289746ecbac0bc0c7da8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 Nov 2023 17:45:51 +0900
Subject: [PATCH 0941/1027] drm/asahi: Fix Clippy complaints

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs  | 2 +-
 drivers/gpu/drm/asahi/object.rs | 4 ++--
 2 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index 073ca63f51ee1f..eba7fe7ed113b5 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -575,7 +575,7 @@ impl Allocator for SimpleAllocator {
             true,
         )?;
 
-        let ptr = unsafe { p.add(offset) } as *mut u8;
+        let ptr = unsafe { p.add(offset) };
         let gpu_ptr = (iova + offset) as u64;
 
         mod_dev_dbg!(
diff --git a/drivers/gpu/drm/asahi/object.rs b/drivers/gpu/drm/asahi/object.rs
index 3e099f42b4c89a..64b81456dde311 100644
--- a/drivers/gpu/drm/asahi/object.rs
+++ b/drivers/gpu/drm/asahi/object.rs
@@ -112,7 +112,7 @@ macro_rules! inner_ptr {
     ($gpuva:expr, $($f:tt)*) => ({
         // This mirrors kernel::offset_of(), except we use type inference to avoid having to know
         // the type of the pointer explicitly.
-        fn uninit_from<'a, T: GpuStruct>(_: GpuPointer<'a, T>) -> core::mem::MaybeUninit<T::Raw<'static>> {
+        fn uninit_from<T: GpuStruct>(_: GpuPointer<'_, T>) -> core::mem::MaybeUninit<T::Raw<'static>> {
             core::mem::MaybeUninit::uninit()
         }
         let tmp = uninit_from($gpuva);
@@ -635,7 +635,7 @@ impl<T: Copy, U: Allocation<T>> GpuArray<T, U> {
 impl<T: Default, U: Allocation<T>> GpuArray<T, U> {
     /// Allocate a new GPU array, initializing each element to its default.
     pub(crate) fn empty(alloc: U, count: usize) -> Result<GpuArray<T, U>> {
-        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr() as *mut T;
+        let p = alloc.ptr().ok_or(EINVAL)?.as_ptr();
         let inner = GpuOnlyArray::new(alloc, count)?;
         let mut pi = p;
         for _i in 0..count {

From be0ba70fa709c052059467bf6c9301189c714cc4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 Nov 2023 19:26:51 +0900
Subject: [PATCH 0942/1027] drm/asahi: render: Fix layered rendering on G14X

We were missing one register and one bit.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index a2b38b2bcfd1f1..585aa6f5ecf69b 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -485,6 +485,7 @@ impl super::Queue::ver {
             | (((tile_info.tiles_x - 1) as u64) << 44)
             | (((tile_info.tiles_y - 1) as u64) << 53)
             | (if unk1 { 0 } else { 0x20_00000000 })
+            | (if cmdbuf.layers > 1 { 0x1_00000000 } else { 0 })
             | ((utile_config as u64 & 0xf000) << 28);
 
         let frag_result = result_writer
@@ -1443,7 +1444,7 @@ impl super::Queue::ver {
                             r.add(0x10171, tile_info.params.unk_24.into());
                             r.add(0x10169, tile_info.params.unk_28.into()); // TA_RENDER_TARGET_MAX
                             r.add(0x12099, unks.vtx_unk_118);
-                            r.add(0x1c9e8, 0);
+                            r.add(0x1c9e8, (tile_info.params.unk_28 & 0x4fff).into());
                             /*
                             r.add(0x10209, 0x100); // Some kind of counter?? Does this matter?
                             r.add(0x1c9f0, 0x100); // Some kind of counter?? Does this matter?

From 6cfc9b2eed3d52edd298bc728f14cd604d198ad8 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 12 Nov 2023 15:53:39 +0900
Subject: [PATCH 0943/1027] rust: Fix x86 build

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 rust/helpers/of.c | 4 ++--
 rust/kernel/of.rs | 5 +++++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/rust/helpers/of.c b/rust/helpers/of.c
index 6a47595ed6a717..c28a9e5261d265 100644
--- a/rust/helpers/of.c
+++ b/rust/helpers/of.c
@@ -2,11 +2,12 @@
 
 #include <linux/of.h>
 
+#ifdef CONFIG_OF
 bool rust_helper_of_node_is_root(const struct device_node *np)
 {
 	return of_node_is_root(np);
 }
-EXPORT_SYMBOL_GPL(rust_helper_of_node_is_root);
+#endif
 
 struct device_node *rust_helper_of_parse_phandle(const struct device_node *np,
                const char *phandle_name,
@@ -14,4 +15,3 @@ struct device_node *rust_helper_of_parse_phandle(const struct device_node *np,
 {
 	return of_parse_phandle(np, phandle_name, index);
 }
-EXPORT_SYMBOL_GPL(rust_helper_of_parse_phandle);
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index e6089f93739346..2390d1fb34db9f 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -163,6 +163,11 @@ impl Node {
 
     /// Returns `true` if the node is the root node.
     pub fn is_root(&self) -> bool {
+        #[cfg(not(CONFIG_OF))]
+        {
+            false
+        }
+        #[cfg(CONFIG_OF)]
         unsafe { bindings::of_node_is_root(self.raw_node) }
     }
 

From e02f244a69751c3b7d5cc584d55e7baf5441ceac Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Mon, 13 Nov 2023 00:10:13 +0900
Subject: [PATCH 0944/1027] rust: Fix x86 build more

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 rust/kernel/of.rs | 44 +++++++++++++++++++++++++++++++++++++++-----
 1 file changed, 39 insertions(+), 5 deletions(-)

diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index 2390d1fb34db9f..ed3fca8409c3d6 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -168,7 +168,9 @@ impl Node {
             false
         }
         #[cfg(CONFIG_OF)]
-        unsafe { bindings::of_node_is_root(self.raw_node) }
+        unsafe {
+            bindings::of_node_is_root(self.raw_node)
+        }
     }
 
     /// Returns the parent node, if any.
@@ -501,22 +503,54 @@ where
 
 /// Returns the root node of the OF device tree (if any).
 pub fn root() -> Option<Node> {
-    unsafe { Node::get_from_raw(bindings::of_root) }
+    #[cfg(not(CONFIG_OF))]
+    {
+        None
+    }
+    #[cfg(CONFIG_OF)]
+    // SAFETY: bindings::of_root is always valid or NULL
+    unsafe {
+        Node::get_from_raw(bindings::of_root)
+    }
 }
 
 /// Returns the /chosen node of the OF device tree (if any).
 pub fn chosen() -> Option<Node> {
-    unsafe { Node::get_from_raw(bindings::of_chosen) }
+    #[cfg(not(CONFIG_OF))]
+    {
+        None
+    }
+    #[cfg(CONFIG_OF)]
+    // SAFETY: bindings::of_chosen is always valid or NULL
+    unsafe {
+        Node::get_from_raw(bindings::of_chosen)
+    }
 }
 
 /// Returns the /aliases node of the OF device tree (if any).
 pub fn aliases() -> Option<Node> {
-    unsafe { Node::get_from_raw(bindings::of_aliases) }
+    #[cfg(not(CONFIG_OF))]
+    {
+        None
+    }
+    #[cfg(CONFIG_OF)]
+    // SAFETY: bindings::of_aliases is always valid or NULL
+    unsafe {
+        Node::get_from_raw(bindings::of_aliases)
+    }
 }
 
 /// Returns the system stdout node of the OF device tree (if any).
 pub fn stdout() -> Option<Node> {
-    unsafe { Node::get_from_raw(bindings::of_stdout) }
+    #[cfg(not(CONFIG_OF))]
+    {
+        None
+    }
+    #[cfg(CONFIG_OF)]
+    // SAFETY: bindings::of_stdout is always valid or NULL
+    unsafe {
+        Node::get_from_raw(bindings::of_stdout)
+    }
 }
 
 #[allow(unused_variables)]

From 08f8fa48c5c65bae7eb77faa30c9cce1f6a11895 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 17 Nov 2023 19:37:50 +0900
Subject: [PATCH 0945/1027] drm/asahi: render, buffer: Fix layered rendering on
 G13X (again)

The meta1 buffer needs to be multiplied by the layer count. This does
not seem to be needed on G14X (where even the stride is not specified
anywhere and no other registers are affected by the layer count).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/buffer.rs       |  2 ++
 drivers/gpu/drm/asahi/queue/render.rs | 11 +++++++----
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/buffer.rs b/drivers/gpu/drm/asahi/buffer.rs
index b1db8e378a38d4..3d9fada72243fd 100644
--- a/drivers/gpu/drm/asahi/buffer.rs
+++ b/drivers/gpu/drm/asahi/buffer.rs
@@ -89,6 +89,8 @@ pub(crate) struct TileInfo {
     pub(crate) tilemap_size: usize,
     /// Size of the Tail Pointer Cache, in bytes (for all layers * clusters).
     pub(crate) tpc_size: usize,
+    /// Number of blocks in the clustering meta buffer (for clustering) per layer.
+    pub(crate) meta1_layer_stride: u32,
     /// Number of blocks in the clustering meta buffer (for clustering).
     pub(crate) meta1_blocks: u32,
     /// Layering metadata size.
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 585aa6f5ecf69b..500ae2e429413d 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -145,8 +145,7 @@ impl super::Queue::ver {
         // No idea where this comes from, but it fits what macOS does...
         // GUESS: Number of 32K heap blocks to fit a 5-byte region header/pointer per tile?
         // That would make a ton of sense...
-        // TODO: Layers? Why the sample count factor here?
-        let meta1_blocks = if num_clusters > 1 {
+        let meta1_layer_stride = if num_clusters > 1 {
             div_ceil(
                 align(tiles_x, 2) * align(tiles_y, 4) * utiles_per_tile,
                 0x1980,
@@ -177,7 +176,11 @@ impl super::Queue::ver {
             //utiles_per_mtile: tiles_per_mtile * utiles_per_tile,
             tilemap_size,
             tpc_size,
-            meta1_blocks,
+            meta1_layer_stride,
+            #[ver(G < G14X)]
+            meta1_blocks: meta1_layer_stride * cmdbuf.layers,
+            #[ver(G >= G14X)]
+            meta1_blocks: meta1_layer_stride,
             layermeta_size: if layers > 1 { 0x100 } else { 0 },
             min_tvb_blocks: min_tvb_blocks as usize,
             params: fw::vertex::raw::TilingParameters {
@@ -1305,7 +1308,7 @@ impl super::Queue::ver {
                         tvb_cluster_meta1: inner
                             .scene
                             .meta_1_pointer()
-                            .map(|x| x.or((tile_info.meta1_blocks as u64) << 50)),
+                            .map(|x| x.or((tile_info.meta1_layer_stride as u64) << 50)),
                         utile_config,
                         unk_4c: 0,
                         ppp_multisamplectl: U64(cmdbuf.ppp_multisamplectl), // fixed

From eab89cfce21752dd470764716dee6276b692e2e0 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 19 Nov 2023 16:34:50 +0900
Subject: [PATCH 0946/1027] drm/asahi: initdata: New init chain API for rebase

Not going to split this up into fixups with all the reformatting...
Lina gets to do that on next rebase.

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 drivers/gpu/drm/asahi/initdata.rs | 1049 ++++++++++++++---------------
 1 file changed, 520 insertions(+), 529 deletions(-)

diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 31cb7070289ee0..56ba255b55fa83 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -49,18 +49,16 @@ impl<'a> InitDataBuilder::ver<'a> {
 
     /// Create the HwDataShared1 structure, which is used in two places in InitData.
     fn hw_shared1(cfg: &'static hw::HwConfig) -> impl Init<raw::HwDataShared1> {
-        init::chain(
-            init!(raw::HwDataShared1 {
-                unk_a4: cfg.shared1_a4,
-                ..Zeroable::zeroed()
-            }),
-            |ret| {
-                for (i, val) in cfg.shared1_tab.iter().enumerate() {
-                    ret.table[i] = *val;
-                }
-                Ok(())
-            },
-        )
+        init!(raw::HwDataShared1 {
+            unk_a4: cfg.shared1_a4,
+            ..Zeroable::zeroed()
+        })
+        .chain(|ret| {
+            for (i, val) in cfg.shared1_tab.iter().enumerate() {
+                ret.table[i] = *val;
+            }
+            Ok(())
+        })
     }
 
     fn init_curve(
@@ -91,78 +89,77 @@ impl<'a> InitDataBuilder::ver<'a> {
         cfg: &'static hw::HwConfig,
         dyncfg: &'a hw::DynConfig,
     ) -> impl Init<raw::HwDataShared2, Error> + 'a {
-        init::chain(
-            try_init!(raw::HwDataShared2 {
-                unk_28: Array::new([0xff; 16]),
-                g14: Default::default(),
-                unk_508: cfg.shared2_unk_508,
-                ..Zeroable::zeroed()
-            }),
-            |ret| {
-                for (i, val) in cfg.shared2_tab.iter().enumerate() {
-                    ret.table[i] = *val;
-                }
+        try_init!(raw::HwDataShared2 {
+            unk_28: Array::new([0xff; 16]),
+            g14: Default::default(),
+            unk_508: cfg.shared2_unk_508,
+            ..Zeroable::zeroed()
+        })
+        .chain(|ret| {
+            for (i, val) in cfg.shared2_tab.iter().enumerate() {
+                ret.table[i] = *val;
+            }
 
-                let curve_cfg = match cfg.shared2_curves.as_ref() {
-                    None => return Ok(()),
-                    Some(a) => a,
-                };
+            let curve_cfg = match cfg.shared2_curves.as_ref() {
+                None => return Ok(()),
+                Some(a) => a,
+            };
 
-                let mut t1 = Vec::new();
-                let mut t3 = Vec::new();
+            let mut t1 = Vec::new();
+            let mut t3 = Vec::new();
 
-                for _ in 0..curve_cfg.t3_scales.len() {
-                    t3.try_push(Vec::new())?;
-                }
+            for _ in 0..curve_cfg.t3_scales.len() {
+                t3.push(Vec::new(), GFP_KERNEL)?;
+            }
 
-                for (i, ps) in dyncfg.pwr.perf_states.iter().enumerate() {
-                    let t3_coef = curve_cfg.t3_coefs[i];
-                    if t3_coef == 0 {
-                        t1.try_push(0xffff)?;
-                        for j in t3.iter_mut() {
-                            j.try_push(0x3ffffff)?;
-                        }
-                        continue;
+            for (i, ps) in dyncfg.pwr.perf_states.iter().enumerate() {
+                let t3_coef = curve_cfg.t3_coefs[i];
+                if t3_coef == 0 {
+                    t1.push(0xffff, GFP_KERNEL)?;
+                    for j in t3.iter_mut() {
+                        j.push(0x3ffffff, GFP_KERNEL)?;
                     }
+                    continue;
+                }
+
+                let f_khz = (ps.freq_hz / 1000) as u64;
+                let v_max = ps.max_volt_mv() as u64;
 
-                    let f_khz = (ps.freq_hz / 1000) as u64;
-                    let v_max = ps.max_volt_mv() as u64;
+                t1.push(
+                    (1000000000 * (curve_cfg.t1_coef as u64) / (f_khz * v_max))
+                        .try_into()
+                        .unwrap(),
+                    GFP_KERNEL,
+                )?;
 
-                    t1.try_push(
-                        (1000000000 * (curve_cfg.t1_coef as u64) / (f_khz * v_max))
+                for (j, scale) in curve_cfg.t3_scales.iter().enumerate() {
+                    t3[j].push(
+                        (t3_coef as u64 * 1000000100 * *scale as u64 / (f_khz * v_max * 6))
                             .try_into()
                             .unwrap(),
                         GFP_KERNEL,
                     )?;
-
-                    for (j, scale) in curve_cfg.t3_scales.iter().enumerate() {
-                        t3[j].try_push(
-                            (t3_coef as u64 * 1000000100 * *scale as u64 / (f_khz * v_max * 6))
-                                .try_into()
-                                .unwrap(),
-                        )?;
-                    }
                 }
+            }
 
-                ret.g14.unk_14 = 0x6000000;
-                Self::init_curve(
-                    &mut ret.g14.curve1,
-                    0,
-                    0x20000000,
-                    &[0xffff],
-                    &[0x0f07],
-                    &[],
-                );
-                Self::init_curve(&mut ret.g14.curve2, 7, 0x80000000, &t1, curve_cfg.t2, &t3);
+            ret.g14.unk_14 = 0x6000000;
+            Self::init_curve(
+                &mut ret.g14.curve1,
+                0,
+                0x20000000,
+                &[0xffff],
+                &[0x0f07],
+                &[],
+            );
+            Self::init_curve(&mut ret.g14.curve2, 7, 0x80000000, &t1, curve_cfg.t2, &t3);
 
-                Ok(())
-            },
-        )
+            Ok(())
+        })
     }
 
     /// Create the HwDataShared3 structure, which is used in two places in InitData.
     fn hw_shared3(cfg: &'static hw::HwConfig) -> impl Init<raw::HwDataShared3> {
-        init::chain(init::zeroed::<raw::HwDataShared3>(), |ret| {
+        init::zeroed::<raw::HwDataShared3>().chain(|ret| {
             if !cfg.shared3_tab.is_empty() {
                 ret.unk_0 = 1;
                 ret.unk_4 = 500;
@@ -181,7 +178,7 @@ impl<'a> InitDataBuilder::ver<'a> {
     ) -> impl Init<raw::T81xxData> {
         let _perf_max_pstate = dyncfg.pwr.perf_max_pstate;
 
-        init::chain(init::zeroed::<raw::T81xxData>(), move |_ret| {
+        init::zeroed::<raw::T81xxData>().chain(move |_ret| {
             match cfg.chip_id {
                 0x8103 | 0x8112 => {
                     #[ver(V < V13_3)]
@@ -236,246 +233,244 @@ impl<'a> InitDataBuilder::ver<'a> {
         self.alloc.private.new_init(init::zeroed(), |_inner, _ptr| {
             let cfg = &self.cfg;
             let dyncfg = &self.dyncfg;
-            init::chain(
-                try_init!(raw::HwDataA::ver {
-                    clocks_per_period: clocks_per_period,
-                    #[ver(V >= V13_0B4)]
-                    clocks_per_period_2: clocks_per_period,
-                    pwr_status: AtomicU32::new(4),
-                    unk_10: f32!(1.0),
-                    actual_pstate: 1,
-                    tgt_pstate: 1,
-                    base_pstate_scaled: base_ps_scaled,
-                    unk_40: 1,
-                    max_pstate_scaled: max_ps_scaled,
-                    min_pstate_scaled: 100,
-                    unk_64c: 625,
-                    pwr_filter_a_neg: f32!(1.0) - pwr_filter_a,
-                    pwr_filter_a: pwr_filter_a,
-                    pwr_integral_gain: pwr.pwr_integral_gain,
-                    pwr_integral_min_clamp: pwr.pwr_integral_min_clamp.into(),
-                    max_power_1: pwr.max_power_mw.into(),
-                    pwr_proportional_gain: pwr.pwr_proportional_gain,
-                    pwr_pstate_related_k: -F32::from(max_ps_scaled) / pwr.max_power_mw.into(),
-                    pwr_pstate_max_dc_offset: pwr.pwr_min_duty_cycle as i32 - max_ps_scaled as i32,
-                    max_pstate_scaled_2: max_ps_scaled,
-                    max_power_2: pwr.max_power_mw,
-                    max_pstate_scaled_3: max_ps_scaled,
-                    ppm_filter_tc_periods_x4: ppm_filter_tc_periods * 4,
-                    ppm_filter_a_neg: f32!(1.0) - ppm_filter_a,
-                    ppm_filter_a: ppm_filter_a,
-                    ppm_ki_dt: pwr.ppm_ki * period_s,
-                    unk_6fc: f32!(65536.0),
-                    ppm_kp: pwr.ppm_kp,
-                    pwr_min_duty_cycle: pwr.pwr_min_duty_cycle,
-                    max_pstate_scaled_4: max_ps_scaled,
-                    unk_71c: f32!(0.0),
-                    max_power_3: pwr.max_power_mw,
-                    cur_power_mw_2: 0x0,
-                    ppm_filter_tc_ms: pwr.ppm_filter_time_constant_ms,
-                    #[ver(V >= V13_0B4)]
-                    ppm_filter_tc_clks: ppm_filter_tc_ms_rounded * base_clock_khz,
-                    perf_tgt_utilization: pwr.perf_tgt_utilization,
-                    perf_boost_min_util: pwr.perf_boost_min_util,
-                    perf_boost_ce_step: pwr.perf_boost_ce_step,
-                    perf_reset_iters: pwr.perf_reset_iters,
-                    unk_774: 6,
-                    unk_778: 1,
-                    perf_filter_drop_threshold: pwr.perf_filter_drop_threshold,
-                    perf_filter_a_neg: f32!(1.0) - perf_filter_a,
-                    perf_filter_a2_neg: f32!(1.0) - perf_filter_a2,
-                    perf_filter_a: perf_filter_a,
-                    perf_filter_a2: perf_filter_a2,
-                    perf_ki: pwr.perf_integral_gain,
-                    perf_ki2: pwr.perf_integral_gain2,
-                    perf_integral_min_clamp: pwr.perf_integral_min_clamp.into(),
-                    unk_79c: f32!(95.0),
-                    perf_kp: pwr.perf_proportional_gain,
-                    perf_kp2: pwr.perf_proportional_gain2,
-                    boost_state_unk_k: F32::from(boost_ps_count) / f32!(0.95),
-                    base_pstate_scaled_2: base_ps_scaled,
-                    max_pstate_scaled_5: max_ps_scaled,
-                    base_pstate_scaled_3: base_ps_scaled,
-                    perf_tgt_utilization_2: pwr.perf_tgt_utilization,
-                    base_pstate_scaled_4: base_ps_scaled,
-                    unk_7fc: f32!(65536.0),
-                    pwr_min_duty_cycle_2: pwr.pwr_min_duty_cycle.into(),
-                    max_pstate_scaled_6: max_ps_scaled.into(),
-                    max_freq_mhz: pwr.max_freq_mhz,
-                    pwr_min_duty_cycle_3: pwr.pwr_min_duty_cycle,
-                    min_pstate_scaled_4: f32!(100.0),
-                    max_pstate_scaled_7: max_ps_scaled,
-                    unk_alpha_neg: f32!(0.8),
-                    unk_alpha: f32!(0.2),
-                    fast_die0_sensor_mask: U64(cfg.fast_sensor_mask[0]),
-                    #[ver(G >= G14X)]
-                    fast_die1_sensor_mask: U64(cfg.fast_sensor_mask[1]),
-                    fast_die0_release_temp_cc: 100 * pwr.fast_die0_release_temp,
-                    unk_87c: cfg.da.unk_87c,
-                    unk_880: 0x4,
-                    unk_894: f32!(1.0),
-
-                    fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
-                    unk_8a8: f32!(65536.0),
-                    fast_die0_kp: pwr.fast_die0_proportional_gain,
-                    pwr_min_duty_cycle_4: pwr.pwr_min_duty_cycle,
-                    max_pstate_scaled_8: max_ps_scaled,
-                    max_pstate_scaled_9: max_ps_scaled,
-                    fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
-                    unk_8cc: cfg.da.unk_8cc,
-                    max_pstate_scaled_10: max_ps_scaled,
-                    max_pstate_scaled_11: max_ps_scaled,
-                    unk_c2c: 1,
-                    power_zone_count: pwr.power_zones.len() as u32,
-                    max_power_4: pwr.max_power_mw,
-                    max_power_5: pwr.max_power_mw,
-                    max_power_6: pwr.max_power_mw,
-                    avg_power_target_filter_a_neg: f32!(1.0) - avg_power_target_filter_a,
-                    avg_power_target_filter_a: avg_power_target_filter_a,
-                    avg_power_target_filter_tc_x4: 4 * pwr.avg_power_target_filter_tc,
-                    avg_power_target_filter_tc_xperiod: period_ms * pwr.avg_power_target_filter_tc,
-                    #[ver(V >= V13_0B4)]
-                    avg_power_target_filter_tc_clks: period_ms
-                        * pwr.avg_power_target_filter_tc
-                        * base_clock_khz,
-                    avg_power_filter_tc_periods_x4: 4 * avg_power_filter_tc_periods,
-                    avg_power_filter_a_neg: f32!(1.0) - avg_power_filter_a,
-                    avg_power_filter_a: avg_power_filter_a,
-                    avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
-                    unk_d20: f32!(65536.0),
-                    avg_power_kp: pwr.avg_power_kp,
-                    avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
-                    max_pstate_scaled_12: max_ps_scaled,
-                    max_pstate_scaled_13: max_ps_scaled,
-                    max_power_7: pwr.max_power_mw.into(),
-                    max_power_8: pwr.max_power_mw,
-                    avg_power_filter_tc_ms: pwr.avg_power_filter_tc_ms,
-                    #[ver(V >= V13_0B4)]
-                    avg_power_filter_tc_clks: avg_power_filter_tc_ms_rounded * base_clock_khz,
-                    max_pstate_scaled_14: max_ps_scaled,
-                    t81xx_data <- Self::t81xx_data(cfg, dyncfg),
-                    #[ver(V >= V13_0B4)]
-                    unk_e10_0 <- {
-                        let filter_a = f32!(1.0) / pwr.se_filter_time_constant.into();
-                        let filter_1_a = f32!(1.0) / pwr.se_filter_time_constant_1.into();
-                        try_init!(raw::HwDataA130Extra {
-                            unk_38: 4,
-                            unk_3c: 8000,
-                            gpu_se_inactive_threshold: pwr.se_inactive_threshold,
-                            gpu_se_engagement_criteria: pwr.se_engagement_criteria,
-                            gpu_se_reset_criteria: pwr.se_reset_criteria,
-                            unk_54: 50,
-                            unk_58: 0x1,
-                            gpu_se_filter_a_neg: f32!(1.0) - filter_a,
-                            gpu_se_filter_1_a_neg: f32!(1.0) - filter_1_a,
-                            gpu_se_filter_a: filter_a,
-                            gpu_se_filter_1_a: filter_1_a,
-                            gpu_se_ki_dt: pwr.se_ki * period_s,
-                            gpu_se_ki_1_dt: pwr.se_ki_1 * period_s,
-                            unk_7c: f32!(65536.0),
-                            gpu_se_kp: pwr.se_kp,
-                            gpu_se_kp_1: pwr.se_kp_1,
-
-                            #[ver(V >= V13_3)]
-                            unk_8c: 100,
-                            #[ver(V < V13_3)]
-                            unk_8c: 40,
-
-                            max_pstate_scaled_1: max_ps_scaled,
-                            unk_9c: f32!(8000.0),
-                            unk_a0: 1400,
-                            gpu_se_filter_time_constant_ms: pwr.se_filter_time_constant * period_ms,
-                            gpu_se_filter_time_constant_1_ms: pwr.se_filter_time_constant_1
-                                * period_ms,
-                            gpu_se_filter_time_constant_clks: U64((pwr.se_filter_time_constant
-                                * clocks_per_period_coarse)
-                                .into()),
-                            gpu_se_filter_time_constant_1_clks: U64((pwr
-                                .se_filter_time_constant_1
-                                * clocks_per_period_coarse)
-                                .into()),
-                            unk_c4: f32!(65536.0),
-                            unk_114: f32!(65536.0),
-                            unk_124: 40,
-                            max_pstate_scaled_2: max_ps_scaled,
-                            ..Zeroable::zeroed()
-                        })
-                    },
-                    fast_die0_sensor_mask_2: U64(cfg.fast_sensor_mask[0]),
-                    #[ver(G >= G14X)]
-                    fast_die1_sensor_mask_2: U64(cfg.fast_sensor_mask[1]),
-                    unk_e24: cfg.da.unk_e24,
-                    unk_e28: 1,
-                    fast_die0_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[0]),
-                    #[ver(G >= G14X)]
-                    fast_die1_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[1]),
-                    #[ver(V < V13_0B4)]
-                    fast_die0_sensor_present: U64(cfg.fast_die0_sensor_present as u64),
-                    unk_163c: 1,
-                    unk_3644: 0,
-                    hws1 <- Self::hw_shared1(cfg),
-                    hws2 <- Self::hw_shared2(cfg, dyncfg),
-                    hws3 <- Self::hw_shared3(cfg),
-                    unk_3ce8: 1,
-                    ..Zeroable::zeroed()
-                }),
-                |raw| {
-                    for i in 0..self.dyncfg.pwr.perf_states.len() {
-                        raw.sram_k[i] = self.cfg.sram_k;
-                    }
-
-                    for (i, coef) in pwr.core_leak_coef.iter().enumerate() {
-                        raw.core_leak_coef[i] = *coef;
-                    }
+            try_init!(raw::HwDataA::ver {
+                clocks_per_period: clocks_per_period,
+                #[ver(V >= V13_0B4)]
+                clocks_per_period_2: clocks_per_period,
+                pwr_status: AtomicU32::new(4),
+                unk_10: f32!(1.0),
+                actual_pstate: 1,
+                tgt_pstate: 1,
+                base_pstate_scaled: base_ps_scaled,
+                unk_40: 1,
+                max_pstate_scaled: max_ps_scaled,
+                min_pstate_scaled: 100,
+                unk_64c: 625,
+                pwr_filter_a_neg: f32!(1.0) - pwr_filter_a,
+                pwr_filter_a: pwr_filter_a,
+                pwr_integral_gain: pwr.pwr_integral_gain,
+                pwr_integral_min_clamp: pwr.pwr_integral_min_clamp.into(),
+                max_power_1: pwr.max_power_mw.into(),
+                pwr_proportional_gain: pwr.pwr_proportional_gain,
+                pwr_pstate_related_k: -F32::from(max_ps_scaled) / pwr.max_power_mw.into(),
+                pwr_pstate_max_dc_offset: pwr.pwr_min_duty_cycle as i32 - max_ps_scaled as i32,
+                max_pstate_scaled_2: max_ps_scaled,
+                max_power_2: pwr.max_power_mw,
+                max_pstate_scaled_3: max_ps_scaled,
+                ppm_filter_tc_periods_x4: ppm_filter_tc_periods * 4,
+                ppm_filter_a_neg: f32!(1.0) - ppm_filter_a,
+                ppm_filter_a: ppm_filter_a,
+                ppm_ki_dt: pwr.ppm_ki * period_s,
+                unk_6fc: f32!(65536.0),
+                ppm_kp: pwr.ppm_kp,
+                pwr_min_duty_cycle: pwr.pwr_min_duty_cycle,
+                max_pstate_scaled_4: max_ps_scaled,
+                unk_71c: f32!(0.0),
+                max_power_3: pwr.max_power_mw,
+                cur_power_mw_2: 0x0,
+                ppm_filter_tc_ms: pwr.ppm_filter_time_constant_ms,
+                #[ver(V >= V13_0B4)]
+                ppm_filter_tc_clks: ppm_filter_tc_ms_rounded * base_clock_khz,
+                perf_tgt_utilization: pwr.perf_tgt_utilization,
+                perf_boost_min_util: pwr.perf_boost_min_util,
+                perf_boost_ce_step: pwr.perf_boost_ce_step,
+                perf_reset_iters: pwr.perf_reset_iters,
+                unk_774: 6,
+                unk_778: 1,
+                perf_filter_drop_threshold: pwr.perf_filter_drop_threshold,
+                perf_filter_a_neg: f32!(1.0) - perf_filter_a,
+                perf_filter_a2_neg: f32!(1.0) - perf_filter_a2,
+                perf_filter_a: perf_filter_a,
+                perf_filter_a2: perf_filter_a2,
+                perf_ki: pwr.perf_integral_gain,
+                perf_ki2: pwr.perf_integral_gain2,
+                perf_integral_min_clamp: pwr.perf_integral_min_clamp.into(),
+                unk_79c: f32!(95.0),
+                perf_kp: pwr.perf_proportional_gain,
+                perf_kp2: pwr.perf_proportional_gain2,
+                boost_state_unk_k: F32::from(boost_ps_count) / f32!(0.95),
+                base_pstate_scaled_2: base_ps_scaled,
+                max_pstate_scaled_5: max_ps_scaled,
+                base_pstate_scaled_3: base_ps_scaled,
+                perf_tgt_utilization_2: pwr.perf_tgt_utilization,
+                base_pstate_scaled_4: base_ps_scaled,
+                unk_7fc: f32!(65536.0),
+                pwr_min_duty_cycle_2: pwr.pwr_min_duty_cycle.into(),
+                max_pstate_scaled_6: max_ps_scaled.into(),
+                max_freq_mhz: pwr.max_freq_mhz,
+                pwr_min_duty_cycle_3: pwr.pwr_min_duty_cycle,
+                min_pstate_scaled_4: f32!(100.0),
+                max_pstate_scaled_7: max_ps_scaled,
+                unk_alpha_neg: f32!(0.8),
+                unk_alpha: f32!(0.2),
+                fast_die0_sensor_mask: U64(cfg.fast_sensor_mask[0]),
+                #[ver(G >= G14X)]
+                fast_die1_sensor_mask: U64(cfg.fast_sensor_mask[1]),
+                fast_die0_release_temp_cc: 100 * pwr.fast_die0_release_temp,
+                unk_87c: cfg.da.unk_87c,
+                unk_880: 0x4,
+                unk_894: f32!(1.0),
+
+                fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
+                unk_8a8: f32!(65536.0),
+                fast_die0_kp: pwr.fast_die0_proportional_gain,
+                pwr_min_duty_cycle_4: pwr.pwr_min_duty_cycle,
+                max_pstate_scaled_8: max_ps_scaled,
+                max_pstate_scaled_9: max_ps_scaled,
+                fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
+                unk_8cc: cfg.da.unk_8cc,
+                max_pstate_scaled_10: max_ps_scaled,
+                max_pstate_scaled_11: max_ps_scaled,
+                unk_c2c: 1,
+                power_zone_count: pwr.power_zones.len() as u32,
+                max_power_4: pwr.max_power_mw,
+                max_power_5: pwr.max_power_mw,
+                max_power_6: pwr.max_power_mw,
+                avg_power_target_filter_a_neg: f32!(1.0) - avg_power_target_filter_a,
+                avg_power_target_filter_a: avg_power_target_filter_a,
+                avg_power_target_filter_tc_x4: 4 * pwr.avg_power_target_filter_tc,
+                avg_power_target_filter_tc_xperiod: period_ms * pwr.avg_power_target_filter_tc,
+                #[ver(V >= V13_0B4)]
+                avg_power_target_filter_tc_clks: period_ms
+                    * pwr.avg_power_target_filter_tc
+                    * base_clock_khz,
+                avg_power_filter_tc_periods_x4: 4 * avg_power_filter_tc_periods,
+                avg_power_filter_a_neg: f32!(1.0) - avg_power_filter_a,
+                avg_power_filter_a: avg_power_filter_a,
+                avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
+                unk_d20: f32!(65536.0),
+                avg_power_kp: pwr.avg_power_kp,
+                avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
+                max_pstate_scaled_12: max_ps_scaled,
+                max_pstate_scaled_13: max_ps_scaled,
+                max_power_7: pwr.max_power_mw.into(),
+                max_power_8: pwr.max_power_mw,
+                avg_power_filter_tc_ms: pwr.avg_power_filter_tc_ms,
+                #[ver(V >= V13_0B4)]
+                avg_power_filter_tc_clks: avg_power_filter_tc_ms_rounded * base_clock_khz,
+                max_pstate_scaled_14: max_ps_scaled,
+                t81xx_data <- Self::t81xx_data(cfg, dyncfg),
+                #[ver(V >= V13_0B4)]
+                unk_e10_0 <- {
+                    let filter_a = f32!(1.0) / pwr.se_filter_time_constant.into();
+                    let filter_1_a = f32!(1.0) / pwr.se_filter_time_constant_1.into();
+                    try_init!(raw::HwDataA130Extra {
+                        unk_38: 4,
+                        unk_3c: 8000,
+                        gpu_se_inactive_threshold: pwr.se_inactive_threshold,
+                        gpu_se_engagement_criteria: pwr.se_engagement_criteria,
+                        gpu_se_reset_criteria: pwr.se_reset_criteria,
+                        unk_54: 50,
+                        unk_58: 0x1,
+                        gpu_se_filter_a_neg: f32!(1.0) - filter_a,
+                        gpu_se_filter_1_a_neg: f32!(1.0) - filter_1_a,
+                        gpu_se_filter_a: filter_a,
+                        gpu_se_filter_1_a: filter_1_a,
+                        gpu_se_ki_dt: pwr.se_ki * period_s,
+                        gpu_se_ki_1_dt: pwr.se_ki_1 * period_s,
+                        unk_7c: f32!(65536.0),
+                        gpu_se_kp: pwr.se_kp,
+                        gpu_se_kp_1: pwr.se_kp_1,
+
+                        #[ver(V >= V13_3)]
+                        unk_8c: 100,
+                        #[ver(V < V13_3)]
+                        unk_8c: 40,
+
+                        max_pstate_scaled_1: max_ps_scaled,
+                        unk_9c: f32!(8000.0),
+                        unk_a0: 1400,
+                        gpu_se_filter_time_constant_ms: pwr.se_filter_time_constant * period_ms,
+                        gpu_se_filter_time_constant_1_ms: pwr.se_filter_time_constant_1
+                            * period_ms,
+                        gpu_se_filter_time_constant_clks: U64((pwr.se_filter_time_constant
+                            * clocks_per_period_coarse)
+                            .into()),
+                        gpu_se_filter_time_constant_1_clks: U64((pwr
+                            .se_filter_time_constant_1
+                            * clocks_per_period_coarse)
+                            .into()),
+                        unk_c4: f32!(65536.0),
+                        unk_114: f32!(65536.0),
+                        unk_124: 40,
+                        max_pstate_scaled_2: max_ps_scaled,
+                        ..Zeroable::zeroed()
+                    })
+                },
+                fast_die0_sensor_mask_2: U64(cfg.fast_sensor_mask[0]),
+                #[ver(G >= G14X)]
+                fast_die1_sensor_mask_2: U64(cfg.fast_sensor_mask[1]),
+                unk_e24: cfg.da.unk_e24,
+                unk_e28: 1,
+                fast_die0_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[0]),
+                #[ver(G >= G14X)]
+                fast_die1_sensor_mask_alt: U64(cfg.fast_sensor_mask_alt[1]),
+                #[ver(V < V13_0B4)]
+                fast_die0_sensor_present: U64(cfg.fast_die0_sensor_present as u64),
+                unk_163c: 1,
+                unk_3644: 0,
+                hws1 <- Self::hw_shared1(cfg),
+                hws2 <- Self::hw_shared2(cfg, dyncfg),
+                hws3 <- Self::hw_shared3(cfg),
+                unk_3ce8: 1,
+                ..Zeroable::zeroed()
+            })
+            .chain(|raw| {
+                for i in 0..self.dyncfg.pwr.perf_states.len() {
+                    raw.sram_k[i] = self.cfg.sram_k;
+                }
 
-                    for (i, coef) in pwr.sram_leak_coef.iter().enumerate() {
-                        raw.sram_leak_coef[i] = *coef;
-                    }
+                for (i, coef) in pwr.core_leak_coef.iter().enumerate() {
+                    raw.core_leak_coef[i] = *coef;
+                }
 
-                    #[ver(V >= V13_0B4)]
-                    if let Some(csafr) = pwr.csafr.as_ref() {
-                        for (i, coef) in csafr.leak_coef_afr.iter().enumerate() {
-                            raw.aux_leak_coef.cs_1[i] = *coef;
-                            raw.aux_leak_coef.cs_2[i] = *coef;
-                        }
+                for (i, coef) in pwr.sram_leak_coef.iter().enumerate() {
+                    raw.sram_leak_coef[i] = *coef;
+                }
 
-                        for (i, coef) in csafr.leak_coef_cs.iter().enumerate() {
-                            raw.aux_leak_coef.afr_1[i] = *coef;
-                            raw.aux_leak_coef.afr_2[i] = *coef;
-                        }
+                #[ver(V >= V13_0B4)]
+                if let Some(csafr) = pwr.csafr.as_ref() {
+                    for (i, coef) in csafr.leak_coef_afr.iter().enumerate() {
+                        raw.aux_leak_coef.cs_1[i] = *coef;
+                        raw.aux_leak_coef.cs_2[i] = *coef;
                     }
 
-                    for i in 0..self.dyncfg.id.num_clusters as usize {
-                        if let Some(coef_a) = self.cfg.unk_coef_a.get(i) {
-                            (*raw.unk_coef_a1[i])[..coef_a.len()].copy_from_slice(coef_a);
-                            (*raw.unk_coef_a2[i])[..coef_a.len()].copy_from_slice(coef_a);
-                        }
-                        if let Some(coef_b) = self.cfg.unk_coef_b.get(i) {
-                            (*raw.unk_coef_b1[i])[..coef_b.len()].copy_from_slice(coef_b);
-                            (*raw.unk_coef_b2[i])[..coef_b.len()].copy_from_slice(coef_b);
-                        }
+                    for (i, coef) in csafr.leak_coef_cs.iter().enumerate() {
+                        raw.aux_leak_coef.afr_1[i] = *coef;
+                        raw.aux_leak_coef.afr_2[i] = *coef;
                     }
+                }
 
-                    for (i, pz) in pwr.power_zones.iter().enumerate() {
-                        raw.power_zones[i].target = pz.target;
-                        raw.power_zones[i].target_off = pz.target - pz.target_offset;
-                        raw.power_zones[i].filter_tc_x4 = 4 * pz.filter_tc;
-                        raw.power_zones[i].filter_tc_xperiod = period_ms * pz.filter_tc;
-                        let filter_a = f32!(1.0) / pz.filter_tc.into();
-                        raw.power_zones[i].filter_a = filter_a;
-                        raw.power_zones[i].filter_a_neg = f32!(1.0) - filter_a;
-                        #[ver(V >= V13_0B4)]
-                        raw.power_zones[i].unk_10 = 1320000000;
+                for i in 0..self.dyncfg.id.num_clusters as usize {
+                    if let Some(coef_a) = self.cfg.unk_coef_a.get(i) {
+                        (*raw.unk_coef_a1[i])[..coef_a.len()].copy_from_slice(coef_a);
+                        (*raw.unk_coef_a2[i])[..coef_a.len()].copy_from_slice(coef_a);
                     }
-
-                    #[ver(V >= V13_0B4 && G >= G14X)]
-                    for (i, j) in raw.hws2.g14.curve2.t1.iter().enumerate() {
-                        raw.unk_hws2[i] = if *j == 0xffff { 0 } else { j / 2 };
+                    if let Some(coef_b) = self.cfg.unk_coef_b.get(i) {
+                        (*raw.unk_coef_b1[i])[..coef_b.len()].copy_from_slice(coef_b);
+                        (*raw.unk_coef_b2[i])[..coef_b.len()].copy_from_slice(coef_b);
                     }
+                }
 
-                    Ok(())
-                },
-            )
+                for (i, pz) in pwr.power_zones.iter().enumerate() {
+                    raw.power_zones[i].target = pz.target;
+                    raw.power_zones[i].target_off = pz.target - pz.target_offset;
+                    raw.power_zones[i].filter_tc_x4 = 4 * pz.filter_tc;
+                    raw.power_zones[i].filter_tc_xperiod = period_ms * pz.filter_tc;
+                    let filter_a = f32!(1.0) / pz.filter_tc.into();
+                    raw.power_zones[i].filter_a = filter_a;
+                    raw.power_zones[i].filter_a_neg = f32!(1.0) - filter_a;
+                    #[ver(V >= V13_0B4)]
+                    raw.power_zones[i].unk_10 = 1320000000;
+                }
+
+                #[ver(V >= V13_0B4 && G >= G14X)]
+                for (i, j) in raw.hws2.g14.curve2.t1.iter().enumerate() {
+                    raw.unk_hws2[i] = if *j == 0xffff { 0 } else { j / 2 };
+                }
+
+                Ok(())
+            })
         })
     }
 
@@ -484,147 +479,145 @@ impl<'a> InitDataBuilder::ver<'a> {
         self.alloc.private.new_init(init::zeroed(), |_inner, _ptr| {
             let cfg = &self.cfg;
             let dyncfg = &self.dyncfg;
-            init::chain(
-                try_init!(raw::HwDataB::ver {
-                    // Userspace VA map related
-                    #[ver(V < V13_0B4)]
-                    unk_0: U64(0x13_00000000),
-                    unk_8: U64(0x14_00000000),
-                    #[ver(V < V13_0B4)]
-                    unk_10: U64(0x1_00000000),
-                    unk_18: U64(0xffc00000),
-                    unk_20: U64(0x11_00000000),
-                    unk_28: U64(0x11_00000000),
-                    // userspace address?
-                    unk_30: U64(0x6f_ffff8000),
-                    // unmapped?
-                    unkptr_38: U64(0xffffffa0_11800000),
-                    // TODO: yuv matrices
-                    chip_id: cfg.chip_id,
-                    unk_454: cfg.db.unk_454,
-                    unk_458: 0x1,
-                    unk_460: 0x1,
-                    unk_464: 0x1,
-                    unk_468: 0x1,
-                    unk_47c: 0x1,
-                    unk_484: 0x1,
-                    unk_48c: 0x1,
-                    base_clock_khz: cfg.base_clock_hz / 1000,
-                    power_sample_period: dyncfg.pwr.power_sample_period,
-                    unk_49c: 0x1,
-                    unk_4a0: 0x1,
-                    unk_4a4: 0x1,
-                    unk_4c0: 0x1f,
-                    unk_4e0: U64(cfg.db.unk_4e0),
-                    unk_4f0: 0x1,
-                    unk_4f4: 0x1,
-                    unk_504: 0x31,
-                    unk_524: 0x1, // use_secure_cache_flush
-                    unk_534: cfg.db.unk_534,
-                    num_frags: dyncfg.id.num_frags * dyncfg.id.num_clusters,
-                    unk_554: 0x1,
-                    uat_ttb_base: U64(dyncfg.uat_ttb_base),
-                    gpu_core_id: cfg.gpu_core as u32,
-                    gpu_rev_id: dyncfg.id.gpu_rev_id as u32,
-                    num_cores: dyncfg.id.num_cores * dyncfg.id.num_clusters,
-                    max_pstate: dyncfg.pwr.perf_states.len() as u32 - 1,
-                    #[ver(V < V13_0B4)]
-                    num_pstates: dyncfg.pwr.perf_states.len() as u32,
-                    #[ver(V < V13_0B4)]
-                    min_sram_volt: dyncfg.pwr.min_sram_microvolt / 1000,
-                    #[ver(V < V13_0B4)]
-                    unk_ab8: cfg.db.unk_ab8,
-                    #[ver(V < V13_0B4)]
-                    unk_abc: cfg.db.unk_abc,
-                    #[ver(V < V13_0B4)]
-                    unk_ac0: 0x1020,
+            try_init!(raw::HwDataB::ver {
+                // Userspace VA map related
+                #[ver(V < V13_0B4)]
+                unk_0: U64(0x13_00000000),
+                unk_8: U64(0x14_00000000),
+                #[ver(V < V13_0B4)]
+                unk_10: U64(0x1_00000000),
+                unk_18: U64(0xffc00000),
+                unk_20: U64(0x11_00000000),
+                unk_28: U64(0x11_00000000),
+                // userspace address?
+                unk_30: U64(0x6f_ffff8000),
+                // unmapped?
+                unkptr_38: U64(0xffffffa0_11800000),
+                // TODO: yuv matrices
+                chip_id: cfg.chip_id,
+                unk_454: cfg.db.unk_454,
+                unk_458: 0x1,
+                unk_460: 0x1,
+                unk_464: 0x1,
+                unk_468: 0x1,
+                unk_47c: 0x1,
+                unk_484: 0x1,
+                unk_48c: 0x1,
+                base_clock_khz: cfg.base_clock_hz / 1000,
+                power_sample_period: dyncfg.pwr.power_sample_period,
+                unk_49c: 0x1,
+                unk_4a0: 0x1,
+                unk_4a4: 0x1,
+                unk_4c0: 0x1f,
+                unk_4e0: U64(cfg.db.unk_4e0),
+                unk_4f0: 0x1,
+                unk_4f4: 0x1,
+                unk_504: 0x31,
+                unk_524: 0x1, // use_secure_cache_flush
+                unk_534: cfg.db.unk_534,
+                num_frags: dyncfg.id.num_frags * dyncfg.id.num_clusters,
+                unk_554: 0x1,
+                uat_ttb_base: U64(dyncfg.uat_ttb_base),
+                gpu_core_id: cfg.gpu_core as u32,
+                gpu_rev_id: dyncfg.id.gpu_rev_id as u32,
+                num_cores: dyncfg.id.num_cores * dyncfg.id.num_clusters,
+                max_pstate: dyncfg.pwr.perf_states.len() as u32 - 1,
+                #[ver(V < V13_0B4)]
+                num_pstates: dyncfg.pwr.perf_states.len() as u32,
+                #[ver(V < V13_0B4)]
+                min_sram_volt: dyncfg.pwr.min_sram_microvolt / 1000,
+                #[ver(V < V13_0B4)]
+                unk_ab8: cfg.db.unk_ab8,
+                #[ver(V < V13_0B4)]
+                unk_abc: cfg.db.unk_abc,
+                #[ver(V < V13_0B4)]
+                unk_ac0: 0x1020,
+
+                #[ver(V >= V13_0B4)]
+                unk_ae4: Array::new([0x0, 0x3, 0x7, 0x7]),
+                #[ver(V < V13_0B4)]
+                unk_ae4: Array::new([0x0, 0xf, 0x3f, 0x3f]),
+                unk_b10: 0x1,
+                timer_offset: U64(0),
+                unk_b24: 0x1,
+                unk_b28: 0x1,
+                unk_b2c: 0x1,
+                unk_b30: cfg.db.unk_b30,
+                #[ver(V >= V13_0B4)]
+                unk_b38_0: 1,
+                #[ver(V >= V13_0B4)]
+                unk_b38_4: 1,
+                unk_b38: Array::new([0xffffffff; 12]),
+                #[ver(V >= V13_0B4 && V < V13_3)]
+                unk_c3c: 0x19,
+                #[ver(V >= V13_3)]
+                unk_c3c: 0x1a,
+                ..Zeroable::zeroed()
+            })
+            .chain(|raw| {
+                #[ver(V >= V13_3)]
+                for i in 0..16 {
+                    raw.unk_arr_0[i] = i as u32;
+                }
 
-                    #[ver(V >= V13_0B4)]
-                    unk_ae4: Array::new([0x0, 0x3, 0x7, 0x7]),
-                    #[ver(V < V13_0B4)]
-                    unk_ae4: Array::new([0x0, 0xf, 0x3f, 0x3f]),
-                    unk_b10: 0x1,
-                    timer_offset: U64(0),
-                    unk_b24: 0x1,
-                    unk_b28: 0x1,
-                    unk_b2c: 0x1,
-                    unk_b30: cfg.db.unk_b30,
-                    #[ver(V >= V13_0B4)]
-                    unk_b38_0: 1,
-                    #[ver(V >= V13_0B4)]
-                    unk_b38_4: 1,
-                    unk_b38: Array::new([0xffffffff; 12]),
-                    #[ver(V >= V13_0B4 && V < V13_3)]
-                    unk_c3c: 0x19,
-                    #[ver(V >= V13_3)]
-                    unk_c3c: 0x1a,
-                    ..Zeroable::zeroed()
-                }),
-                |raw| {
-                    #[ver(V >= V13_3)]
-                    for i in 0..16 {
-                        raw.unk_arr_0[i] = i as u32;
+                let base_ps = self.dyncfg.pwr.perf_base_pstate as usize;
+                let max_ps = self.dyncfg.pwr.perf_max_pstate as usize;
+                let base_freq = self.dyncfg.pwr.perf_states[base_ps].freq_hz;
+                let max_freq = self.dyncfg.pwr.perf_states[max_ps].freq_hz;
+
+                for (i, ps) in self.dyncfg.pwr.perf_states.iter().enumerate() {
+                    raw.frequencies[i] = ps.freq_hz / 1000000;
+                    for (j, mv) in ps.volt_mv.iter().enumerate() {
+                        let sram_mv = (*mv).max(self.dyncfg.pwr.min_sram_microvolt / 1000);
+                        raw.voltages[i][j] = *mv;
+                        raw.voltages_sram[i][j] = sram_mv;
                     }
+                    for j in ps.volt_mv.len()..raw.voltages[i].len() {
+                        raw.voltages[i][j] = raw.voltages[i][0];
+                        raw.voltages_sram[i][j] = raw.voltages_sram[i][0];
+                    }
+                    raw.sram_k[i] = self.cfg.sram_k;
+                    raw.rel_max_powers[i] = ps.pwr_mw * 100 / self.dyncfg.pwr.max_power_mw;
+                    raw.rel_boost_freqs[i] = if i > base_ps {
+                        (ps.freq_hz - base_freq) / ((max_freq - base_freq) / 100)
+                    } else {
+                        0
+                    };
+                }
 
-                    let base_ps = self.dyncfg.pwr.perf_base_pstate as usize;
-                    let max_ps = self.dyncfg.pwr.perf_max_pstate as usize;
-                    let base_freq = self.dyncfg.pwr.perf_states[base_ps].freq_hz;
-                    let max_freq = self.dyncfg.pwr.perf_states[max_ps].freq_hz;
+                #[ver(V >= V13_0B4)]
+                if let Some(csafr) = self.dyncfg.pwr.csafr.as_ref() {
+                    let aux = &mut raw.aux_ps;
+                    aux.cs_max_pstate = (csafr.perf_states_cs.len() - 1).try_into()?;
+                    aux.afr_max_pstate = (csafr.perf_states_afr.len() - 1).try_into()?;
 
-                    for (i, ps) in self.dyncfg.pwr.perf_states.iter().enumerate() {
-                        raw.frequencies[i] = ps.freq_hz / 1000000;
+                    for (i, ps) in csafr.perf_states_cs.iter().enumerate() {
+                        aux.cs_frequencies[i] = ps.freq_hz / 1000000;
                         for (j, mv) in ps.volt_mv.iter().enumerate() {
-                            let sram_mv = (*mv).max(self.dyncfg.pwr.min_sram_microvolt / 1000);
-                            raw.voltages[i][j] = *mv;
-                            raw.voltages_sram[i][j] = sram_mv;
-                        }
-                        for j in ps.volt_mv.len()..raw.voltages[i].len() {
-                            raw.voltages[i][j] = raw.voltages[i][0];
-                            raw.voltages_sram[i][j] = raw.voltages_sram[i][0];
+                            let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
+                            aux.cs_voltages[i][j] = *mv;
+                            aux.cs_voltages_sram[i][j] = sram_mv;
                         }
-                        raw.sram_k[i] = self.cfg.sram_k;
-                        raw.rel_max_powers[i] = ps.pwr_mw * 100 / self.dyncfg.pwr.max_power_mw;
-                        raw.rel_boost_freqs[i] = if i > base_ps {
-                            (ps.freq_hz - base_freq) / ((max_freq - base_freq) / 100)
-                        } else {
-                            0
-                        };
                     }
 
-                    #[ver(V >= V13_0B4)]
-                    if let Some(csafr) = self.dyncfg.pwr.csafr.as_ref() {
-                        let aux = &mut raw.aux_ps;
-                        aux.cs_max_pstate = (csafr.perf_states_cs.len() - 1).try_into()?;
-                        aux.afr_max_pstate = (csafr.perf_states_afr.len() - 1).try_into()?;
-
-                        for (i, ps) in csafr.perf_states_cs.iter().enumerate() {
-                            aux.cs_frequencies[i] = ps.freq_hz / 1000000;
-                            for (j, mv) in ps.volt_mv.iter().enumerate() {
-                                let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
-                                aux.cs_voltages[i][j] = *mv;
-                                aux.cs_voltages_sram[i][j] = sram_mv;
-                            }
-                        }
-
-                        for (i, ps) in csafr.perf_states_afr.iter().enumerate() {
-                            aux.afr_frequencies[i] = ps.freq_hz / 1000000;
-                            for (j, mv) in ps.volt_mv.iter().enumerate() {
-                                let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
-                                aux.afr_voltages[i][j] = *mv;
-                                aux.afr_voltages_sram[i][j] = sram_mv;
-                            }
+                    for (i, ps) in csafr.perf_states_afr.iter().enumerate() {
+                        aux.afr_frequencies[i] = ps.freq_hz / 1000000;
+                        for (j, mv) in ps.volt_mv.iter().enumerate() {
+                            let sram_mv = (*mv).max(csafr.min_sram_microvolt / 1000);
+                            aux.afr_voltages[i][j] = *mv;
+                            aux.afr_voltages_sram[i][j] = sram_mv;
                         }
                     }
+                }
 
-                    // Special case override for T602x
-                    #[ver(G == G14X)]
-                    if dyncfg.id.gpu_rev_id == hw::GpuRevisionID::B1 {
-                        raw.gpu_rev_id = hw::GpuRevisionID::B0 as u32;
-                    }
+                // Special case override for T602x
+                #[ver(G == G14X)]
+                if dyncfg.id.gpu_rev_id == hw::GpuRevisionID::B1 {
+                    raw.gpu_rev_id = hw::GpuRevisionID::B0 as u32;
+                }
 
-                    Ok(())
-                },
-            )
+                Ok(())
+            })
         })
     }
 
@@ -642,113 +635,111 @@ impl<'a> InitDataBuilder::ver<'a> {
             let max_ps = pwr.perf_max_pstate;
             let max_ps_scaled = 100 * max_ps;
 
-            init::chain(
-                try_init!(raw::Globals::ver {
-                    //ktrace_enable: 0xffffffff,
-                    ktrace_enable: 0,
-                    #[ver(V >= V13_2)]
-                    unk_24_0: 3000,
-                    unk_24: 0,
-                    #[ver(V >= V13_0B4)]
-                    debug: 0,
-                    unk_28: 1,
-                    #[ver(G >= G14X)]
-                    unk_2c_0: 1,
-                    #[ver(V >= V13_0B4 && G < G14X)]
-                    unk_2c_0: 0,
-                    unk_2c: 1,
-                    unk_30: 0,
-                    unk_34: 120,
-                    sub <- try_init!(raw::GlobalsSub::ver {
-                        unk_54: cfg.global_unk_54,
-                        unk_56: 40,
-                        unk_58: 0xffff,
-                        unk_5e: U32(1),
-                        unk_66: U32(1),
-                        ..Zeroable::zeroed()
-                    }),
-                    unk_8900: 1,
-                    pending_submissions: AtomicU32::new(0),
-                    max_power: pwr.max_power_mw,
-                    max_pstate_scaled: max_ps_scaled,
-                    max_pstate_scaled_2: max_ps_scaled,
-                    max_pstate_scaled_3: max_ps_scaled,
-                    power_zone_count: pwr.power_zones.len() as u32,
-                    avg_power_filter_tc_periods: avg_power_filter_tc_periods,
-                    avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
-                    avg_power_kp: pwr.avg_power_kp,
-                    avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
-                    avg_power_target_filter_tc: pwr.avg_power_target_filter_tc,
-                    unk_89bc: cfg.da.unk_8cc,
-                    fast_die0_release_temp: 100 * pwr.fast_die0_release_temp,
-                    unk_89c4: cfg.da.unk_87c,
-                    fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
-                    fast_die0_kp: pwr.fast_die0_proportional_gain,
-                    fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
-                    unk_89e0: 1,
-                    max_power_2: pwr.max_power_mw,
-                    ppm_kp: pwr.ppm_kp,
-                    ppm_ki_dt: pwr.ppm_ki * period_s,
-                    #[ver(V >= V13_0B4)]
-                    unk_89f4_8: 1,
-                    unk_89f4: 0,
-                    hws1 <- Self::hw_shared1(cfg),
-                    hws2 <- Self::hw_shared2(cfg, dyncfg),
-                    hws3 <- Self::hw_shared3(cfg),
-                    #[ver(V >= V13_0B4)]
-                    idle_off_standby_timer: pwr.idle_off_standby_timer,
-                    #[ver(V >= V13_0B4)]
-                    unk_hws2_4: cfg.unk_hws2_4.map(Array::new).unwrap_or_default(),
-                    #[ver(V >= V13_0B4)]
-                    unk_hws2_24: cfg.unk_hws2_24,
-                    unk_900c: 1,
-                    #[ver(V >= V13_0B4)]
-                    unk_9010_0: 1,
-                    #[ver(V >= V13_0B4)]
-                    unk_903c: 1,
-                    #[ver(V < V13_0B4)]
-                    unk_903c: 0,
-                    fault_control: *crate::fault_control.read(),
-                    do_init: 1,
-                    unk_11020: 40,
-                    unk_11024: 10,
-                    unk_11028: 250,
-                    #[ver(V >= V13_0B4)]
-                    unk_1102c_0: 1,
-                    #[ver(V >= V13_0B4)]
-                    unk_1102c_4: 1,
-                    #[ver(V >= V13_0B4)]
-                    unk_1102c_8: 100,
-                    #[ver(V >= V13_0B4)]
-                    unk_1102c_c: 1,
-                    idle_off_delay_ms: AtomicU32::new(pwr.idle_off_delay_ms),
-                    fender_idle_off_delay_ms: pwr.fender_idle_off_delay_ms,
-                    fw_early_wake_timeout_ms: pwr.fw_early_wake_timeout_ms,
-                    unk_118e0: 40,
-                    #[ver(V >= V13_0B4)]
-                    unk_118e4_0: 50,
-                    #[ver(V >= V13_0B4)]
-                    unk_11edc: 0,
-                    #[ver(V >= V13_0B4)]
-                    unk_11efc: 0,
+            try_init!(raw::Globals::ver {
+                //ktrace_enable: 0xffffffff,
+                ktrace_enable: 0,
+                #[ver(V >= V13_2)]
+                unk_24_0: 3000,
+                unk_24: 0,
+                #[ver(V >= V13_0B4)]
+                debug: 0,
+                unk_28: 1,
+                #[ver(G >= G14X)]
+                unk_2c_0: 1,
+                #[ver(V >= V13_0B4 && G < G14X)]
+                unk_2c_0: 0,
+                unk_2c: 1,
+                unk_30: 0,
+                unk_34: 120,
+                sub <- try_init!(raw::GlobalsSub::ver {
+                    unk_54: cfg.global_unk_54,
+                    unk_56: 40,
+                    unk_58: 0xffff,
+                    unk_5e: U32(1),
+                    unk_66: U32(1),
                     ..Zeroable::zeroed()
                 }),
-                |raw| {
-                    for (i, pz) in self.dyncfg.pwr.power_zones.iter().enumerate() {
-                        raw.power_zones[i].target = pz.target;
-                        raw.power_zones[i].target_off = pz.target - pz.target_offset;
-                        raw.power_zones[i].filter_tc = pz.filter_tc;
-                    }
+                unk_8900: 1,
+                pending_submissions: AtomicU32::new(0),
+                max_power: pwr.max_power_mw,
+                max_pstate_scaled: max_ps_scaled,
+                max_pstate_scaled_2: max_ps_scaled,
+                max_pstate_scaled_3: max_ps_scaled,
+                power_zone_count: pwr.power_zones.len() as u32,
+                avg_power_filter_tc_periods: avg_power_filter_tc_periods,
+                avg_power_ki_dt: pwr.avg_power_ki_only * period_s,
+                avg_power_kp: pwr.avg_power_kp,
+                avg_power_min_duty_cycle: pwr.avg_power_min_duty_cycle,
+                avg_power_target_filter_tc: pwr.avg_power_target_filter_tc,
+                unk_89bc: cfg.da.unk_8cc,
+                fast_die0_release_temp: 100 * pwr.fast_die0_release_temp,
+                unk_89c4: cfg.da.unk_87c,
+                fast_die0_prop_tgt_delta: 100 * pwr.fast_die0_prop_tgt_delta,
+                fast_die0_kp: pwr.fast_die0_proportional_gain,
+                fast_die0_ki_dt: pwr.fast_die0_integral_gain * period_s,
+                unk_89e0: 1,
+                max_power_2: pwr.max_power_mw,
+                ppm_kp: pwr.ppm_kp,
+                ppm_ki_dt: pwr.ppm_ki * period_s,
+                #[ver(V >= V13_0B4)]
+                unk_89f4_8: 1,
+                unk_89f4: 0,
+                hws1 <- Self::hw_shared1(cfg),
+                hws2 <- Self::hw_shared2(cfg, dyncfg),
+                hws3 <- Self::hw_shared3(cfg),
+                #[ver(V >= V13_0B4)]
+                idle_off_standby_timer: pwr.idle_off_standby_timer,
+                #[ver(V >= V13_0B4)]
+                unk_hws2_4: cfg.unk_hws2_4.map(Array::new).unwrap_or_default(),
+                #[ver(V >= V13_0B4)]
+                unk_hws2_24: cfg.unk_hws2_24,
+                unk_900c: 1,
+                #[ver(V >= V13_0B4)]
+                unk_9010_0: 1,
+                #[ver(V >= V13_0B4)]
+                unk_903c: 1,
+                #[ver(V < V13_0B4)]
+                unk_903c: 0,
+                fault_control: *crate::fault_control.read(),
+                do_init: 1,
+                unk_11020: 40,
+                unk_11024: 10,
+                unk_11028: 250,
+                #[ver(V >= V13_0B4)]
+                unk_1102c_0: 1,
+                #[ver(V >= V13_0B4)]
+                unk_1102c_4: 1,
+                #[ver(V >= V13_0B4)]
+                unk_1102c_8: 100,
+                #[ver(V >= V13_0B4)]
+                unk_1102c_c: 1,
+                idle_off_delay_ms: AtomicU32::new(pwr.idle_off_delay_ms),
+                fender_idle_off_delay_ms: pwr.fender_idle_off_delay_ms,
+                fw_early_wake_timeout_ms: pwr.fw_early_wake_timeout_ms,
+                unk_118e0: 40,
+                #[ver(V >= V13_0B4)]
+                unk_118e4_0: 50,
+                #[ver(V >= V13_0B4)]
+                unk_11edc: 0,
+                #[ver(V >= V13_0B4)]
+                unk_11efc: 0,
+                ..Zeroable::zeroed()
+            })
+            .chain(|raw| {
+                for (i, pz) in self.dyncfg.pwr.power_zones.iter().enumerate() {
+                    raw.power_zones[i].target = pz.target;
+                    raw.power_zones[i].target_off = pz.target - pz.target_offset;
+                    raw.power_zones[i].filter_tc = pz.filter_tc;
+                }
 
-                    if let Some(tab) = self.cfg.global_tab.as_ref() {
-                        for (i, x) in tab.iter().enumerate() {
-                            raw.unk_118ec[i] = *x;
-                        }
-                        raw.unk_118e8 = 1;
+                if let Some(tab) = self.cfg.global_tab.as_ref() {
+                    for (i, x) in tab.iter().enumerate() {
+                        raw.unk_118ec[i] = *x;
                     }
-                    Ok(())
-                },
-            )
+                    raw.unk_118e8 = 1;
+                }
+                Ok(())
+            })
         })
     }
 

From bbc87e03110e52643b6b9ed2b805ace3d79618b6 Mon Sep 17 00:00:00 2001
From: Hector Martin <marcan@marcan.st>
Date: Sun, 19 Nov 2023 16:37:28 +0900
Subject: [PATCH 0947/1027] rust: Add missing impl_trait_in_assoc_type feature

Signed-off-by: Hector Martin <marcan@marcan.st>
---
 scripts/Makefile.build | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/scripts/Makefile.build b/scripts/Makefile.build
index 8b5efdb3f60d46..6321cf545cac3c 100644
--- a/scripts/Makefile.build
+++ b/scripts/Makefile.build
@@ -263,7 +263,7 @@ $(obj)/%.lst: $(obj)/%.c FORCE
 # Compile Rust sources (.rs)
 # ---------------------------------------------------------------------------
 
-rust_allowed_features := allocator_api,new_uninit,type_alias_impl_trait
+rust_allowed_features := allocator_api,new_uninit,type_alias_impl_trait,impl_trait_in_assoc_type
 
 # `--out-dir` is required to avoid temporaries being created by `rustc` in the
 # current working directory, which may be not accessible in the out-of-tree

From 4e2c636b4a04f617bc5bf7d606f44053c357f22c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 13:48:02 +0900
Subject: [PATCH 0948/1027] rust: bindgen: Exclude list functions with
 unsupported ABIs

CONFIG_LIST_HARDENED causes certain list functions to use what bindgen
considers an unsupported ABI:

panicked at 'Invalid or unknown abi 14 for function
"__list_del_entry_valid_or_report" (Function { name:
"__list_del_entry_valid_or_report", mangled_name:
Some("__list_del_entry_valid_or_report"), link_name:
None, signature: TypeId(ItemId(38153)), kind: Function,
linkage: External })',
/usr/share/cargo/registry/bindgen-0.68.1/codegen/mod.rs:4195:17

Exclude these, since they are internal functions we don't use directly
anyway.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/bindgen_parameters | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/rust/bindgen_parameters b/rust/bindgen_parameters
index a721d466bee4b2..517cff7ab2a8f5 100644
--- a/rust/bindgen_parameters
+++ b/rust/bindgen_parameters
@@ -24,3 +24,7 @@
 # These functions use the `__preserve_most` calling convention, which neither bindgen
 # nor Rust currently understand, and which Clang currently declares to be unstable.
 --blocklist-function __list_.*_report
+# CONFIG_LIST_HARDENED triggers "Invalid or unknown abi 14" for these
+--blocklist-function __list_valid_slowpath
+--blocklist-function __list_add_valid_or_report
+--blocklist-function __list_del_entry_valid_or_report

From bf9ac1391d33c48a50a127422a9aecca38a7988f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 14:01:51 +0900
Subject: [PATCH 0949/1027] drm/shmem-helper: Add lockdep asserts to
 vmap/vunmap

Since commit 21aa27ddc582 ("drm/shmem-helper: Switch to reservation
lock"), the drm_gem_shmem_vmap and drm_gem_shmem_vunmap functions
require that the caller holds the DMA reservation lock for the object.
Add lockdep assertions to help validate this.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/drm_gem_shmem_helper.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/drm_gem_shmem_helper.c b/drivers/gpu/drm/drm_gem_shmem_helper.c
index 34bf55b4559ccf..d7383d2cda677d 100644
--- a/drivers/gpu/drm/drm_gem_shmem_helper.c
+++ b/drivers/gpu/drm/drm_gem_shmem_helper.c
@@ -316,6 +316,8 @@ int drm_gem_shmem_vmap(struct drm_gem_shmem_object *shmem,
 	struct drm_gem_object *obj = &shmem->base;
 	int ret = 0;
 
+	dma_resv_assert_held(obj->resv);
+
 	if (obj->import_attach) {
 		ret = dma_buf_vmap(obj->import_attach->dmabuf, map);
 		if (!ret) {
@@ -382,6 +384,8 @@ void drm_gem_shmem_vunmap(struct drm_gem_shmem_object *shmem,
 {
 	struct drm_gem_object *obj = &shmem->base;
 
+	dma_resv_assert_held(obj->resv);
+
 	if (obj->import_attach) {
 		dma_buf_vunmap(obj->import_attach->dmabuf, map);
 	} else {

From bb907a4a12b509dfb2c0b8b56952fceabdef824f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 15:48:23 +0900
Subject: [PATCH 0950/1027] driver, of: Mangle the device ID machinery further
 to remove const_trait_impl

This unstable feature is broken/gone in 1.73. To work around this
without breaking the API, turn IdArray::new() into a macro so that it
can use concrete types (which can still have const associated
functions) instead of a trait.

This is quite ugly...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/driver.rs | 116 +++++++++++++++++++++++++++---------------
 rust/kernel/lib.rs    |   1 -
 rust/kernel/of.rs     |   8 ++-
 3 files changed, 82 insertions(+), 43 deletions(-)

diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index acb9adf806b0f0..7926410bdee10b 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -126,7 +126,6 @@ impl<T: DriverOps> Drop for Registration<T> {
 ///   - [`RawDeviceId::ZERO`] is actually a zeroed-out version of the raw device id.
 ///   - [`RawDeviceId::to_rawid`] stores `offset` in the context/data field of the raw device id so
 ///     that buses can recover the pointer to the data.
-#[const_trait]
 pub unsafe trait RawDeviceId {
     /// The raw type that holds the device id.
     ///
@@ -138,13 +137,6 @@ pub unsafe trait RawDeviceId {
     /// Id tables created from [`Self`] use [`Self::ZERO`] as the sentinel to indicate the end of
     /// the table.
     const ZERO: Self::RawType;
-
-    /// Converts an id into a raw id.
-    ///
-    /// `offset` is the offset from the memory location where the raw device id is stored to the
-    /// location where its associated context information is stored. Implementations must store
-    /// this in the appropriate context/data field of the raw type.
-    fn to_rawid(&self, offset: isize) -> Self::RawType;
 }
 
 /// A zero-terminated device id array.
@@ -166,35 +158,7 @@ pub struct IdArray<T: RawDeviceId, U, const N: usize> {
 }
 
 impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
-    /// Creates a new instance of the array.
-    ///
-    /// The contents are derived from the given identifiers and context information.
-    pub const fn new(ids: [T; N], infos: [Option<U>; N]) -> Self
-    where
-        T: ~const RawDeviceId + Copy,
-        T::RawType: Copy + Clone,
-    {
-        let mut array = Self {
-            ids: IdArrayIds {
-                ids: [T::ZERO; N],
-                sentinel: T::ZERO,
-            },
-            id_infos: infos,
-        };
-        let mut i = 0usize;
-        while i < N {
-            // SAFETY: Both pointers are within `array` (or one byte beyond), consequently they are
-            // derived from the same allocated object. We are using a `u8` pointer, whose size 1,
-            // so the pointers are necessarily 1-byte aligned.
-            let offset = unsafe {
-                (&array.id_infos[i] as *const _ as *const u8)
-                    .offset_from(&array.ids.ids[i] as *const _ as _)
-            };
-            array.ids.ids[i] = ids[i].to_rawid(offset);
-            i += 1;
-        }
-        array
-    }
+    const U_NONE: Option<U> = None;
 
     /// Returns an `IdTable` backed by `self`.
     ///
@@ -214,10 +178,82 @@ impl<T: RawDeviceId, U, const N: usize> IdArray<T, U, N> {
     /// Returns the inner IdArrayIds array, without the context data.
     pub const fn as_ids(&self) -> IdArrayIds<T, N>
     where
-        T: ~const RawDeviceId + Copy,
+        T: RawDeviceId + Copy,
     {
         self.ids
     }
+
+    /// Creates a new instance of the array.
+    ///
+    /// The contents are derived from the given identifiers and context information.
+    #[doc(hidden)]
+    pub const unsafe fn new(raw_ids: [T::RawType; N], infos: [Option<U>; N]) -> Self
+    where
+        T: RawDeviceId + Copy,
+        T::RawType: Copy + Clone,
+    {
+        Self {
+            ids: IdArrayIds {
+                ids: raw_ids,
+                sentinel: T::ZERO,
+            },
+            id_infos: infos,
+        }
+    }
+
+    #[doc(hidden)]
+    pub const fn get_offset(idx: usize) -> isize
+    where
+        T: RawDeviceId + Copy,
+        T::RawType: Copy + Clone,
+    {
+        // SAFETY: We are only using this dummy value to get offsets.
+        let array = unsafe { Self::new([T::ZERO; N], [Self::U_NONE; N]) };
+        // SAFETY: Both pointers are within `array` (or one byte beyond), consequently they are
+        // derived from the same allocated object. We are using a `u8` pointer, whose size 1,
+        // so the pointers are necessarily 1-byte aligned.
+        let ret = unsafe {
+            (&array.id_infos[idx] as *const _ as *const u8)
+                .offset_from(&array.ids.ids[idx] as *const _ as _)
+        };
+        core::mem::forget(array);
+        ret
+    }
+}
+
+// Creates a new ID array. This is a macro so it can take as a parameter the concrete ID type in order
+// to call to_rawid() on it, and still remain const. This is necessary until a new const_trait_impl
+// implementation lands, since the existing implementation was removed in Rust 1.73.
+#[macro_export]
+#[doc(hidden)]
+macro_rules! _new_id_array {
+    (($($args:tt)*), $id_type:ty) => {{
+        /// Creates a new instance of the array.
+        ///
+        /// The contents are derived from the given identifiers and context information.
+        const fn new< U, const N: usize>(ids: [$id_type; N], infos: [Option<U>; N])
+            -> $crate::driver::IdArray<$id_type, U, N>
+        where
+            $id_type: $crate::driver::RawDeviceId + Copy,
+            <$id_type as $crate::driver::RawDeviceId>::RawType: Copy + Clone,
+        {
+            let mut raw_ids =
+                [<$id_type as $crate::driver::RawDeviceId>::ZERO; N];
+            let mut i = 0usize;
+            while i < N {
+                let offset: isize = $crate::driver::IdArray::<$id_type, U, N>::get_offset(i);
+                raw_ids[i] = ids[i].to_rawid(offset);
+                i += 1;
+            }
+
+            // SAFETY: We are passing valid arguments computed with the correct offsets.
+            unsafe {
+                $crate::driver::IdArray::<$id_type, U, N>::new(raw_ids, infos)
+            }
+       }
+
+        new($($args)*)
+    }}
 }
 
 /// A device id table.
@@ -375,8 +411,8 @@ macro_rules! define_id_array {
     ($table_name:ident, $id_type:ty, $data_type:ty, [ $($t:tt)* ]) => {
         const $table_name:
             $crate::driver::IdArray<$id_type, $data_type, { $crate::count_paren_items!($($t)*) }> =
-                $crate::driver::IdArray::new(
-                    $crate::first_item!($id_type, $($t)*), $crate::second_item!($($t)*));
+                $crate::_new_id_array!((
+                    $crate::first_item!($id_type, $($t)*), $crate::second_item!($($t)*)), $id_type);
     };
 }
 
diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 5453e8b2591300..4faeee9bad1efd 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -16,7 +16,6 @@
 #![feature(coerce_unsized)]
 #![feature(const_mut_refs)]
 #![feature(const_refs_to_cell)]
-#![feature(const_trait_impl)]
 #![feature(dispatch_from_dyn)]
 #![feature(duration_constants)]
 #![feature(new_uninit)]
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index ed3fca8409c3d6..a48f0207605c5a 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -15,6 +15,7 @@ use core::num::NonZeroU32;
 use crate::{
     alloc::flags::*,
     bindings, driver,
+    driver::RawDeviceId,
     prelude::*,
     str::{BStr, CStr},
 };
@@ -74,7 +75,7 @@ macro_rules! module_of_id_table {
 }
 
 // SAFETY: `ZERO` is all zeroed-out and `to_rawid` stores `offset` in `of_device_id::data`.
-unsafe impl const driver::RawDeviceId for DeviceId {
+unsafe impl driver::RawDeviceId for DeviceId {
     type RawType = bindings::of_device_id;
     const ZERO: Self::RawType = bindings::of_device_id {
         name: [0; 32],
@@ -82,8 +83,11 @@ unsafe impl const driver::RawDeviceId for DeviceId {
         compatible: [0; 128],
         data: core::ptr::null(),
     };
+}
 
-    fn to_rawid(&self, offset: isize) -> Self::RawType {
+impl DeviceId {
+    #[doc(hidden)]
+    pub const fn to_rawid(&self, offset: isize) -> <Self as driver::RawDeviceId>::RawType {
         let DeviceId::Compatible(compatible) = self;
         let mut id = Self::ZERO;
         let mut i = 0;

From 79cf28f4804957ad76ff369ca7741bd44b8e5e76 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 16:00:14 +0900
Subject: [PATCH 0951/1027] drm/asahi: workqueue: Work around ICE in Rust 1.74

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 21 +++++++++++----------
 1 file changed, 11 insertions(+), 10 deletions(-)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 4415dac068d449..146d09a92ae948 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -658,16 +658,17 @@ impl WorkQueue::ver {
 
         let info_pointer = inner.info.weak_pointer();
 
-        let mutex_init = match pipe_type {
-            PipeType::Vertex => Mutex::new_named(inner, c_str!("WorkQueue::inner (Vertex)")),
-            PipeType::Fragment => Mutex::new_named(inner, c_str!("WorkQueue::inner (Fragment)")),
-            PipeType::Compute => Mutex::new_named(inner, c_str!("WorkQueue::inner (Compute)")),
-        };
-
-        Arc::pin_init(pin_init!(Self {
-            info_pointer,
-            inner <- mutex_init,
-        }))
+        Arc::pin_init(
+            pin_init!(Self {
+                info_pointer,
+                inner <- match pipe_type {
+                    PipeType::Vertex => Mutex::new_named(inner, c_str!("WorkQueue::inner (Vertex)")),
+                    PipeType::Fragment => Mutex::new_named(inner, c_str!("WorkQueue::inner (Fragment)")),
+                    PipeType::Compute => Mutex::new_named(inner, c_str!("WorkQueue::inner (Compute)")),
+                },
+            }),
+            GFP_KERNEL,
+        )
     }
 
     pub(crate) fn event_info(&self) -> Option<QueueEventInfo::ver> {

From cfaac0d722feb5e33b3b89fd3d5d8e6a7fc63077 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 22 Nov 2023 16:48:19 +0900
Subject: [PATCH 0952/1027] drm/asahi: queue: Alocate the NotifierList as
 shared

macOS does it this way, and the firmware needs it (no cache management!)

Fixes firmware crashes with piglit with high concurrency.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/mod.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
index b86dc6aa3324c7..fd0b3983d018fb 100644
--- a/drivers/gpu/drm/asahi/queue/mod.rs
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -382,7 +382,8 @@ impl Queue::ver {
 
         let data = dev.data();
 
-        let mut notifier_list = alloc.private.new_default::<fw::event::NotifierList>()?;
+        // Must be shared, no cache management on this one!
+        let mut notifier_list = alloc.shared.new_default::<fw::event::NotifierList>()?;
 
         let self_ptr = notifier_list.weak_pointer();
         notifier_list.with_mut(|raw, _inner| {

From 4c9e9ac268b36f9200f5e43c007212493abb6472 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 5 Jan 2024 13:49:51 +0900
Subject: [PATCH 0953/1027] drm/asahi: compute: Allow no preemption flag

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/compute.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index 44780048126242..6853c65a458c0c 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -72,7 +72,7 @@ impl super::Queue::ver {
         }
         let cmdbuf = unsafe { cmdbuf.assume_init() };
 
-        if cmdbuf.flags != 0 {
+        if cmdbuf.flags & !(uapi::ASAHI_COMPUTE_NO_PREEMPTION as u64) != 0 {
             return Err(EINVAL);
         }
 

From c97c716ab221c5eff619ea9ce3ca7a4da53aeed1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 17 Jan 2024 17:19:51 +0900
Subject: [PATCH 0954/1027] drm/asahi: Identify and implement helper config
 register

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/compute.rs    | 2 +-
 drivers/gpu/drm/asahi/fw/fragment.rs   | 2 +-
 drivers/gpu/drm/asahi/fw/vertex.rs     | 3 ++-
 drivers/gpu/drm/asahi/queue/compute.rs | 4 ++--
 drivers/gpu/drm/asahi/queue/render.rs  | 7 ++++---
 5 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/compute.rs b/drivers/gpu/drm/asahi/fw/compute.rs
index e29935d9bc5f35..860b9034bfb44c 100644
--- a/drivers/gpu/drm/asahi/fw/compute.rs
+++ b/drivers/gpu/drm/asahi/fw/compute.rs
@@ -24,7 +24,7 @@ pub(crate) mod raw {
         pub(crate) helper_program: u32,
         pub(crate) unk_44: u32,
         pub(crate) helper_arg: U64,
-        pub(crate) helper_unk: u32,
+        pub(crate) helper_cfg: u32,
         pub(crate) unk_54: u32,
         pub(crate) unk_58: u32,
         pub(crate) unk_5c: u32,
diff --git a/drivers/gpu/drm/asahi/fw/fragment.rs b/drivers/gpu/drm/asahi/fw/fragment.rs
index 586f3414a02957..cba69b967f5957 100644
--- a/drivers/gpu/drm/asahi/fw/fragment.rs
+++ b/drivers/gpu/drm/asahi/fw/fragment.rs
@@ -141,7 +141,7 @@ pub(crate) mod raw {
         pub(crate) isp_bgobjvals: u32,
         pub(crate) unk_38: u32,
         pub(crate) unk_3c: u32,
-        pub(crate) unk_40: u32,
+        pub(crate) helper_cfg: u32,
         pub(crate) __pad: Pad<0xac>,
     }
 
diff --git a/drivers/gpu/drm/asahi/fw/vertex.rs b/drivers/gpu/drm/asahi/fw/vertex.rs
index 1b2e3d978018ce..77f4b7eda11e01 100644
--- a/drivers/gpu/drm/asahi/fw/vertex.rs
+++ b/drivers/gpu/drm/asahi/fw/vertex.rs
@@ -25,7 +25,8 @@ pub(crate) mod raw {
         pub(crate) tpc_stride: u32,
         pub(crate) unk_24: u32,
         pub(crate) unk_28: u32,
-        pub(crate) __pad: Pad<0x74>,
+        pub(crate) helper_cfg: u32,
+        pub(crate) __pad: Pad<0x70>,
     }
 
     #[versions(AGX)]
diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index 6853c65a458c0c..0b04562fb114ae 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -282,7 +282,7 @@ impl super::Queue::ver {
                         helper_program: cmdbuf.helper_program, // Internal program addr | 1
                         unk_44: 0,
                         helper_arg: U64(cmdbuf.helper_arg), // Only if internal program used
-                        helper_unk: cmdbuf.helper_unk, // 0x40 if internal program used
+                        helper_cfg: cmdbuf.helper_cfg, // 0x40 if internal program used
                         unk_54: 0,
                         unk_58: 1,
                         unk_5c: 0,
@@ -303,7 +303,7 @@ impl super::Queue::ver {
                             r.add(0x10071, 0x1100000000); // USC_EXEC_BASE_CP
                             r.add(0x11841, cmdbuf.helper_program.into());
                             r.add(0x11849, cmdbuf.helper_arg);
-                            r.add(0x11f81, cmdbuf.helper_unk.into());
+                            r.add(0x11f81, cmdbuf.helper_cfg.into());
                             r.add(0x1a440, 0x24201);
                             r.add(0x12091, cmdbuf.iogpu_unk_40.into());
                             /*
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 500ae2e429413d..6f86e737939601 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -200,6 +200,7 @@ impl super::Queue::ver {
                 } else {
                     0x8000
                 },
+                helper_cfg: cmdbuf.vertex_helper_cfg,
                 __pad: Default::default(),
             },
         })
@@ -862,7 +863,7 @@ impl super::Queue::ver {
                         isp_bgobjvals: unks.load_bgobjvals as u32,
                         unk_38: unks.frg_unk_38 as u32,
                         unk_3c: unks.frg_unk_3c as u32,
-                        unk_40: unks.frg_unk_40 as u32,
+                        helper_cfg: cmdbuf.fragment_helper_cfg,
                         __pad: Default::default(),
                     }),
                     #[ver(G >= G14X)]
@@ -935,7 +936,7 @@ impl super::Queue::ver {
                             r.add(0x16451, 0x0); // ISP_RENDER_ORIGIN
                             r.add(0x11821, cmdbuf.fragment_helper_program.into());
                             r.add(0x11829, cmdbuf.fragment_helper_arg);
-                            r.add(0x11f79, 0);
+                            r.add(0x11f79, cmdbuf.fragment_helper_cfg.into());
                             r.add(0x15359, 0);
                             r.add(0x10069, 0x11_00000000); // USC_EXEC_BASE_ISP
                             r.add(0x16020, 0);
@@ -1429,7 +1430,7 @@ impl super::Queue::ver {
                             r.add(0x10061, 0x11_00000000); // USC_EXEC_BASE_TA
                             r.add(0x11801, cmdbuf.vertex_helper_program.into());
                             r.add(0x11809, cmdbuf.vertex_helper_arg);
-                            r.add(0x11f71, 0);
+                            r.add(0x11f71, cmdbuf.vertex_helper_cfg.into());
                             r.add(0x1c0b1, tile_info.params.rgn_size.into()); // TE_PSG
                             r.add(0x1c850, tile_info.params.rgn_size.into());
                             r.add(0x10131, tile_info.params.unk_4.into());

From 3531fe2ad0dccb4bc732074a43208ced10e1119f Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 17 Jan 2024 17:20:29 +0900
Subject: [PATCH 0955/1027] drm/asahi: Check command structure sizes

Eventually this can be used to extend the structure at the end
backwards-compatibly, for cases where we missed core fields in the UAPI.
More discrete features should be implemented via extensions.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/compute.rs | 9 +++++++++
 drivers/gpu/drm/asahi/queue/render.rs  | 9 +++++++++
 2 files changed, 18 insertions(+)

diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index 0b04562fb114ae..ad4f6b4b546ad4 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -55,6 +55,15 @@ impl super::Queue::ver {
 
         mod_dev_dbg!(self.dev, "[Submission {}] Compute!\n", id);
 
+        if cmd.cmd_buffer_size as usize != core::mem::size_of::<uapi::drm_asahi_cmd_compute>() {
+            cls_pr_debug!(
+                Errors,
+                "Invalid compute command size ({:#x})\n",
+                cmd.cmd_buffer_size
+            );
+            return Err(EINVAL);
+        }
+
         let mut cmdbuf_reader = unsafe {
             UserSlicePtr::new(
                 cmd.cmd_buffer as usize as *mut _,
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 6f86e737939601..1f59ceefb09a29 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -222,6 +222,15 @@ impl super::Queue::ver {
 
         mod_dev_dbg!(self.dev, "[Submission {}] Render!\n", id);
 
+        if cmd.cmd_buffer_size as usize != core::mem::size_of::<uapi::drm_asahi_cmd_render>() {
+            cls_pr_debug!(
+                Errors,
+                "Invalid render command size ({:#x})\n",
+                cmd.cmd_buffer_size
+            );
+            return Err(EINVAL);
+        }
+
         let mut cmdbuf_reader = unsafe {
             UserSlicePtr::new(
                 cmd.cmd_buffer as usize as *mut _,

From eb67ab2438bc52efe1e336d335d64a62f85ec18a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Mon, 6 Mar 2023 19:57:22 +0900
Subject: [PATCH 0956/1027] drm/scheduler: Clean up jobs when the scheduler is
 torn down.

drm_sched_fini() currently leaves any pending jobs dangling, which
causes segfaults and other badness when job completion fences are
signaled after the scheduler is torn down.

Explicitly detach all jobs from their completion callbacks and free
them. This makes it possible to write a sensible safe abstraction for
drm_sched, without having to externally duplicate the tracking of
in-flight jobs.

This shouldn't regress any existing drivers, since calling
drm_sched_fini() with any pending jobs is broken and this change should
be a no-op if there are no pending jobs.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/scheduler/sched_main.c | 25 +++++++++++++++++++++++++
 1 file changed, 25 insertions(+)

diff --git a/drivers/gpu/drm/scheduler/sched_main.c b/drivers/gpu/drm/scheduler/sched_main.c
index a124d5e77b5e86..1c020301536df0 100644
--- a/drivers/gpu/drm/scheduler/sched_main.c
+++ b/drivers/gpu/drm/scheduler/sched_main.c
@@ -1334,8 +1334,33 @@ EXPORT_SYMBOL(drm_sched_init);
 void drm_sched_fini(struct drm_gpu_scheduler *sched)
 {
 	struct drm_sched_entity *s_entity;
+	struct drm_sched_job *s_job, *tmp;
 	int i;
 
+	/*
+	* Stop the scheduler, detaching all jobs from their hardware callbacks
+	* and cleaning up complete jobs.
+	*/
+	drm_sched_stop(sched, NULL);
+
+	/*
+	 * Iterate through the pending job list and free all jobs.
+	 * This assumes the driver has either guaranteed jobs are already stopped, or that
+	 * otherwise it is responsible for keeping any necessary data structures for
+	 * in-progress jobs alive even when the free_job() callback is called early (e.g. by
+	 * putting them in its own queue or doing its own refcounting).
+	 */
+	list_for_each_entry_safe(s_job, tmp, &sched->pending_list, list) {
+		spin_lock(&sched->job_list_lock);
+		list_del_init(&s_job->list);
+		spin_unlock(&sched->job_list_lock);
+
+		drm_sched_fence_finished(s_job->s_fence, -ESRCH);
+
+		WARN_ON(s_job->s_fence->parent);
+		sched->ops->free_job(s_job);
+	}
+
 	drm_sched_wqueue_stop(sched);
 
 	for (i = DRM_SCHED_PRIORITY_KERNEL; i < sched->num_rqs; i++) {

From a68a62f16b1c735be0f1f6d97855f6250cf08dbb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Tue, 23 Apr 2024 18:47:51 +0200
Subject: [PATCH 0957/1027] HACK: rust: x86: Set data-layout based on rustc's
 LLVM version

LLVM changed the data layout in x86 between 17 and 18 but Rust 1.77.0
and later checks for matching data layouts.

Parse the used LLVM version from `rustc -v --version` and generate a
matching target.json based on that.

We might need to keep this even with rust 1.78 depending ron the rust
package in Fedora 39.

Link: https://lore.kernel.org/lkml/20240401212303.537355-4-ojeda@kernel.org/
Signed-off-by: Janne Grunau <j@jannau.net>
---
 init/Kconfig                    |  5 +++++
 scripts/generate_rust_target.rs | 27 +++++++++++++++++++++++----
 2 files changed, 28 insertions(+), 4 deletions(-)

diff --git a/init/Kconfig b/init/Kconfig
index 5783a0b8751726..575e0778e10d53 100644
--- a/init/Kconfig
+++ b/init/Kconfig
@@ -1922,6 +1922,11 @@ config RUSTC_VERSION_TEXT
 	depends on RUST
 	default "$(shell,$(RUSTC) --version 2>/dev/null)"
 
+config RUSTC_LLVM_VERSION_TEXT
+	string
+	depends on RUST
+        default $(shell,command -v $(RUSTC) >/dev/null 2>&1 && $(RUSTC) -v --version | grep '^LLVM version' | sed -e 's/^.*: *//' || echo n)
+
 config BINDGEN_VERSION_TEXT
 	string
 	depends on RUST
diff --git a/scripts/generate_rust_target.rs b/scripts/generate_rust_target.rs
index 404edf7587e088..d189870b1dd27f 100644
--- a/scripts/generate_rust_target.rs
+++ b/scripts/generate_rust_target.rs
@@ -158,10 +158,29 @@ fn main() {
         }
     } else if cfg.has("X86_64") {
         ts.push("arch", "x86_64");
-        ts.push(
-            "data-layout",
-            "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
-        );
+        let mut llvm_version: u32 = 18;
+        if cfg.has("RUSTC_LLVM_VERSION_TEXT") {
+            let llvm_str = cfg
+                .0
+                .get("CONFIG_RUSTC_LLVM_VERSION_TEXT")
+                .unwrap()
+                .split_once(".")
+                .unwrap()
+                .0;
+            llvm_version = llvm_str.parse().unwrap();
+        }
+        // intentially broken indent
+        if llvm_version >= 18 {
+            ts.push(
+                "data-layout",
+                "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128",
+            );
+        } else {
+            ts.push(
+                "data-layout",
+                "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128",
+            );
+        }
         let mut features = "-mmx,+soft-float".to_string();
         if cfg.has("MITIGATION_RETPOLINE") {
             features += ",+retpoline-external-thunk";

From b7613729275ef67c8b54e66ec8b82ca5518ce4ca Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sun, 5 May 2024 11:40:29 +0200
Subject: [PATCH 0958/1027] *RFL fixup: kernel::driver: implement Send for
 Registration

required after ("rust: kernel: require `Send` for `Module` implementations")
commit 323617f649c0966ad5e741e47e27e06d3a680d8f upstream.

Fixes: 0d4917899844 ("*RFL import: kernel::driver")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/kernel/driver.rs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/rust/kernel/driver.rs b/rust/kernel/driver.rs
index 7926410bdee10b..d8ae669ac805b9 100644
--- a/rust/kernel/driver.rs
+++ b/rust/kernel/driver.rs
@@ -52,6 +52,10 @@ pub struct Registration<T: DriverOps> {
     concrete_reg: UnsafeCell<T::RegType>,
 }
 
+// SAFETY: The only action allowed in a `Registration` instance is dropping it, so it is safe to
+// share references to it with multiple threads as nothing else can be done.
+unsafe impl<T: DriverOps> Send for Registration<T> {}
+
 // SAFETY: `Registration` has no fields or methods accessible via `&Registration`, so it is safe to
 // share references to it with multiple threads as nothing can be done.
 unsafe impl<T: DriverOps> Sync for Registration<T> {}

From 775c5e83d2e4c67e9ce0bb3bf52273295851ecc0 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 May 2024 21:19:25 +0200
Subject: [PATCH 0959/1027] Revert "rust: str: implement `Display` and `Debug`
 for `BStr`"

This reverts commit 4951ddd51b816b5e4095cd3cd3bd46fb73b96a65.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/kernel/str.rs | 185 ++-------------------------------------------
 1 file changed, 7 insertions(+), 178 deletions(-)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index bb8d4f41475b59..59272dc112b3e9 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -10,102 +10,9 @@ use core::ops::{self, Deref, DerefMut, Index};
 use crate::error::{code::*, Error};
 
 /// Byte string without UTF-8 validity guarantee.
-#[repr(transparent)]
-pub struct BStr([u8]);
-
-impl BStr {
-    /// Returns the length of this string.
-    #[inline]
-    pub const fn len(&self) -> usize {
-        self.0.len()
-    }
-
-    /// Returns `true` if the string is empty.
-    #[inline]
-    pub const fn is_empty(&self) -> bool {
-        self.len() == 0
-    }
-
-    /// Creates a [`BStr`] from a `[u8]`.
-    #[inline]
-    pub const fn from_bytes(bytes: &[u8]) -> &Self {
-        // SAFETY: `BStr` is transparent to `[u8]`.
-        unsafe { &*(bytes as *const [u8] as *const BStr) }
-    }
-}
-
-impl fmt::Display for BStr {
-    /// Formats printable ASCII characters, escaping the rest.
-    ///
-    /// ```
-    /// # use kernel::{fmt, b_str, str::{BStr, CString}};
-    /// let ascii = b_str!("Hello, BStr!");
-    /// let s = CString::try_from_fmt(fmt!("{}", ascii)).unwrap();
-    /// assert_eq!(s.as_bytes(), "Hello, BStr!".as_bytes());
-    ///
-    /// let non_ascii = b_str!("🦀");
-    /// let s = CString::try_from_fmt(fmt!("{}", non_ascii)).unwrap();
-    /// assert_eq!(s.as_bytes(), "\\xf0\\x9f\\xa6\\x80".as_bytes());
-    /// ```
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        for &b in &self.0 {
-            match b {
-                // Common escape codes.
-                b'\t' => f.write_str("\\t")?,
-                b'\n' => f.write_str("\\n")?,
-                b'\r' => f.write_str("\\r")?,
-                // Printable characters.
-                0x20..=0x7e => f.write_char(b as char)?,
-                _ => write!(f, "\\x{:02x}", b)?,
-            }
-        }
-        Ok(())
-    }
-}
-
-impl fmt::Debug for BStr {
-    /// Formats printable ASCII characters with a double quote on either end,
-    /// escaping the rest.
-    ///
-    /// ```
-    /// # use kernel::{fmt, b_str, str::{BStr, CString}};
-    /// // Embedded double quotes are escaped.
-    /// let ascii = b_str!("Hello, \"BStr\"!");
-    /// let s = CString::try_from_fmt(fmt!("{:?}", ascii)).unwrap();
-    /// assert_eq!(s.as_bytes(), "\"Hello, \\\"BStr\\\"!\"".as_bytes());
-    ///
-    /// let non_ascii = b_str!("😺");
-    /// let s = CString::try_from_fmt(fmt!("{:?}", non_ascii)).unwrap();
-    /// assert_eq!(s.as_bytes(), "\"\\xf0\\x9f\\x98\\xba\"".as_bytes());
-    /// ```
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        f.write_char('"')?;
-        for &b in &self.0 {
-            match b {
-                // Common escape codes.
-                b'\t' => f.write_str("\\t")?,
-                b'\n' => f.write_str("\\n")?,
-                b'\r' => f.write_str("\\r")?,
-                // String escape characters.
-                b'\"' => f.write_str("\\\"")?,
-                b'\\' => f.write_str("\\\\")?,
-                // Printable characters.
-                0x20..=0x7e => f.write_char(b as char)?,
-                _ => write!(f, "\\x{:02x}", b)?,
-            }
-        }
-        f.write_char('"')
-    }
-}
-
-impl Deref for BStr {
-    type Target = [u8];
-
-    #[inline]
-    fn deref(&self) -> &Self::Target {
-        &self.0
-    }
-}
+///
+/// `BStr` is simply an alias to `[u8]`, but has a more evident semantical meaning.
+pub type BStr = [u8];
 
 /// Creates a new [`BStr`] from a string literal.
 ///
@@ -123,7 +30,7 @@ impl Deref for BStr {
 macro_rules! b_str {
     ($str:literal) => {{
         const S: &'static str = $str;
-        const C: &'static $crate::str::BStr = $crate::str::BStr::from_bytes(S.as_bytes());
+        const C: &'static $crate::str::BStr = S.as_bytes();
         C
     }};
 }
@@ -438,7 +345,7 @@ impl fmt::Debug for CStr {
 impl AsRef<BStr> for CStr {
     #[inline]
     fn as_ref(&self) -> &BStr {
-        BStr::from_bytes(self.as_bytes())
+        self.as_bytes()
     }
 }
 
@@ -447,7 +354,7 @@ impl Deref for CStr {
 
     #[inline]
     fn deref(&self) -> &Self::Target {
-        self.as_ref()
+        self.as_bytes()
     }
 }
 
@@ -494,7 +401,7 @@ where
 
     #[inline]
     fn index(&self, index: Idx) -> &Self::Output {
-        &self.as_ref()[index]
+        &self.as_bytes()[index]
     }
 }
 
@@ -524,21 +431,6 @@ macro_rules! c_str {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use alloc::format;
-
-    const ALL_ASCII_CHARS: &'static str =
-        "\\x01\\x02\\x03\\x04\\x05\\x06\\x07\\x08\\x09\\x0a\\x0b\\x0c\\x0d\\x0e\\x0f\
-        \\x10\\x11\\x12\\x13\\x14\\x15\\x16\\x17\\x18\\x19\\x1a\\x1b\\x1c\\x1d\\x1e\\x1f \
-        !\"#$%&'()*+,-./0123456789:;<=>?@\
-        ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\\x7f\
-        \\x80\\x81\\x82\\x83\\x84\\x85\\x86\\x87\\x88\\x89\\x8a\\x8b\\x8c\\x8d\\x8e\\x8f\
-        \\x90\\x91\\x92\\x93\\x94\\x95\\x96\\x97\\x98\\x99\\x9a\\x9b\\x9c\\x9d\\x9e\\x9f\
-        \\xa0\\xa1\\xa2\\xa3\\xa4\\xa5\\xa6\\xa7\\xa8\\xa9\\xaa\\xab\\xac\\xad\\xae\\xaf\
-        \\xb0\\xb1\\xb2\\xb3\\xb4\\xb5\\xb6\\xb7\\xb8\\xb9\\xba\\xbb\\xbc\\xbd\\xbe\\xbf\
-        \\xc0\\xc1\\xc2\\xc3\\xc4\\xc5\\xc6\\xc7\\xc8\\xc9\\xca\\xcb\\xcc\\xcd\\xce\\xcf\
-        \\xd0\\xd1\\xd2\\xd3\\xd4\\xd5\\xd6\\xd7\\xd8\\xd9\\xda\\xdb\\xdc\\xdd\\xde\\xdf\
-        \\xe0\\xe1\\xe2\\xe3\\xe4\\xe5\\xe6\\xe7\\xe8\\xe9\\xea\\xeb\\xec\\xed\\xee\\xef\
-        \\xf0\\xf1\\xf2\\xf3\\xf4\\xf5\\xf6\\xf7\\xf8\\xf9\\xfa\\xfb\\xfc\\xfd\\xfe\\xff";
 
     #[test]
     fn test_cstr_to_str() {
@@ -563,69 +455,6 @@ mod tests {
         let unchecked_str = unsafe { checked_cstr.as_str_unchecked() };
         assert_eq!(unchecked_str, "🐧");
     }
-
-    #[test]
-    fn test_cstr_display() {
-        let hello_world = CStr::from_bytes_with_nul(b"hello, world!\0").unwrap();
-        assert_eq!(format!("{}", hello_world), "hello, world!");
-        let non_printables = CStr::from_bytes_with_nul(b"\x01\x09\x0a\0").unwrap();
-        assert_eq!(format!("{}", non_printables), "\\x01\\x09\\x0a");
-        let non_ascii = CStr::from_bytes_with_nul(b"d\xe9j\xe0 vu\0").unwrap();
-        assert_eq!(format!("{}", non_ascii), "d\\xe9j\\xe0 vu");
-        let good_bytes = CStr::from_bytes_with_nul(b"\xf0\x9f\xa6\x80\0").unwrap();
-        assert_eq!(format!("{}", good_bytes), "\\xf0\\x9f\\xa6\\x80");
-    }
-
-    #[test]
-    fn test_cstr_display_all_bytes() {
-        let mut bytes: [u8; 256] = [0; 256];
-        // fill `bytes` with [1..=255] + [0]
-        for i in u8::MIN..=u8::MAX {
-            bytes[i as usize] = i.wrapping_add(1);
-        }
-        let cstr = CStr::from_bytes_with_nul(&bytes).unwrap();
-        assert_eq!(format!("{}", cstr), ALL_ASCII_CHARS);
-    }
-
-    #[test]
-    fn test_cstr_debug() {
-        let hello_world = CStr::from_bytes_with_nul(b"hello, world!\0").unwrap();
-        assert_eq!(format!("{:?}", hello_world), "\"hello, world!\"");
-        let non_printables = CStr::from_bytes_with_nul(b"\x01\x09\x0a\0").unwrap();
-        assert_eq!(format!("{:?}", non_printables), "\"\\x01\\x09\\x0a\"");
-        let non_ascii = CStr::from_bytes_with_nul(b"d\xe9j\xe0 vu\0").unwrap();
-        assert_eq!(format!("{:?}", non_ascii), "\"d\\xe9j\\xe0 vu\"");
-        let good_bytes = CStr::from_bytes_with_nul(b"\xf0\x9f\xa6\x80\0").unwrap();
-        assert_eq!(format!("{:?}", good_bytes), "\"\\xf0\\x9f\\xa6\\x80\"");
-    }
-
-    #[test]
-    fn test_bstr_display() {
-        let hello_world = BStr::from_bytes(b"hello, world!");
-        assert_eq!(format!("{}", hello_world), "hello, world!");
-        let escapes = BStr::from_bytes(b"_\t_\n_\r_\\_\'_\"_");
-        assert_eq!(format!("{}", escapes), "_\\t_\\n_\\r_\\_'_\"_");
-        let others = BStr::from_bytes(b"\x01");
-        assert_eq!(format!("{}", others), "\\x01");
-        let non_ascii = BStr::from_bytes(b"d\xe9j\xe0 vu");
-        assert_eq!(format!("{}", non_ascii), "d\\xe9j\\xe0 vu");
-        let good_bytes = BStr::from_bytes(b"\xf0\x9f\xa6\x80");
-        assert_eq!(format!("{}", good_bytes), "\\xf0\\x9f\\xa6\\x80");
-    }
-
-    #[test]
-    fn test_bstr_debug() {
-        let hello_world = BStr::from_bytes(b"hello, world!");
-        assert_eq!(format!("{:?}", hello_world), "\"hello, world!\"");
-        let escapes = BStr::from_bytes(b"_\t_\n_\r_\\_\'_\"_");
-        assert_eq!(format!("{:?}", escapes), "\"_\\t_\\n_\\r_\\\\_'_\\\"_\"");
-        let others = BStr::from_bytes(b"\x01");
-        assert_eq!(format!("{:?}", others), "\"\\x01\"");
-        let non_ascii = BStr::from_bytes(b"d\xe9j\xe0 vu");
-        assert_eq!(format!("{:?}", non_ascii), "\"d\\xe9j\\xe0 vu\"");
-        let good_bytes = BStr::from_bytes(b"\xf0\x9f\xa6\x80");
-        assert_eq!(format!("{:?}", good_bytes), "\"\\xf0\\x9f\\xa6\\x80\"");
-    }
 }
 
 /// Allows formatting of [`fmt::Arguments`] into a raw buffer.

From f68a2d7e0776b3c039436a4beebf528f57748f35 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 May 2024 23:02:11 +0200
Subject: [PATCH 0960/1027] Revert "rust: remove `params` from `module` macro
 example"

This reverts commit 19843452dca40e28d6d3f4793d998b681d505c7f.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/macros/lib.rs | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/rust/macros/lib.rs b/rust/macros/lib.rs
index 99ac924f2af795..4ab9488eb13c52 100644
--- a/rust/macros/lib.rs
+++ b/rust/macros/lib.rs
@@ -37,6 +37,18 @@ use proc_macro::TokenStream;
 ///     description: "My very own kernel module!",
 ///     license: "GPL",
 ///     alias: ["alternate_module_name"],
+///     params: {
+///        my_i32: i32 {
+///            default: 42,
+///            permissions: 0o000,
+///            description: "Example of i32",
+///        },
+///        writeable_i32: i32 {
+///            default: 42,
+///            permissions: 0o644,
+///            description: "Example of i32",
+///        },
+///    },
 /// }
 ///
 /// struct MyModule;

From 132dede8c654a027123182f3ed9c7f3c329fd80c Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 13 May 2024 23:19:27 +0200
Subject: [PATCH 0961/1027] Revert "rust: macros: fix soundness issue in
 `module!` macro"

This reverts commit 7044dcff8301b29269016ebd17df27c4736140d2.

Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/macros/module.rs | 192 +++++++++++++++++-------------------------
 1 file changed, 76 insertions(+), 116 deletions(-)

diff --git a/rust/macros/module.rs b/rust/macros/module.rs
index f5b9d6d8a430fe..9be7d312ac3a38 100644
--- a/rust/macros/module.rs
+++ b/rust/macros/module.rs
@@ -544,6 +544,17 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
             /// Used by the printing macros, e.g. [`info!`].
             const __LOG_PREFIX: &[u8] = b\"{name}\\0\";
 
+            /// The \"Rust loadable module\" mark.
+            //
+            // This may be best done another way later on, e.g. as a new modinfo
+            // key or a new section. For the moment, keep it simple.
+            #[cfg(MODULE)]
+            #[doc(hidden)]
+            #[used]
+            static __IS_RUST_MODULE: () = ();
+
+            static mut __MOD: Option<{type_}> = None;
+
             // SAFETY: `__this_module` is constructed by the kernel at load time and will not be
             // freed until the module is unloaded.
             #[cfg(MODULE)]
@@ -559,133 +570,82 @@ pub(crate) fn module(ts: TokenStream) -> TokenStream {
                 kernel::ThisModule::from_ptr(core::ptr::null_mut())
             }};
 
-            // Double nested modules, since then nobody can access the public items inside.
-            mod __module_init {{
-                mod __module_init {{
-                    use super::super::{type_};
-
-                    /// The \"Rust loadable module\" mark.
-                    //
-                    // This may be best done another way later on, e.g. as a new modinfo
-                    // key or a new section. For the moment, keep it simple.
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[used]
-                    static __IS_RUST_MODULE: () = ();
-
-                    static mut __MOD: Option<{type_}> = None;
-
-                    // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
-                    /// # Safety
-                    ///
-                    /// This function must not be called after module initialization, because it may be
-                    /// freed after that completes.
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    #[link_section = \".init.text\"]
-                    pub unsafe extern \"C\" fn init_module() -> core::ffi::c_int {{
-                        // SAFETY: This function is inaccessible to the outside due to the double
-                        // module wrapping it. It is called exactly once by the C side via its
-                        // unique name.
-                        unsafe {{ __init() }}
-                    }}
+            // Loadable modules need to export the `{{init,cleanup}}_module` identifiers.
+            /// # Safety
+            ///
+            /// This function must not be called after module initialization, because it may be
+            /// freed after that completes.
+            #[cfg(MODULE)]
+            #[doc(hidden)]
+            #[no_mangle]
+            #[link_section = \".init.text\"]
+            pub unsafe extern \"C\" fn init_module() -> core::ffi::c_int {{
+                __init()
+            }}
 
-                    #[cfg(MODULE)]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    pub extern \"C\" fn cleanup_module() {{
-                        // SAFETY:
-                        // - This function is inaccessible to the outside due to the double
-                        //   module wrapping it. It is called exactly once by the C side via its
-                        //   unique name,
-                        // - furthermore it is only called after `init_module` has returned `0`
-                        //   (which delegates to `__init`).
-                        unsafe {{ __exit() }}
-                    }}
+            #[cfg(MODULE)]
+            #[doc(hidden)]
+            #[no_mangle]
+            pub extern \"C\" fn cleanup_module() {{
+                __exit()
+            }}
 
-                    // Built-in modules are initialized through an initcall pointer
-                    // and the identifiers need to be unique.
-                    #[cfg(not(MODULE))]
-                    #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
-                    #[doc(hidden)]
-                    #[link_section = \"{initcall_section}\"]
-                    #[used]
-                    pub static __{name}_initcall: extern \"C\" fn() -> core::ffi::c_int = __{name}_init;
-
-                    #[cfg(not(MODULE))]
-                    #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
-                    core::arch::global_asm!(
-                        r#\".section \"{initcall_section}\", \"a\"
-                        __{name}_initcall:
-                            .long   __{name}_init - .
-                            .previous
-                        \"#
-                    );
-
-                    #[cfg(not(MODULE))]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    pub extern \"C\" fn __{name}_init() -> core::ffi::c_int {{
-                        // SAFETY: This function is inaccessible to the outside due to the double
-                        // module wrapping it. It is called exactly once by the C side via its
-                        // placement above in the initcall section.
-                        unsafe {{ __init() }}
-                    }}
+            // Built-in modules are initialized through an initcall pointer
+            // and the identifiers need to be unique.
+            #[cfg(not(MODULE))]
+            #[cfg(not(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS))]
+            #[doc(hidden)]
+            #[link_section = \"{initcall_section}\"]
+            #[used]
+            pub static __{name}_initcall: extern \"C\" fn() -> core::ffi::c_int = __{name}_init;
 
-                    #[cfg(not(MODULE))]
-                    #[doc(hidden)]
-                    #[no_mangle]
-                    pub extern \"C\" fn __{name}_exit() {{
-                        // SAFETY:
-                        // - This function is inaccessible to the outside due to the double
-                        //   module wrapping it. It is called exactly once by the C side via its
-                        //   unique name,
-                        // - furthermore it is only called after `__{name}_init` has returned `0`
-                        //   (which delegates to `__init`).
-                        unsafe {{ __exit() }}
-                    }}
+            #[cfg(not(MODULE))]
+            #[cfg(CONFIG_HAVE_ARCH_PREL32_RELOCATIONS)]
+            core::arch::global_asm!(
+                r#\".section \"{initcall_section}\", \"a\"
+                __{name}_initcall:
+                    .long   __{name}_init - .
+                    .previous
+                \"#
+            );
 
-                    /// # Safety
-                    ///
-                    /// This function must only be called once.
-                    unsafe fn __init() -> core::ffi::c_int {{
-                        match <{type_} as kernel::Module>::init(kernel::c_str!(\"{name}\"), &THIS_MODULE) {{
-                            Ok(m) => {{
-                                // SAFETY: No data race, since `__MOD` can only be accessed by this
-                                // module and there only `__init` and `__exit` access it. These
-                                // functions are only called once and `__exit` cannot be called
-                                // before or during `__init`.
-                                unsafe {{
-                                    __MOD = Some(m);
-                                }}
-                                return 0;
-                            }}
-                            Err(e) => {{
-                                return e.to_errno();
-                            }}
-                        }}
-                    }}
+            #[cfg(not(MODULE))]
+            #[doc(hidden)]
+            #[no_mangle]
+            pub extern \"C\" fn __{name}_init() -> core::ffi::c_int {{
+                __init()
+            }}
 
-                    /// # Safety
-                    ///
-                    /// This function must
-                    /// - only be called once,
-                    /// - be called after `__init` has been called and returned `0`.
-                    unsafe fn __exit() {{
-                        // SAFETY: No data race, since `__MOD` can only be accessed by this module
-                        // and there only `__init` and `__exit` access it. These functions are only
-                        // called once and `__init` was already called.
+            #[cfg(not(MODULE))]
+            #[doc(hidden)]
+            #[no_mangle]
+            pub extern \"C\" fn __{name}_exit() {{
+                __exit()
+            }}
+
+            fn __init() -> core::ffi::c_int {{
+                match <{type_} as kernel::Module>::init(kernel::c_str!(\"{name}\"), &THIS_MODULE) {{
+                    Ok(m) => {{
                         unsafe {{
-                            // Invokes `drop()` on `__MOD`, which should be used for cleanup.
-                            __MOD = None;
+                            __MOD = Some(m);
                         }}
+                        return 0;
+                    }}
+                    Err(e) => {{
+                        return e.to_errno();
                     }}
+                }}
+            }}
 
-                    {modinfo}
-                    {generated_array_types}
+            fn __exit() {{
+                unsafe {{
+                    // Invokes `drop()` on `__MOD`, which should be used for cleanup.
+                    __MOD = None;
                 }}
             }}
+
+            {modinfo}
+                    {generated_array_types}
         ",
         type_ = info.type_,
         name = info.name,

From ea2a0aa8e0817f3be0297d24d79c023640555c0a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 1 Jun 2024 11:00:33 +0200
Subject: [PATCH 0962/1027] rust: Replace -fmin-function-alignment with
 -falign-functions for bindgen

Commit 5270316c9fec ("kbuild: Use -fmin-function-alignment when
available") introduces `-fmin-function-alignment` if supported to remove
a workaround for gcc not respecting `-falign-functions` for cold
functions. bindgen does not recognize `-fmin-function-alignment` but
`-falign-functions` is sufficient as long as bindgen is LLVM backed. See
c27cd083cfb9 ("Compiler attributes: GCC cold function alignment
workarounds").
So replace `-fmin-function-alignment` with `-falign-functions` for
bindgen's C flags.

Fixes: 5270316c9fec ("kbuild: Use -fmin-function-alignment when available")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/Makefile | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/rust/Makefile b/rust/Makefile
index 5d1624db247a80..eefa15344343ec 100644
--- a/rust/Makefile
+++ b/rust/Makefile
@@ -264,7 +264,13 @@ else
 bindgen_c_flags_lto = $(bindgen_c_flags)
 endif
 
-bindgen_c_flags_final = $(bindgen_c_flags_lto) -D__BINDGEN__
+# replace '-fmin-function-alignment' with '-falign-functions'. LLVM does not
+# ignore the latter in cold functions. See:
+# 5270316c9fec ("kbuild: Use -fmin-function-alignment when available")
+# c27cd083cfb9 ("Compiler attributes: GCC cold function alignment workarounds")
+bindgen_c_flags_align = $(subst -fmin-function-alignment=,-falign-functions=,$(bindgen_c_flags_lto))
+
+bindgen_c_flags_final = $(bindgen_c_flags_align) -D__BINDGEN__
 
 quiet_cmd_bindgen = BINDGEN $@
       cmd_bindgen = \

From 0d8e5411bc0005a08ad8fad9391b12b66e718ff4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 4 Jun 2024 19:22:16 +0900
Subject: [PATCH 0963/1027] drm/asahi: alloc: Do not allocate memory to free
 memory

The existing garbage mechanism could allocate a relatively unbounded vec
when freeing garbage, which was hurting memory exhaustion scenarios.
The only reason we need that buffer is to move garbage out of the lock
so we can drop it without deadlocks. Replace it with a 128-size
pre-allocated garbage buffer, and loop around reusing it.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs | 54 +++++++++++++++++++++-------------
 1 file changed, 33 insertions(+), 21 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index eba7fe7ed113b5..304d61eddc450a 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -720,6 +720,7 @@ pub(crate) struct HeapAllocator {
     guard_nodes: Vec<mm::Node<HeapAllocatorInner, HeapAllocationInner>>,
     mm: mm::Allocator<HeapAllocatorInner, HeapAllocationInner>,
     name: CString,
+    garbage: Option<Vec<mm::Node<HeapAllocatorInner, HeapAllocationInner>>>,
 }
 
 impl HeapAllocator {
@@ -773,6 +774,15 @@ impl HeapAllocator {
             guard_nodes: Vec::new(),
             mm,
             name,
+            garbage: if keep_garbage {
+                Some({
+                    let mut v = Vec::new();
+                    v.reserve(128, GFP_KERNEL)?;
+                    v
+                })
+            } else {
+                None
+            },
         })
     }
 
@@ -1038,29 +1048,31 @@ impl Allocator for HeapAllocator {
         })
     }
 
-    fn collect_garbage(&mut self, count: usize) {
-        // Take the garbage out of the inner block, so we can safely drop it without deadlocking
-        let mut garbage = Vec::new();
-
-        if garbage.try_reserve(count).is_err() {
-            dev_crit!(
-                self.dev,
-                "HeapAllocator[{}]:collect_garbage: failed to reserve space\n",
-                &*self.name,
-            );
-            return;
-        }
+    fn collect_garbage(&mut self, mut count: usize) {
+        if let Some(garbage) = self.garbage.as_mut() {
+            garbage.clear();
+
+            while count > 0 {
+                let block = count.min(garbage.capacity());
+                assert!(block > 0);
+
+                // Take the garbage out of the inner block, so we can safely drop it without deadlocking
+                self.mm.with_inner(|inner| {
+                    if let Some(g) = inner.garbage.as_mut() {
+                        for node in g.drain(0..block) {
+                            inner.total_garbage -= node.size() as usize;
+                            garbage
+                                .push(node, GFP_KERNEL)
+                                .expect("push() failed after reserve()");
+                        }
+                    }
+                });
 
-        self.mm.with_inner(|inner| {
-            if let Some(g) = inner.garbage.as_mut() {
-                for node in g.drain(0..count) {
-                    inner.total_garbage -= node.size() as usize;
-                    garbage
-                        .try_push(node)
-                        .expect("try_push() failed after reserve()");
-                }
+                count -= block;
+                // Now drop it
+                garbage.clear();
             }
-        });
+        }
     }
 }
 

From be88519176edcb09cfbb34524d13915d9549cab7 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 19:50:07 +0900
Subject: [PATCH 0964/1027] drm/gpuvm: Add drm_gpuvm_bo_unmap()

Analogous to drm_gpuvm_bo_unmap_ops_create, this is a callback-driven
unmap function for a given BO.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/drm_gpuvm.c | 43 +++++++++++++++++++++++++++++++++++++
 include/drm/drm_gpuvm.h     |  1 +
 2 files changed, 44 insertions(+)

diff --git a/drivers/gpu/drm/drm_gpuvm.c b/drivers/gpu/drm/drm_gpuvm.c
index f9eb56f24bef29..691b46d961fda9 100644
--- a/drivers/gpu/drm/drm_gpuvm.c
+++ b/drivers/gpu/drm/drm_gpuvm.c
@@ -2664,6 +2664,49 @@ drm_gpuvm_prefetch_ops_create(struct drm_gpuvm *gpuvm,
 }
 EXPORT_SYMBOL_GPL(drm_gpuvm_prefetch_ops_create);
 
+/**
+ * drm_gpuvm_bo_unmap() - unmaps a GEM
+ * @vm_bo: the &drm_gpuvm_bo abstraction
+ *
+ * This function calls the unmap callback for every GPUVA attached to a GEM.
+ *
+ * It is the callers responsibility to protect the GEMs GPUVA list against
+ * concurrent access using the GEMs dma_resv lock.
+ *
+ * Returns: a pointer to the &drm_gpuva_ops on success, an ERR_PTR on failure
+ */
+int
+drm_gpuvm_bo_unmap(struct drm_gpuvm_bo *vm_bo, void *priv)
+{
+	struct drm_gpuva_op *op;
+	int ret;
+
+	if (unlikely(!vm_bo->vm))
+		return -EINVAL;
+
+	const struct drm_gpuvm_ops *vm_ops = vm_bo->vm->ops;
+
+	if (unlikely(!(vm_ops && vm_ops->sm_step_unmap)))
+		return -EINVAL;
+
+	struct drm_gpuva_ops *ops = drm_gpuvm_bo_unmap_ops_create(vm_bo);
+        if (IS_ERR(ops))
+                return PTR_ERR(ops);
+
+	drm_gpuva_for_each_op(op, ops) {
+		drm_WARN_ON(vm_bo->vm->drm, op->op != DRM_GPUVA_OP_UNMAP);
+
+		ret = op_unmap_cb(vm_ops, priv, op->unmap.va, false);
+		if (ret)
+			goto cleanup;
+	}
+
+cleanup:
+	drm_gpuva_ops_free(vm_bo->vm, ops);
+	return ret;
+}
+EXPORT_SYMBOL_GPL(drm_gpuvm_bo_unmap);
+
 /**
  * drm_gpuvm_bo_unmap_ops_create() - creates the &drm_gpuva_ops to unmap a GEM
  * @vm_bo: the &drm_gpuvm_bo abstraction
diff --git a/include/drm/drm_gpuvm.h b/include/drm/drm_gpuvm.h
index 00d4e43b76b6c1..a79529689ee7d3 100644
--- a/include/drm/drm_gpuvm.h
+++ b/include/drm/drm_gpuvm.h
@@ -1205,6 +1205,7 @@ int drm_gpuvm_sm_map(struct drm_gpuvm *gpuvm, void *priv,
 
 int drm_gpuvm_sm_unmap(struct drm_gpuvm *gpuvm, void *priv,
 		       u64 addr, u64 range);
+int drm_gpuvm_bo_unmap(struct drm_gpuvm_bo *bo, void *priv);
 
 void drm_gpuva_map(struct drm_gpuvm *gpuvm,
 		   struct drm_gpuva *va,

From bac66dd65b9d423920f79d39f27ceeb5806f7e94 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 14:15:47 +0900
Subject: [PATCH 0965/1027] drm/asahi: microseq: Fix warnings

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/microseq.rs | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/drivers/gpu/drm/asahi/fw/microseq.rs b/drivers/gpu/drm/asahi/fw/microseq.rs
index 2f6555799baf59..f3b5856bbf2be6 100644
--- a/drivers/gpu/drm/asahi/fw/microseq.rs
+++ b/drivers/gpu/drm/asahi/fw/microseq.rs
@@ -66,6 +66,7 @@ impl OpHeader {
 
 macro_rules! simple_op {
     ($name:ident) => {
+        #[allow(dead_code)]
         #[derive(Debug, Copy, Clone)]
         pub(crate) struct $name(OpHeader);
 
@@ -86,6 +87,7 @@ pub(crate) mod op {
     simple_op!(FinalizeCompute);
     simple_op!(WaitForIdle2);
 
+    #[allow(dead_code)]
     #[derive(Debug, Copy, Clone)]
     pub(crate) struct RetireStamp(OpHeader);
     impl RetireStamp {
@@ -93,6 +95,7 @@ pub(crate) mod op {
             RetireStamp(OpHeader::with_args(OpCode::RetireStamp, 0x40000000));
     }
 
+    #[allow(dead_code)]
     #[derive(Debug, Copy, Clone)]
     pub(crate) struct WaitForIdle(OpHeader);
     impl WaitForIdle {
@@ -101,6 +104,7 @@ pub(crate) mod op {
         }
     }
 
+    #[allow(dead_code)]
     #[derive(Debug, Copy, Clone)]
     pub(crate) struct Timestamp(OpHeader);
     impl Timestamp {

From 59dbaf01db5a07f468741d9c2f6c7f6ac293fb49 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 14:17:05 +0900
Subject: [PATCH 0966/1027] rust: drm: Add GPUVM Manager abstraction

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/Kconfig   |   5 +
 rust/bindings/bindings_helper.h |   4 +
 rust/helpers/drm_gpuvm.c        |  34 ++
 rust/helpers/helpers.c          |   1 +
 rust/kernel/drm/drv.rs          |   2 +
 rust/kernel/drm/gpuvm.rs        | 665 ++++++++++++++++++++++++++++++++
 rust/kernel/drm/mod.rs          |   2 +
 7 files changed, 713 insertions(+)
 create mode 100644 rust/helpers/drm_gpuvm.c
 create mode 100644 rust/kernel/drm/gpuvm.rs

diff --git a/drivers/gpu/drm/asahi/Kconfig b/drivers/gpu/drm/asahi/Kconfig
index 4ce4a773db5d43..de1b16afef5c64 100644
--- a/drivers/gpu/drm/asahi/Kconfig
+++ b/drivers/gpu/drm/asahi/Kconfig
@@ -8,6 +8,10 @@ config RUST_DRM_GEM_SHMEM_HELPER
 	bool
 	select DRM_GEM_SHMEM_HELPER
 
+config RUST_DRM_GPUVM
+	bool
+	select DRM_GPUVM
+
 config RUST_APPLE_RTKIT
 	bool
 	select APPLE_RTKIT
@@ -23,6 +27,7 @@ config DRM_ASAHI
 	select IOMMU_SUPPORT
 	select IOMMU_IO_PGTABLE_LPAE
 	select RUST_DRM_GEM_SHMEM_HELPER
+	select RUST_DRM_GPUVM
 	select RUST_APPLE_RTKIT
 	help
 	  DRM driver for Apple AGX GPUs (G13x, found in the M1 SoC family)
diff --git a/rust/bindings/bindings_helper.h b/rust/bindings/bindings_helper.h
index 1891cd0de0b791..ed6d06feb6ca9f 100644
--- a/rust/bindings/bindings_helper.h
+++ b/rust/bindings/bindings_helper.h
@@ -8,9 +8,11 @@
 
 #include <drm/drm_device.h>
 #include <drm/drm_drv.h>
+#include <drm/drm_exec.h>
 #include <drm/drm_file.h>
 #include <drm/drm_gem.h>
 #include <drm/drm_gem_shmem_helper.h>
+#include <drm/drm_gpuvm.h>
 #include <drm/drm_ioctl.h>
 #include <drm/drm_syncobj.h>
 #include <drm/gpu_scheduler.h>
@@ -75,3 +77,5 @@ const xa_mark_t BINDINGS_XA_MARK_2 = XA_MARK_2;
 const xa_mark_t BINDINGS_XA_PRESENT = XA_PRESENT;
 const xa_mark_t BINDINGS_XA_MARK_MAX = XA_MARK_MAX;
 const xa_mark_t BINDINGS_XA_FREE_MARK = XA_FREE_MARK;
+
+const uint32_t BINDINGS_DRM_EXEC_INTERRUPTIBLE_WAIT = DRM_EXEC_INTERRUPTIBLE_WAIT;
diff --git a/rust/helpers/drm_gpuvm.c b/rust/helpers/drm_gpuvm.c
new file mode 100644
index 00000000000000..f4f4ea2c4ec897
--- /dev/null
+++ b/rust/helpers/drm_gpuvm.c
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <drm/drm_gpuvm.h>
+
+#ifdef CONFIG_DRM
+#ifdef CONFIG_DRM_GPUVM
+
+struct drm_gpuvm *rust_helper_drm_gpuvm_get(struct drm_gpuvm *obj)
+{
+	return drm_gpuvm_get(obj);
+}
+
+void rust_helper_drm_gpuvm_exec_unlock(struct drm_gpuvm_exec *vm_exec)
+{
+	return drm_gpuvm_exec_unlock(vm_exec);
+}
+
+void rust_helper_drm_gpuva_init_from_op(struct drm_gpuva *va, struct drm_gpuva_op_map *op)
+{
+	drm_gpuva_init_from_op(va, op);
+}
+
+struct drm_gpuvm_bo *rust_helper_drm_gpuvm_bo_get(struct drm_gpuvm_bo *vm_bo)
+{
+	return drm_gpuvm_bo_get(vm_bo);
+}
+
+bool rust_helper_drm_gpuvm_is_extobj(struct drm_gpuvm *gpuvm, struct drm_gem_object *obj)
+{
+	return drm_gpuvm_is_extobj(gpuvm, obj);
+}
+
+#endif
+#endif
diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index 7d087e8191923c..a2cc0e26eca70a 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -15,6 +15,7 @@
 #include "dma-fence.c"
 #include "dma-resv.c"
 #include "drm_gem.c"
+#include "drm_gpuvm.c"
 #include "drm_syncobj.c"
 #include "err.c"
 #include "iomem.c"
diff --git a/rust/kernel/drm/drv.rs b/rust/kernel/drm/drv.rs
index a7a8393ec604e8..fe4441a2c41db6 100644
--- a/rust/kernel/drm/drv.rs
+++ b/rust/kernel/drm/drv.rs
@@ -40,6 +40,8 @@ pub const FEAT_SYNCOBJ: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ;
 /// Driver supports the timeline flavor of DRM sync objects for explicit synchronization of command
 /// submission.
 pub const FEAT_SYNCOBJ_TIMELINE: u32 = bindings::drm_driver_feature_DRIVER_SYNCOBJ_TIMELINE;
+/// Driver uses the GEM GPUVA manager.
+pub const FEAT_GEM_GPUVA: u32 = bindings::drm_driver_feature_DRIVER_GEM_GPUVA;
 
 /// Information data for a DRM Driver.
 pub struct DriverInfo {
diff --git a/rust/kernel/drm/gpuvm.rs b/rust/kernel/drm/gpuvm.rs
new file mode 100644
index 00000000000000..47fd4fbc17ebe8
--- /dev/null
+++ b/rust/kernel/drm/gpuvm.rs
@@ -0,0 +1,665 @@
+// SPDX-License-Identifier: GPL-2.0 OR MIT
+
+//! DRM Sync Objects
+//!
+//! C header: [`include/drm/drm_gpuvm.h`](../../../../include/drm/drm_gpuvm.h)
+
+#![allow(missing_docs)]
+
+use crate::{
+    alloc::flags::*,
+    bindings,
+    drm::{device, drv},
+    error::{
+        code::{EINVAL, ENOMEM},
+        from_result, to_result, Result,
+    },
+    init,
+    prelude::*,
+    types::{ARef, AlwaysRefCounted, Opaque},
+};
+
+use crate::drm::gem::IntoGEMObject;
+use core::cell::UnsafeCell;
+use core::marker::{PhantomData, PhantomPinned};
+use core::mem::ManuallyDrop;
+use core::ops::{Deref, DerefMut, Range};
+use core::ptr::NonNull;
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVm (a GPU address space).
+pub trait DriverGpuVm: Sized {
+    /// The parent `Driver` implementation for this `DriverGpuVm`.
+    type Driver: drv::Driver;
+    type GpuVa: DriverGpuVa = ();
+    type GpuVmBo: DriverGpuVmBo = ();
+    type StepContext = ();
+
+    fn step_map(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+    fn step_unmap(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpUnMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+    fn step_remap(
+        self: &mut UpdatingGpuVm<'_, Self>,
+        op: &mut OpReMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result;
+}
+
+struct StepContext<'a, T: DriverGpuVm> {
+    gpuvm: &'a GpuVm<T>,
+    ctx: &'a mut T::StepContext,
+}
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVa (a mapping in GPU address space).
+pub trait DriverGpuVa: Sized {}
+
+impl DriverGpuVa for () {}
+
+/// Trait that must be implemented by DRM drivers to represent a DRM GpuVmBo (a connection between a BO and a VM).
+pub trait DriverGpuVmBo: Sized {
+    fn new() -> impl PinInit<Self>;
+}
+
+/// Provide a default implementation for trivial types
+impl<T: Default> DriverGpuVmBo for T {
+    fn new() -> impl PinInit<Self> {
+        init::default()
+    }
+}
+
+#[repr(transparent)]
+pub struct OpMap<T: DriverGpuVm>(bindings::drm_gpuva_op_map, PhantomData<T>);
+#[repr(transparent)]
+pub struct OpUnMap<T: DriverGpuVm>(bindings::drm_gpuva_op_unmap, PhantomData<T>);
+#[repr(transparent)]
+pub struct OpReMap<T: DriverGpuVm>(bindings::drm_gpuva_op_remap, PhantomData<T>);
+
+impl<T: DriverGpuVm> OpMap<T> {
+    pub fn addr(&self) -> u64 {
+        self.0.va.addr
+    }
+    pub fn range(&self) -> u64 {
+        self.0.va.range
+    }
+    pub fn offset(&self) -> u64 {
+        self.0.gem.offset
+    }
+    pub fn object(&self) -> &<T::Driver as drv::Driver>::Object {
+        // SAFETY: The GEM object is only ever passed as a Driver object below, so
+        // the type must be correct.
+        let p = unsafe {
+            <<T::Driver as drv::Driver>::Object as IntoGEMObject>::from_gem_obj(self.0.gem.obj)
+        };
+        // SAFETY: The GEM object has an active reference for the lifetime of this op
+        unsafe { &*p }
+    }
+    pub fn map_and_link_va(
+        &mut self,
+        gpuvm: &mut UpdatingGpuVm<'_, T>,
+        gpuva: Pin<Box<GpuVa<T>>>,
+        gpuvmbo: &ARef<GpuVmBo<T>>,
+    ) -> Result<(), Pin<Box<GpuVa<T>>>> {
+        // SAFETY: We are handing off the GpuVa ownership and it will not be moved.
+        let p = Box::leak(unsafe { Pin::into_inner_unchecked(gpuva) });
+        // SAFETY: These C functions are called with the correct invariants
+        unsafe {
+            bindings::drm_gpuva_init_from_op(&mut p.gpuva, &mut self.0);
+            if bindings::drm_gpuva_insert(gpuvm.0.gpuvm() as *mut _, &mut p.gpuva) != 0 {
+                // EEXIST, return the GpuVa to the caller as an error
+                return Err(Pin::new_unchecked(Box::from_raw(p)));
+            };
+            // SAFETY: This takes a new reference to the gpuvmbo.
+            bindings::drm_gpuva_link(&mut p.gpuva, &gpuvmbo.bo as *const _ as *mut _);
+        }
+        Ok(())
+    }
+}
+
+impl<T: DriverGpuVm> OpUnMap<T> {
+    pub fn va(&self) -> Option<&GpuVa<T>> {
+        if self.0.va.is_null() {
+            return None;
+        }
+        // SAFETY: Container invariant is guaranteed for ops structs created for our types.
+        let p = unsafe { crate::container_of!(self.0.va, GpuVa<T>, gpuva) as *mut GpuVa<T> };
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        Some(unsafe { &*p })
+    }
+    pub fn unmap_and_unlink_va(&mut self) -> Option<Pin<Box<GpuVa<T>>>> {
+        if self.0.va.is_null() {
+            return None;
+        }
+        // SAFETY: Container invariant is guaranteed for ops structs created for our types.
+        let p = unsafe { crate::container_of!(self.0.va, GpuVa<T>, gpuva) as *mut GpuVa<T> };
+
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        unsafe {
+            bindings::drm_gpuva_unmap(&mut self.0);
+            bindings::drm_gpuva_unlink(self.0.va);
+        }
+
+        // Unlinking/unmapping relinquishes ownership of the GpuVa object,
+        // so clear the pointer
+        self.0.va = core::ptr::null_mut();
+        // SAFETY: The GpuVa object reference is valid per the op_unmap contract
+        Some(unsafe { Pin::new_unchecked(Box::from_raw(p)) })
+    }
+}
+
+impl<T: DriverGpuVm> OpReMap<T> {
+    pub fn prev_map(&mut self) -> Option<&mut OpMap<T>> {
+        // SAFETY: The prev pointer must be valid if not-NULL per the op_remap contract
+        unsafe { (self.0.prev as *mut OpMap<T>).as_mut() }
+    }
+    pub fn next_map(&mut self) -> Option<&mut OpMap<T>> {
+        // SAFETY: The next pointer must be valid if not-NULL per the op_remap contract
+        unsafe { (self.0.next as *mut OpMap<T>).as_mut() }
+    }
+    pub fn unmap(&mut self) -> &mut OpUnMap<T> {
+        // SAFETY: The unmap pointer is always valid per the op_remap contract
+        unsafe { (self.0.unmap as *mut OpUnMap<T>).as_mut().unwrap() }
+    }
+}
+
+/// A base GPU VA.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVa<T: DriverGpuVm> {
+    #[pin]
+    gpuva: bindings::drm_gpuva,
+    #[pin]
+    inner: T::GpuVa,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+// SAFETY: This type is safe to zero-init (as far as C is concerned).
+unsafe impl init::Zeroable for bindings::drm_gpuva {}
+
+impl<T: DriverGpuVm> GpuVa<T> {
+    pub fn new<E>(inner: impl PinInit<T::GpuVa, E>) -> Result<Pin<Box<GpuVa<T>>>>
+    where
+        Error: From<E>,
+    {
+        Box::try_pin_init(
+            try_pin_init!(Self {
+                gpuva <- init::zeroed(),
+                inner <- inner,
+                _p: PhantomPinned
+            }),
+            GFP_KERNEL,
+        )
+    }
+
+    pub fn addr(&self) -> u64 {
+        self.gpuva.va.addr
+    }
+    pub fn range(&self) -> u64 {
+        self.gpuva.va.range
+    }
+    pub fn vm_bo(&self) -> ARef<GpuVmBo<T>> {
+        // SAFETY: Container invariant is guaranteed for ops structs created for our types.
+        let p =
+            unsafe { crate::container_of!(self.gpuva.vm_bo, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+
+        // SAFETY: We incref and wrap in an ARef, so the reference count is consistent
+        unsafe {
+            bindings::drm_gpuvm_bo_get(self.gpuva.vm_bo);
+            ARef::from_raw(NonNull::new_unchecked(p))
+        }
+    }
+    pub fn offset(&self) -> u64 {
+        self.gpuva.gem.offset
+    }
+}
+
+/// A base GpuVm BO.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVmBo<T: DriverGpuVm> {
+    #[pin]
+    bo: bindings::drm_gpuvm_bo,
+    #[pin]
+    inner: T::GpuVmBo,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+impl<T: DriverGpuVm> GpuVmBo<T> {
+    /// Return a reference to the inner driver data for this GpuVmBo
+    pub fn inner(&self) -> &T::GpuVmBo {
+        &self.inner
+    }
+}
+
+// SAFETY: DRM GpuVmBo objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVmBo<T> {
+    fn inc_ref(&self) {
+        // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref().
+        unsafe { bindings::drm_gpuvm_bo_get(&self.bo as *const _ as *mut _) };
+    }
+
+    unsafe fn dec_ref(mut obj: NonNull<Self>) {
+        // SAFETY: drm_gpuvm_bo_put() requires holding the gpuva lock, which is the dma_resv lock by default.
+        // The drm_gpuvm_put function satisfies the requirements for dec_ref().
+        // (We do not support custom locks yet.)
+        unsafe {
+            let resv = (*obj.as_mut().bo.obj).resv;
+            bindings::dma_resv_lock(resv, core::ptr::null_mut());
+            bindings::drm_gpuvm_bo_put(&mut obj.as_mut().bo);
+            bindings::dma_resv_unlock(resv);
+        }
+    }
+}
+
+/// A base GPU VM.
+#[repr(C)]
+#[pin_data]
+pub struct GpuVm<T: DriverGpuVm> {
+    #[pin]
+    gpuvm: Opaque<bindings::drm_gpuvm>,
+    #[pin]
+    inner: UnsafeCell<T>,
+    #[pin]
+    _p: PhantomPinned,
+}
+
+pub(super) unsafe extern "C" fn vm_free_callback<T: DriverGpuVm>(
+    raw_gpuvm: *mut bindings::drm_gpuvm,
+) {
+    // SAFETY: Container invariant is guaranteed for objects using our callback.
+    let p = unsafe {
+        crate::container_of!(
+            raw_gpuvm as *mut Opaque<bindings::drm_gpuvm>,
+            GpuVm<T>,
+            gpuvm
+        ) as *mut GpuVm<T>
+    };
+
+    // SAFETY: p is guaranteed to be valid for drm_gpuvm objects using this callback.
+    unsafe { drop(Box::from_raw(p)) };
+}
+
+pub(super) unsafe extern "C" fn vm_bo_alloc_callback<T: DriverGpuVm>() -> *mut bindings::drm_gpuvm_bo
+{
+    let obj: Result<Pin<Box<GpuVmBo<T>>>> = Box::try_pin_init(
+        try_pin_init!(GpuVmBo::<T> {
+            bo <- init::default(),
+            inner <- T::GpuVmBo::new(),
+            _p: PhantomPinned
+        }),
+        GFP_KERNEL,
+    );
+
+    match obj {
+        Ok(obj) =>
+        // SAFETY: The DRM core will keep this object pinned
+        unsafe {
+            let p = Box::leak(Pin::into_inner_unchecked(obj));
+            &mut p.bo
+        },
+        Err(_) => core::ptr::null_mut(),
+    }
+}
+
+pub(super) unsafe extern "C" fn vm_bo_free_callback<T: DriverGpuVm>(
+    raw_vm_bo: *mut bindings::drm_gpuvm_bo,
+) {
+    // SAFETY: Container invariant is guaranteed for objects using this callback.
+    let p = unsafe { crate::container_of!(raw_vm_bo, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+
+    // SAFETY: p is guaranteed to be valid for drm_gpuvm_bo objects using this callback.
+    unsafe { drop(Box::from_raw(p)) };
+}
+
+pub(super) unsafe extern "C" fn step_map_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpMap is a transparent wrapper.
+    let map = unsafe { &mut *((&mut (*op).__bindgen_anon_1.map) as *mut _ as *mut OpMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    from_result(|| {
+        UpdatingGpuVm(ctx.gpuvm).step_map(map, ctx.ctx)?;
+        Ok(0)
+    })
+}
+
+pub(super) unsafe extern "C" fn step_remap_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpReMap is a transparent wrapper.
+    let remap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.remap) as *mut _ as *mut OpReMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    from_result(|| {
+        UpdatingGpuVm(ctx.gpuvm).step_remap(remap, ctx.ctx)?;
+        Ok(0)
+    })
+}
+pub(super) unsafe extern "C" fn step_unmap_callback<T: DriverGpuVm>(
+    op: *mut bindings::drm_gpuva_op,
+    _priv: *mut core::ffi::c_void,
+) -> core::ffi::c_int {
+    // SAFETY: We know this is a map op, and OpUnMap is a transparent wrapper.
+    let unmap = unsafe { &mut *((&mut (*op).__bindgen_anon_1.unmap) as *mut _ as *mut OpUnMap<T>) };
+    // SAFETY: This is a pointer to a StepContext created inline in sm_map(), which is
+    // guaranteed to outlive this function.
+    let ctx = unsafe { &mut *(_priv as *mut StepContext<'_, T>) };
+
+    from_result(|| {
+        UpdatingGpuVm(ctx.gpuvm).step_unmap(unmap, ctx.ctx)?;
+        Ok(0)
+    })
+}
+
+pub(super) unsafe extern "C" fn exec_lock_gem_object(
+    vm_exec: *mut bindings::drm_gpuvm_exec,
+) -> core::ffi::c_int {
+    // SAFETY: The gpuvm_exec object is valid and priv_ is a GEM object pointer
+    // when this callback is used
+    unsafe { bindings::drm_exec_lock_obj(&mut (*vm_exec).exec, (*vm_exec).extra.priv_ as *mut _) }
+}
+
+impl<T: DriverGpuVm> GpuVm<T> {
+    const OPS: bindings::drm_gpuvm_ops = bindings::drm_gpuvm_ops {
+        vm_free: Some(vm_free_callback::<T>),
+        op_alloc: None,
+        op_free: None,
+        vm_bo_alloc: Some(vm_bo_alloc_callback::<T>),
+        vm_bo_free: Some(vm_bo_free_callback::<T>),
+        vm_bo_validate: None,
+        sm_step_map: Some(step_map_callback::<T>),
+        sm_step_remap: Some(step_remap_callback::<T>),
+        sm_step_unmap: Some(step_unmap_callback::<T>),
+    };
+
+    fn gpuvm(&self) -> *const bindings::drm_gpuvm {
+        self.gpuvm.get()
+    }
+
+    pub fn new<E>(
+        name: &'static CStr,
+        dev: &device::Device<T::Driver>,
+        r_obj: &<T::Driver as drv::Driver>::Object,
+        range: Range<u64>,
+        reserve_range: Range<u64>,
+        inner: impl PinInit<T, E>,
+    ) -> Result<ARef<GpuVm<T>>>
+    where
+        Error: From<E>,
+    {
+        let obj: Pin<Box<Self>> = Box::try_pin_init(
+            try_pin_init!(Self {
+                // SAFETY: drm_gpuvm_init cannot fail and always initializes the member
+                gpuvm <- unsafe {
+                    init::pin_init_from_closure(move |slot: *mut Opaque<bindings::drm_gpuvm> | {
+                        // Zero-init required by drm_gpuvm_init
+                        *slot = Opaque::zeroed();
+                        bindings::drm_gpuvm_init(
+                            Opaque::raw_get(slot),
+                            name.as_char_ptr(),
+                            0,
+                            dev.raw_mut(),
+                            r_obj.gem_obj() as *const _ as *mut _,
+                            range.start,
+                            range.end - range.start,
+                            reserve_range.start,
+                            reserve_range.end - reserve_range.start,
+                            &Self::OPS
+                        );
+                        Ok(())
+                    })
+                },
+                // SAFETY: Just passing through to the initializer argument
+                inner <- unsafe {
+                    init::pin_init_from_closure(move |slot: *mut UnsafeCell<T> | {
+                        inner.__pinned_init(slot as *mut _)
+                    })
+                },
+                _p: PhantomPinned
+            }),
+            GFP_KERNEL,
+        )?;
+
+        // SAFETY: We never move out of the object
+        let vm_ref = unsafe {
+            ARef::from_raw(NonNull::new_unchecked(Box::leak(
+                Pin::into_inner_unchecked(obj),
+            )))
+        };
+
+        Ok(vm_ref)
+    }
+
+    pub fn exec_lock<'a, 'b>(
+        &'a self,
+        obj: Option<&'b <T::Driver as drv::Driver>::Object>,
+    ) -> Result<LockedGpuVm<'a, 'b, T>> {
+        // Do not try to lock the object if it is internal (since it is already locked).
+        let is_ext = obj.map(|a| self.is_extobj(a)).unwrap_or(false);
+
+        let mut guard = ManuallyDrop::new(LockedGpuVm {
+            gpuvm: self,
+            // vm_exec needs to be pinned, so stick it in a Box.
+            vm_exec: Box::init(
+                init!(bindings::drm_gpuvm_exec {
+                    vm: self.gpuvm() as *mut _,
+                    flags: bindings::BINDINGS_DRM_EXEC_INTERRUPTIBLE_WAIT,
+                    exec: Default::default(),
+                    extra: match (is_ext, obj) {
+                        (true, Some(obj)) => bindings::drm_gpuvm_exec__bindgen_ty_1 {
+                            fn_: Some(exec_lock_gem_object),
+                            priv_: obj.gem_obj() as *const _ as *mut _,
+                        },
+                        _ => Default::default(),
+                    },
+                    num_fences: 0,
+                }),
+                GFP_KERNEL,
+            )?,
+            obj,
+        });
+
+        // SAFETY: The object is valid and was initialized above
+        to_result(unsafe { bindings::drm_gpuvm_exec_lock(&mut *guard.vm_exec) })?;
+
+        Ok(ManuallyDrop::into_inner(guard))
+    }
+
+    /// Returns true if the given object is external to the GPUVM
+    /// (that is, if it does not share the DMA reservation object of the GPUVM).
+    pub fn is_extobj(&self, obj: &impl IntoGEMObject) -> bool {
+        let gem = obj.gem_obj() as *const _ as *mut _;
+        // SAFETY: This is safe to call as long as the arguments are valid pointers.
+        unsafe { bindings::drm_gpuvm_is_extobj(self.gpuvm() as *mut _, gem) }
+    }
+}
+
+// SAFETY: DRM GpuVm objects are always reference counted and the get/put functions
+// satisfy the requirements.
+unsafe impl<T: DriverGpuVm> AlwaysRefCounted for GpuVm<T> {
+    fn inc_ref(&self) {
+        // SAFETY: The drm_gpuvm_get function satisfies the requirements for inc_ref().
+        unsafe { bindings::drm_gpuvm_get(&self.gpuvm as *const _ as *mut _) };
+    }
+
+    unsafe fn dec_ref(obj: NonNull<Self>) {
+        // SAFETY: The drm_gpuvm_put function satisfies the requirements for dec_ref().
+        unsafe { bindings::drm_gpuvm_put(Opaque::raw_get(&(*obj.as_ptr()).gpuvm)) };
+    }
+}
+
+pub struct LockedGpuVm<'a, 'b, T: DriverGpuVm> {
+    gpuvm: &'a GpuVm<T>,
+    vm_exec: Box<bindings::drm_gpuvm_exec>,
+    obj: Option<&'b <T::Driver as drv::Driver>::Object>,
+}
+
+impl<T: DriverGpuVm> LockedGpuVm<'_, '_, T> {
+    pub fn find_bo(&mut self) -> Option<ARef<GpuVmBo<T>>> {
+        let obj = self.obj?;
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        let p = unsafe {
+            bindings::drm_gpuvm_bo_find(
+                self.gpuvm.gpuvm() as *mut _,
+                obj.gem_obj() as *const _ as *mut _,
+            )
+        };
+        if p.is_null() {
+            None
+        } else {
+            // SAFETY: All the drm_gpuvm_bo objects in this GpuVm are always allocated by us as GpuVmBo<T>.
+            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+            // SAFETY: We checked for NULL above, and the types ensure that
+            // this object was created by vm_bo_alloc_callback<T>.
+            Some(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
+        }
+    }
+
+    pub fn obtain_bo(&mut self) -> Result<ARef<GpuVmBo<T>>> {
+        let obj = self.obj.ok_or(EINVAL)?;
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        let p = unsafe {
+            bindings::drm_gpuvm_bo_obtain(
+                self.gpuvm.gpuvm() as *mut _,
+                obj.gem_obj() as *const _ as *mut _,
+            )
+        };
+        if p.is_null() {
+            Err(ENOMEM)
+        } else {
+            // SAFETY: Container invariant is guaranteed for GpuVmBo objects for this GpuVm.
+            let p = unsafe { crate::container_of!(p, GpuVmBo<T>, bo) as *mut GpuVmBo<T> };
+            // SAFETY: We checked for NULL above, and the types ensure that
+            // this object was created by vm_bo_alloc_callback<T>.
+            Ok(unsafe { ARef::from_raw(NonNull::new_unchecked(p)) })
+        }
+    }
+
+    pub fn sm_map(
+        &mut self,
+        ctx: &mut T::StepContext,
+        req_addr: u64,
+        req_range: u64,
+        req_offset: u64,
+    ) -> Result {
+        let obj = self.obj.ok_or(EINVAL)?;
+        let mut ctx = StepContext {
+            ctx,
+            gpuvm: self.gpuvm,
+        };
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        to_result(unsafe {
+            bindings::drm_gpuvm_sm_map(
+                self.gpuvm.gpuvm() as *mut _,
+                &mut ctx as *mut _ as *mut _,
+                req_addr,
+                req_range,
+                obj.gem_obj() as *const _ as *mut _,
+                req_offset,
+            )
+        })
+    }
+
+    pub fn sm_unmap(&mut self, ctx: &mut T::StepContext, req_addr: u64, req_range: u64) -> Result {
+        let mut ctx = StepContext {
+            ctx,
+            gpuvm: self.gpuvm,
+        };
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        to_result(unsafe {
+            bindings::drm_gpuvm_sm_unmap(
+                self.gpuvm.gpuvm() as *mut _,
+                &mut ctx as *mut _ as *mut _,
+                req_addr,
+                req_range,
+            )
+        })
+    }
+
+    pub fn bo_unmap(&mut self, ctx: &mut T::StepContext, bo: &ARef<GpuVmBo<T>>) -> Result {
+        let mut ctx = StepContext {
+            ctx,
+            gpuvm: self.gpuvm,
+        };
+        // SAFETY: LockedGpuVm implies the right locks are held.
+        to_result(unsafe {
+            bindings::drm_gpuvm_bo_unmap(&bo.bo as *const _ as *mut _, &mut ctx as *mut _ as *mut _)
+        })
+    }
+}
+
+impl<T: DriverGpuVm> Deref for LockedGpuVm<'_, '_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        // SAFETY: The existence of this LockedGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &*self.gpuvm.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> DerefMut for LockedGpuVm<'_, '_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &mut *self.gpuvm.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> Drop for LockedGpuVm<'_, '_, T> {
+    fn drop(&mut self) {
+        // SAFETY: We hold the lock, so it's safe to unlock
+        unsafe {
+            bindings::drm_gpuvm_exec_unlock(&mut *self.vm_exec);
+        }
+    }
+}
+
+pub struct UpdatingGpuVm<'a, T: DriverGpuVm>(&'a GpuVm<T>);
+
+impl<T: DriverGpuVm> UpdatingGpuVm<'_, T> {}
+
+impl<T: DriverGpuVm> Deref for UpdatingGpuVm<'_, T> {
+    type Target = T;
+
+    fn deref(&self) -> &T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &*self.0.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> DerefMut for UpdatingGpuVm<'_, T> {
+    fn deref_mut(&mut self) -> &mut T {
+        // SAFETY: The existence of this UpdatingGpuVm implies the lock is held,
+        // so this is the only reference
+        unsafe { &mut *self.0.inner.get() }
+    }
+}
+
+impl<T: DriverGpuVm> core::ops::Receiver for UpdatingGpuVm<'_, T> {}
+
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Sync for GpuVm<T> {}
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Send for GpuVm<T> {}
+
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Sync for GpuVmBo<T> {}
+// SAFETY: All our trait methods take locks
+unsafe impl<T: DriverGpuVm> Send for GpuVmBo<T> {}
diff --git a/rust/kernel/drm/mod.rs b/rust/kernel/drm/mod.rs
index b1f182453ec1dc..50d1bb9139dcd3 100644
--- a/rust/kernel/drm/mod.rs
+++ b/rust/kernel/drm/mod.rs
@@ -6,6 +6,8 @@ pub mod device;
 pub mod drv;
 pub mod file;
 pub mod gem;
+#[cfg(CONFIG_DRM_GPUVM = "y")]
+pub mod gpuvm;
 pub mod ioctl;
 pub mod mm;
 pub mod sched;

From f095159a70b39db801ccb118f0fe8af8354b2348 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 14:17:34 +0900
Subject: [PATCH 0967/1027] rust: init: Add default() utility function

Initializer for types with Default::default() implementations in init
context. This, by nature, only works for types which are not pinned.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init.rs | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index 83130c22dd0edc..324b5b607eca77 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -1253,6 +1253,21 @@ pub unsafe trait PinnedDrop: __internal::HasPinData {
     fn drop(self: Pin<&mut Self>, only_call_from_drop: __internal::OnlyCallFromDrop);
 }
 
+/// Create a new default T.
+///
+/// The returned initializer will use Default::default to initialize the `slot`.
+#[inline]
+pub fn default<T: Default>() -> impl Init<T> {
+    // SAFETY: Because `T: Default`, T cannot require pinning and
+    // we can just move the data into the slot.
+    unsafe {
+        init_from_closure(|slot: *mut T| {
+            *slot = Default::default();
+            Ok(())
+        })
+    }
+}
+
 /// Marker trait for types that can be initialized by writing just zeroes.
 ///
 /// # Safety

From 9457bc790f20d34182966136303799df30ae31c4 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 17:45:22 +0900
Subject: [PATCH 0968/1027] kernel/error: Make
 clippy::undocumented_unsafe_blocks happy

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/error.rs | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/rust/kernel/error.rs b/rust/kernel/error.rs
index 8dec422b5e2feb..6c4a99cf0b3f5f 100644
--- a/rust/kernel/error.rs
+++ b/rust/kernel/error.rs
@@ -276,8 +276,8 @@ impl fmt::Debug for Error {
         match self.name() {
             // Print out number if no name can be found.
             None => f.debug_tuple("Error").field(&-self.0).finish(),
-            // SAFETY: These strings are ASCII-only.
             Some(name) => f
+                // SAFETY: These strings are ASCII-only.
                 .debug_tuple(unsafe { core::str::from_utf8_unchecked(name) })
                 .finish(),
         }
@@ -382,6 +382,7 @@ pub(crate) fn from_err_ptr<T>(ptr: *mut T) -> Result<*mut T> {
     if unsafe { bindings::IS_ERR(const_ptr) } {
         // SAFETY: The FFI function does not deref the pointer.
         let err = unsafe { bindings::PTR_ERR(const_ptr) };
+        #[allow(clippy::unnecessary_cast)]
         // CAST: If `IS_ERR()` returns `true`,
         // then `PTR_ERR()` is guaranteed to return a
         // negative value greater-or-equal to `-bindings::MAX_ERRNO`,
@@ -391,7 +392,6 @@ pub(crate) fn from_err_ptr<T>(ptr: *mut T) -> Result<*mut T> {
         //
         // SAFETY: `IS_ERR()` ensures `err` is a
         // negative value greater-or-equal to `-bindings::MAX_ERRNO`.
-        #[allow(clippy::unnecessary_cast)]
         return Err(unsafe { Error::from_errno_unchecked(err as core::ffi::c_int) });
     }
     Ok(ptr)

From 0f37e37c86e3e1f50304bc84654d5fb0df4b7f23 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 17:46:16 +0900
Subject: [PATCH 0969/1027] rust: allocator: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/alloc/allocator.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/alloc/allocator.rs b/rust/kernel/alloc/allocator.rs
index e6ea601f38c6d9..cb239c45fac35d 100644
--- a/rust/kernel/alloc/allocator.rs
+++ b/rust/kernel/alloc/allocator.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! Allocator support.
 

From 9584d720e363ddafccee2c53ec7663d2a079153e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 17:48:16 +0900
Subject: [PATCH 0970/1027] rust: init: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/init.rs b/rust/kernel/init.rs
index 324b5b607eca77..bf57976db08088 100644
--- a/rust/kernel/init.rs
+++ b/rust/kernel/init.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: Apache-2.0 OR MIT
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! API to safely and fallibly initialize pinned `struct`s using in-place constructors.
 //!

From 37a0282763a4516238cc42b8e9f2f96506ee9b20 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 17:49:36 +0900
Subject: [PATCH 0971/1027] rust: io_buffer: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/io_buffer.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/io_buffer.rs b/rust/kernel/io_buffer.rs
index abfc1f2e5faa3b..f0921ae80df4ba 100644
--- a/rust/kernel/io_buffer.rs
+++ b/rust/kernel/io_buffer.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! Buffers used in IO.
 

From 35e64e21e601714f3832ddccc2715a8b8d1ea529 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 17:51:53 +0900
Subject: [PATCH 0972/1027] rust: init: macros: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/init/macros.rs | 14 ++++++++++++++
 1 file changed, 14 insertions(+)

diff --git a/rust/kernel/init/macros.rs b/rust/kernel/init/macros.rs
index 9a0c4650ef676d..2c77744a519e5e 100644
--- a/rust/kernel/init/macros.rs
+++ b/rust/kernel/init/macros.rs
@@ -513,6 +513,8 @@ macro_rules! __pinned_drop {
             }
         ),
     ) => {
+        #[allow(clippy::undocumented_unsafe_blocks)]
+        // FIXME
         unsafe $($impl_sig)* {
             // Inherit all attributes and the type/ident tokens for the signature.
             $(#[$($attr)*])*
@@ -867,11 +869,15 @@ macro_rules! __pin_data {
             {
                 type PinData = __ThePinData<$($ty_generics)*>;
 
+                #[allow(clippy::undocumented_unsafe_blocks)]
+                // FIXME
                 unsafe fn __pin_data() -> Self::PinData {
                     __ThePinData { __phantom: ::core::marker::PhantomData }
                 }
             }
 
+            #[allow(clippy::undocumented_unsafe_blocks)]
+            // FIXME
             unsafe impl<$($impl_generics)*>
                 $crate::init::__internal::PinData for __ThePinData<$($ty_generics)*>
             where $($whr)*
@@ -997,6 +1003,8 @@ macro_rules! __pin_data {
                     slot: *mut $p_type,
                     init: impl $crate::init::PinInit<$p_type, E>,
                 ) -> ::core::result::Result<(), E> {
+                    #[allow(clippy::undocumented_unsafe_blocks)]
+                    // FIXME
                     unsafe { $crate::init::PinInit::__pinned_init(init, slot) }
                 }
             )*
@@ -1007,6 +1015,8 @@ macro_rules! __pin_data {
                     slot: *mut $type,
                     init: impl $crate::init::Init<$type, E>,
                 ) -> ::core::result::Result<(), E> {
+                    #[allow(clippy::undocumented_unsafe_blocks)]
+                    // FIXME
                     unsafe { $crate::init::Init::__init(init, slot) }
                 }
             )*
@@ -1121,6 +1131,8 @@ macro_rules! __init_internal {
         // no possibility of returning without `unsafe`.
         struct __InitOk;
         // Get the data about fields from the supplied type.
+        #[allow(clippy::undocumented_unsafe_blocks)]
+        // FIXME
         let data = unsafe {
             use $crate::init::__internal::$has_data;
             // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal
@@ -1176,6 +1188,7 @@ macro_rules! __init_internal {
         let init = move |slot| -> ::core::result::Result<(), $err> {
             init(slot).map(|__InitOk| ())
         };
+        #[allow(clippy::undocumented_unsafe_blocks)]
         let init = unsafe { $crate::init::$construct_closure::<_, $err>(init) };
         init
     }};
@@ -1324,6 +1337,7 @@ macro_rules! __init_internal {
         // Endpoint, nothing more to munch, create the initializer.
         // Since we are in the closure that is never called, this will never get executed.
         // We abuse `slot` to get the correct type inference here:
+        #[allow(clippy::undocumented_unsafe_blocks)]
         unsafe {
             // Here we abuse `paste!` to retokenize `$t`. Declarative macros have some internal
             // information that is associated to already parsed fragments, so a path fragment

From 87558335f550bd1b1a6429406d0a4d6aed7b7e47 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:00:29 +0900
Subject: [PATCH 0973/1027] rust: module_param: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/io_mem.rs       | 3 ++-
 rust/kernel/module_param.rs | 2 ++
 rust/kernel/of.rs           | 1 +
 3 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/io_mem.rs b/rust/kernel/io_mem.rs
index 5bb8800b04f59b..e0337314b6d313 100644
--- a/rust/kernel/io_mem.rs
+++ b/rust/kernel/io_mem.rs
@@ -163,10 +163,11 @@ impl<const SIZE: usize> IoMem<SIZE> {
         }
 
         // Try to map the resource.
-        // SAFETY: Just mapping the memory range.
         let addr = if res.flags & (bindings::IORESOURCE_MEM_NONPOSTED as core::ffi::c_ulong) != 0 {
+            // SAFETY: Just mapping the memory range.
             unsafe { bindings::ioremap_np(res.offset, res.size as _) }
         } else {
+            // SAFETY: Just mapping the memory range.
             unsafe { bindings::ioremap(res.offset, res.size as _) }
         };
 
diff --git a/rust/kernel/module_param.rs b/rust/kernel/module_param.rs
index 96ab23af44b11e..b1abdb62eed475 100644
--- a/rust/kernel/module_param.rs
+++ b/rust/kernel/module_param.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! Types for module parameters.
 //!
diff --git a/rust/kernel/of.rs b/rust/kernel/of.rs
index a48f0207605c5a..527f8bb3209633 100644
--- a/rust/kernel/of.rs
+++ b/rust/kernel/of.rs
@@ -172,6 +172,7 @@ impl Node {
             false
         }
         #[cfg(CONFIG_OF)]
+        // SAFETY: `raw_node` is valid per the type invariant
         unsafe {
             bindings::of_node_is_root(self.raw_node)
         }

From 22ed6f3cfde6f31f242c51213c0eaa074bab9ca9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:00:44 +0900
Subject: [PATCH 0974/1027] rust: print: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/print.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/print.rs b/rust/kernel/print.rs
index a78aa3514a0af0..7812fa898ab131 100644
--- a/rust/kernel/print.rs
+++ b/rust/kernel/print.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! Printing facilities.
 //!

From 8ae18b2a0d5cdce7cc40ab6f7c2e505f7f7a785a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:05:19 +0900
Subject: [PATCH 0975/1027] kernel: str: Add/fix SAFETY comments

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/str.rs | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/rust/kernel/str.rs b/rust/kernel/str.rs
index 59272dc112b3e9..ecc625b87b8a76 100644
--- a/rust/kernel/str.rs
+++ b/rust/kernel/str.rs
@@ -69,10 +69,10 @@ impl CStr {
     /// Returns the length of this string with `NUL`.
     #[inline]
     pub const fn len_with_nul(&self) -> usize {
-        // SAFETY: This is one of the invariant of `CStr`.
-        // We add a `unreachable_unchecked` here to hint the optimizer that
-        // the value returned from this function is non-zero.
         if self.0.is_empty() {
+            // SAFETY: This is one of the invariant of `CStr`.
+            // We add a `unreachable_unchecked` here to hint the optimizer that
+            // the value returned from this function is non-zero.
             unsafe { core::hint::unreachable_unchecked() };
         }
         self.0.len()
@@ -208,6 +208,7 @@ impl CStr {
     /// ```
     #[inline]
     pub unsafe fn as_str_unchecked(&self) -> &str {
+        // SAFETY: Depends on the above safety contract
         unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
     }
 

From 9030462057645785d5793103abb141468bc72d4e Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:06:49 +0900
Subject: [PATCH 0976/1027] rust: sync: condvar: Make
 clippy::undocumented_unsafe_blocks happy

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync/condvar.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/sync/condvar.rs b/rust/kernel/sync/condvar.rs
index a90275a4a32933..620a44d21ce2e3 100644
--- a/rust/kernel/sync/condvar.rs
+++ b/rust/kernel/sync/condvar.rs
@@ -92,8 +92,8 @@ pub struct CondVar {
     _pin: PhantomPinned,
 }
 
-// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on any thread.
 #[allow(clippy::non_send_fields_in_send_ty)]
+// SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on any thread.
 unsafe impl Send for CondVar {}
 
 // SAFETY: `CondVar` only uses a `struct wait_queue_head`, which is safe to use on multiple threads

From 620d8a8937625efede0f940840ee4a0af809c6a8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:07:49 +0900
Subject: [PATCH 0977/1027] rust: sync: lock: Make
 clippy::undocumented_unsafe_blocks happy

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/sync/lock.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/sync/lock.rs b/rust/kernel/sync/lock.rs
index 8f227dfe79458c..8768d406a623dc 100644
--- a/rust/kernel/sync/lock.rs
+++ b/rust/kernel/sync/lock.rs
@@ -211,8 +211,8 @@ impl<T: ?Sized, B: Backend> Guard<'_, T, B> {
         // SAFETY: The caller owns the lock, so it is safe to unlock it.
         unsafe { B::unlock(self.lock.state.get(), &self.state) };
 
-        // SAFETY: The lock was just unlocked above and is being relocked now.
         let _relock =
+            // SAFETY: The lock was just unlocked above and is being relocked now.
             ScopeGuard::new(|| unsafe { B::relock(self.lock.state.get(), &mut self.state) });
 
         cb()

From 4c26b6ac7134320a99072a6b4209d9a3a78e868a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:09:13 +0900
Subject: [PATCH 0978/1027] rust: user_ptr: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/user_ptr.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/user_ptr.rs b/rust/kernel/user_ptr.rs
index 084535675c4a31..b5fcf07cda7680 100644
--- a/rust/kernel/user_ptr.rs
+++ b/rust/kernel/user_ptr.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! User pointers.
 //!

From 539b756d83ea82e31bb94f968d21785b09545fb1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:10:01 +0900
Subject: [PATCH 0979/1027] rust: kernel: workqueue: Disable
 clippy::undocumented_unsafe_blocks lint

The missing SAFETY comments should be fixed later...

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/workqueue.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/rust/kernel/workqueue.rs b/rust/kernel/workqueue.rs
index 7ae66a9f8a8abc..1c9414fee17100 100644
--- a/rust/kernel/workqueue.rs
+++ b/rust/kernel/workqueue.rs
@@ -1,4 +1,6 @@
 // SPDX-License-Identifier: GPL-2.0
+// FIXME
+#![allow(clippy::undocumented_unsafe_blocks)]
 
 //! Work queues.
 //!

From 3eb8514be7ea6645b7f4efc697cc863b5ad723b1 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 18:13:10 +0900
Subject: [PATCH 0980/1027] rust: Enable clippy::undocumented_unsafe_blocks
 lint

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/lib.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/rust/kernel/lib.rs b/rust/kernel/lib.rs
index 4faeee9bad1efd..d5e4130ddd7311 100644
--- a/rust/kernel/lib.rs
+++ b/rust/kernel/lib.rs
@@ -22,6 +22,7 @@
 #![feature(receiver_trait)]
 #![feature(type_alias_impl_trait)]
 #![feature(unsize)]
+#![warn(clippy::undocumented_unsafe_blocks)]
 
 // Ensure conditional compilation based on the kernel configuration works;
 // otherwise we may silently break things like initcall handling.

From 9b218a51eafa8facf01ee417b0b5c2072504e206 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 16:34:43 +0900
Subject: [PATCH 0981/1027] drm/asahi: Don't lock up when unmapping PTEs fails

If a bug causes PTEs to be unmapped twice, the unmap loop gets stuck
spamming WARNs forever. Just skip a page and try again so we can make
forward progress.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/mmu.rs | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 22407748f27f4b..89cdee1b8c97a7 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -255,7 +255,16 @@ impl VmInner {
         let mut left = pgcount;
         while left > 0 {
             let mapped_iova = self.map_iova(iova, pgsize * left)?;
-            let unmapped = self.page_table.unmap_pages(mapped_iova, pgsize, left);
+            let mut unmapped = self.page_table.unmap_pages(mapped_iova, pgsize, left);
+            if unmapped == 0 {
+                dev_err!(
+                    self.dev,
+                    "unmap_pages {:#x}:{:#x} returned 0\n",
+                    mapped_iova,
+                    left
+                );
+                unmapped = pgsize; // Pretend we unmapped one page and try again...
+            }
             assert!(unmapped <= left * pgsize);
 
             left -= unmapped / pgsize;

From c38b3939a23f1a535ed2963b714eacaeaf7a1124 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 8 May 2024 20:12:37 +0900
Subject: [PATCH 0982/1027] drm/asahi: Convert to GPUVM and implement more
 VM_BIND ops

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs       |  52 +--
 drivers/gpu/drm/asahi/driver.rs      |   7 +-
 drivers/gpu/drm/asahi/file.rs        |  88 +++--
 drivers/gpu/drm/asahi/fw/initdata.rs |   4 +-
 drivers/gpu/drm/asahi/gem.rs         | 118 +-----
 drivers/gpu/drm/asahi/gpu.rs         |  59 +--
 drivers/gpu/drm/asahi/initdata.rs    |   2 +
 drivers/gpu/drm/asahi/mmu.rs         | 512 ++++++++++++++++++++++-----
 rust/kernel/sync/lockdep.rs          |   2 +
 9 files changed, 572 insertions(+), 272 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index 304d61eddc450a..329ae65057e63c 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -444,7 +444,7 @@ pub(crate) struct SimpleAllocation {
     ptr: Option<NonNull<u8>>,
     gpu_ptr: u64,
     size: usize,
-    vm: mmu::Vm,
+    _mapping: mmu::KernelMapping,
     obj: crate::gem::ObjectRef,
 }
 
@@ -464,7 +464,6 @@ impl Drop for SimpleAllocation {
                 vmap.as_mut_slice().fill(0x42);
             }
         }
-        self.obj.drop_vm_mappings(self.vm.id());
     }
 }
 
@@ -566,7 +565,7 @@ impl Allocator for SimpleAllocator {
         if debug_enabled(DebugFlags::FillAllocations) {
             obj.vmap()?.as_mut_slice().fill(0xde);
         }
-        let iova = obj.map_into_range(
+        let mapping = obj.map_into_range(
             &self.vm,
             self.start,
             self.end,
@@ -575,6 +574,8 @@ impl Allocator for SimpleAllocator {
             true,
         )?;
 
+        let iova = mapping.iova();
+
         let ptr = unsafe { p.add(offset) };
         let gpu_ptr = (iova + offset) as u64;
 
@@ -592,7 +593,7 @@ impl Allocator for SimpleAllocator {
             ptr: NonNull::new(ptr),
             gpu_ptr,
             size,
-            vm: self.vm.clone(),
+            _mapping: mapping,
             obj,
         })
     }
@@ -696,11 +697,10 @@ impl RawAllocation for HeapAllocation {
 struct HeapAllocatorInner {
     dev: AsahiDevRef,
     allocated: usize,
-    backing_objects: Vec<(crate::gem::ObjectRef, u64)>,
+    backing_objects: Vec<(crate::gem::ObjectRef, mmu::KernelMapping, u64)>,
     garbage: Option<Vec<mm::Node<HeapAllocatorInner, HeapAllocationInner>>>,
     total_garbage: usize,
     name: CString,
-    vm_id: u64,
 }
 
 /// A heap allocator which uses the DRM MM range allocator to manage its objects.
@@ -754,7 +754,6 @@ impl HeapAllocator {
             backing_objects: Vec::new(),
             // TODO: This clearly needs a try_clone() or similar
             name: CString::try_from_fmt(fmt!("{}", &*name))?,
-            vm_id: vm.id(),
             garbage: if keep_garbage { Some(Vec::new()) } else { None },
             total_garbage: 0,
         };
@@ -817,16 +816,18 @@ impl HeapAllocator {
         }
 
         let gpu_ptr = self.top;
-        if let Err(e) = obj.map_at(&self.vm, gpu_ptr, self.prot, self.cpu_maps) {
-            dev_err!(
-                &self.dev,
-                "HeapAllocator[{}]::add_block: Failed to map at {:#x} ({:?})\n",
-                &*self.name,
-                gpu_ptr,
-                e
-            );
-            return Err(e);
-        }
+        let mapping = obj
+            .map_at(&self.vm, gpu_ptr, self.prot, self.cpu_maps)
+            .map_err(|err| {
+                dev_err!(
+                    &self.dev,
+                    "HeapAllocator[{}]::add_block: Failed to map at {:#x} ({:?})\n",
+                    &*self.name,
+                    gpu_ptr,
+                    err
+                );
+                err
+            })?;
 
         self.mm
             .with_inner(|inner| inner.backing_objects.reserve(1, GFP_KERNEL))?;
@@ -874,8 +875,11 @@ impl HeapAllocator {
             new_top
         );
 
-        self.mm
-            .with_inner(|inner| inner.backing_objects.try_push((obj, gpu_ptr)))?;
+        self.mm.with_inner(|inner| {
+            inner
+                .backing_objects
+                .push((obj, mapping, gpu_ptr), GFP_KERNEL)
+        })?;
 
         self.top = new_top;
 
@@ -896,8 +900,8 @@ impl HeapAllocator {
             inner
                 .backing_objects
                 .binary_search_by(|obj| {
-                    let start = obj.1;
-                    let end = obj.1 + obj.0.size() as u64;
+                    let start = obj.2;
+                    let end = obj.2 + obj.0.size() as u64;
                     if start > addr {
                         Ordering::Greater
                     } else if end <= addr {
@@ -1013,7 +1017,7 @@ impl Allocator for HeapAllocator {
                 let idx = idx.unwrap_or(inner.backing_objects.len() - 1);
                 let obj = &mut inner.backing_objects[idx];
                 let p = obj.0.vmap()?.as_mut_ptr() as *mut u8;
-                Ok((obj.1, obj.0.size(), p))
+                Ok((obj.2, obj.0.size(), p))
             })?;
             assert!(obj_start <= start);
             assert!(obj_start + obj_size as u64 >= end);
@@ -1091,10 +1095,6 @@ impl Drop for HeapAllocatorInner {
                 &*self.name,
                 self.allocated
             );
-        } else {
-            for mut obj in self.backing_objects.drain(..) {
-                obj.0.drop_vm_mappings(self.vm_id);
-            }
         }
     }
 }
diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs
index d52ea625e9caae..f99494265234a7 100644
--- a/drivers/gpu/drm/asahi/driver.rs
+++ b/drivers/gpu/drm/asahi/driver.rs
@@ -52,8 +52,11 @@ impl drv::Driver for AsahiDriver {
     type Object = gem::Object;
 
     const INFO: drv::DriverInfo = INFO;
-    const FEATURES: u32 =
-        drv::FEAT_GEM | drv::FEAT_RENDER | drv::FEAT_SYNCOBJ | drv::FEAT_SYNCOBJ_TIMELINE;
+    const FEATURES: u32 = drv::FEAT_GEM
+        | drv::FEAT_RENDER
+        | drv::FEAT_SYNCOBJ
+        | drv::FEAT_SYNCOBJ_TIMELINE
+        | drv::FEAT_GEM_GPUVA;
 
     kernel::declare_drm_ioctls! {
         (ASAHI_GET_PARAMS,      drm_asahi_get_params,
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 341dd678dc35a6..6415f0de9be4ee 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -30,13 +30,19 @@ struct Vm {
     ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
     ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
     vm: mmu::Vm,
-    dummy_obj: gem::ObjectRef,
+    _dummy_mapping: mmu::KernelMapping,
 }
 
 impl Drop for Vm {
     fn drop(&mut self) {
-        // Mappings create a reference loop, make sure to break it.
-        self.dummy_obj.drop_vm_mappings(self.vm.id());
+        // When the user Vm is dropped, unmap everything in the user range
+        if self
+            .vm
+            .unmap_range(mmu::IOVA_USER_BASE as u64, VM_USER_END)
+            .is_err()
+        {
+            pr_err!("Vm::Drop: vm.unmap_range() failed\n");
+        }
     }
 }
 
@@ -154,16 +160,16 @@ const VM_SHADER_END: u64 = 0x11_ffffffff;
 /// Start address of the general user mapping region.
 const VM_USER_START: u64 = 0x20_00000000;
 /// End address of the general user mapping region.
-const VM_USER_END: u64 = 0x5f_ffffffff;
+const VM_USER_END: u64 = 0x6f_ffff0000;
 
 /// Start address of the kernel-managed GPU-only mapping region.
-const VM_DRV_GPU_START: u64 = 0x60_00000000;
+const VM_DRV_GPU_START: u64 = 0x70_00000000;
 /// End address of the kernel-managed GPU-only mapping region.
-const VM_DRV_GPU_END: u64 = 0x60_ffffffff;
+const VM_DRV_GPU_END: u64 = 0x70_ffffffff;
 /// Start address of the kernel-managed GPU/FW shared mapping region.
-const VM_DRV_GPUFW_START: u64 = 0x61_00000000;
+const VM_DRV_GPUFW_START: u64 = 0x71_00000000;
 /// End address of the kernel-managed GPU/FW shared mapping region.
-const VM_DRV_GPUFW_END: u64 = 0x61_ffffffff;
+const VM_DRV_GPUFW_END: u64 = 0x71_ffffffff;
 /// Address of a special dummy page?
 const VM_UNK_PAGE: u64 = 0x6f_ffff8000;
 
@@ -298,7 +304,7 @@ impl File {
 
         let gpu = &device.data().gpu;
         let file_id = file.inner().id;
-        let vm = gpu.new_vm(file_id)?;
+        let vm = gpu.new_vm()?;
 
         let resv = file.inner().vms().reserve()?;
         let id: u32 = resv.index().try_into()?;
@@ -343,15 +349,15 @@ impl File {
         );
         let mut dummy_obj = gem::new_kernel_object(device, 0x4000)?;
         dummy_obj.vmap()?.as_mut_slice().fill(0);
-        dummy_obj.map_at(&vm, VM_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
+        let dummy_mapping = dummy_obj.map_at(&vm, VM_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
 
         mod_dev_dbg!(device, "[File {} VM {}]: VM created\n", file_id, id);
         resv.store(Box::new(Vm {
             ualloc,
             ualloc_priv,
             vm,
-            dummy_obj,
-        })?)?;
+            _dummy_mapping: dummy_mapping,
+        }, GFP_KERNEL,)?)?;
 
         data.vm_id = id;
 
@@ -492,15 +498,10 @@ impl File {
         data: &mut uapi::drm_asahi_gem_bind,
         file: &DrmFile,
     ) -> Result<u32> {
-        if data.offset != 0 {
-            pr_err!("gem_bind: Offset not supported yet\n");
-            return Err(EINVAL); // Not supported yet
-        }
-
-        if (data.addr | data.range) as usize & mmu::UAT_PGMSK != 0 {
+        if (data.addr | data.range | data.offset) as usize & mmu::UAT_PGMSK != 0 {
             cls_pr_debug!(
                 Errors,
-                "gem_bind: Addr/range not page aligned: {:#x} {:#x}\n",
+                "gem_bind: Addr/range/offset not page aligned: {:#x} {:#x}\n",
                 data.addr,
                 data.range
             );
@@ -512,12 +513,7 @@ impl File {
             return Err(EINVAL);
         }
 
-        let mut bo = gem::lookup_handle(file, data.handle)?;
-
-        if data.range != bo.size().try_into()? {
-            pr_err!("gem_bind: Partial maps not supported yet\n");
-            return Err(EINVAL); // Not supported yet
-        }
+        let bo = gem::lookup_handle(file, data.handle)?;
 
         let start = data.addr;
         let end = data.addr + data.range - 1;
@@ -590,11 +586,36 @@ impl File {
             .vm
             .clone();
 
-        bo.map_at(&vm, start, prot, true)?;
+        vm.bind_object(&bo.gem, data.addr, data.range, data.offset, prot)?;
 
         Ok(0)
     }
 
+    pub(crate) fn unbind_gem_object(file: &DrmFile, bo: &gem::Object) -> Result {
+        let mut index = 0;
+        loop {
+            let item = file
+                .inner()
+                .vms()
+                .find(index, xarray::XArray::<Box<Vm>>::MAX);
+            match item {
+                Some((idx, file_vm)) => {
+                    // Clone since we can't hold the xarray spinlock while
+                    // calling drop_mappings()
+                    let vm = file_vm.borrow().vm.clone();
+                    core::mem::drop(file_vm);
+                    vm.drop_mappings(bo)?;
+                    if idx == xarray::XArray::<Box<Vm>>::MAX {
+                        break;
+                    }
+                    index = idx + 1;
+                }
+                None => break,
+            }
+        }
+        Ok(())
+    }
+
     pub(crate) fn do_gem_unbind_all(
         _device: &AsahiDevice,
         data: &mut uapi::drm_asahi_gem_bind,
@@ -605,20 +626,18 @@ impl File {
             return Err(EINVAL);
         }
 
-        let mut bo = gem::lookup_handle(file, data.handle)?;
+        let bo = gem::lookup_handle(file, data.handle)?;
 
         if data.vm_id == 0 {
-            bo.drop_file_mappings(file.inner().id);
+            Self::unbind_gem_object(file, &bo.gem)?;
         } else {
-            let vm_id = file
-                .inner()
+            file.inner()
                 .vms()
                 .get(data.vm_id.try_into()?)
                 .ok_or(ENOENT)?
                 .borrow()
                 .vm
-                .id();
-            bo.drop_vm_mappings(vm_id);
+                .drop_mappings(&bo.gem)?;
         }
 
         Ok(0)
@@ -828,11 +847,6 @@ impl File {
             Ok(_) => Ok(0),
         }
     }
-
-    /// Returns the unique file ID for this `File`.
-    pub(crate) fn file_id(&self) -> u64 {
-        self.id
-    }
 }
 
 impl Drop for File {
diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index f6d766c5f0480e..4c53846e81607a 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -4,7 +4,7 @@
 
 use super::channels;
 use super::types::*;
-use crate::{default_zeroed, gem, no_debug, trivial_gpustruct};
+use crate::{default_zeroed, gem, mmu, no_debug, trivial_gpustruct};
 
 pub(crate) mod raw {
     use super::*;
@@ -1326,6 +1326,8 @@ pub(crate) struct RuntimePointers {
     pub(crate) unkptr_1c8: GpuArray<u8>,
 
     pub(crate) buffer_mgr_ctl: gem::ObjectRef,
+    pub(crate) buffer_mgr_ctl_low_mapping: Option<mmu::KernelMapping>,
+    pub(crate) buffer_mgr_ctl_high_mapping: Option<mmu::KernelMapping>,
 }
 
 #[versions(AGX)]
diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs
index b7b6640940e04b..a53863713bf188 100644
--- a/drivers/gpu/drm/asahi/gem.rs
+++ b/drivers/gpu/drm/asahi/gem.rs
@@ -11,8 +11,6 @@ use kernel::{
     drm::{gem, gem::shmem},
     error::Result,
     prelude::*,
-    soc::apple::rtkit,
-    sync::Mutex,
     uapi,
 };
 
@@ -20,7 +18,7 @@ use kernel::drm::gem::BaseObject;
 
 use core::sync::atomic::{AtomicU64, Ordering};
 
-use crate::{debug::*, driver::AsahiDevice, file::DrmFile, mmu, util::*};
+use crate::{debug::*, driver::AsahiDevice, file, file::DrmFile, mmu, util::*};
 
 const DEBUG_CLASS: DebugFlags = DebugFlags::Gem;
 
@@ -33,9 +31,6 @@ pub(crate) struct DriverObject {
     flags: u32,
     /// VM ID for VM-private objects.
     vm_id: Option<u64>,
-    /// Locked list of mapping tuples: (file_id, vm_id, mapping)
-    #[pin]
-    mappings: Mutex<Vec<(u64, u64, crate::mmu::Mapping)>>,
     /// ID for debug
     id: u64,
 }
@@ -58,34 +53,6 @@ crate::no_debug!(ObjectRef);
 
 static GEM_ID: AtomicU64 = AtomicU64::new(0);
 
-impl DriverObject {
-    /// Drop all object mappings for a given file ID.
-    ///
-    /// Used on file close.
-    fn drop_file_mappings(&self, file_id: u64) {
-        let mut mappings = self.mappings.lock();
-        for (index, (mapped_fid, _mapped_vmid, _mapping)) in mappings.iter().enumerate() {
-            if *mapped_fid == file_id {
-                mappings.swap_remove(index);
-                return;
-            }
-        }
-    }
-
-    /// Drop all object mappings for a given VM ID.
-    ///
-    /// Used on VM destroy.
-    fn drop_vm_mappings(&self, vm_id: u64) {
-        let mut mappings = self.mappings.lock();
-        for (index, (_mapped_fid, mapped_vmid, _mapping)) in mappings.iter().enumerate() {
-            if *mapped_vmid == vm_id {
-                mappings.swap_remove(index);
-                return;
-            }
-        }
-    }
-}
-
 impl ObjectRef {
     /// Create a new wrapper for a raw GEM object reference.
     pub(crate) fn new(gem: gem::ObjectRef<shmem::Object<DriverObject>>) -> ObjectRef {
@@ -100,27 +67,12 @@ impl ObjectRef {
         Ok(self.vmap.as_mut().unwrap())
     }
 
-    /// Return the IOVA of this object at which it is mapped in a given `Vm` identified by its ID,
-    /// if it is mapped in that `Vm`.
-    pub(crate) fn iova(&self, vm_id: u64) -> Option<usize> {
-        let mappings = self.gem.mappings.lock();
-        for (_mapped_fid, mapped_vmid, mapping) in mappings.iter() {
-            if *mapped_vmid == vm_id {
-                return Some(mapping.iova());
-            }
-        }
-
-        None
-    }
-
     /// Returns the size of an object in bytes
     pub(crate) fn size(&self) -> usize {
         self.gem.size()
     }
 
     /// Maps an object into a given `Vm` at any free address within a given range.
-    ///
-    /// Returns Err(EBUSY) if there is already a mapping.
     pub(crate) fn map_into_range(
         &mut self,
         vm: &crate::mmu::Vm,
@@ -129,32 +81,26 @@ impl ObjectRef {
         alignment: u64,
         prot: u32,
         guard: bool,
-    ) -> Result<usize> {
+    ) -> Result<crate::mmu::KernelMapping> {
         let vm_id = vm.id();
 
         if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
             return Err(EINVAL);
         }
 
-        let mut mappings = self.gem.mappings.lock();
-        for (_mapped_fid, mapped_vmid, _mapping) in mappings.iter() {
-            if *mapped_vmid == vm_id {
-                return Err(EBUSY);
-            }
-        }
-
-        let sgt = self.gem.sg_table()?;
-        let new_mapping =
-            vm.map_in_range(self.gem.size(), sgt, alignment, start, end, prot, guard)?;
-
-        let iova = new_mapping.iova();
-        mappings.try_push((vm.file_id(), vm_id, new_mapping))?;
-        Ok(iova)
+        vm.map_in_range(
+            self.gem.size(),
+            &self.gem,
+            alignment,
+            start,
+            end,
+            prot,
+            guard,
+        )
     }
 
     /// Maps an object into a given `Vm` at a specific address.
     ///
-    /// Returns Err(EBUSY) if there is already a mapping.
     /// Returns Err(ENOSPC) if the requested address is already busy.
     pub(crate) fn map_at(
         &mut self,
@@ -162,37 +108,14 @@ impl ObjectRef {
         addr: u64,
         prot: u32,
         guard: bool,
-    ) -> Result {
+    ) -> Result<crate::mmu::KernelMapping> {
         let vm_id = vm.id();
 
         if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
             return Err(EINVAL);
         }
 
-        let mut mappings = self.gem.mappings.lock();
-        for (_mapped_fid, mapped_vmid, _mapping) in mappings.iter() {
-            if *mapped_vmid == vm_id {
-                return Err(EBUSY);
-            }
-        }
-
-        let sgt = self.gem.sg_table()?;
-        let new_mapping = vm.map_at(addr, self.gem.size(), sgt, prot, guard)?;
-
-        let iova = new_mapping.iova();
-        assert!(iova == addr as usize);
-        mappings.try_push((vm.file_id(), vm_id, new_mapping))?;
-        Ok(())
-    }
-
-    /// Drop all mappings for this object owned by a given `Vm` identified by its ID.
-    pub(crate) fn drop_vm_mappings(&mut self, vm_id: u64) {
-        self.gem.drop_vm_mappings(vm_id);
-    }
-
-    /// Drop all mappings for this object owned by a given `File` identified by its ID.
-    pub(crate) fn drop_file_mappings(&mut self, file_id: u64) {
-        self.gem.drop_file_mappings(file_id);
+        vm.map_at(addr, self.gem.size(), &self.gem, prot, guard)
     }
 }
 
@@ -247,7 +170,6 @@ impl gem::BaseDriverObject<Object> for DriverObject {
             kernel: false,
             flags: 0,
             vm_id: None,
-            mappings <- Mutex::new(Vec::new()),
             id,
         })
     }
@@ -255,20 +177,12 @@ impl gem::BaseDriverObject<Object> for DriverObject {
     /// Callback to drop all mappings for a GEM object owned by a given `File`
     fn close(obj: &Object, file: &DrmFile) {
         mod_pr_debug!("DriverObject::close vm_id={:?} id={}\n", obj.vm_id, obj.id);
-        obj.drop_file_mappings(file.inner().file_id());
+        if file::File::unbind_gem_object(file, obj).is_err() {
+            pr_err!("DriverObject::close: Failed to unbind GEM object\n");
+        }
     }
 }
 
 impl shmem::DriverObject for DriverObject {
     type Driver = crate::driver::AsahiDriver;
 }
-
-impl rtkit::Buffer for ObjectRef {
-    fn iova(&self) -> Result<usize> {
-        self.iova(0).ok_or(EIO)
-    }
-    fn buf(&mut self) -> Result<&mut [u8]> {
-        let vmap = self.vmap.as_mut().ok_or(ENOMEM)?;
-        Ok(vmap.as_mut_slice())
-    }
-}
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index e9200be2477249..6d65d4c0833691 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -206,7 +206,7 @@ pub(crate) struct GpuManager {
     crashed: AtomicBool,
     #[pin]
     alloc: Mutex<KernelAllocators>,
-    io_mappings: Vec<mmu::Mapping>,
+    io_mappings: Vec<mmu::KernelMapping>,
     next_mmio_iova: u64,
     #[pin]
     rtkit: Mutex<Option<rtkit::RtKit<GpuManager::ver>>>,
@@ -242,7 +242,7 @@ pub(crate) trait GpuManager: Send + Sync {
     /// Get a reference to the KernelAllocators.
     fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend>;
     /// Create a new `Vm` given a unique `File` ID.
-    fn new_vm(&self, file_id: u64) -> Result<mmu::Vm>;
+    fn new_vm(&self) -> Result<mmu::Vm>;
     /// Bind a `Vm` to an available slot and return the `VmBind`.
     fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind>;
     /// Create a new user command queue.
@@ -293,11 +293,26 @@ trait GpuManagerPriv {
     fn end_op(&self);
 }
 
+pub(crate) struct RtkitObject {
+    obj: gem::ObjectRef,
+    mapping: mmu::KernelMapping,
+}
+
+impl rtkit::Buffer for RtkitObject {
+    fn iova(&self) -> Result<usize> {
+        Ok(self.mapping.iova())
+    }
+    fn buf(&mut self) -> Result<&mut [u8]> {
+        let vmap = self.obj.vmap()?;
+        Ok(vmap.as_mut_slice())
+    }
+}
+
 #[versions(AGX)]
 #[vtable]
 impl rtkit::Operations for GpuManager::ver {
     type Data = Arc<GpuManager::ver>;
-    type Buffer = gem::ObjectRef;
+    type Buffer = RtkitObject;
 
     fn recv_message(data: <Self::Data as ForeignOwnable>::Borrowed<'_>, ep: u8, msg: u64) {
         let dev = &data.dev;
@@ -338,7 +353,7 @@ impl rtkit::Operations for GpuManager::ver {
 
         let mut obj = gem::new_kernel_object(dev, size)?;
         obj.vmap()?;
-        let iova = obj.map_into_range(
+        let mapping = obj.map_into_range(
             data.uat.kernel_vm(),
             IOVA_KERN_RTKIT_BASE,
             IOVA_KERN_RTKIT_TOP,
@@ -346,8 +361,8 @@ impl rtkit::Operations for GpuManager::ver {
             mmu::PROT_FW_SHARED_RW,
             true,
         )?;
-        mod_dev_dbg!(dev, "shmem_alloc() -> VA {:#x}\n", iova);
-        Ok(obj)
+        mod_dev_dbg!(dev, "shmem_alloc() -> VA {:#x}\n", mapping.iova());
+        Ok(RtkitObject { obj, mapping })
     }
 }
 
@@ -429,18 +444,20 @@ impl GpuManager::ver {
         let event_manager = Self::make_event_manager(&mut alloc)?;
         let mut initdata = Self::make_initdata(dev, cfg, &dyncfg, &mut alloc)?;
 
-        initdata.runtime_pointers.buffer_mgr_ctl.map_at(
-            uat.kernel_lower_vm(),
-            IOVA_KERN_GPU_BUFMGR_LOW,
-            mmu::PROT_GPU_SHARED_RW,
-            false,
-        )?;
-        initdata.runtime_pointers.buffer_mgr_ctl.map_at(
-            uat.kernel_vm(),
-            IOVA_KERN_GPU_BUFMGR_HIGH,
-            mmu::PROT_FW_SHARED_RW,
-            false,
-        )?;
+        initdata.runtime_pointers.buffer_mgr_ctl_low_mapping =
+            Some(initdata.runtime_pointers.buffer_mgr_ctl.map_at(
+                uat.kernel_lower_vm(),
+                IOVA_KERN_GPU_BUFMGR_LOW,
+                mmu::PROT_GPU_SHARED_RW,
+                false,
+            )?);
+        initdata.runtime_pointers.buffer_mgr_ctl_high_mapping =
+            Some(initdata.runtime_pointers.buffer_mgr_ctl.map_at(
+                uat.kernel_vm(),
+                IOVA_KERN_GPU_BUFMGR_HIGH,
+                mmu::PROT_FW_SHARED_RW,
+                false,
+            )?);
 
         let mut mgr = Self::make_mgr(dev, cfg, dyncfg, uat, alloc, event_manager, initdata)?;
 
@@ -563,7 +580,7 @@ impl GpuManager::ver {
     }
 
     /// Return a mutable reference to the io_mappings member
-    fn io_mappings_mut(self: Pin<&mut Self>) -> &mut Vec<mmu::Mapping> {
+    fn io_mappings_mut(self: Pin<&mut Self>) -> &mut Vec<mmu::KernelMapping> {
         // SAFETY: io_mappings does not require structural pinning.
         unsafe { &mut self.get_unchecked_mut().io_mappings }
     }
@@ -1199,8 +1216,8 @@ impl GpuManager for GpuManager::ver {
         guard
     }
 
-    fn new_vm(&self, file_id: u64) -> Result<mmu::Vm> {
-        self.uat.new_vm(self.ids.vm.next(), file_id)
+    fn new_vm(&self) -> Result<mmu::Vm> {
+        self.uat.new_vm(self.ids.vm.next())
     }
 
     fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind> {
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 56ba255b55fa83..2766a9cf060d95 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -785,6 +785,8 @@ impl<'a> InitDataBuilder::ver<'a> {
                     unkptr_1c8: alloc.private.array_empty_tagged(0x1000, b"I1C8")?,
 
                     buffer_mgr_ctl,
+                    buffer_mgr_ctl_low_mapping: None,
+                    buffer_mgr_ctl_high_mapping: None,
                 })
             },
             |inner, _ptr| {
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 89cdee1b8c97a7..bd128af30798a6 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -18,7 +18,7 @@ use core::time::Duration;
 
 use kernel::{
     bindings, c_str, delay, device,
-    drm::mm,
+    drm::{gpuvm, mm},
     error::{to_result, Result},
     io_pgtable,
     io_pgtable::{prot, AppleUAT, IoPageTable},
@@ -29,7 +29,7 @@ use kernel::{
         Arc, Mutex,
     },
     time::{clock, Now},
-    types::ForeignOwnable,
+    types::{ARef, ForeignOwnable},
 };
 
 use crate::debug::*;
@@ -66,9 +66,9 @@ pub(crate) const UAT_IAS: usize = 39;
 pub(crate) const UAT_IAS_KERN: usize = 36;
 
 /// Lower/user base VA
-const IOVA_USER_BASE: usize = UAT_PGSZ;
+pub(crate) const IOVA_USER_BASE: usize = UAT_PGSZ;
 /// Lower/user top VA
-const IOVA_USER_TOP: usize = (1 << UAT_IAS) - 1;
+pub(crate) const IOVA_USER_TOP: usize = (1 << UAT_IAS) - 1;
 /// Upper/kernel base VA
 // const IOVA_TTBR1_BASE: usize = 0xffffff8000000000;
 /// Driver-managed kernel base VA
@@ -81,7 +81,7 @@ const TTBR_ASID_SHIFT: usize = 48;
 
 const PTE_TABLE: u64 = 0x3; // BIT(0) | BIT(1)
 
-// Mapping protection types
+// KernelMapping protection types
 
 // Note: prot::CACHE means "cache coherency", which for UAT means *uncached*,
 // since uncached mappings from the GFX ASC side are cache coherent with the AP cache.
@@ -181,12 +181,213 @@ struct VmInner {
     min_va: usize,
     max_va: usize,
     page_table: AppleUAT<Uat>,
-    mm: mm::Allocator<(), MappingInner>,
+    mm: mm::Allocator<(), KernelMappingInner>,
     uat_inner: Arc<UatInner>,
+    binding: Arc<Mutex<VmBinding>>,
+    id: u64,
+}
+
+/// Slot binding-related inner data for a Vm instance.
+struct VmBinding {
     active_users: usize,
     binding: Option<slotalloc::Guard<SlotInner>>,
     bind_token: Option<slotalloc::SlotToken>,
-    id: u64,
+    ttb: u64,
+}
+
+/// Data associated with a VM <=> BO pairing
+#[pin_data]
+struct VmBo {
+    #[pin]
+    sgt: Mutex<Option<gem::SGTable>>,
+}
+
+impl gpuvm::DriverGpuVmBo for VmBo {
+    fn new() -> impl PinInit<Self> {
+        pin_init!(VmBo {
+            sgt <- Mutex::new_named(None, c_str!("VmBinding")),
+        })
+    }
+}
+
+#[derive(Default)]
+struct StepContext {
+    new_va: Option<Pin<Box<gpuvm::GpuVa<VmInner>>>>,
+    prev_va: Option<Pin<Box<gpuvm::GpuVa<VmInner>>>>,
+    next_va: Option<Pin<Box<gpuvm::GpuVa<VmInner>>>>,
+    vm_bo: Option<ARef<gpuvm::GpuVmBo<VmInner>>>,
+    prot: u32,
+}
+
+impl gpuvm::DriverGpuVm for VmInner {
+    type Driver = driver::AsahiDriver;
+    type GpuVmBo = VmBo;
+    type StepContext = StepContext;
+
+    fn step_map(
+        self: &mut gpuvm::UpdatingGpuVm<'_, Self>,
+        op: &mut gpuvm::OpMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result {
+        let mut iova = op.addr() as usize;
+        let mut left = op.range() as usize;
+        let mut offset = op.offset() as usize;
+
+        let bo = ctx.vm_bo.as_ref().expect("step_map with no BO");
+
+        let guard = bo.inner().sgt.lock();
+        for range in guard.as_ref().expect("step_map with no SGT").iter() {
+            let mut addr = range.dma_address();
+            let mut len = range.dma_len();
+
+            if left == 0 {
+                break;
+            }
+
+            if offset > 0 {
+                let skip = len.min(offset);
+                addr += skip;
+                len -= skip;
+                offset -= skip;
+            }
+
+            if len == 0 {
+                continue;
+            }
+
+            assert!(offset == 0);
+
+            len = len.min(left);
+
+            mod_dev_dbg!(
+                self.dev,
+                "MMU: map: {:#x}:{:#x} -> {:#x}\n",
+                addr,
+                len,
+                iova
+            );
+
+            self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, ctx.prot)?;
+
+            left -= len;
+            iova += len;
+        }
+
+        let gpuva = ctx.new_va.take().expect("Multiple step_map calls");
+
+        if op
+            .map_and_link_va(
+                self,
+                gpuva,
+                ctx.vm_bo.as_ref().expect("step_map with no BO"),
+            )
+            .is_err()
+        {
+            dev_err!(
+                self.dev,
+                "map_and_link_va failed: {:#x} [{:#x}] -> {:#x}\n",
+                op.offset(),
+                op.range(),
+                op.addr()
+            );
+            return Err(EINVAL);
+        }
+        Ok(())
+    }
+    fn step_unmap(
+        self: &mut gpuvm::UpdatingGpuVm<'_, Self>,
+        op: &mut gpuvm::OpUnMap<Self>,
+        _ctx: &mut Self::StepContext,
+    ) -> Result {
+        let va = op.va().expect("step_unmap: missing VA");
+
+        mod_dev_dbg!(self.dev, "MMU: unmap: {:#x}:{:#x}\n", va.addr(), va.range());
+
+        self.unmap_pages(
+            va.addr() as usize,
+            UAT_PGSZ,
+            (va.range() as usize) >> UAT_PGBIT,
+        )?;
+
+        if let Some(asid) = self.slot() {
+            mem::tlbi_range(asid as u8, va.addr() as usize, va.range() as usize);
+            mod_dev_dbg!(
+                self.dev,
+                "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n",
+                asid,
+                va.addr(),
+                va.range(),
+            );
+            mem::sync();
+        }
+
+        if op.unmap_and_unlink_va().is_none() {
+            dev_err!(self.dev, "step_unmap: could not unlink gpuva");
+        }
+        Ok(())
+    }
+    fn step_remap(
+        self: &mut gpuvm::UpdatingGpuVm<'_, Self>,
+        op: &mut gpuvm::OpReMap<Self>,
+        ctx: &mut Self::StepContext,
+    ) -> Result {
+        let prev_gpuva = ctx.prev_va.take().expect("Multiple step_remap calls");
+        let next_gpuva = ctx.next_va.take().expect("Multiple step_remap calls");
+        let va = op.unmap().va().expect("No previous VA");
+        let orig_addr = va.addr();
+        let orig_range = va.range();
+        let vm_bo = va.vm_bo();
+
+        // Only unmap the hole between prev/next, if they exist
+        let unmap_start = if let Some(op) = op.prev_map() {
+            op.addr() + op.range()
+        } else {
+            orig_addr
+        };
+
+        let unmap_end = if let Some(op) = op.next_map() {
+            op.addr()
+        } else {
+            orig_addr + orig_range
+        };
+
+        let unmap_range = unmap_end - unmap_start;
+
+        mod_dev_dbg!(
+            self.dev,
+            "MMU: unmap for remap: {:#x}:{:#x} (from {:#x}:{:#x})\n",
+            unmap_start,
+            unmap_range,
+            orig_addr,
+            orig_range
+        );
+
+        self.unmap_pages(
+            unmap_start as usize,
+            UAT_PGSZ,
+            (unmap_range as usize) >> UAT_PGBIT,
+        )?;
+
+        if op.unmap().unmap_and_unlink_va().is_none() {
+            dev_err!(self.dev, "step_unmap: could not unlink gpuva");
+        }
+
+        if let Some(prev_op) = op.prev_map() {
+            if prev_op.map_and_link_va(self, prev_gpuva, &vm_bo).is_err() {
+                dev_err!(self.dev, "step_remap: could not relink prev gpuva");
+                return Err(EINVAL);
+            }
+        }
+
+        if let Some(next_op) = op.next_map() {
+            if next_op.map_and_link_va(self, next_gpuva, &vm_bo).is_err() {
+                dev_err!(self.dev, "step_remap: could not relink next gpuva");
+                return Err(EINVAL);
+            }
+        }
+
+        Ok(())
+    }
 }
 
 impl VmInner {
@@ -204,7 +405,9 @@ impl VmInner {
             // interim, and then it gets killed / drops its mappings without doing any
             // final rendering). Anything doing active maps/unmaps is probably also
             // rendering and therefore likely bound.
-            self.bind_token
+            self.binding
+                .lock()
+                .bind_token
                 .as_ref()
                 .map(|token| (token.last_slot() + UAT_USER_CTX_START as u32))
         }
@@ -275,9 +478,10 @@ impl VmInner {
     }
 
     /// Map an `mm::Node` representing an mapping in VA space.
-    fn map_node(&mut self, node: &mm::Node<(), MappingInner>, prot: u32) -> Result {
+    fn map_node(&mut self, node: &mm::Node<(), KernelMappingInner>, prot: u32) -> Result {
         let mut iova = node.start() as usize;
-        let sgt = node.sgt.as_ref().ok_or(EINVAL)?;
+        let guard = node.bo.as_ref().ok_or(EINVAL)?.inner().sgt.lock();
+        let sgt = guard.as_ref().ok_or(EINVAL)?;
 
         for range in sgt.iter() {
             let addr = range.dma_address();
@@ -286,7 +490,7 @@ impl VmInner {
             if (addr | len | iova) & UAT_PGMSK != 0 {
                 dev_err!(
                     self.dev,
-                    "MMU: Mapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
+                    "MMU: KernelMapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
                     addr,
                     len,
                     iova
@@ -314,8 +518,8 @@ impl VmInner {
 #[derive(Clone)]
 pub(crate) struct Vm {
     id: u64,
-    file_id: u64,
-    inner: Arc<Mutex<VmInner>>,
+    inner: ARef<gpuvm::GpuVm<VmInner>>,
+    binding: Arc<Mutex<VmBinding>>,
 }
 no_debug!(Vm);
 
@@ -341,40 +545,49 @@ impl VmBind {
 
 impl Drop for VmBind {
     fn drop(&mut self) {
-        let mut inner = self.0.inner.lock();
+        let mut binding = self.0.binding.lock();
 
-        assert_ne!(inner.active_users, 0);
-        inner.active_users -= 1;
-        mod_pr_debug!("MMU: slot {} active users {}\n", self.1, inner.active_users);
-        if inner.active_users == 0 {
-            inner.binding = None;
+        assert_ne!(binding.active_users, 0);
+        binding.active_users -= 1;
+        mod_pr_debug!(
+            "MMU: slot {} active users {}\n",
+            self.1,
+            binding.active_users
+        );
+        if binding.active_users == 0 {
+            binding.binding = None;
         }
     }
 }
 
 impl Clone for VmBind {
     fn clone(&self) -> VmBind {
-        let mut inner = self.0.inner.lock();
+        let mut binding = self.0.binding.lock();
 
-        inner.active_users += 1;
-        mod_pr_debug!("MMU: slot {} active users {}\n", self.1, inner.active_users);
+        binding.active_users += 1;
+        mod_pr_debug!(
+            "MMU: slot {} active users {}\n",
+            self.1,
+            binding.active_users
+        );
         VmBind(self.0.clone(), self.1)
     }
 }
 
 /// Inner data required for an object mapping into a [`Vm`].
-pub(crate) struct MappingInner {
-    owner: Arc<Mutex<VmInner>>,
+pub(crate) struct KernelMappingInner {
+    owner: ARef<gpuvm::GpuVm<VmInner>>,
     uat_inner: Arc<UatInner>,
     prot: u32,
     mapped_size: usize,
-    sgt: Option<gem::SGTable>,
+    bo: Option<ARef<gpuvm::GpuVmBo<VmInner>>>,
 }
 
 /// An object mapping into a [`Vm`], which reserves the address range from use by other mappings.
-pub(crate) struct Mapping(mm::Node<(), MappingInner>);
 
-impl Mapping {
+pub(crate) struct KernelMapping(mm::Node<(), KernelMappingInner>);
+
+impl KernelMapping {
     /// Returns the IOVA base of this mapping
     pub(crate) fn iova(&self) -> usize {
         self.0.start() as usize
@@ -388,7 +601,12 @@ impl Mapping {
     /// Remap a cached mapping as uncached, then synchronously flush that range of VAs from the
     /// coprocessor cache. This is required to safely unmap cached/private mappings.
     fn remap_uncached_and_flush(&mut self) {
-        let mut owner = self.0.owner.lock();
+        let mut owner = self
+            .0
+            .owner
+            .exec_lock(None)
+            .expect("Failed to exec_lock in remap_uncached_and_flush");
+
         mod_dev_dbg!(
             owner.dev,
             "MMU: remap as uncached {:#x}:{:#x}\n",
@@ -512,8 +730,9 @@ impl Mapping {
         // Slot is unlocked here
     }
 }
+no_debug!(KernelMapping);
 
-impl Drop for Mapping {
+impl Drop for KernelMapping {
     fn drop(&mut self) {
         // This is the main unmap function for UAT mappings.
         // The sequence of operations here is finicky, due to the interaction
@@ -540,7 +759,11 @@ impl Drop for Mapping {
             self.remap_uncached_and_flush();
         }
 
-        let mut owner = self.0.owner.lock();
+        let mut owner = self
+            .0
+            .owner
+            .exec_lock(None)
+            .expect("exec_lock failed in KernelMapping::drop");
         mod_dev_dbg!(
             owner.dev,
             "MMU: unmap {:#x}:{:#x}\n",
@@ -775,8 +998,9 @@ impl Vm {
         cfg: &'static hw::HwConfig,
         is_kernel: bool,
         id: u64,
-        file_id: u64,
     ) -> Result<Vm> {
+        let dummy_obj = gem::new_kernel_object(dev, 0x4000)?;
+
         let page_table = AppleUAT::new(
             dev,
             io_pgtable::Config {
@@ -801,11 +1025,31 @@ impl Vm {
 
         let mm = mm::Allocator::new(min_va as u64, (max_va - min_va + 1) as u64, ())?;
 
+        let binding = Arc::pin_init(
+            Mutex::new_named(
+                VmBinding {
+                    binding: None,
+                    bind_token: None,
+                    active_users: 0,
+                    ttb: page_table.cfg().ttbr,
+                },
+                c_str!("VmBinding"),
+            ),
+            GFP_KERNEL,
+        )?;
+
+        let binding_clone = binding.clone();
         Ok(Vm {
             id,
-            file_id,
-            inner: Arc::pin_init(Mutex::new_named(
-                VmInner {
+            inner: gpuvm::GpuVm::new(
+                c_str!("Asahi::GpuVm"),
+                dev,
+                &*(dummy_obj.gem),
+                min_va as u64,
+                (max_va - min_va + 1) as u64,
+                0,
+                0,
+                init!(VmInner {
                     dev: dev.into(),
                     min_va,
                     max_va,
@@ -813,19 +1057,17 @@ impl Vm {
                     page_table,
                     mm,
                     uat_inner,
-                    binding: None,
-                    bind_token: None,
-                    active_users: 0,
+                    binding: binding_clone,
                     id,
-                },
-                c_str!("VmInner"),
-            ))?,
+                }),
+            )?,
+            binding,
         })
     }
 
     /// Get the translation table base for this Vm
     fn ttb(&self) -> u64 {
-        self.inner.lock().ttb()
+        self.binding.lock().ttb
     }
 
     /// Map a GEM object (using its `SGTable`) into this Vm at a free address in a given range.
@@ -833,22 +1075,30 @@ impl Vm {
     pub(crate) fn map_in_range(
         &self,
         size: usize,
-        sgt: gem::SGTable,
+        gem: &gem::Object,
         alignment: u64,
         start: u64,
         end: u64,
         prot: u32,
         guard: bool,
-    ) -> Result<Mapping> {
-        let mut inner = self.inner.lock();
+    ) -> Result<KernelMapping> {
+        let sgt = gem.sg_table()?;
+        let mut inner = self.inner.exec_lock(Some(gem))?;
+        let vm_bo = inner.obtain_bo()?;
+
+        let mut vm_bo_guard = vm_bo.inner().sgt.lock();
+        if vm_bo_guard.is_none() {
+            vm_bo_guard.replace(sgt);
+        }
+        core::mem::drop(vm_bo_guard);
 
         let uat_inner = inner.uat_inner.clone();
         let node = inner.mm.insert_node_in_range(
-            MappingInner {
+            KernelMappingInner {
                 owner: self.inner.clone(),
                 uat_inner,
                 prot,
-                sgt: Some(sgt),
+                bo: Some(vm_bo),
                 mapped_size: size,
             },
             (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page
@@ -860,28 +1110,37 @@ impl Vm {
         )?;
 
         inner.map_node(&node, prot)?;
-        Ok(Mapping(node))
+        Ok(KernelMapping(node))
     }
 
-    /// Map a GEM object (using its `SGTable`) into this Vm at a specific address.
+    /// Map a GEM object into this Vm at a specific address.
     #[allow(clippy::too_many_arguments)]
     pub(crate) fn map_at(
         &self,
         addr: u64,
         size: usize,
-        sgt: gem::SGTable,
+        gem: &gem::Object,
         prot: u32,
         guard: bool,
-    ) -> Result<Mapping> {
-        let mut inner = self.inner.lock();
+    ) -> Result<KernelMapping> {
+        let sgt = gem.sg_table()?;
+        let mut inner = self.inner.exec_lock(Some(gem))?;
+
+        let vm_bo = inner.obtain_bo()?;
+
+        let mut vm_bo_guard = vm_bo.inner().sgt.lock();
+        if vm_bo_guard.is_none() {
+            vm_bo_guard.replace(sgt);
+        }
+        core::mem::drop(vm_bo_guard);
 
         let uat_inner = inner.uat_inner.clone();
         let node = inner.mm.reserve_node(
-            MappingInner {
+            KernelMappingInner {
                 owner: self.inner.clone(),
                 uat_inner,
                 prot,
-                sgt: Some(sgt),
+                bo: Some(vm_bo),
                 mapped_size: size,
             },
             addr,
@@ -890,17 +1149,76 @@ impl Vm {
         )?;
 
         inner.map_node(&node, prot)?;
-        Ok(Mapping(node))
+        Ok(KernelMapping(node))
+    }
+
+    /// Map a range of a GEM object into this Vm using GPUVM.
+    #[allow(clippy::too_many_arguments)]
+    pub(crate) fn bind_object(
+        &self,
+        gem: &gem::Object,
+        addr: u64,
+        size: u64,
+        offset: u64,
+        prot: u32,
+    ) -> Result {
+        // Mapping needs a complete context
+        let mut ctx = StepContext {
+            new_va: Some(gpuvm::GpuVa::<VmInner>::new(init::default())?),
+            prev_va: Some(gpuvm::GpuVa::<VmInner>::new(init::default())?),
+            next_va: Some(gpuvm::GpuVa::<VmInner>::new(init::default())?),
+            vm_bo: None,
+            prot,
+        };
+
+        let sgt = gem.sg_table()?;
+        let mut inner = self.inner.exec_lock(Some(gem))?;
+
+        let vm_bo = inner.obtain_bo()?;
+
+        let mut vm_bo_guard = vm_bo.inner().sgt.lock();
+        if vm_bo_guard.is_none() {
+            vm_bo_guard.replace(sgt);
+        }
+        core::mem::drop(vm_bo_guard);
+
+        ctx.vm_bo = Some(vm_bo);
+
+        if (addr | size | offset) as usize & UAT_PGMSK != 0 {
+            dev_err!(
+                inner.dev,
+                "MMU: Map step {:#x} [{:#x}] -> {:#x} is not page-aligned\n",
+                offset,
+                size,
+                addr
+            );
+            return Err(EINVAL);
+        }
+
+        mod_dev_dbg!(
+            inner.dev,
+            "MMU: sm_map: {:#x} [{:#x}] -> {:#x}\n",
+            offset,
+            size,
+            addr
+        );
+        inner.sm_map(&mut ctx, addr, size, offset)
     }
 
     /// Add a direct MMIO mapping to this Vm at a free address.
-    pub(crate) fn map_io(&self, iova: u64, phys: usize, size: usize, prot: u32) -> Result<Mapping> {
-        let mut inner = self.inner.lock();
+    pub(crate) fn map_io(
+        &self,
+        iova: u64,
+        phys: usize,
+        size: usize,
+        prot: u32,
+    ) -> Result<KernelMapping> {
+        let mut inner = self.inner.exec_lock(None)?;
 
         if (iova as usize | phys | size) & UAT_PGMSK != 0 {
             dev_err!(
                 inner.dev,
-                "MMU: Mapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
+                "MMU: KernelMapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
                 phys,
                 size,
                 iova
@@ -918,11 +1236,11 @@ impl Vm {
 
         let uat_inner = inner.uat_inner.clone();
         let node = inner.mm.reserve_node(
-            MappingInner {
+            KernelMappingInner {
                 owner: self.inner.clone(),
                 uat_inner,
                 prot,
-                sgt: None,
+                bo: None,
                 mapped_size: size,
             },
             iova,
@@ -932,32 +1250,60 @@ impl Vm {
 
         inner.map_pages(iova as usize, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
 
-        Ok(Mapping(node))
+        Ok(KernelMapping(node))
+    }
+
+    /// Unmap everything in an address range.
+    pub(crate) fn unmap_range(&self, iova: u64, size: u64) -> Result {
+        // Unmapping a range can only do a single split, so just preallocate
+        // the prev and next GpuVas
+        let mut ctx = StepContext {
+            prev_va: Some(gpuvm::GpuVa::<VmInner>::new(init::default())?),
+            next_va: Some(gpuvm::GpuVa::<VmInner>::new(init::default())?),
+            ..Default::default()
+        };
+
+        let mut inner = self.inner.exec_lock(None)?;
+
+        mod_dev_dbg!(inner.dev, "MMU: sm_unmap: {:#x}:{:#x}\n", iova, size);
+        inner.sm_unmap(&mut ctx, iova, size)
+    }
+
+    /// Drop mappings for a given bo.
+    pub(crate) fn drop_mappings(&self, gem: &gem::Object) -> Result {
+        // Removing whole mappings only does unmaps, so no preallocated VAs
+        let mut ctx = Default::default();
+
+        let mut inner = self.inner.exec_lock(Some(gem))?;
+
+        if let Some(bo) = inner.find_bo() {
+            mod_dev_dbg!(inner.dev, "MMU: bo_unmap\n");
+            inner.bo_unmap(&mut ctx, &bo)?;
+            mod_dev_dbg!(inner.dev, "MMU: bo_unmap done\n");
+        }
+
+        Ok(())
     }
 
     /// Returns the unique ID of this Vm
     pub(crate) fn id(&self) -> u64 {
         self.id
     }
-
-    /// Returns the unique File ID of the owner of this Vm
-    pub(crate) fn file_id(&self) -> u64 {
-        self.file_id
-    }
 }
 
 impl Drop for VmInner {
     fn drop(&mut self) {
-        assert_eq!(self.active_users, 0);
+        let mut binding = self.binding.lock();
+        assert_eq!(binding.active_users, 0);
 
         mod_pr_debug!(
             "VmInner::Drop [{}]: bind_token={:?}\n",
             self.id,
-            self.bind_token
+            binding.bind_token
         );
 
         // Make sure this VM is not mapped to a TTB if it was
-        if let Some(token) = self.bind_token.take() {
+        if let Some(token) = binding.bind_token.take() {
             let idx = (token.last_slot() as usize) + UAT_USER_CTX_START;
             let ttb = self.ttb() | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT;
 
@@ -979,7 +1325,7 @@ impl Drop for VmInner {
             uat_inner.handoff().unlock();
             core::mem::drop(uat_inner);
 
-            // In principle we dropped all the Mappings already, but we might as
+            // In principle we dropped all the KernelMappings already, but we might as
             // well play it safe and invalidate the whole ASID.
             if inval {
                 mod_pr_debug!(
@@ -1093,16 +1439,16 @@ impl Uat {
 
     /// Binds a `Vm` to a slot, preferring the last used one.
     pub(crate) fn bind(&self, vm: &Vm) -> Result<VmBind> {
-        let mut inner = vm.inner.lock();
+        let mut binding = vm.binding.lock();
 
-        if inner.binding.is_none() {
-            assert_eq!(inner.active_users, 0);
+        if binding.binding.is_none() {
+            assert_eq!(binding.active_users, 0);
 
-            let slot = self.slots.get(inner.bind_token)?;
+            let slot = self.slots.get(binding.bind_token)?;
             if slot.changed() {
                 mod_pr_debug!("Vm Bind [{}]: bind_token={:?}\n", vm.id, slot.token(),);
                 let idx = (slot.slot() as usize) + UAT_USER_CTX_START;
-                let ttb = inner.ttb() | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT;
+                let ttb = binding.ttb | TTBR_VALID | (idx as u64) << TTBR_ASID_SHIFT;
 
                 let uat_inner = self.inner.lock();
 
@@ -1130,20 +1476,20 @@ impl Uat {
                 mem::sync();
             }
 
-            inner.bind_token = Some(slot.token());
-            inner.binding = Some(slot);
+            binding.bind_token = Some(slot.token());
+            binding.binding = Some(slot);
         }
 
-        inner.active_users += 1;
+        binding.active_users += 1;
 
-        let slot = inner.binding.as_ref().unwrap().slot() + UAT_USER_CTX_START as u32;
-        mod_pr_debug!("MMU: slot {} active users {}\n", slot, inner.active_users);
+        let slot = binding.binding.as_ref().unwrap().slot() + UAT_USER_CTX_START as u32;
+        mod_pr_debug!("MMU: slot {} active users {}\n", slot, binding.active_users);
         Ok(VmBind(vm.clone(), slot))
     }
 
     /// Creates a new `Vm` linked to this UAT.
-    pub(crate) fn new_vm(&self, id: u64, file_id: u64) -> Result<Vm> {
-        Vm::new(&self.dev, self.inner.clone(), self.cfg, false, id, file_id)
+    pub(crate) fn new_vm(&self, id: u64) -> Result<Vm> {
+        Vm::new(&self.dev, self.inner.clone(), self.cfg, false, id)
     }
 
     /// Creates the reference-counted inner data for a new `Uat` instance.
@@ -1189,8 +1535,8 @@ impl Uat {
         let pagetables_rgn = Self::map_region(dev, c_str!("pagetables"), PAGETABLES_SIZE, true)?;
 
         dev_info!(dev, "MMU: Creating kernel page tables\n");
-        let kernel_lower_vm = Vm::new(dev, inner.clone(), cfg, false, 1, 0)?;
-        let kernel_vm = Vm::new(dev, inner.clone(), cfg, true, 0, 0)?;
+        let kernel_lower_vm = Vm::new(dev, inner.clone(), cfg, false, 1)?;
+        let kernel_vm = Vm::new(dev, inner.clone(), cfg, true, 0)?;
 
         dev_info!(dev, "MMU: Kernel page tables created\n");
 
diff --git a/rust/kernel/sync/lockdep.rs b/rust/kernel/sync/lockdep.rs
index 0b480ab35a4ba0..d5593f506abe2b 100644
--- a/rust/kernel/sync/lockdep.rs
+++ b/rust/kernel/sync/lockdep.rs
@@ -54,6 +54,8 @@ impl LockClassKey {
 // SAFETY: `bindings::lock_class_key` just represents an opaque memory location, and is never
 // actually dereferenced.
 unsafe impl Send for LockClassKey {}
+// SAFETY: `bindings::lock_class_key` just represents an opaque memory location, and is never
+// actually dereferenced.
 unsafe impl Sync for LockClassKey {}
 
 // Location is 'static but not really, since module unloads will

From aefea5588f56910a5aad423a05c86620108a58df Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 17:40:38 +0900
Subject: [PATCH 0983/1027] drm/asahi: Refactor address types

VAs are u64, PAs and sizes are usize.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs  |  2 +-
 drivers/gpu/drm/asahi/file.rs   |  2 +-
 drivers/gpu/drm/asahi/gpu.rs    | 14 +++---
 drivers/gpu/drm/asahi/hw/mod.rs |  4 +-
 drivers/gpu/drm/asahi/mmu.rs    | 76 +++++++++++++++------------------
 5 files changed, 46 insertions(+), 52 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index 329ae65057e63c..1c5540189235b7 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -577,7 +577,7 @@ impl Allocator for SimpleAllocator {
         let iova = mapping.iova();
 
         let ptr = unsafe { p.add(offset) };
-        let gpu_ptr = (iova + offset) as u64;
+        let gpu_ptr = iova + offset as u64;
 
         mod_dev_dbg!(
             &self.dev,
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 6415f0de9be4ee..3b3ef00d1f946a 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -38,7 +38,7 @@ impl Drop for Vm {
         // When the user Vm is dropped, unmap everything in the user range
         if self
             .vm
-            .unmap_range(mmu::IOVA_USER_BASE as u64, VM_USER_END)
+            .unmap_range(mmu::IOVA_USER_BASE, VM_USER_END)
             .is_err()
         {
             pr_err!("Vm::Drop: vm.unmap_range() failed\n");
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 6d65d4c0833691..51d2a090e7e534 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -300,7 +300,7 @@ pub(crate) struct RtkitObject {
 
 impl rtkit::Buffer for RtkitObject {
     fn iova(&self) -> Result<usize> {
-        Ok(self.mapping.iova())
+        Ok(self.mapping.iova() as usize)
     }
     fn buf(&mut self) -> Result<&mut [u8]> {
         let vmap = self.obj.vmap()?;
@@ -538,20 +538,20 @@ impl GpuManager::ver {
 
         #[ver(V >= V13_0B4)]
         if let Some(base) = cfg.sram_base {
-            let size = cfg.sram_size.unwrap() as usize;
+            let size = cfg.sram_size.unwrap();
             let iova = mgr.as_mut().alloc_mmio_iova(size);
 
-            let mapping =
-                mgr.uat
-                    .kernel_vm()
-                    .map_io(iova, base as usize, size, mmu::PROT_FW_SHARED_RW)?;
+            let mapping = mgr
+                .uat
+                .kernel_vm()
+                .map_io(iova, base, size, mmu::PROT_FW_SHARED_RW)?;
 
             mgr.as_mut()
                 .initdata_mut()
                 .runtime_pointers
                 .hwdata_b
                 .with_mut(|raw, _| {
-                    raw.sgx_sram_ptr = U64(mapping.iova() as u64);
+                    raw.sgx_sram_ptr = U64(mapping.iova());
                 });
 
             mgr.as_mut().io_mappings_mut().push(mapping, GFP_KERNEL)?;
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index 0d28299da764c1..5b55e660dfb1a1 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -307,9 +307,9 @@ pub(crate) struct HwConfig {
     /// Required MMIO mappings for this GPU/firmware.
     pub(crate) io_mappings: &'static [Option<IOMapping>],
     /// SRAM base
-    pub(crate) sram_base: Option<u64>,
+    pub(crate) sram_base: Option<usize>,
     /// SRAM size
-    pub(crate) sram_size: Option<u64>,
+    pub(crate) sram_size: Option<usize>,
 }
 
 /// Dynamic (fetched from hardware/DT) configuration.
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index bd128af30798a6..7010bd76d6bb3c 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -66,15 +66,15 @@ pub(crate) const UAT_IAS: usize = 39;
 pub(crate) const UAT_IAS_KERN: usize = 36;
 
 /// Lower/user base VA
-pub(crate) const IOVA_USER_BASE: usize = UAT_PGSZ;
+pub(crate) const IOVA_USER_BASE: u64 = UAT_PGSZ as u64;
 /// Lower/user top VA
-pub(crate) const IOVA_USER_TOP: usize = (1 << UAT_IAS) - 1;
+pub(crate) const IOVA_USER_TOP: u64 = 1 << (UAT_IAS as u64);
 /// Upper/kernel base VA
 // const IOVA_TTBR1_BASE: usize = 0xffffff8000000000;
 /// Driver-managed kernel base VA
-const IOVA_KERN_BASE: usize = 0xffffffa000000000;
+const IOVA_KERN_BASE: u64 = 0xffffffa000000000;
 /// Driver-managed kernel top VA
-const IOVA_KERN_TOP: usize = 0xffffffafffffffff;
+const IOVA_KERN_TOP: u64 = 0xffffffb000000000;
 
 const TTBR_VALID: u64 = 0x1; // BIT(0)
 const TTBR_ASID_SHIFT: usize = 48;
@@ -178,8 +178,8 @@ const PAGETABLES_SIZE: usize = UAT_PGSZ;
 struct VmInner {
     dev: driver::AsahiDevRef,
     is_kernel: bool,
-    min_va: usize,
-    max_va: usize,
+    min_va: u64,
+    max_va: u64,
     page_table: AppleUAT<Uat>,
     mm: mm::Allocator<(), KernelMappingInner>,
     uat_inner: Arc<UatInner>,
@@ -229,7 +229,7 @@ impl gpuvm::DriverGpuVm for VmInner {
         op: &mut gpuvm::OpMap<Self>,
         ctx: &mut Self::StepContext,
     ) -> Result {
-        let mut iova = op.addr() as usize;
+        let mut iova = op.addr();
         let mut left = op.range() as usize;
         let mut offset = op.offset() as usize;
 
@@ -270,7 +270,7 @@ impl gpuvm::DriverGpuVm for VmInner {
             self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, ctx.prot)?;
 
             left -= len;
-            iova += len;
+            iova += len as u64;
         }
 
         let gpuva = ctx.new_va.take().expect("Multiple step_map calls");
@@ -303,11 +303,7 @@ impl gpuvm::DriverGpuVm for VmInner {
 
         mod_dev_dbg!(self.dev, "MMU: unmap: {:#x}:{:#x}\n", va.addr(), va.range());
 
-        self.unmap_pages(
-            va.addr() as usize,
-            UAT_PGSZ,
-            (va.range() as usize) >> UAT_PGBIT,
-        )?;
+        self.unmap_pages(va.addr(), UAT_PGSZ, (va.range() >> UAT_PGBIT) as usize)?;
 
         if let Some(asid) = self.slot() {
             mem::tlbi_range(asid as u8, va.addr() as usize, va.range() as usize);
@@ -362,11 +358,7 @@ impl gpuvm::DriverGpuVm for VmInner {
             orig_range
         );
 
-        self.unmap_pages(
-            unmap_start as usize,
-            UAT_PGSZ,
-            (unmap_range as usize) >> UAT_PGBIT,
-        )?;
+        self.unmap_pages(unmap_start, UAT_PGSZ, (unmap_range >> UAT_PGBIT) as usize)?;
 
         if op.unmap().unmap_and_unlink_va().is_none() {
             dev_err!(self.dev, "step_unmap: could not unlink gpuva");
@@ -419,8 +411,8 @@ impl VmInner {
     }
 
     /// Map an IOVA to the shifted address the underlying io_pgtable uses.
-    fn map_iova(&self, iova: usize, size: usize) -> Result<usize> {
-        if iova < self.min_va || (iova + size - 1) > self.max_va {
+    fn map_iova(&self, iova: u64, size: usize) -> Result<u64> {
+        if iova < self.min_va || (iova + size as u64) > self.max_va {
             Err(EINVAL)
         } else if self.is_kernel {
             Ok(iova - self.min_va)
@@ -432,7 +424,7 @@ impl VmInner {
     /// Map a contiguous range of virtual->physical pages.
     fn map_pages(
         &mut self,
-        mut iova: usize,
+        mut iova: u64,
         mut paddr: usize,
         pgsize: usize,
         pgcount: usize,
@@ -441,24 +433,26 @@ impl VmInner {
         let mut left = pgcount;
         while left > 0 {
             let mapped_iova = self.map_iova(iova, pgsize * left)?;
-            let mapped = self
-                .page_table
-                .map_pages(mapped_iova, paddr, pgsize, left, prot)?;
+            let mapped =
+                self.page_table
+                    .map_pages(mapped_iova as usize, paddr, pgsize, left, prot)?;
             assert!(mapped <= left * pgsize);
 
             left -= mapped / pgsize;
             paddr += mapped;
-            iova += mapped;
+            iova += mapped as u64;
         }
         Ok(pgcount * pgsize)
     }
 
     /// Unmap a contiguous range of pages.
-    fn unmap_pages(&mut self, mut iova: usize, pgsize: usize, pgcount: usize) -> Result<usize> {
+    fn unmap_pages(&mut self, mut iova: u64, pgsize: usize, pgcount: usize) -> Result<usize> {
         let mut left = pgcount;
         while left > 0 {
             let mapped_iova = self.map_iova(iova, pgsize * left)?;
-            let mut unmapped = self.page_table.unmap_pages(mapped_iova, pgsize, left);
+            let mut unmapped = self
+                .page_table
+                .unmap_pages(mapped_iova as usize, pgsize, left);
             if unmapped == 0 {
                 dev_err!(
                     self.dev,
@@ -471,7 +465,7 @@ impl VmInner {
             assert!(unmapped <= left * pgsize);
 
             left -= unmapped / pgsize;
-            iova += unmapped;
+            iova += unmapped as u64;
         }
 
         Ok(pgcount * pgsize)
@@ -479,7 +473,7 @@ impl VmInner {
 
     /// Map an `mm::Node` representing an mapping in VA space.
     fn map_node(&mut self, node: &mm::Node<(), KernelMappingInner>, prot: u32) -> Result {
-        let mut iova = node.start() as usize;
+        let mut iova = node.start();
         let guard = node.bo.as_ref().ok_or(EINVAL)?.inner().sgt.lock();
         let sgt = guard.as_ref().ok_or(EINVAL)?;
 
@@ -487,7 +481,7 @@ impl VmInner {
             let addr = range.dma_address();
             let len = range.dma_len();
 
-            if (addr | len | iova) & UAT_PGMSK != 0 {
+            if (addr | len | iova as usize) & UAT_PGMSK != 0 {
                 dev_err!(
                     self.dev,
                     "MMU: KernelMapping {:#x}:{:#x} -> {:#x} is not page-aligned\n",
@@ -508,7 +502,7 @@ impl VmInner {
 
             self.map_pages(iova, addr, UAT_PGSZ, len >> UAT_PGBIT, prot)?;
 
-            iova += len;
+            iova += len as u64;
         }
         Ok(())
     }
@@ -589,8 +583,8 @@ pub(crate) struct KernelMapping(mm::Node<(), KernelMappingInner>);
 
 impl KernelMapping {
     /// Returns the IOVA base of this mapping
-    pub(crate) fn iova(&self) -> usize {
-        self.0.start() as usize
+    pub(crate) fn iova(&self) -> u64 {
+        self.0.start()
     }
 
     /// Returns the size of this mapping in bytes
@@ -700,13 +694,13 @@ impl KernelMapping {
         // Lock this flush slot, and write the range to it
         let flush = self.0.uat_inner.lock_flush(flush_slot);
         let pages = self.size() >> UAT_PGBIT;
-        flush.begin_flush(self.iova() as u64, self.size() as u64);
+        flush.begin_flush(self.iova(), self.size() as u64);
         if pages >= 0x10000 {
             dev_err!(owner.dev, "MMU: Flush too big ({:#x} pages))\n", pages);
         }
 
         let cmd = fw::channels::FwCtlMsg {
-            addr: fw::types::U64(self.iova() as u64),
+            addr: fw::types::U64(self.iova()),
             unk_8: 0,
             slot: flush_slot,
             page_count: pages as u16,
@@ -784,7 +778,7 @@ impl Drop for KernelMapping {
         }
 
         if let Some(asid) = owner.slot() {
-            mem::tlbi_range(asid as u8, self.iova(), self.size());
+            mem::tlbi_range(asid as u8, self.iova() as usize, self.size());
             mod_dev_dbg!(
                 owner.dev,
                 "MMU: flush range: asid={:#x} start={:#x} len={:#x}\n",
@@ -1023,7 +1017,7 @@ impl Vm {
             IOVA_USER_TOP
         };
 
-        let mm = mm::Allocator::new(min_va as u64, (max_va - min_va + 1) as u64, ())?;
+        let mm = mm::Allocator::new(min_va, max_va - min_va, ())?;
 
         let binding = Arc::pin_init(
             Mutex::new_named(
@@ -1045,8 +1039,8 @@ impl Vm {
                 c_str!("Asahi::GpuVm"),
                 dev,
                 &*(dummy_obj.gem),
-                min_va as u64,
-                (max_va - min_va + 1) as u64,
+                min_va,
+                max_va - min_va,
                 0,
                 0,
                 init!(VmInner {
@@ -1184,7 +1178,7 @@ impl Vm {
 
         ctx.vm_bo = Some(vm_bo);
 
-        if (addr | size | offset) as usize & UAT_PGMSK != 0 {
+        if (addr | size | offset) & (UAT_PGMSK as u64) != 0 {
             dev_err!(
                 inner.dev,
                 "MMU: Map step {:#x} [{:#x}] -> {:#x} is not page-aligned\n",
@@ -1248,7 +1242,7 @@ impl Vm {
             0,
         )?;
 
-        inner.map_pages(iova as usize, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
+        inner.map_pages(iova, phys, UAT_PGSZ, size >> UAT_PGBIT, prot)?;
 
         Ok(KernelMapping(node))
     }

From 4f1019dfc2f33fdf00e78ea6b2cc644f585047ea Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 17:56:26 +0900
Subject: [PATCH 0984/1027] drm/asahi: util: Add RangeExt helpers for Range<T>

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/util.rs | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/drivers/gpu/drm/asahi/util.rs b/drivers/gpu/drm/asahi/util.rs
index 8d1a37f17cd887..ad1c61920d5ee4 100644
--- a/drivers/gpu/drm/asahi/util.rs
+++ b/drivers/gpu/drm/asahi/util.rs
@@ -42,3 +42,34 @@ where
 
     (a + b - one) / b
 }
+
+pub(crate) trait RangeExt<T> {
+    fn overlaps(&self, other: Self) -> bool;
+    fn is_superset(&self, other: Self) -> bool;
+    fn len(&self) -> usize;
+    fn range(&self) -> T;
+}
+
+impl<T: PartialOrd<T> + Default + Copy + Sub<Output = T>> RangeExt<T> for core::ops::Range<T>
+where
+    usize: core::convert::TryFrom<T>,
+    <usize as core::convert::TryFrom<T>>::Error: core::fmt::Debug,
+{
+    fn overlaps(&self, other: Self) -> bool {
+        !(self.is_empty() || other.is_empty() || self.end <= other.start || other.end <= self.start)
+    }
+    fn is_superset(&self, other: Self) -> bool {
+        !self.is_empty()
+            && (other.is_empty() || (other.start >= self.start && other.end <= self.end))
+    }
+    fn range(&self) -> T {
+        if self.is_empty() {
+            Default::default()
+        } else {
+            self.end - self.start
+        }
+    }
+    fn len(&self) -> usize {
+        self.range().try_into().unwrap()
+    }
+}

From 4237f073d46a4dabd42e60b730620a129772e7e5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 18:03:32 +0900
Subject: [PATCH 0985/1027] drm/asahi: mmu: Convert to using Range

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/mmu.rs | 37 +++++++++++++++++-------------------
 1 file changed, 17 insertions(+), 20 deletions(-)

diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 7010bd76d6bb3c..b9b454bd29828d 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -12,6 +12,7 @@
 
 use core::fmt::Debug;
 use core::mem::size_of;
+use core::ops::Range;
 use core::ptr::NonNull;
 use core::sync::atomic::{fence, AtomicU32, AtomicU64, AtomicU8, Ordering};
 use core::time::Duration;
@@ -34,7 +35,7 @@ use kernel::{
 
 use crate::debug::*;
 use crate::no_debug;
-use crate::{driver, fw, gem, hw, mem, slotalloc};
+use crate::{driver, fw, gem, hw, mem, slotalloc, util::RangeExt};
 
 const DEBUG_CLASS: DebugFlags = DebugFlags::Mmu;
 
@@ -69,12 +70,17 @@ pub(crate) const UAT_IAS_KERN: usize = 36;
 pub(crate) const IOVA_USER_BASE: u64 = UAT_PGSZ as u64;
 /// Lower/user top VA
 pub(crate) const IOVA_USER_TOP: u64 = 1 << (UAT_IAS as u64);
+/// Lower/user VA range
+pub(crate) const IOVA_USER_RANGE: Range<u64> = IOVA_USER_BASE..IOVA_USER_TOP;
+
 /// Upper/kernel base VA
 // const IOVA_TTBR1_BASE: usize = 0xffffff8000000000;
 /// Driver-managed kernel base VA
 const IOVA_KERN_BASE: u64 = 0xffffffa000000000;
 /// Driver-managed kernel top VA
 const IOVA_KERN_TOP: u64 = 0xffffffb000000000;
+/// Lower/user VA range
+const IOVA_KERN_RANGE: Range<u64> = IOVA_KERN_BASE..IOVA_KERN_TOP;
 
 const TTBR_VALID: u64 = 0x1; // BIT(0)
 const TTBR_ASID_SHIFT: usize = 48;
@@ -178,8 +184,7 @@ const PAGETABLES_SIZE: usize = UAT_PGSZ;
 struct VmInner {
     dev: driver::AsahiDevRef,
     is_kernel: bool,
-    min_va: u64,
-    max_va: u64,
+    va_range: Range<u64>,
     page_table: AppleUAT<Uat>,
     mm: mm::Allocator<(), KernelMappingInner>,
     uat_inner: Arc<UatInner>,
@@ -412,10 +417,10 @@ impl VmInner {
 
     /// Map an IOVA to the shifted address the underlying io_pgtable uses.
     fn map_iova(&self, iova: u64, size: usize) -> Result<u64> {
-        if iova < self.min_va || (iova + size as u64) > self.max_va {
+        if !self.va_range.is_superset(iova..(iova + size as u64)) {
             Err(EINVAL)
         } else if self.is_kernel {
-            Ok(iova - self.min_va)
+            Ok(iova - self.va_range.start)
         } else {
             Ok(iova)
         }
@@ -1006,18 +1011,13 @@ impl Vm {
             },
             (),
         )?;
-        let min_va = if is_kernel {
-            IOVA_KERN_BASE
-        } else {
-            IOVA_USER_BASE
-        };
-        let max_va = if is_kernel {
-            IOVA_KERN_TOP
+        let va_range = if is_kernel {
+            IOVA_KERN_RANGE
         } else {
-            IOVA_USER_TOP
+            IOVA_USER_RANGE
         };
 
-        let mm = mm::Allocator::new(min_va, max_va - min_va, ())?;
+        let mm = mm::Allocator::new(va_range.start, va_range.range(), ())?;
 
         let binding = Arc::pin_init(
             Mutex::new_named(
@@ -1039,14 +1039,11 @@ impl Vm {
                 c_str!("Asahi::GpuVm"),
                 dev,
                 &*(dummy_obj.gem),
-                min_va,
-                max_va - min_va,
-                0,
-                0,
+                va_range.clone(),
+                0..0,
                 init!(VmInner {
                     dev: dev.into(),
-                    min_va,
-                    max_va,
+                    va_range,
                     is_kernel,
                     page_table,
                     mm,

From d92fe479965c654e2b5a836d1501030042c1cf23 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 18:09:05 +0900
Subject: [PATCH 0986/1027] drm/asahi: Move the unknown dummy page to the top
 of the address space

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs     |  5 ++---
 drivers/gpu/drm/asahi/initdata.rs | 10 ++++++----
 drivers/gpu/drm/asahi/mmu.rs      |  4 ++++
 3 files changed, 12 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 3b3ef00d1f946a..5016b64877c325 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -170,8 +170,6 @@ const VM_DRV_GPU_END: u64 = 0x70_ffffffff;
 const VM_DRV_GPUFW_START: u64 = 0x71_00000000;
 /// End address of the kernel-managed GPU/FW shared mapping region.
 const VM_DRV_GPUFW_END: u64 = 0x71_ffffffff;
-/// Address of a special dummy page?
-const VM_UNK_PAGE: u64 = 0x6f_ffff8000;
 
 impl drm::file::DriverFile for File {
     type Driver = driver::AsahiDriver;
@@ -349,7 +347,8 @@ impl File {
         );
         let mut dummy_obj = gem::new_kernel_object(device, 0x4000)?;
         dummy_obj.vmap()?.as_mut_slice().fill(0);
-        let dummy_mapping = dummy_obj.map_at(&vm, VM_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
+        let dummy_mapping =
+            dummy_obj.map_at(&vm, mmu::IOVA_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
 
         mod_dev_dbg!(device, "[File {} VM {}]: VM created\n", file_id, id);
         resv.store(Box::new(Vm {
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index 2766a9cf060d95..a3fd7a87ab79de 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -487,10 +487,12 @@ impl<'a> InitDataBuilder::ver<'a> {
                 #[ver(V < V13_0B4)]
                 unk_10: U64(0x1_00000000),
                 unk_18: U64(0xffc00000),
-                unk_20: U64(0x11_00000000),
-                unk_28: U64(0x11_00000000),
-                // userspace address?
-                unk_30: U64(0x6f_ffff8000),
+                // USC start
+                unk_20: U64(0), // U64(0x11_00000000),
+                unk_28: U64(0), // U64(0x11_00000000),
+                // Unknown page
+                //unk_30: U64(0x6f_ffff8000),
+                unk_30: U64(mmu::IOVA_UNK_PAGE),
                 // unmapped?
                 unkptr_38: U64(0xffffffa0_11800000),
                 // TODO: yuv matrices
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index b9b454bd29828d..09218036afcfc2 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -87,6 +87,10 @@ const TTBR_ASID_SHIFT: usize = 48;
 
 const PTE_TABLE: u64 = 0x3; // BIT(0) | BIT(1)
 
+/// Address of a special dummy page?
+//const IOVA_UNK_PAGE: u64 = 0x6f_ffff8000;
+pub(crate) const IOVA_UNK_PAGE: u64 = IOVA_USER_TOP - 2 * UAT_PGSZ as u64;
+
 // KernelMapping protection types
 
 // Note: prot::CACHE means "cache coherency", which for UAT means *uncached*,

From f1112ed398b949ae74c50b558e32b0797c0497db Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 19:06:01 +0900
Subject: [PATCH 0987/1027] drm/asahi: Convert more ranges to Range<>

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/alloc.rs |  31 ++++------
 drivers/gpu/drm/asahi/file.rs  |  52 ++++++++--------
 drivers/gpu/drm/asahi/gem.rs   |  14 +----
 drivers/gpu/drm/asahi/gpu.rs   | 106 ++++++++++++++-------------------
 drivers/gpu/drm/asahi/mmu.rs   |   7 +--
 5 files changed, 92 insertions(+), 118 deletions(-)

diff --git a/drivers/gpu/drm/asahi/alloc.rs b/drivers/gpu/drm/asahi/alloc.rs
index 1c5540189235b7..69c198ca6d328a 100644
--- a/drivers/gpu/drm/asahi/alloc.rs
+++ b/drivers/gpu/drm/asahi/alloc.rs
@@ -19,6 +19,7 @@ use crate::driver::{AsahiDevRef, AsahiDevice};
 use crate::fw::types::Zeroable;
 use crate::mmu;
 use crate::object::{GpuArray, GpuObject, GpuOnlyArray, GpuStruct, GpuWeakPointer};
+use crate::util::RangeExt;
 
 use core::cmp::Ordering;
 use core::fmt;
@@ -26,6 +27,7 @@ use core::fmt::{Debug, Formatter};
 use core::marker::PhantomData;
 use core::mem;
 use core::mem::MaybeUninit;
+use core::ops::Range;
 use core::ptr::NonNull;
 
 const DEBUG_CLASS: DebugFlags = DebugFlags::Alloc;
@@ -491,8 +493,7 @@ impl RawAllocation for SimpleAllocation {
 /// GPU's idea of object size what we expect.
 pub(crate) struct SimpleAllocator {
     dev: AsahiDevRef,
-    start: u64,
-    end: u64,
+    range: Range<u64>,
     prot: u32,
     vm: mmu::Vm,
     min_align: usize,
@@ -506,8 +507,7 @@ impl SimpleAllocator {
     pub(crate) fn new(
         dev: &AsahiDevice,
         vm: &mmu::Vm,
-        start: u64,
-        end: u64,
+        range: Range<u64>,
         min_align: usize,
         prot: u32,
         _block_size: usize,
@@ -521,8 +521,7 @@ impl SimpleAllocator {
         Ok(SimpleAllocator {
             dev: dev.into(),
             vm: vm.clone(),
-            start,
-            end,
+            range,
             prot,
             min_align,
             cpu_maps,
@@ -567,8 +566,7 @@ impl Allocator for SimpleAllocator {
         }
         let mapping = obj.map_into_range(
             &self.vm,
-            self.start,
-            self.end,
+            self.range.clone(),
             self.min_align.max(mmu::UAT_PGSZ) as u64,
             self.prot,
             true,
@@ -709,8 +707,7 @@ struct HeapAllocatorInner {
 /// never shrinks it.
 pub(crate) struct HeapAllocator {
     dev: AsahiDevRef,
-    start: u64,
-    end: u64,
+    range: Range<u64>,
     top: u64,
     prot: u32,
     vm: mmu::Vm,
@@ -730,8 +727,7 @@ impl HeapAllocator {
     pub(crate) fn new(
         dev: &AsahiDevice,
         vm: &mmu::Vm,
-        start: u64,
-        end: u64,
+        range: Range<u64>,
         min_align: usize,
         prot: u32,
         block_size: usize,
@@ -758,14 +754,13 @@ impl HeapAllocator {
             total_garbage: 0,
         };
 
-        let mm = mm::Allocator::new(start, end - start + 1, inner)?;
+        let mm = mm::Allocator::new(range.start, range.range(), inner)?;
 
         Ok(HeapAllocator {
             dev: dev.into(),
             vm: vm.clone(),
-            start,
-            end,
-            top: start,
+            top: range.start,
+            range,
             prot,
             min_align,
             block_size: block_size.max(min_align),
@@ -802,7 +797,7 @@ impl HeapAllocator {
             size_aligned,
         );
 
-        if self.top.saturating_add(size_aligned as u64) >= self.end {
+        if self.top.saturating_add(size_aligned as u64) > self.range.end {
             dev_err!(
                 &self.dev,
                 "HeapAllocator[{}]::add_block: Exhausted VA space\n",
@@ -888,7 +883,7 @@ impl HeapAllocator {
             &self.dev,
             "{} Heap: grow to {} bytes\n",
             &*self.name,
-            self.top - self.start
+            self.top - self.range.start
         );
 
         Ok(())
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 5016b64877c325..a7446d368c27cc 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -314,30 +314,34 @@ impl File {
             file_id,
             id
         );
-        let ualloc = Arc::pin_init(Mutex::new(alloc::DefaultAllocator::new(
-            device,
-            &vm,
-            VM_DRV_GPU_START,
-            VM_DRV_GPU_END,
-            buffer::PAGE_SIZE,
-            mmu::PROT_GPU_SHARED_RW,
-            512 * 1024,
-            true,
-            fmt!("File {} VM {} GPU Shared", file_id, id),
-            false,
-        )?))?;
-        let ualloc_priv = Arc::pin_init(Mutex::new(alloc::DefaultAllocator::new(
-            device,
-            &vm,
-            VM_DRV_GPUFW_START,
-            VM_DRV_GPUFW_END,
-            buffer::PAGE_SIZE,
-            mmu::PROT_GPU_FW_PRIV_RW,
-            64 * 1024,
-            true,
-            fmt!("File {} VM {} GPU FW Private", file_id, id),
-            false,
-        )?))?;
+        let ualloc = Arc::pin_init(
+            Mutex::new(alloc::DefaultAllocator::new(
+                device,
+                &vm,
+                VM_DRV_GPU_START..VM_DRV_GPU_END,
+                buffer::PAGE_SIZE,
+                mmu::PROT_GPU_SHARED_RW,
+                512 * 1024,
+                true,
+                fmt!("File {} VM {} GPU Shared", file_id, id),
+                false,
+            )?),
+            GFP_KERNEL,
+        )?;
+        let ualloc_priv = Arc::pin_init(
+            Mutex::new(alloc::DefaultAllocator::new(
+                device,
+                &vm,
+                VM_DRV_GPUFW_START..VM_DRV_GPUFW_END,
+                buffer::PAGE_SIZE,
+                mmu::PROT_GPU_FW_PRIV_RW,
+                64 * 1024,
+                true,
+                fmt!("File {} VM {} GPU FW Private", file_id, id),
+                false,
+            )?),
+            GFP_KERNEL,
+        )?;
 
         mod_dev_dbg!(
             device,
diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs
index a53863713bf188..07772d414ab967 100644
--- a/drivers/gpu/drm/asahi/gem.rs
+++ b/drivers/gpu/drm/asahi/gem.rs
@@ -16,6 +16,7 @@ use kernel::{
 
 use kernel::drm::gem::BaseObject;
 
+use core::ops::Range;
 use core::sync::atomic::{AtomicU64, Ordering};
 
 use crate::{debug::*, driver::AsahiDevice, file, file::DrmFile, mmu, util::*};
@@ -76,8 +77,7 @@ impl ObjectRef {
     pub(crate) fn map_into_range(
         &mut self,
         vm: &crate::mmu::Vm,
-        start: u64,
-        end: u64,
+        range: Range<u64>,
         alignment: u64,
         prot: u32,
         guard: bool,
@@ -88,15 +88,7 @@ impl ObjectRef {
             return Err(EINVAL);
         }
 
-        vm.map_in_range(
-            self.gem.size(),
-            &self.gem,
-            alignment,
-            start,
-            end,
-            prot,
-            guard,
-        )
+        vm.map_in_range(self.gem.size(), &self.gem, alignment, range, prot, guard)
     }
 
     /// Maps an object into a given `Vm` at a specific address.
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 51d2a090e7e534..c0550b294ab0b8 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -12,6 +12,7 @@
 //! itself with version dependence.
 
 use core::any::Any;
+use core::ops::Range;
 use core::sync::atomic::{AtomicBool, AtomicU64, Ordering};
 use core::time::Duration;
 
@@ -69,33 +70,19 @@ const DOORBELL_DEVCTRL: u64 = 0x11;
 
 // Upper kernel half VA address ranges.
 /// Private (cached) firmware structure VA range base.
-const IOVA_KERN_PRIV_BASE: u64 = 0xffffffa000000000;
-/// Private (cached) firmware structure VA range top.
-const IOVA_KERN_PRIV_TOP: u64 = 0xffffffa5ffffffff;
+const IOVA_KERN_PRIV_RANGE: Range<u64> = 0xffffffa000000000..0xffffffa600000000;
 /// Private (cached) GPU-RO firmware structure VA range base.
-const IOVA_KERN_GPU_RO_BASE: u64 = 0xffffffa600000000;
-/// Private (cached) GPU-RO firmware structure VA range top.
-const IOVA_KERN_GPU_RO_TOP: u64 = 0xffffffa7ffffffff;
+const IOVA_KERN_GPU_RO_RANGE: Range<u64> = 0xffffffa600000000..0xffffffa800000000;
 /// Shared (uncached) firmware structure VA range base.
-const IOVA_KERN_SHARED_BASE: u64 = 0xffffffa800000000;
-/// Shared (uncached) firmware structure VA range top.
-const IOVA_KERN_SHARED_TOP: u64 = 0xffffffa9ffffffff;
+const IOVA_KERN_SHARED_RANGE: Range<u64> = 0xffffffa800000000..0xffffffaa00000000;
 /// Shared (uncached) read-only firmware structure VA range base.
-const IOVA_KERN_SHARED_RO_BASE: u64 = 0xffffffaa00000000;
-/// Shared (uncached) read-only firmware structure VA range top.
-const IOVA_KERN_SHARED_RO_TOP: u64 = 0xffffffabffffffff;
+const IOVA_KERN_SHARED_RO_RANGE: Range<u64> = 0xffffffaa00000000..0xffffffac00000000;
 /// GPU/FW shared structure VA range base.
-const IOVA_KERN_GPU_BASE: u64 = 0xffffffac00000000;
-/// GPU/FW shared structure VA range top.
-const IOVA_KERN_GPU_TOP: u64 = 0xffffffadffffffff;
+const IOVA_KERN_GPU_RANGE: Range<u64> = 0xffffffac00000000..0xffffffae00000000;
 /// GPU/FW shared structure VA range base.
-const IOVA_KERN_RTKIT_BASE: u64 = 0xffffffae00000000;
-/// GPU/FW shared structure VA range top.
-const IOVA_KERN_RTKIT_TOP: u64 = 0xffffffae0fffffff;
+const IOVA_KERN_RTKIT_RANGE: Range<u64> = 0xffffffae00000000..0xffffffae10000000;
 /// FW MMIO VA range base.
-const IOVA_KERN_MMIO_BASE: u64 = 0xffffffaf00000000;
-/// FW MMIO VA range top.
-const IOVA_KERN_MMIO_TOP: u64 = 0xffffffafffffffff;
+const IOVA_KERN_MMIO_RANGE: Range<u64> = 0xffffffaf00000000..0xffffffb000000000;
 
 /// GPU/FW buffer manager control address (context 0 low)
 pub(crate) const IOVA_KERN_GPU_BUFMGR_LOW: u64 = 0x20_0000_0000;
@@ -355,8 +342,7 @@ impl rtkit::Operations for GpuManager::ver {
         obj.vmap()?;
         let mapping = obj.map_into_range(
             data.uat.kernel_vm(),
-            IOVA_KERN_RTKIT_BASE,
-            IOVA_KERN_RTKIT_TOP,
+            IOVA_KERN_RTKIT_RANGE,
             mmu::UAT_PGSZ as u64,
             mmu::PROT_FW_SHARED_RW,
             true,
@@ -382,8 +368,7 @@ impl GpuManager::ver {
             private: alloc::DefaultAllocator::new(
                 dev,
                 uat.kernel_vm(),
-                IOVA_KERN_PRIV_BASE,
-                IOVA_KERN_PRIV_TOP,
+                IOVA_KERN_PRIV_RANGE,
                 0x80,
                 mmu::PROT_FW_PRIV_RW,
                 1024 * 1024,
@@ -394,8 +379,7 @@ impl GpuManager::ver {
             shared: alloc::DefaultAllocator::new(
                 dev,
                 uat.kernel_vm(),
-                IOVA_KERN_SHARED_BASE,
-                IOVA_KERN_SHARED_TOP,
+                IOVA_KERN_SHARED_RANGE,
                 0x80,
                 mmu::PROT_FW_SHARED_RW,
                 1024 * 1024,
@@ -406,8 +390,7 @@ impl GpuManager::ver {
             shared_ro: alloc::DefaultAllocator::new(
                 dev,
                 uat.kernel_vm(),
-                IOVA_KERN_SHARED_RO_BASE,
-                IOVA_KERN_SHARED_RO_TOP,
+                IOVA_KERN_SHARED_RO_RANGE,
                 0x80,
                 mmu::PROT_FW_SHARED_RO,
                 64 * 1024,
@@ -418,8 +401,7 @@ impl GpuManager::ver {
             gpu: alloc::DefaultAllocator::new(
                 dev,
                 uat.kernel_vm(),
-                IOVA_KERN_GPU_BASE,
-                IOVA_KERN_GPU_TOP,
+                IOVA_KERN_GPU_RANGE,
                 0x80,
                 mmu::PROT_GPU_FW_SHARED_RW,
                 64 * 1024,
@@ -430,8 +412,7 @@ impl GpuManager::ver {
             gpu_ro: alloc::DefaultAllocator::new(
                 dev,
                 uat.kernel_vm(),
-                IOVA_KERN_GPU_RO_BASE,
-                IOVA_KERN_GPU_RO_TOP,
+                IOVA_KERN_GPU_RO_RANGE,
                 0x80,
                 mmu::PROT_GPU_RO_FW_PRIV_RW,
                 1024 * 1024,
@@ -593,7 +574,7 @@ impl GpuManager::ver {
         let addr = *next_ref;
         let next = addr + (size + mmu::UAT_PGSZ) as u64;
 
-        assert!(next - 1 <= IOVA_KERN_MMIO_TOP);
+        assert!(next <= IOVA_KERN_MMIO_RANGE.end);
 
         *next_ref = next;
 
@@ -703,34 +684,37 @@ impl GpuManager::ver {
         )?;
 
         let alloc_ref = &mut alloc;
-        let tx_channels = Box::init(try_init!(TxChannels::ver {
-            device_control: channel::DeviceControlChannel::ver::new(dev, alloc_ref)?,
-        }))?;
-
-        let x = UniqueArc::pin_init(try_pin_init!(GpuManager::ver {
-            dev: dev.into(),
-            cfg,
-            dyncfg: *dyncfg,
-            initdata: *initdata,
-            uat: *uat,
-            io_mappings: Vec::new(),
-            next_mmio_iova: IOVA_KERN_MMIO_BASE,
-            rtkit <- Mutex::new_named(None, c_str!("rtkit")),
-            crashed: AtomicBool::new(false),
-            event_manager,
-            alloc <- Mutex::new_named(alloc, c_str!("alloc")),
-            fwctl_channel <- Mutex::new_named(fwctl_channel, c_str!("fwctl_channel")),
-            rx_channels <- Mutex::new_named(*rx_channels, c_str!("rx_channels")),
-            tx_channels <- Mutex::new_named(*tx_channels, c_str!("tx_channels")),
-            pipes,
-            buffer_mgr,
-            ids: Default::default(),
-            garbage_work <- Mutex::new_named(Vec::new(), c_str!("garbage_work")),
-            garbage_contexts <- Mutex::new_named(Vec::new(), c_str!("garbage_contexts")),
-        }))?;
+        let tx_channels = Box::init(
+            try_init!(TxChannels::ver {
+                device_control: channel::DeviceControlChannel::ver::new(dev, alloc_ref)?,
+            }),
+            GFP_KERNEL,
+        )?;
 
-        Ok(x)
-    }
+        let x = UniqueArc::pin_init(
+            try_pin_init!(GpuManager::ver {
+                dev: dev.into(),
+                cfg,
+                dyncfg: *dyncfg,
+                initdata: *initdata,
+                uat: *uat,
+                io_mappings: Vec::new(),
+                next_mmio_iova: IOVA_KERN_MMIO_RANGE.start,
+                rtkit <- Mutex::new_named(None, c_str!("rtkit")),
+                crashed: AtomicBool::new(false),
+                event_manager,
+                alloc <- Mutex::new_named(alloc, c_str!("alloc")),
+                fwctl_channel <- Mutex::new_named(fwctl_channel, c_str!("fwctl_channel")),
+                rx_channels <- Mutex::new_named(*rx_channels, c_str!("rx_channels")),
+                tx_channels <- Mutex::new_named(*tx_channels, c_str!("tx_channels")),
+                pipes,
+                buffer_mgr,
+                ids: Default::default(),
+                garbage_work <- Mutex::new_named(Vec::new(), c_str!("garbage_work")),
+                garbage_contexts <- Mutex::new_named(Vec::new(), c_str!("garbage_contexts")),
+            }),
+            GFP_KERNEL,
+        )?;
 
         Ok(x)
     }
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 09218036afcfc2..b796b45fdeb740 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -1072,8 +1072,7 @@ impl Vm {
         size: usize,
         gem: &gem::Object,
         alignment: u64,
-        start: u64,
-        end: u64,
+        range: Range<u64>,
         prot: u32,
         guard: bool,
     ) -> Result<KernelMapping> {
@@ -1099,8 +1098,8 @@ impl Vm {
             (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page
             alignment,
             0,
-            start,
-            end,
+            range.start,
+            range.end,
             mm::InsertMode::Best,
         )?;
 

From 39548373c211252158cc0680eede118eb6a074bb Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 15 Jun 2024 13:48:03 +0900
Subject: [PATCH 0988/1027] drm/asahi: mmu: Fix lockdep issues with GpuVm

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/mmu.rs | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index b796b45fdeb740..e847effb1515c2 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -18,8 +18,8 @@ use core::sync::atomic::{fence, AtomicU32, AtomicU64, AtomicU8, Ordering};
 use core::time::Duration;
 
 use kernel::{
-    bindings, c_str, delay, device,
-    drm::{gpuvm, mm},
+    bindings, c_str, delay, device, drm,
+    drm::{gem::BaseObject, gpuvm, mm},
     error::{to_result, Result},
     io_pgtable,
     io_pgtable::{prot, AppleUAT, IoPageTable},
@@ -579,11 +579,16 @@ impl Clone for VmBind {
 
 /// Inner data required for an object mapping into a [`Vm`].
 pub(crate) struct KernelMappingInner {
+    // Drop order matters:
+    // - Drop the GpuVmBo first, which resv locks its BO and drops a GpuVm reference
+    // - Drop the GEM BO next, since BO free can take the resv lock itself
+    // - Drop the owner GpuVm last, since that again can take resv locks when the refcount drops to 0
+    bo: Option<ARef<gpuvm::GpuVmBo<VmInner>>>,
+    _gem: Option<drm::gem::ObjectRef<gem::Object>>,
     owner: ARef<gpuvm::GpuVm<VmInner>>,
     uat_inner: Arc<UatInner>,
     prot: u32,
     mapped_size: usize,
-    bo: Option<ARef<gpuvm::GpuVmBo<VmInner>>>,
 }
 
 /// An object mapping into a [`Vm`], which reserves the address range from use by other mappings.
@@ -1093,6 +1098,7 @@ impl Vm {
                 uat_inner,
                 prot,
                 bo: Some(vm_bo),
+                _gem: Some(gem.reference()),
                 mapped_size: size,
             },
             (size + if guard { UAT_PGSZ } else { 0 }) as u64, // Add guard page
@@ -1135,6 +1141,7 @@ impl Vm {
                 uat_inner,
                 prot,
                 bo: Some(vm_bo),
+                _gem: Some(gem.reference()),
                 mapped_size: size,
             },
             addr,
@@ -1235,6 +1242,7 @@ impl Vm {
                 uat_inner,
                 prot,
                 bo: None,
+                _gem: None,
                 mapped_size: size,
             },
             iova,
@@ -1274,6 +1282,9 @@ impl Vm {
             mod_dev_dbg!(inner.dev, "MMU: bo_unmap\n");
             inner.bo_unmap(&mut ctx, &bo)?;
             mod_dev_dbg!(inner.dev, "MMU: bo_unmap done\n");
+            // We need to drop the exec_lock first, then the GpuVmBo since that will take the lock itself.
+            core::mem::drop(inner);
+            core::mem::drop(bo);
         }
 
         Ok(())

From c3c2a9eccb10f1f245aa53ae168d09c7cb465346 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Jun 2024 16:11:09 +0900
Subject: [PATCH 0989/1027] rust: drm: gem: shmem: Add share_dma_resv()
 function

Allow a GEM object to share another object's DMA reservation, for use
with drm_gpuvm. To keep memory safety, we hold a reference to the GEM
object owning the resv, and drop it when the child object is freed.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/kernel/drm/gem/shmem.rs | 33 ++++++++++++++++++++++++++++++++-
 1 file changed, 32 insertions(+), 1 deletion(-)

diff --git a/rust/kernel/drm/gem/shmem.rs b/rust/kernel/drm/gem/shmem.rs
index 93f47a871c8720..4b2a46d6374ebb 100644
--- a/rust/kernel/drm/gem/shmem.rs
+++ b/rust/kernel/drm/gem/shmem.rs
@@ -17,7 +17,7 @@ use core::{
     slice,
 };
 
-use gem::BaseObject;
+use gem::{BaseObject, IntoGEMObject};
 
 /// Trait which must be implemented by drivers using shmem-backed GEM objects.
 pub trait DriverObject: gem::BaseDriverObject<Object<Self>> {
@@ -72,6 +72,8 @@ pub struct Object<T: DriverObject> {
     // The DRM core ensures the Device exists as long as its objects exist, so we don't need to
     // manage the reference count here.
     dev: *const bindings::drm_device,
+    // Parent object that owns this object's DMA reservation object
+    parent_resv_obj: *const bindings::drm_gem_object,
     #[pin]
     inner: T,
 }
@@ -98,6 +100,7 @@ unsafe extern "C" fn gem_create_object<T: DriverObject>(
         // SAFETY: GEM ensures the device lives as long as its objects live
         inner <- T::new(unsafe { device::Device::borrow(dev)}, size),
         dev,
+        parent_resv_obj: core::ptr::null(),
     });
 
     // SAFETY: p is a valid pointer to an uninitialized Object<T>.
@@ -129,6 +132,15 @@ unsafe extern "C" fn free_callback<T: DriverObject>(obj: *mut bindings::drm_gem_
         core::ptr::drop_in_place(&mut (*p).inner);
     }
 
+    // SAFETY: parent_resv_obj is either NULL or a valid reference to the
+    // GEM object owning the DMA reservation for this object, which we drop
+    // here.
+    unsafe {
+        if !(*p).parent_resv_obj.is_null() {
+            bindings::drm_gem_object_put((*p).parent_resv_obj as *const _ as *mut _);
+        }
+    }
+
     // SAFETY: This pointer has to be valid, since p is valid
     unsafe {
         bindings::drm_gem_shmem_free(&mut (*p).obj);
@@ -231,6 +243,25 @@ impl<T: DriverObject> Object<T> {
         // SAFETY: mut_shmem always returns a valid pointer
         (unsafe { *self.mut_shmem() }).set_map_wc(map_wc);
     }
+
+    /// Share the dma_resv object from another GEM object.
+    ///
+    /// Should be called before the object is used/shared. Can only be called once.
+    pub fn share_dma_resv(&mut self, from_object: &impl IntoGEMObject) -> Result {
+        let from_obj = from_object.gem_obj();
+        if !self.parent_resv_obj.is_null() {
+            Err(EBUSY)
+        } else {
+            // SAFETY: from_obj is a valid object pointer per the trait Invariant.
+            unsafe {
+                bindings::drm_gem_object_get(from_obj as *const _ as *mut _);
+            }
+            self.parent_resv_obj = from_obj;
+            let gem = self.mut_gem_obj();
+            gem.resv = from_obj.resv;
+            Ok(())
+        }
+    }
 }
 
 impl<T: DriverObject> Deref for Object<T> {

From ce7833bb956fc30b54176a02056863450ce35531 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Jun 2024 16:13:14 +0900
Subject: [PATCH 0990/1027] drm/asahi: Implement GEM objects sharing a single
 DMA resv

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs |  6 +++---
 drivers/gpu/drm/asahi/gem.rs  | 33 ++++++++++++++-------------------
 drivers/gpu/drm/asahi/mmu.rs  | 13 ++++++++++---
 3 files changed, 27 insertions(+), 25 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index a7446d368c27cc..6e1f5a1b30508d 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -406,7 +406,7 @@ impl File {
             return Err(EINVAL);
         }
 
-        let vm_id = if data.flags & uapi::ASAHI_GEM_VM_PRIVATE != 0 {
+        let resv_obj = if data.flags & uapi::ASAHI_GEM_VM_PRIVATE != 0 {
             Some(
                 file.inner()
                     .vms()
@@ -414,13 +414,13 @@ impl File {
                     .ok_or(ENOENT)?
                     .borrow()
                     .vm
-                    .id(),
+                    .get_resv_obj(),
             )
         } else {
             None
         };
 
-        let bo = gem::new_object(device, data.size.try_into()?, data.flags, vm_id)?;
+        let bo = gem::new_object(device, data.size.try_into()?, data.flags, resv_obj.as_ref())?;
 
         let handle = bo.gem.create_handle(file)?;
         data.handle = handle;
diff --git a/drivers/gpu/drm/asahi/gem.rs b/drivers/gpu/drm/asahi/gem.rs
index 07772d414ab967..1d22ff0a390ae0 100644
--- a/drivers/gpu/drm/asahi/gem.rs
+++ b/drivers/gpu/drm/asahi/gem.rs
@@ -30,8 +30,6 @@ pub(crate) struct DriverObject {
     kernel: bool,
     /// Object creation flags.
     flags: u32,
-    /// VM ID for VM-private objects.
-    vm_id: Option<u64>,
     /// ID for debug
     id: u64,
 }
@@ -82,12 +80,10 @@ impl ObjectRef {
         prot: u32,
         guard: bool,
     ) -> Result<crate::mmu::KernelMapping> {
-        let vm_id = vm.id();
-
-        if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
+        // Only used for kernel objects now
+        if !self.gem.kernel {
             return Err(EINVAL);
         }
-
         vm.map_in_range(self.gem.size(), &self.gem, alignment, range, prot, guard)
     }
 
@@ -101,9 +97,7 @@ impl ObjectRef {
         prot: u32,
         guard: bool,
     ) -> Result<crate::mmu::KernelMapping> {
-        let vm_id = vm.id();
-
-        if self.gem.vm_id.is_some() && self.gem.vm_id != Some(vm_id) {
+        if self.gem.flags & uapi::ASAHI_GEM_VM_PRIVATE != 0 && vm.is_extobj(&self.gem) {
             return Err(EINVAL);
         }
 
@@ -128,21 +122,23 @@ pub(crate) fn new_object(
     dev: &AsahiDevice,
     size: usize,
     flags: u32,
-    vm_id: Option<u64>,
+    parent_object: Option<&gem::ObjectRef<Object>>,
 ) -> Result<ObjectRef> {
+    if (flags & uapi::ASAHI_GEM_VM_PRIVATE != 0) != parent_object.is_some() {
+        return Err(EINVAL);
+    }
+
     let mut gem = shmem::Object::<DriverObject>::new(dev, align(size, mmu::UAT_PGSZ))?;
     gem.kernel = false;
     gem.flags = flags;
-    gem.vm_id = vm_id;
 
-    gem.set_exportable(vm_id.is_none());
+    gem.set_exportable(parent_object.is_none());
     gem.set_wc(flags & uapi::ASAHI_GEM_WRITEBACK == 0);
+    if let Some(parent) = parent_object {
+        gem.share_dma_resv(&**parent)?;
+    }
 
-    mod_pr_debug!(
-        "DriverObject new user object: vm_id={:?} id={}\n",
-        vm_id,
-        gem.id
-    );
+    mod_pr_debug!("DriverObject new user object: id={}\n", gem.id);
     Ok(ObjectRef::new(gem.into_ref()))
 }
 
@@ -161,14 +157,13 @@ impl gem::BaseDriverObject<Object> for DriverObject {
         try_pin_init!(DriverObject {
             kernel: false,
             flags: 0,
-            vm_id: None,
             id,
         })
     }
 
     /// Callback to drop all mappings for a GEM object owned by a given `File`
     fn close(obj: &Object, file: &DrmFile) {
-        mod_pr_debug!("DriverObject::close vm_id={:?} id={}\n", obj.vm_id, obj.id);
+        mod_pr_debug!("DriverObject::close id={}\n", obj.id);
         if file::File::unbind_gem_object(file, obj).is_err() {
             pr_err!("DriverObject::close: Failed to unbind GEM object\n");
         }
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index e847effb1515c2..0a7d639cbc1dde 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -522,6 +522,7 @@ impl VmInner {
 pub(crate) struct Vm {
     id: u64,
     inner: ARef<gpuvm::GpuVm<VmInner>>,
+    dummy_obj: drm::gem::ObjectRef<gem::Object>,
     binding: Arc<Mutex<VmBinding>>,
 }
 no_debug!(Vm);
@@ -1044,6 +1045,7 @@ impl Vm {
         let binding_clone = binding.clone();
         Ok(Vm {
             id,
+            dummy_obj: dummy_obj.gem.clone(),
             inner: gpuvm::GpuVm::new(
                 c_str!("Asahi::GpuVm"),
                 dev,
@@ -1290,9 +1292,14 @@ impl Vm {
         Ok(())
     }
 
-    /// Returns the unique ID of this Vm
-    pub(crate) fn id(&self) -> u64 {
-        self.id
+    /// Returns the dummy GEM object used to hold the shared DMA reservation locks
+    pub(crate) fn get_resv_obj(&self) -> drm::gem::ObjectRef<gem::Object> {
+        self.dummy_obj.clone()
+    }
+
+    /// Check whether an object is external to this GpuVm
+    pub(crate) fn is_extobj(&self, gem: &gem::Object) -> bool {
+        self.inner.is_extobj(gem)
     }
 }
 

From 6fe84dd6e380de60795ccc54664cf41c8fa191c5 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 11 Jul 2024 21:15:14 +0900
Subject: [PATCH 0991/1027] drm/asahi: queue: Split into Queue and QueueInner

Work around mutability issues when entity.new_job() takes a mutable
reference to the entity by moving all the fields used by the
submit_render() and submit_compute() functions to an inner struct,
eliminating the double-mutable-borrow.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/compute.rs |  2 +-
 drivers/gpu/drm/asahi/queue/mod.rs     | 61 ++++++++++++++++----------
 drivers/gpu/drm/asahi/queue/render.rs  |  2 +-
 3 files changed, 39 insertions(+), 26 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index ad4f6b4b546ad4..b5c1e3329dd3bf 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -27,7 +27,7 @@ use kernel::user_ptr::UserSlicePtr;
 const DEBUG_CLASS: DebugFlags = DebugFlags::Compute;
 
 #[versions(AGX)]
-impl super::Queue::ver {
+impl super::QueueInner::ver {
     /// Submit work to a compute queue.
     pub(super) fn submit_compute(
         &self,
diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
index fd0b3983d018fb..07df5cab29c1d9 100644
--- a/drivers/gpu/drm/asahi/queue/mod.rs
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -99,16 +99,22 @@ pub(crate) struct Queue {
     _sched: sched::Scheduler<QueueJob::ver>,
     entity: sched::Entity<QueueJob::ver>,
     vm: mmu::Vm,
-    ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
     q_vtx: Option<SubQueue::ver>,
     q_frag: Option<SubQueue::ver>,
     q_comp: Option<SubQueue::ver>,
+    fence_ctx: FenceContexts,
+    inner: QueueInner::ver,
+}
+
+#[versions(AGX)]
+pub(crate) struct QueueInner {
+    dev: AsahiDevRef,
+    ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
     buffer: Option<buffer::Buffer::ver>,
     gpu_context: Arc<workqueue::GpuContext>,
     notifier_list: Arc<GpuObject<fw::event::NotifierList>>,
     notifier: Arc<GpuObject<fw::event::Notifier::ver>>,
     id: u64,
-    fence_ctx: FenceContexts,
     #[ver(V >= V13_0B4)]
     counter: AtomicU64,
 }
@@ -430,22 +436,25 @@ impl Queue::ver {
             _sched: sched,
             entity,
             vm,
-            ualloc,
             q_vtx: None,
             q_frag: None,
             q_comp: None,
-            gpu_context: Arc::try_new(workqueue::GpuContext::new(
-                dev,
-                alloc,
-                buffer.as_ref().map(|b| b.any_ref()),
-            )?)?,
-            buffer,
-            notifier_list: Arc::try_new(notifier_list)?,
-            notifier,
-            id,
             fence_ctx: FenceContexts::new(1, QUEUE_NAME, QUEUE_CLASS_KEY)?,
-            #[ver(V >= V13_0B4)]
-            counter: AtomicU64::new(0),
+            inner: QueueInner::ver {
+                dev: dev.into(),
+                ualloc,
+                gpu_context: Arc::new(
+                    workqueue::GpuContext::new(dev, alloc, buffer.as_ref().map(|b| b.any_ref()))?,
+                    GFP_KERNEL,
+                )?,
+
+                buffer,
+                notifier_list: Arc::new(notifier_list, GFP_KERNEL)?,
+                notifier,
+                id,
+                #[ver(V >= V13_0B4)]
+                counter: AtomicU64::new(0),
+            },
         };
 
         // Rendering structures
@@ -455,15 +464,19 @@ impl Queue::ver {
                 *crate::initial_tvb_size.read(&lock)
             };
 
-            ret.buffer.as_ref().unwrap().ensure_blocks(tvb_blocks)?;
+            ret.inner
+                .buffer
+                .as_ref()
+                .unwrap()
+                .ensure_blocks(tvb_blocks)?;
 
             ret.q_vtx = Some(SubQueue::ver {
                 wq: workqueue::WorkQueue::ver::new(
                     dev,
                     alloc,
                     event_manager.clone(),
-                    ret.gpu_context.clone(),
-                    ret.notifier_list.clone(),
+                    ret.inner.gpu_context.clone(),
+                    ret.inner.notifier_list.clone(),
                     channel::PipeType::Vertex,
                     id,
                     priority,
@@ -483,8 +496,8 @@ impl Queue::ver {
                     dev,
                     alloc,
                     event_manager.clone(),
-                    ret.gpu_context.clone(),
-                    ret.notifier_list.clone(),
+                    ret.inner.gpu_context.clone(),
+                    ret.inner.notifier_list.clone(),
                     channel::PipeType::Fragment,
                     id,
                     priority,
@@ -500,8 +513,8 @@ impl Queue::ver {
                     dev,
                     alloc,
                     event_manager,
-                    ret.gpu_context.clone(),
-                    ret.notifier_list.clone(),
+                    ret.inner.gpu_context.clone(),
+                    ret.inner.notifier_list.clone(),
                     channel::PipeType::Compute,
                     id,
                     priority,
@@ -736,7 +749,7 @@ impl Queue for Queue::ver {
 
             match cmd.cmd_type {
                 uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_RENDER => {
-                    self.submit_render(
+                    self.inner.submit_render(
                         &mut job,
                         &cmd,
                         result_writer,
@@ -757,7 +770,7 @@ impl Queue for Queue::ver {
                     )?;
                 }
                 uapi::drm_asahi_cmd_type_DRM_ASAHI_CMD_COMPUTE => {
-                    self.submit_compute(
+                    self.inner.submit_compute(
                         &mut job,
                         &cmd,
                         result_writer,
@@ -807,6 +820,6 @@ impl Queue for Queue::ver {
 #[versions(AGX)]
 impl Drop for Queue::ver {
     fn drop(&mut self) {
-        mod_dev_dbg!(self.dev, "[Queue {}] Dropping queue\n", self.id);
+        mod_dev_dbg!(self.dev, "[Queue {}] Dropping queue\n", self.inner.id);
     }
 }
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 1f59ceefb09a29..20f569dd373b4b 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -68,7 +68,7 @@ impl RenderResult {
 }
 
 #[versions(AGX)]
-impl super::Queue::ver {
+impl super::QueueInner::ver {
     /// Get the appropriate tiling parameters for a given userspace command buffer.
     fn get_tiling_params(
         cmdbuf: &uapi::drm_asahi_cmd_render,

From a6345d4073f48b51631a0914a153d159df791d3a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 17 Jul 2024 19:20:16 +0900
Subject: [PATCH 0992/1027] drm/asahi: workqueue: Tweak some debug logs

Silence some logs that are possible and harmless in rare cases, add more
debug to another log that is questionable.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 29 +++++++++++++++++++++++++----
 1 file changed, 25 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 146d09a92ae948..d88e38642e9e8a 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -371,7 +371,16 @@ impl Job::ver {
         } else if let Some(work) = inner.pending.first() {
             Some(work.get_fence())
         } else {
-            pr_err!("WorkQueue: Cannot submit, but queue is empty?\n");
+            pr_err!(
+                "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?})\n",
+                inner.free_slots(),
+                self.event_count,
+                inner.free_space(),
+                self.pending.len(),
+                inner.pending.len(),
+                inner.last_submitted,
+                inner.last_completed,
+            );
             None
         }
     }
@@ -752,7 +761,11 @@ impl WorkQueue for WorkQueue::ver {
         let event = inner.event.as_ref();
         let value = match event {
             None => {
-                pr_err!("WorkQueue: signal() called but no event?\n");
+                mod_pr_debug!("WorkQueue: signal() called but no event?\n");
+
+                if inner.pending_jobs > 0 || !inner.pending.is_empty() {
+                    pr_crit!("WorkQueue: signal() called with no event and pending jobs.\n");
+                }
                 return true;
             }
             Some(event) => event.0.current(),
@@ -849,7 +862,11 @@ impl WorkQueue for WorkQueue::ver {
         let mut inner = self.inner.lock();
 
         if inner.event.is_none() {
-            pr_err!("WorkQueue: signal_fault() called but no event?\n");
+            mod_pr_debug!("WorkQueue: signal_fault() called but no event?\n");
+
+            if inner.pending_jobs > 0 || !inner.pending.is_empty() {
+                pr_crit!("WorkQueue: signal_fault() called with no event and pending jobs.\n");
+            }
             return;
         }
 
@@ -878,7 +895,11 @@ impl WorkQueue for WorkQueue::ver {
         let mut inner = self.inner.lock();
 
         if inner.event.is_none() {
-            pr_err!("WorkQueue: fail_all() called but no event?\n");
+            mod_pr_debug!("WorkQueue: fail_all() called but no event?\n");
+
+            if inner.pending_jobs > 0 || !inner.pending.is_empty() {
+                pr_crit!("WorkQueue: fail_all() called with no event and pending jobs.\n");
+            }
             return;
         }
 

From ec62cd88770a0150f36ca2c4d12f7a4d3d2bd638 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 27 Jul 2024 11:01:12 +0200
Subject: [PATCH 0993/1027] drm/asahi: run rustfmt

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/gpu/drm/asahi/file.rs | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 6e1f5a1b30508d..4b611123fa5e98 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -355,12 +355,15 @@ impl File {
             dummy_obj.map_at(&vm, mmu::IOVA_UNK_PAGE, mmu::PROT_GPU_SHARED_RW, true)?;
 
         mod_dev_dbg!(device, "[File {} VM {}]: VM created\n", file_id, id);
-        resv.store(Box::new(Vm {
-            ualloc,
-            ualloc_priv,
-            vm,
-            _dummy_mapping: dummy_mapping,
-        }, GFP_KERNEL,)?)?;
+        resv.store(Box::new(
+            Vm {
+                ualloc,
+                ualloc_priv,
+                vm,
+                _dummy_mapping: dummy_mapping,
+            },
+            GFP_KERNEL,
+        )?)?;
 
         data.vm_id = id;
 

From 10d7adb7636cbf9897c050b7b25f9dcccd474928 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Fri, 10 May 2024 19:18:33 +0900
Subject: [PATCH 0994/1027] drm/asahi: file: Update to newer VM_BIND API

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs          | 141 +++++++++++++------------
 drivers/gpu/drm/asahi/gpu.rs           |   6 +-
 drivers/gpu/drm/asahi/mmu.rs           |  28 +++--
 drivers/gpu/drm/asahi/queue/compute.rs |   4 +-
 drivers/gpu/drm/asahi/queue/render.rs  |   8 +-
 5 files changed, 99 insertions(+), 88 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 4b611123fa5e98..587cb4d424b812 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -9,8 +9,9 @@
 
 use crate::debug::*;
 use crate::driver::AsahiDevice;
-use crate::{alloc, buffer, driver, gem, mmu, queue};
+use crate::{alloc, buffer, driver, gem, mmu, queue, util::RangeExt};
 use core::mem::MaybeUninit;
+use core::ops::Range;
 use kernel::dma_fence::RawDmaFence;
 use kernel::drm::gem::BaseObject;
 use kernel::error::code::*;
@@ -30,16 +31,29 @@ struct Vm {
     ualloc: Arc<Mutex<alloc::DefaultAllocator>>,
     ualloc_priv: Arc<Mutex<alloc::DefaultAllocator>>,
     vm: mmu::Vm,
+    kernel_range: Range<u64>,
     _dummy_mapping: mmu::KernelMapping,
 }
 
 impl Drop for Vm {
     fn drop(&mut self) {
         // When the user Vm is dropped, unmap everything in the user range
-        if self
-            .vm
-            .unmap_range(mmu::IOVA_USER_BASE, VM_USER_END)
-            .is_err()
+        let left_range = VM_USER_RANGE.start..self.kernel_range.start;
+        let right_range = self.kernel_range.end..VM_USER_RANGE.end;
+
+        if !left_range.is_empty()
+            && self
+                .vm
+                .unmap_range(left_range.start, left_range.range())
+                .is_err()
+        {
+            pr_err!("Vm::Drop: vm.unmap_range() failed\n");
+        }
+        if !right_range.is_empty()
+            && self
+                .vm
+                .unmap_range(right_range.start, right_range.range())
+                .is_err()
         {
             pr_err!("Vm::Drop: vm.unmap_range() failed\n");
         }
@@ -153,23 +167,11 @@ pub(crate) struct File {
 /// Convenience type alias for our DRM `File` type.
 pub(crate) type DrmFile = drm::file::File<File>;
 
-/// Start address of the 32-bit USC address space.
-const VM_SHADER_START: u64 = 0x11_00000000;
-/// End address of the 32-bit USC address space.
-const VM_SHADER_END: u64 = 0x11_ffffffff;
-/// Start address of the general user mapping region.
-const VM_USER_START: u64 = 0x20_00000000;
-/// End address of the general user mapping region.
-const VM_USER_END: u64 = 0x6f_ffff0000;
-
-/// Start address of the kernel-managed GPU-only mapping region.
-const VM_DRV_GPU_START: u64 = 0x70_00000000;
-/// End address of the kernel-managed GPU-only mapping region.
-const VM_DRV_GPU_END: u64 = 0x70_ffffffff;
-/// Start address of the kernel-managed GPU/FW shared mapping region.
-const VM_DRV_GPUFW_START: u64 = 0x71_00000000;
-/// End address of the kernel-managed GPU/FW shared mapping region.
-const VM_DRV_GPUFW_END: u64 = 0x71_ffffffff;
+/// Available VM range for the user
+const VM_USER_RANGE: Range<u64> = mmu::IOVA_USER_USABLE_RANGE;
+
+/// Minimum reserved AS for kernel mappings
+const VM_KERNEL_MIN_SIZE: u64 = 0x20000000;
 
 impl drm::file::DriverFile for File {
     type Driver = driver::AsahiDriver;
@@ -248,10 +250,11 @@ impl File {
 
             vm_page_size: mmu::UAT_PGSZ as u32,
             pad1: 0,
-            vm_user_start: VM_USER_START,
-            vm_user_end: VM_USER_END,
-            vm_shader_start: VM_SHADER_START,
-            vm_shader_end: VM_SHADER_END,
+            vm_user_start: VM_USER_RANGE.start,
+            vm_user_end: VM_USER_RANGE.end,
+            vm_usc_start: 0, // Arbitrary
+            vm_usc_end: 0,
+            vm_kernel_min_size: VM_KERNEL_MIN_SIZE,
 
             max_syncs_per_submission: 0,
             max_commands_per_submission: MAX_COMMANDS_PER_SUBMISSION,
@@ -300,9 +303,25 @@ impl File {
             return Err(EINVAL);
         }
 
+        let kernel_range = data.kernel_start..data.kernel_end;
+
+        // Validate requested kernel range
+        if !VM_USER_RANGE.is_superset(kernel_range.clone())
+            || kernel_range.range() < VM_KERNEL_MIN_SIZE
+            || kernel_range.start & (mmu::UAT_PGMSK as u64) != 0
+            || kernel_range.end & (mmu::UAT_PGMSK as u64) != 0
+        {
+            cls_pr_debug!(Errors, "vm_create: Invalid kernel range\n");
+            return Err(EINVAL);
+        }
+
+        let kernel_half_size = (kernel_range.range() >> 1) & !(mmu::UAT_PGMSK as u64);
+        let kernel_gpu_range = kernel_range.start..(kernel_range.start + kernel_half_size);
+        let kernel_gpufw_range = kernel_gpu_range.end..kernel_range.end;
+
         let gpu = &device.data().gpu;
         let file_id = file.inner().id;
-        let vm = gpu.new_vm()?;
+        let vm = gpu.new_vm(kernel_range.clone())?;
 
         let resv = file.inner().vms().reserve()?;
         let id: u32 = resv.index().try_into()?;
@@ -318,7 +337,7 @@ impl File {
             Mutex::new(alloc::DefaultAllocator::new(
                 device,
                 &vm,
-                VM_DRV_GPU_START..VM_DRV_GPU_END,
+                kernel_gpu_range,
                 buffer::PAGE_SIZE,
                 mmu::PROT_GPU_SHARED_RW,
                 512 * 1024,
@@ -332,7 +351,7 @@ impl File {
             Mutex::new(alloc::DefaultAllocator::new(
                 device,
                 &vm,
-                VM_DRV_GPUFW_START..VM_DRV_GPUFW_END,
+                kernel_gpufw_range,
                 buffer::PAGE_SIZE,
                 mmu::PROT_GPU_FW_PRIV_RW,
                 64 * 1024,
@@ -360,6 +379,7 @@ impl File {
                 ualloc,
                 ualloc_priv,
                 vm,
+                kernel_range,
                 _dummy_mapping: dummy_mapping,
             },
             GFP_KERNEL,
@@ -522,47 +542,17 @@ impl File {
         let bo = gem::lookup_handle(file, data.handle)?;
 
         let start = data.addr;
-        let end = data.addr + data.range - 1;
-
-        if (VM_SHADER_START..=VM_SHADER_END).contains(&start) {
-            if !(VM_SHADER_START..=VM_SHADER_END).contains(&end) {
-                cls_pr_debug!(
-                    Errors,
-                    "gem_bind: Invalid map range {:#x}..{:#x} (straddles shader range)\n",
-                    start,
-                    end
-                );
-                return Err(EINVAL); // Invalid map range
-            }
-        } else if (VM_USER_START..=VM_USER_END).contains(&start) {
-            if !(VM_USER_START..=VM_USER_END).contains(&end) {
-                cls_pr_debug!(
-                    Errors,
-                    "gem_bind: Invalid map range {:#x}..{:#x} (straddles user range)\n",
-                    start,
-                    end
-                );
-                return Err(EINVAL); // Invalid map range
-            }
-        } else {
-            cls_pr_debug!(
-                Errors,
-                "gem_bind: Invalid map range {:#x}..{:#x}\n",
-                start,
-                end
-            );
-            return Err(EINVAL); // Invalid map range
-        }
+        let end = data.addr.checked_add(data.range).ok_or(EINVAL)?;
+        let range = start..end;
 
-        // Just in case
-        if end >= VM_DRV_GPU_START {
+        if !VM_USER_RANGE.is_superset(range.clone()) {
             cls_pr_debug!(
                 Errors,
-                "gem_bind: Invalid map range {:#x}..{:#x} (intrudes in kernel range)\n",
+                "gem_bind: Invalid map range {:#x}..{:#x} (not contained in user range)\n",
                 start,
                 end
             );
-            return Err(EINVAL);
+            return Err(EINVAL); // Invalid map range
         }
 
         let prot = if data.flags & uapi::ASAHI_BIND_READ != 0 {
@@ -582,15 +572,26 @@ impl File {
             return Err(EINVAL); // Must specify one of ASAHI_BIND_{READ,WRITE}
         };
 
-        // Clone it immediately so we aren't holding the XArray lock
-        let vm = file
+        let guard = file
             .inner()
             .vms()
             .get(data.vm_id.try_into()?)
-            .ok_or(ENOENT)?
-            .borrow()
-            .vm
-            .clone();
+            .ok_or(ENOENT)?;
+
+        // Clone it immediately so we aren't holding the XArray lock
+        let vm = guard.borrow().vm.clone();
+        let kernel_range = guard.borrow().kernel_range.clone();
+        core::mem::drop(guard);
+
+        if kernel_range.overlaps(range) {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Invalid map range {:#x}..{:#x} (intrudes in kernel range)\n",
+                start,
+                end
+            );
+            return Err(EINVAL);
+        }
 
         vm.bind_object(&bo.gem, data.addr, data.range, data.offset, prot)?;
 
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index c0550b294ab0b8..0d489ef4ce7b61 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -229,7 +229,7 @@ pub(crate) trait GpuManager: Send + Sync {
     /// Get a reference to the KernelAllocators.
     fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend>;
     /// Create a new `Vm` given a unique `File` ID.
-    fn new_vm(&self) -> Result<mmu::Vm>;
+    fn new_vm(&self, kernel_range: Range<u64>) -> Result<mmu::Vm>;
     /// Bind a `Vm` to an available slot and return the `VmBind`.
     fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind>;
     /// Create a new user command queue.
@@ -1200,8 +1200,8 @@ impl GpuManager for GpuManager::ver {
         guard
     }
 
-    fn new_vm(&self) -> Result<mmu::Vm> {
-        self.uat.new_vm(self.ids.vm.next())
+    fn new_vm(&self, kernel_range: Range<u64>) -> Result<mmu::Vm> {
+        self.uat.new_vm(self.ids.vm.next(), kernel_range)
     }
 
     fn bind_vm(&self, vm: &mmu::Vm) -> Result<mmu::VmBind> {
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 0a7d639cbc1dde..3a97a2d5304254 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -90,6 +90,8 @@ const PTE_TABLE: u64 = 0x3; // BIT(0) | BIT(1)
 /// Address of a special dummy page?
 //const IOVA_UNK_PAGE: u64 = 0x6f_ffff8000;
 pub(crate) const IOVA_UNK_PAGE: u64 = IOVA_USER_TOP - 2 * UAT_PGSZ as u64;
+/// User VA range excluding the unk page
+pub(crate) const IOVA_USER_USABLE_RANGE: Range<u64> = IOVA_USER_BASE..IOVA_UNK_PAGE;
 
 // KernelMapping protection types
 
@@ -1004,6 +1006,7 @@ impl Vm {
     fn new(
         dev: &driver::AsahiDevice,
         uat_inner: Arc<UatInner>,
+        kernel_range: Range<u64>,
         cfg: &'static hw::HwConfig,
         is_kernel: bool,
         id: u64,
@@ -1021,10 +1024,10 @@ impl Vm {
             },
             (),
         )?;
-        let va_range = if is_kernel {
-            IOVA_KERN_RANGE
+        let (va_range, gpuvm_range) = if is_kernel {
+            (IOVA_KERN_RANGE, kernel_range.clone())
         } else {
-            IOVA_USER_RANGE
+            (IOVA_USER_RANGE, IOVA_USER_USABLE_RANGE)
         };
 
         let mm = mm::Allocator::new(va_range.start, va_range.range(), ())?;
@@ -1050,8 +1053,8 @@ impl Vm {
                 c_str!("Asahi::GpuVm"),
                 dev,
                 &*(dummy_obj.gem),
-                va_range.clone(),
-                0..0,
+                gpuvm_range,
+                kernel_range,
                 init!(VmInner {
                     dev: dev.into(),
                     va_range,
@@ -1500,8 +1503,15 @@ impl Uat {
     }
 
     /// Creates a new `Vm` linked to this UAT.
-    pub(crate) fn new_vm(&self, id: u64) -> Result<Vm> {
-        Vm::new(&self.dev, self.inner.clone(), self.cfg, false, id)
+    pub(crate) fn new_vm(&self, id: u64, kernel_range: Range<u64>) -> Result<Vm> {
+        Vm::new(
+            &self.dev,
+            self.inner.clone(),
+            kernel_range,
+            self.cfg,
+            false,
+            id,
+        )
     }
 
     /// Creates the reference-counted inner data for a new `Uat` instance.
@@ -1547,8 +1557,8 @@ impl Uat {
         let pagetables_rgn = Self::map_region(dev, c_str!("pagetables"), PAGETABLES_SIZE, true)?;
 
         dev_info!(dev, "MMU: Creating kernel page tables\n");
-        let kernel_lower_vm = Vm::new(dev, inner.clone(), cfg, false, 1)?;
-        let kernel_vm = Vm::new(dev, inner.clone(), cfg, true, 0)?;
+        let kernel_lower_vm = Vm::new(dev, inner.clone(), IOVA_USER_RANGE, cfg, false, 1)?;
+        let kernel_vm = Vm::new(dev, inner.clone(), IOVA_KERN_RANGE, cfg, true, 0)?;
 
         dev_info!(dev, "MMU: Kernel page tables created\n");
 
diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index b5c1e3329dd3bf..20ae88b8e5e52d 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -286,7 +286,7 @@ impl super::QueueInner::ver {
                         preempt_buf3: inner.preempt_buf.gpu_offset_pointer(preempt3_off),
                         preempt_buf4: inner.preempt_buf.gpu_offset_pointer(preempt4_off),
                         preempt_buf5: inner.preempt_buf.gpu_offset_pointer(preempt5_off),
-                        pipeline_base: U64(0x11_00000000),
+                        pipeline_base: U64(cmdbuf.usc_base),
                         unk_38: U64(0x8c60),
                         helper_program: cmdbuf.helper_program, // Internal program addr | 1
                         unk_44: 0,
@@ -309,7 +309,7 @@ impl super::QueueInner::ver {
                             r.add(0x1a4d8, inner.preempt_buf.gpu_offset_pointer(preempt3_off).into());
                             r.add(0x1a4e0, inner.preempt_buf.gpu_offset_pointer(preempt4_off).into());
                             r.add(0x1a4e8, inner.preempt_buf.gpu_offset_pointer(preempt5_off).into());
-                            r.add(0x10071, 0x1100000000); // USC_EXEC_BASE_CP
+                            r.add(0x10071, cmdbuf.usc_base); // USC_EXEC_BASE_CP
                             r.add(0x11841, cmdbuf.helper_program.into());
                             r.add(0x11849, cmdbuf.helper_arg);
                             r.add(0x11f81, cmdbuf.helper_cfg.into());
diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index 20f569dd373b4b..e04ab73a756af5 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -842,7 +842,7 @@ impl super::QueueInner::ver {
                         tile_config: U64(tile_config),
                         aux_fb: inner.aux_fb.gpu_pointer(),
                         unk_108: Default::default(),
-                        pipeline_base: U64(0x11_00000000),
+                        pipeline_base: U64(cmdbuf.fragment_usc_base),
                         unk_140: U64(unks.frg_unk_140),
                         helper_program: cmdbuf.fragment_helper_program,
                         unk_14c: 0,
@@ -947,7 +947,7 @@ impl super::QueueInner::ver {
                             r.add(0x11829, cmdbuf.fragment_helper_arg);
                             r.add(0x11f79, cmdbuf.fragment_helper_cfg.into());
                             r.add(0x15359, 0);
-                            r.add(0x10069, 0x11_00000000); // USC_EXEC_BASE_ISP
+                            r.add(0x10069, cmdbuf.fragment_usc_base); // USC_EXEC_BASE_ISP
                             r.add(0x16020, 0);
                             r.add(0x16461, inner.aux_fb.gpu_pointer().into());
                             r.add(0x16090, inner.aux_fb.gpu_pointer().into());
@@ -1344,7 +1344,7 @@ impl super::QueueInner::ver {
                         #[ver(G < G14)]
                         unk_ac: unks.tiling_control_2 as u32, // fixed
                         unk_b0: Default::default(), // fixed
-                        pipeline_base: U64(0x11_00000000),
+                        pipeline_base: U64(cmdbuf.vertex_usc_base),
                         #[ver(G < G14)]
                         tvb_cluster_meta4: inner
                             .scene
@@ -1436,7 +1436,7 @@ impl super::QueueInner::ver {
                             r.add(0x1c1a9, 0); // 0x10151 bit 1 enables
                             r.add(0x1c1b1, 0);
                             r.add(0x1c1b9, 0);
-                            r.add(0x10061, 0x11_00000000); // USC_EXEC_BASE_TA
+                            r.add(0x10061, cmdbuf.vertex_usc_base); // USC_EXEC_BASE_TA
                             r.add(0x11801, cmdbuf.vertex_helper_program.into());
                             r.add(0x11809, cmdbuf.vertex_helper_arg);
                             r.add(0x11f71, cmdbuf.vertex_helper_cfg.into());

From 3654d1559c92064db62b86ed4154a90bbef9cd33 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 22 Jun 2024 17:25:02 +0900
Subject: [PATCH 0995/1027] drm/asahi: Signal soft fault support to userspace

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs   | 6 +++++-
 drivers/gpu/drm/asahi/hw/mod.rs | 8 +++++++-
 2 files changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 587cb4d424b812..5baf5f54631a9a 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -9,7 +9,7 @@
 
 use crate::debug::*;
 use crate::driver::AsahiDevice;
-use crate::{alloc, buffer, driver, gem, mmu, queue, util::RangeExt};
+use crate::{alloc, buffer, driver, gem, hw, mmu, queue, util::RangeExt};
 use core::mem::MaybeUninit;
 use core::ops::Range;
 use kernel::dma_fence::RawDmaFence;
@@ -280,6 +280,10 @@ impl File {
             params.firmware_version[i] = *gpu.get_dyncfg().firmware_version.get(i).unwrap_or(&0);
         }
 
+        if *crate::fault_control.read() == 0xb {
+            params.feat_compat |= hw::feat::compat::SOFT_FAULTS;
+        }
+
         let size = core::mem::size_of::<uapi::drm_asahi_params_global>().min(data.size.try_into()?);
 
         // SAFETY: We only write to this userptr once, so there are no TOCTOU issues.
diff --git a/drivers/gpu/drm/asahi/hw/mod.rs b/drivers/gpu/drm/asahi/hw/mod.rs
index 5b55e660dfb1a1..2665dbccbec127 100644
--- a/drivers/gpu/drm/asahi/hw/mod.rs
+++ b/drivers/gpu/drm/asahi/hw/mod.rs
@@ -90,7 +90,13 @@ pub(crate) enum GpuRevisionID {
 /// GPU driver/hardware features, from the UABI.
 pub(crate) mod feat {
     /// Backwards-compatible features.
-    pub(crate) mod compat {}
+    pub(crate) mod compat {
+        use kernel::uapi;
+
+        /// Soft MMU faults enabled.
+        pub(crate) const SOFT_FAULTS: u64 =
+            uapi::drm_asahi_feat_compat_DRM_ASAHI_FEAT_SOFT_FAULTS as u64;
+    }
 
     /// Backwards-incompatible features.
     pub(crate) mod incompat {

From 0bac0541d858a58c9952f3d9cfda382c2a03df9a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sat, 27 Jul 2024 17:05:22 +0900
Subject: [PATCH 0996/1027] drm/asahi: Enable soft faults by default

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/asahi.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs
index 52fed0b9bb2b8a..1382e73177cc67 100644
--- a/drivers/gpu/drm/asahi/asahi.rs
+++ b/drivers/gpu/drm/asahi/asahi.rs
@@ -39,7 +39,7 @@ module_platform_driver! {
             description: "Debug flags",
         },
         fault_control: u32 {
-            default: 0,
+            default: 0xb,
             permissions: 0,
             description: "Fault control (0x0: hard faults, 0xb: macOS default)",
         },

From 8bc9070247d4b46fe98e8c40646d5e6edb70c8bb Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 29 Jul 2024 21:03:18 +0200
Subject: [PATCH 0997/1027] rust: kernel: net: phy: Fix Module::init()
 signature

fixup! rust: Add `name` argument to Module::init()

Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/kernel/net/phy.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/net/phy.rs b/rust/kernel/net/phy.rs
index 91dac63ffa170b..55e7d646d6e88c 100644
--- a/rust/kernel/net/phy.rs
+++ b/rust/kernel/net/phy.rs
@@ -887,7 +887,7 @@ macro_rules! module_phy_driver {
                 [$($crate::net::phy::create_phy_driver::<$driver>()),+];
 
             impl $crate::Module for Module {
-                fn init(module: &'static ThisModule) -> Result<Self> {
+                fn init(_: &'static kernel::prelude::CStr, module: &'static ThisModule) -> Result<Self> {
                     // SAFETY: The anonymous constant guarantees that nobody else can access
                     // the `DRIVERS` static. The array is used only in the C side.
                     let drivers = unsafe { &mut DRIVERS };

From 45b3995ab9aab00e5acb433a568a5ded71226d79 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 7 Aug 2024 04:36:32 +0900
Subject: [PATCH 0998/1027] drm/asahi: Fix u32 mult overflow on large
 tilebufs/TPCs

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/render.rs | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/render.rs b/drivers/gpu/drm/asahi/queue/render.rs
index e04ab73a756af5..889337528c72cf 100644
--- a/drivers/gpu/drm/asahi/queue/render.rs
+++ b/drivers/gpu/drm/asahi/queue/render.rs
@@ -88,6 +88,15 @@ impl super::QueueInner::ver {
             return Err(EINVAL);
         }
 
+        // Overflow safety: all these calculations are done in u32.
+        // At 64Kx64K max dimensions above, this is 2**32 pixels max.
+        // In terms of tiles that are always larger than one pixel,
+        // this can never overflow. Note that real actual dimensions
+        // are limited to 16K * 16K below anyway.
+        //
+        // Once we multiply by the layer count, then we need to check
+        // for overflow or use u64.
+
         let tile_width = 32u32;
         let tile_height = 32u32;
 
@@ -135,12 +144,13 @@ impl super::QueueInner::ver {
         let rgn_entry_size = 5;
         // Macrotile stride in 32-bit words
         let rgn_size = align(rgn_entry_size * tiles_per_mtile * utiles_per_tile, 4) / 4;
-        let tilemap_size = (4 * rgn_size * mtiles * layers) as usize;
+        let tilemap_size = (4 * rgn_size * mtiles) as usize * layers as usize;
 
         let tpc_entry_size = 8;
         // TPC stride in 32-bit words
         let tpc_mtile_stride = tpc_entry_size * utiles_per_tile * tiles_per_mtile / 4;
-        let tpc_size = (num_clusters * (4 * tpc_mtile_stride * mtiles) * layers) as usize;
+        let tpc_size =
+            (4 * tpc_mtile_stride * mtiles) as usize * layers as usize * num_clusters as usize;
 
         // No idea where this comes from, but it fits what macOS does...
         // GUESS: Number of 32K heap blocks to fit a 5-byte region header/pointer per tile?

From 04b4ad0a41cc3ce5550f16778af37c5b59543811 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 16 Sep 2024 13:08:07 +0200
Subject: [PATCH 0999/1027] rust: kernel::platform: Adapt to anon union for
 remove()/remove_new()

Expected to be dropped once upstream removes `remove_new().

Signed-off-by: Janne Grunau <j@jannau.net>
---
 rust/kernel/platform.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/rust/kernel/platform.rs b/rust/kernel/platform.rs
index 43865dc71fd6f4..f257eb8423d922 100644
--- a/rust/kernel/platform.rs
+++ b/rust/kernel/platform.rs
@@ -38,7 +38,7 @@ impl<T: Driver> driver::DriverOps for Adapter<T> {
 
         pdrv.driver.name = name.as_char_ptr();
         pdrv.probe = Some(Self::probe_callback);
-        pdrv.remove = Some(Self::remove_callback);
+        pdrv.__bindgen_anon_1.remove = Some(Self::remove_callback);
         if let Some(t) = T::OF_DEVICE_ID_TABLE {
             pdrv.driver.of_match_table = t.as_ref();
         }

From 2e6fee038139683c75c9479a0f97a00478a9356a Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Mon, 16 Sep 2024 13:20:09 +0200
Subject: [PATCH 1000/1027] rust: block: rnull: Adapt to asahi's rust base
 branch

Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/block/rnull.rs | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/block/rnull.rs b/drivers/block/rnull.rs
index b0227cf9ddd387..8681d77c7ddd5d 100644
--- a/drivers/block/rnull.rs
+++ b/drivers/block/rnull.rs
@@ -20,6 +20,7 @@ use kernel::{
     error::Result,
     new_mutex, pr_info,
     prelude::*,
+    str::CStr,
     sync::{Arc, Mutex},
     types::ARef,
 };
@@ -36,7 +37,7 @@ struct NullBlkModule {
 }
 
 impl kernel::Module for NullBlkModule {
-    fn init(_module: &'static ThisModule) -> Result<Self> {
+    fn init(_name: &'static CStr, _module: &'static ThisModule) -> Result<Self> {
         pr_info!("Rust null_blk loaded\n");
         let tagset = Arc::pin_init(TagSet::new(1, 256, 1), flags::GFP_KERNEL)?;
 

From 4974e69d5abe410f238b8ec08677595088eb3d45 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 00:08:43 +0900
Subject: [PATCH 1001/1027] drm/asahi: Workqueue: Add more debug

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 21 +++++++++++++++++----
 1 file changed, 17 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index d88e38642e9e8a..6010feba79135c 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -372,7 +372,7 @@ impl Job::ver {
             Some(work.get_fence())
         } else {
             pr_err!(
-                "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?})\n",
+                "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?}) ev={:#x?}\n",
                 inner.free_slots(),
                 self.event_count,
                 inner.free_space(),
@@ -380,6 +380,7 @@ impl Job::ver {
                 inner.pending.len(),
                 inner.last_submitted,
                 inner.last_completed,
+                inner.event.as_ref().map(|a| a.1),
             );
             None
         }
@@ -510,7 +511,7 @@ impl Drop for Job::ver {
         if self.committed && !self.submitted {
             let pipe_type = inner.pipe_type;
             let event = inner.event.as_mut().expect("Job lost its event");
-            mod_pr_debug!(
+            pr_info!(
                 "WorkQueue({:?}): Roll back {} events (slot {} val {:#x?}) and {} commands\n",
                 pipe_type,
                 self.event_count,
@@ -546,8 +547,8 @@ impl<'a> Drop for JobSubmission::ver<'a> {
 
         let pipe_type = inner.pipe_type;
         let event = inner.event.as_mut().expect("JobSubmission lost its event");
-        mod_pr_debug!(
-            "WorkQueue({:?}): Roll back {} events (slot {} val {:#x?}) and {} commands\n",
+        pr_info!(
+            "WorkQueue({:?}): JobSubmission: Roll back {} events (slot {} val {:#x?}) and {} commands\n",
             pipe_type,
             self.event_count,
             event.0.slot(),
@@ -771,6 +772,18 @@ impl WorkQueue for WorkQueue::ver {
             Some(event) => event.0.current(),
         };
 
+        if let Some(lc) = inner.last_completed {
+            if value < lc {
+                pr_err!(
+                    "WorkQueue: event rolled back? cur {:#x?}, lc {:#x?}, ls {:#x?}",
+                    value,
+                    inner.last_completed,
+                    inner.last_submitted
+                );
+            }
+        } else {
+            pr_crit!("WorkQueue: signal() called with no last_completed.\n");
+        }
         inner.last_completed = Some(value);
 
         mod_pr_debug!(

From 05ea9c7313aa7cf1fa2a9b0eeb7c9b62bdf5163a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 00:32:54 +0900
Subject: [PATCH 1002/1027] drm/asahi: Fix event tracking when JobSubmission is
 dropped

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 6010feba79135c..ab77fa12ad3f55 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -556,8 +556,10 @@ impl<'a> Drop for JobSubmission::ver<'a> {
             self.command_count
         );
         event.1.sub(self.event_count as u32);
+        let val = event.1;
         inner.commit_seq -= self.command_count as u64;
         inner.event_seq -= self.event_count as u64;
+        inner.last_submitted = Some(val);
         mod_pr_debug!("WorkQueue({:?}): Dropped JobSubmission\n", inner.pipe_type);
     }
 }

From 4ef80610e8ae483e2296dc1c1322fb15ed8a8612 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 03:28:40 +0900
Subject: [PATCH 1003/1027] drm/asahi: gpu: Show unknown field in timeouts

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/channel.rs | 5 ++---
 drivers/gpu/drm/asahi/gpu.rs     | 5 +++--
 2 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/drivers/gpu/drm/asahi/channel.rs b/drivers/gpu/drm/asahi/channel.rs
index e46c17f98a146f..709fb3d1dbe128 100644
--- a/drivers/gpu/drm/asahi/channel.rs
+++ b/drivers/gpu/drm/asahi/channel.rs
@@ -353,10 +353,10 @@ impl EventChannel::ver {
                         },
                         EventMsg::Timeout {
                             counter,
+                            unk_8,
                             event_slot,
-                            ..
                         } => match self.gpu.as_ref() {
-                            Some(gpu) => gpu.handle_timeout(counter, event_slot),
+                            Some(gpu) => gpu.handle_timeout(counter, event_slot, unk_8),
                             None => {
                                 dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
                             }
@@ -374,7 +374,6 @@ impl EventChannel::ver {
                             vm_slot,
                             buffer_slot,
                             counter,
-                            ..
                         } => match self.gpu.as_ref() {
                             Some(gpu) => {
                                 self.buf_mgr.grow(buffer_slot);
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 0d489ef4ce7b61..06f95d7c2c9699 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -253,7 +253,7 @@ pub(crate) trait GpuManager: Send + Sync {
     /// TODO: Does this actually work?
     fn flush_fw_cache(&self) -> Result;
     /// Handle a GPU work timeout event.
-    fn handle_timeout(&self, counter: u32, event_slot: i32);
+    fn handle_timeout(&self, counter: u32, event_slot: i32, unk: u32);
     /// Handle a GPU fault event.
     fn handle_fault(&self);
     /// Acknowledge a Buffer grow op.
@@ -1294,7 +1294,7 @@ impl GpuManager for GpuManager::ver {
         &self.ids
     }
 
-    fn handle_timeout(&self, counter: u32, event_slot: i32) {
+    fn handle_timeout(&self, counter: u32, event_slot: i32, unk: u32) {
         dev_err!(self.dev, " (\\________/) \n");
         dev_err!(self.dev, "  |        |  \n");
         dev_err!(self.dev, "'.| \\  , / |.'\n");
@@ -1304,6 +1304,7 @@ impl GpuManager for GpuManager::ver {
         dev_err!(self.dev, "** GPU timeout nya~!!!!! **\n");
         dev_err!(self.dev, "  Event slot: {}\n", event_slot);
         dev_err!(self.dev, "  Timeout count: {}\n", counter);
+        dev_err!(self.dev, "  Unk: {}\n", unk);
 
         // If we have fault info, consider it a fault.
         let error = match self.get_fault_info() {

From 42d9e1d4d2ead0d7d5c66e83e7732659f7b732cc Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 03:30:36 +0900
Subject: [PATCH 1004/1027] fixup! drm/asahi: Add the Asahi driver for Apple
 AGX GPUs

---
 drivers/gpu/drm/asahi/fw/channels.rs  |  7 ++++---
 drivers/gpu/drm/asahi/fw/initdata.rs  | 14 +++++++-------
 drivers/gpu/drm/asahi/fw/workqueue.rs |  4 ++--
 drivers/gpu/drm/asahi/gpu.rs          |  6 ++++++
 drivers/gpu/drm/asahi/initdata.rs     | 10 +++++-----
 drivers/gpu/drm/asahi/workqueue.rs    |  4 ++--
 6 files changed, 26 insertions(+), 19 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index 85bfc1cec0a255..f48020c75be8bc 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -173,13 +173,16 @@ pub(crate) enum DeviceControlMsg {
     Unk0a(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk0b(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk0c(Array<DEVICECONTROL_SZ::ver, u8>),
+    #[ver(V >= V13_3)]
+    Unk0d(Array<DEVICECONTROL_SZ::ver, u8>),
     GrowTVBAck {
         unk_4: u32,
         buffer_slot: u32,
         vm_slot: u32,
         counter: u32,
         subpipe: u32,
-        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x14 }>,
+        halt_count: U64,
+        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x1c }>,
     },
     Unk0e(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk0f(Array<DEVICECONTROL_SZ::ver, u8>),
@@ -190,8 +193,6 @@ pub(crate) enum DeviceControlMsg {
     Unk14(Array<DEVICECONTROL_SZ::ver, u8>), // Init?
     Unk15(Array<DEVICECONTROL_SZ::ver, u8>), // Enable something
     Unk16(Array<DEVICECONTROL_SZ::ver, u8>), // Disable something
-    #[ver(V >= V13_3)]
-    Unk17(Array<DEVICECONTROL_SZ::ver, u8>),
     DestroyContext {
         unk_4: u32,
         ctx_23: u8,
diff --git a/drivers/gpu/drm/asahi/fw/initdata.rs b/drivers/gpu/drm/asahi/fw/initdata.rs
index 4c53846e81607a..d81a9b6b9df044 100644
--- a/drivers/gpu/drm/asahi/fw/initdata.rs
+++ b/drivers/gpu/drm/asahi/fw/initdata.rs
@@ -30,8 +30,8 @@ pub(crate) mod raw {
     #[derive(Debug, Default)]
     #[repr(C)]
     pub(crate) struct FwStatusFlags {
-        pub(crate) halt_count: AtomicU32,
-        __pad0: Pad<0xc>,
+        pub(crate) halt_count: AtomicU64,
+        __pad0: Pad<0x8>,
         pub(crate) halted: AtomicU32,
         __pad1: Pad<0xc>,
         pub(crate) resume: AtomicU32,
@@ -1159,9 +1159,9 @@ pub(crate) mod raw {
         pub(crate) unk_10e88: Array<0x188, u8>,
         pub(crate) idle_ts: U64,
         pub(crate) idle_unk: U64,
-        pub(crate) unk_11020: u32,
-        pub(crate) unk_11024: u32,
-        pub(crate) unk_11028: u32,
+        pub(crate) progress_check_interval_3d: u32,
+        pub(crate) progress_check_interval_ta: u32,
+        pub(crate) progress_check_interval_cl: u32,
 
         #[ver(V >= V13_0B4)]
         pub(crate) unk_1102c_0: u32,
@@ -1202,10 +1202,10 @@ pub(crate) mod raw {
         #[ver(V >= V13_3)]
         pub(crate) unk_118e0_9c_x: Array<0x8, u8>,
 
-        pub(crate) unk_118e0: u32,
+        pub(crate) cl_context_switch_timeout_ms: u32,
 
         #[ver(V >= V13_0B4)]
-        pub(crate) unk_118e4_0: u32,
+        pub(crate) cl_kill_timeout_ms: u32,
 
         pub(crate) cdm_context_store_latency_threshold: u32,
         pub(crate) unk_118e8: u32,
diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs
index 8ad8bb1b0eee22..9ffa55e7c5a741 100644
--- a/drivers/gpu/drm/asahi/fw/workqueue.rs
+++ b/drivers/gpu/drm/asahi/fw/workqueue.rs
@@ -132,6 +132,8 @@ pub(crate) mod raw {
         pub(crate) unk_58: U64,
         pub(crate) busy: AtomicU32,
         pub(crate) __pad: Pad<0x20>,
+        #[ver(V >= V13_2 && G < G14X)]
+        pub(crate) unk_84_0: u32,
         pub(crate) unk_84_state: AtomicU32,
         pub(crate) unk_88: u32,
         pub(crate) unk_8c: u32,
@@ -139,8 +141,6 @@ pub(crate) mod raw {
         pub(crate) unk_94: u32,
         pub(crate) pending: AtomicU32,
         pub(crate) unk_9c: u32,
-        #[ver(V >= V13_2 && G < G14X)]
-        pub(crate) unk_a0_0: u32,
         pub(crate) gpu_context: GpuPointer<'a, super::GpuContextData>,
         pub(crate) unk_a8: U64,
         #[ver(V >= V13_2 && G < G14X)]
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 06f95d7c2c9699..87065d1dc97a70 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -1332,12 +1332,18 @@ impl GpuManager for GpuManager::ver {
     }
 
     fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32) {
+        let halt_count = self
+            .initdata
+            .fw_status
+            .with(|raw, _inner| raw.flags.halt_count.load(Ordering::Relaxed));
+
         let dc = fw::channels::DeviceControlMsg::ver::GrowTVBAck {
             unk_4: 1,
             buffer_slot,
             vm_slot,
             counter,
             subpipe: 0, // TODO
+            halt_count: U64(halt_count),
             __pad: Default::default(),
         };
 
diff --git a/drivers/gpu/drm/asahi/initdata.rs b/drivers/gpu/drm/asahi/initdata.rs
index a3fd7a87ab79de..d8573af9aec860 100644
--- a/drivers/gpu/drm/asahi/initdata.rs
+++ b/drivers/gpu/drm/asahi/initdata.rs
@@ -704,9 +704,9 @@ impl<'a> InitDataBuilder::ver<'a> {
                 unk_903c: 0,
                 fault_control: *crate::fault_control.read(),
                 do_init: 1,
-                unk_11020: 40,
-                unk_11024: 10,
-                unk_11028: 250,
+                progress_check_interval_3d: 40,
+                progress_check_interval_ta: 10,
+                progress_check_interval_cl: 250,
                 #[ver(V >= V13_0B4)]
                 unk_1102c_0: 1,
                 #[ver(V >= V13_0B4)]
@@ -718,9 +718,9 @@ impl<'a> InitDataBuilder::ver<'a> {
                 idle_off_delay_ms: AtomicU32::new(pwr.idle_off_delay_ms),
                 fender_idle_off_delay_ms: pwr.fender_idle_off_delay_ms,
                 fw_early_wake_timeout_ms: pwr.fw_early_wake_timeout_ms,
-                unk_118e0: 40,
+                cl_context_switch_timeout_ms: 40,
                 #[ver(V >= V13_0B4)]
-                unk_118e4_0: 50,
+                cl_kill_timeout_ms: 50,
                 #[ver(V >= V13_0B4)]
                 unk_11edc: 0,
                 #[ver(V >= V13_0B4)]
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index ab77fa12ad3f55..348b09668b63ea 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -636,6 +636,8 @@ impl WorkQueue::ver {
                         unk_58: Default::default(),
                         busy: Default::default(),
                         __pad: Default::default(),
+                        #[ver(V >= V13_2 && G < G14X)]
+                        unk_84_0: 0,
                         unk_84_state: Default::default(),
                         unk_88: 0,
                         unk_8c: 0,
@@ -643,8 +645,6 @@ impl WorkQueue::ver {
                         unk_94: 0,
                         pending: Default::default(),
                         unk_9c: 0,
-                        #[ver(V >= V13_2 && G < G14X)]
-                        unk_a0_0: 0,
                         gpu_context: inner.gpu_context.gpu_pointer(),
                         unk_a8: Default::default(),
                         #[ver(V >= V13_2 && G < G14X)]

From ffa966626840368580c5109d0ef5508772a04c44 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 03:32:28 +0900
Subject: [PATCH 1005/1027] fixup! drm/asahi: Add the Asahi driver UAPI

---
 include/uapi/drm/asahi_drm.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/uapi/drm/asahi_drm.h b/include/uapi/drm/asahi_drm.h
index af2e8b5801b53e..2ac3857c0cfa27 100644
--- a/include/uapi/drm/asahi_drm.h
+++ b/include/uapi/drm/asahi_drm.h
@@ -544,6 +544,7 @@ enum drm_asahi_status {
 	DRM_ASAHI_STATUS_FAULT,
 	DRM_ASAHI_STATUS_KILLED,
 	DRM_ASAHI_STATUS_NO_DEVICE,
+	DRM_ASAHI_STATUS_CHANNEL_ERROR,
 };
 
 enum drm_asahi_fault {

From 4315422be310ccb8cfc574b3545842cb21699f9c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 03:32:47 +0900
Subject: [PATCH 1006/1027] drm/asahi: Handle channel errors

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/channel.rs      | 25 ++++++++
 drivers/gpu/drm/asahi/debug.rs        |  1 +
 drivers/gpu/drm/asahi/event.rs        | 10 ++++
 drivers/gpu/drm/asahi/fw/channels.rs  | 34 +++++++++--
 drivers/gpu/drm/asahi/fw/workqueue.rs |  2 +-
 drivers/gpu/drm/asahi/gpu.rs          | 86 ++++++++++++++++++++++++++-
 drivers/gpu/drm/asahi/workqueue.rs    | 57 +++++++++++++++---
 7 files changed, 201 insertions(+), 14 deletions(-)

diff --git a/drivers/gpu/drm/asahi/channel.rs b/drivers/gpu/drm/asahi/channel.rs
index 709fb3d1dbe128..990f6469de52b1 100644
--- a/drivers/gpu/drm/asahi/channel.rs
+++ b/drivers/gpu/drm/asahi/channel.rs
@@ -383,6 +383,31 @@ impl EventChannel::ver {
                                 dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
                             }
                         },
+                        EventMsg::ChannelError {
+                            error_type,
+                            pipe_type,
+                            event_slot,
+                            event_value,
+                        } => match self.gpu.as_ref() {
+                            Some(gpu) => {
+                                let error_type = match error_type {
+                                    0 => ChannelErrorType::MemoryError,
+                                    1 => ChannelErrorType::DMKill,
+                                    2 => ChannelErrorType::Aborted,
+                                    3 => ChannelErrorType::Unk3,
+                                    a => ChannelErrorType::Unknown(a),
+                                };
+                                gpu.handle_channel_error(
+                                    error_type,
+                                    pipe_type,
+                                    event_slot,
+                                    event_value,
+                                );
+                            }
+                            None => {
+                                dev_crit!(self.dev, "EventChannel: No GPU manager available!\n")
+                            }
+                        },
                         msg => {
                             dev_crit!(self.dev, "Unknown event message: {:?}\n", msg);
                         }
diff --git a/drivers/gpu/drm/asahi/debug.rs b/drivers/gpu/drm/asahi/debug.rs
index ab8490bd536bcc..e4b06d3853d87d 100644
--- a/drivers/gpu/drm/asahi/debug.rs
+++ b/drivers/gpu/drm/asahi/debug.rs
@@ -66,6 +66,7 @@ pub(crate) enum DebugFlags {
     Debug6 = 54,
     Debug7 = 55,
 
+    VerboseFaults = 61,
     AllowUnknownOverrides = 62,
     OopsOnGpuCrash = 63,
 }
diff --git a/drivers/gpu/drm/asahi/event.rs b/drivers/gpu/drm/asahi/event.rs
index 9e17ca0e1d7a26..563b62d9fe28a4 100644
--- a/drivers/gpu/drm/asahi/event.rs
+++ b/drivers/gpu/drm/asahi/event.rs
@@ -216,6 +216,16 @@ impl EventManager {
         }
     }
 
+    /// Returns a reference to the workqueue owning an event.
+    pub(crate) fn get_owner(
+        &self,
+        slot: u32,
+    ) -> Option<Arc<dyn workqueue::WorkQueue + Send + Sync>> {
+        self.alloc
+            .with_inner(|inner| inner.owners[slot as usize].as_ref().cloned())
+            .map(|a| a.clone())
+    }
+
     /// Fail all commands, used when the GPU crashes.
     pub(crate) fn fail_all(&self, error: workqueue::WorkError) {
         let mut owners: Vec<Arc<dyn workqueue::WorkQueue + Send + Sync>> = Vec::new();
diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index f48020c75be8bc..cf1f1ec4eddd77 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -184,8 +184,16 @@ pub(crate) enum DeviceControlMsg {
         halt_count: U64,
         __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x1c }>,
     },
-    Unk0e(Array<DEVICECONTROL_SZ::ver, u8>),
-    Unk0f(Array<DEVICECONTROL_SZ::ver, u8>),
+    RecoverChannel {
+        pipe_type: u32,
+        work_queue: GpuWeakPointer<super::workqueue::QueueInfo::ver>,
+        event_value: u32,
+        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x10 }>,
+    },
+    IdlePowerOff {
+        val: u32,
+        __pad: Pad<{ DEVICECONTROL_SZ::ver - 0x4 }>,
+    },
     Unk10(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk11(Array<DEVICECONTROL_SZ::ver, u8>),
     Unk12(Array<DEVICECONTROL_SZ::ver, u8>),
@@ -236,6 +244,17 @@ pub(crate) struct FwCtlMsg {
 
 pub(crate) const EVENT_SZ: usize = 0x34;
 
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+#[repr(C, u32)]
+#[allow(dead_code)]
+pub(crate) enum ChannelErrorType {
+    MemoryError,
+    DMKill,
+    Aborted,
+    Unk3,
+    Unknown(u32),
+}
+
 #[derive(Debug, Copy, Clone)]
 #[repr(C, u32)]
 #[allow(dead_code)]
@@ -258,12 +277,19 @@ pub(crate) enum EventMsg {
         vm_slot: u32,
         buffer_slot: u32,
         counter: u32,
-    }, // Max discriminant: 0x7
+    },
+    ChannelError {
+        error_type: u32,
+        pipe_type: u32,
+        event_slot: u32,
+        event_value: u32,
+    },
+    // Max discriminant: 0x8
 }
 
 static_assert!(core::mem::size_of::<EventMsg>() == 4 + EVENT_SZ);
 
-pub(crate) const EVENT_MAX: u32 = 0x7;
+pub(crate) const EVENT_MAX: u32 = 0x8;
 
 #[derive(Copy, Clone)]
 #[repr(C)]
diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs
index 9ffa55e7c5a741..1b55d8cb6ca273 100644
--- a/drivers/gpu/drm/asahi/fw/workqueue.rs
+++ b/drivers/gpu/drm/asahi/fw/workqueue.rs
@@ -135,7 +135,7 @@ pub(crate) mod raw {
         #[ver(V >= V13_2 && G < G14X)]
         pub(crate) unk_84_0: u32,
         pub(crate) unk_84_state: AtomicU32,
-        pub(crate) unk_88: u32,
+        pub(crate) error_count: AtomicU32,
         pub(crate) unk_8c: u32,
         pub(crate) unk_90: u32,
         pub(crate) unk_94: u32,
diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index 87065d1dc97a70..ca27ce9c0f4c9d 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -35,7 +35,7 @@ use kernel::{
 use crate::alloc::Allocator;
 use crate::debug::*;
 use crate::driver::{AsahiDevRef, AsahiDevice};
-use crate::fw::channels::PipeType;
+use crate::fw::channels::{ChannelErrorType, PipeType};
 use crate::fw::types::{U32, U64};
 use crate::{
     alloc, buffer, channel, event, fw, gem, hw, initdata, mem, mmu, queue, regs, workqueue,
@@ -256,6 +256,14 @@ pub(crate) trait GpuManager: Send + Sync {
     fn handle_timeout(&self, counter: u32, event_slot: i32, unk: u32);
     /// Handle a GPU fault event.
     fn handle_fault(&self);
+    /// Handle a channel error event.
+    fn handle_channel_error(
+        &self,
+        error_type: ChannelErrorType,
+        pipe_type: u32,
+        event_slot: u32,
+        event_value: u32,
+    );
     /// Acknowledge a Buffer grow op.
     fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32);
     /// Wait for the GPU to become idle and power off.
@@ -1331,6 +1339,82 @@ impl GpuManager for GpuManager::ver {
         self.recover();
     }
 
+    fn handle_channel_error(
+        &self,
+        error_type: ChannelErrorType,
+        pipe_type: u32,
+        event_slot: u32,
+        event_value: u32,
+    ) {
+        dev_err!(self.dev, " (\\________/) \n");
+        dev_err!(self.dev, "  |        |  \n");
+        dev_err!(self.dev, "'.| \\  , / |.'\n");
+        dev_err!(self.dev, "--| / (( \\ |--\n");
+        dev_err!(self.dev, ".'|  _-_-  |'.\n");
+        dev_err!(self.dev, "  |________|  \n");
+        dev_err!(self.dev, "GPU channel error nya~!!!!!\n");
+        dev_err!(self.dev, "  Error type: {:?}\n", error_type);
+        dev_err!(self.dev, "  Pipe type: {}\n", pipe_type);
+        dev_err!(self.dev, "  Event slot: {}\n", event_slot);
+        dev_err!(self.dev, "  Event value: {:#x?}\n", event_value);
+
+        self.event_manager.mark_error(
+            event_slot,
+            event_value,
+            workqueue::WorkError::ChannelError(error_type),
+        );
+
+        let wq = match self.event_manager.get_owner(event_slot) {
+            Some(wq) => wq,
+            None => {
+                dev_err!(self.dev, "Workqueue not found for this event slot!\n");
+                return;
+            }
+        };
+
+        let wq = match wq.as_any().downcast_ref::<workqueue::WorkQueue::ver>() {
+            Some(wq) => wq,
+            None => {
+                dev_crit!(self.dev, "GpuManager mismatched with WorkQueue!\n");
+                return;
+            }
+        };
+
+        if debug_enabled(DebugFlags::VerboseFaults) {
+            wq.dump_info();
+        }
+
+        let dc = fw::channels::DeviceControlMsg::ver::RecoverChannel {
+            pipe_type,
+            work_queue: wq.info_pointer(),
+            event_value,
+            __pad: Default::default(),
+        };
+
+        mod_dev_dbg!(self.dev, "Recover Channel command: {:?}\n", &dc);
+        let mut txch = self.tx_channels.lock();
+
+        let token = txch.device_control.send(&dc);
+        {
+            let mut guard = self.rtkit.lock();
+            let rtk = guard.as_mut().unwrap();
+            if rtk
+                .send_message(EP_DOORBELL, MSG_TX_DOORBELL | DOORBELL_DEVCTRL)
+                .is_err()
+            {
+                dev_err!(self.dev, "Failed to send Recover Channel command\n");
+            }
+        }
+
+        if txch.device_control.wait_for(token).is_err() {
+            dev_err!(self.dev, "Timed out waiting for Recover Channel command\n");
+        }
+
+        if debug_enabled(DebugFlags::VerboseFaults) {
+            wq.dump_info();
+        }
+    }
+
     fn ack_grow(&self, buffer_slot: u32, vm_slot: u32, counter: u32) {
         let halt_count = self
             .initdata
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 348b09668b63ea..5e4084e3d0dd0f 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -14,13 +14,14 @@
 //! up its associated event.
 
 use crate::debug::*;
-use crate::fw::channels::PipeType;
+use crate::fw::channels::{ChannelErrorType, PipeType};
 use crate::fw::types::*;
 use crate::fw::workqueue::*;
 use crate::no_debug;
 use crate::object::OpaqueGpuObject;
 use crate::regs::FaultReason;
 use crate::{channel, driver, event, fw, gpu, object, regs};
+use core::any::Any;
 use core::num::NonZeroU64;
 use core::sync::atomic::Ordering;
 use kernel::{
@@ -48,6 +49,8 @@ pub(crate) enum WorkError {
     Fault(regs::FaultInfo),
     /// Work failed due to an error caused by other concurrent GPU work.
     Killed,
+    /// Channel error
+    ChannelError(ChannelErrorType),
     /// The GPU crashed.
     NoDevice,
     /// Unknown reason.
@@ -79,6 +82,9 @@ impl From<WorkError> for uapi::drm_asahi_result_info {
                 status: match a {
                     WorkError::Timeout => uapi::drm_asahi_status_DRM_ASAHI_STATUS_TIMEOUT,
                     WorkError::Killed => uapi::drm_asahi_status_DRM_ASAHI_STATUS_KILLED,
+                    WorkError::ChannelError(_) => {
+                        uapi::drm_asahi_status_DRM_ASAHI_STATUS_CHANNEL_ERROR
+                    }
                     WorkError::NoDevice => uapi::drm_asahi_status_DRM_ASAHI_STATUS_NO_DEVICE,
                     _ => uapi::drm_asahi_status_DRM_ASAHI_STATUS_UNKNOWN_ERROR,
                 },
@@ -97,6 +103,7 @@ impl From<WorkError> for kernel::error::Error {
             WorkError::Unknown => ENODATA,
             WorkError::Killed => ECANCELED,
             WorkError::NoDevice => ENODEV,
+            WorkError::ChannelError(_) => EIO,
         }
     }
 }
@@ -601,20 +608,26 @@ impl WorkQueue::ver {
         size: u32,
     ) -> Result<Arc<WorkQueue::ver>> {
         let gpu_buf = alloc.private.array_empty_tagged(0x2c18, b"GPBF")?;
-        let shared = &mut alloc.shared;
+        let mut state = alloc.shared.new_default::<RingState>()?;
+        let ring = alloc.shared.array_empty(size as usize)?;
         let inner = WorkQueueInner::ver {
             dev: dev.into(),
             event_manager,
-            info: alloc.private.new_init(
+            // Use shared (coherent) state with verbose faults so we can dump state correctly
+            info: if debug_enabled(DebugFlags::VerboseFaults) {
+                &mut alloc.shared
+            } else {
+                &mut alloc.private
+            }
+            .new_init(
                 try_init!(QueueInfo::ver {
                     state: {
-                        let mut s = shared.new_default::<RingState>()?;
-                        s.with_mut(|raw, _inner| {
+                        state.with_mut(|raw, _inner| {
                             raw.rb_size = size;
                         });
-                        s
+                        state
                     },
-                    ring: shared.array_empty(size as usize)?,
+                    ring,
                     gpu_buf,
                     notifier_list: notifier_list,
                     gpu_context: gpu_context,
@@ -639,7 +652,7 @@ impl WorkQueue::ver {
                         #[ver(V >= V13_2 && G < G14X)]
                         unk_84_0: 0,
                         unk_84_state: Default::default(),
-                        unk_88: 0,
+                        error_count: Default::default(),
                         unk_8c: 0,
                         unk_90: 0,
                         unk_94: 0,
@@ -744,11 +757,35 @@ impl WorkQueue::ver {
     pub(crate) fn pipe_type(&self) -> PipeType {
         self.inner.lock().pipe_type
     }
+
+    pub(crate) fn dump_info(&self) {
+        pr_info!("WorkQueue @ {:?}:", self.info_pointer);
+        self.inner.lock().info.with(|raw, _inner| {
+            pr_info!("  GPU rptr1: {:#x}", raw.gpu_rptr1.load(Ordering::Relaxed));
+            pr_info!("  GPU rptr1: {:#x}", raw.gpu_rptr2.load(Ordering::Relaxed));
+            pr_info!("  GPU rptr1: {:#x}", raw.gpu_rptr3.load(Ordering::Relaxed));
+            pr_info!("  Event ID: {:#x}", raw.event_id.load(Ordering::Relaxed));
+            pr_info!("  Busy: {:#x}", raw.busy.load(Ordering::Relaxed));
+            pr_info!("  Unk 84: {:#x}", raw.unk_84_state.load(Ordering::Relaxed));
+            pr_info!(
+                "  Error count: {:#x}",
+                raw.error_count.load(Ordering::Relaxed)
+            );
+            pr_info!("  Pending: {:#x}", raw.pending.load(Ordering::Relaxed));
+        });
+    }
+
+    pub(crate) fn info_pointer(&self) -> GpuWeakPointer<QueueInfo::ver> {
+        self.info_pointer
+    }
 }
 
 /// Trait used to erase the version-specific type of WorkQueues, to avoid leaking
 /// version-specificity into the event module.
 pub(crate) trait WorkQueue {
+    /// Cast as an Any type.
+    fn as_any(&self) -> &dyn Any;
+
     fn signal(&self) -> bool;
     fn mark_error(&self, value: event::EventValue, error: WorkError);
     fn fail_all(&self, error: WorkError);
@@ -756,6 +793,10 @@ pub(crate) trait WorkQueue {
 
 #[versions(AGX)]
 impl WorkQueue for WorkQueue::ver {
+    fn as_any(&self) -> &dyn Any {
+        self
+    }
+
     /// Signal a workqueue that some work was completed.
     ///
     /// This will check the event stamp value to find out exactly how many commands were processed.

From e896e2b1d09fcd68f5360739e9adf59ae35c2a66 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 21:55:30 +0900
Subject: [PATCH 1007/1027] drm/asahi: event: Initialize stamps to different
 values

Makes debugging a bit easier.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/event.rs | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/drivers/gpu/drm/asahi/event.rs b/drivers/gpu/drm/asahi/event.rs
index 563b62d9fe28a4..b757cfeec6ec24 100644
--- a/drivers/gpu/drm/asahi/event.rs
+++ b/drivers/gpu/drm/asahi/event.rs
@@ -150,6 +150,12 @@ impl EventManager {
             owners,
         };
 
+        for slot in 0..NUM_EVENTS {
+            inner.stamps[slot as usize]
+                .0
+                .store((slot as u32) << 24, Ordering::Relaxed);
+        }
+
         Ok(EventManager {
             alloc: slotalloc::SlotAllocator::new(
                 NUM_EVENTS,

From 403b72c5d3b1e839bf7ed56a2931f45ec0911ca8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 22:00:45 +0900
Subject: [PATCH 1008/1027] drm/asahi: workqueue,queue: More debug

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/queue/mod.rs | 25 ++++++++++++++++-----
 drivers/gpu/drm/asahi/workqueue.rs | 35 ++++++++++++++++++++++++++----
 2 files changed, 51 insertions(+), 9 deletions(-)

diff --git a/drivers/gpu/drm/asahi/queue/mod.rs b/drivers/gpu/drm/asahi/queue/mod.rs
index 07df5cab29c1d9..487b511e439d61 100644
--- a/drivers/gpu/drm/asahi/queue/mod.rs
+++ b/drivers/gpu/drm/asahi/queue/mod.rs
@@ -205,7 +205,7 @@ impl QueueJob::ver {
     }
 
     fn commit(&mut self) -> Result {
-        mod_dev_dbg!(self.dev, "QueueJob: Committing\n");
+        mod_dev_dbg!(self.dev, "QueueJob {}: Committing\n", self.id);
 
         self.sj_vtx.as_mut().map(|a| a.commit()).unwrap_or(Ok(()))?;
         self.sj_frag
@@ -794,16 +794,31 @@ impl Queue for Queue::ver {
             }
         }
 
-        mod_dev_dbg!(self.dev, "Queue: Committing job\n");
+        mod_dev_dbg!(
+            self.dev,
+            "Queue {}: Committing job {}\n",
+            self.inner.id,
+            job.id
+        );
         job.commit()?;
 
-        mod_dev_dbg!(self.dev, "Queue: Arming job\n");
+        mod_dev_dbg!(self.dev, "Queue {}: Arming job {}\n", self.inner.id, job.id);
         let job = job.arm();
         let out_fence = job.fences().finished();
-        mod_dev_dbg!(self.dev, "Queue: Pushing job\n");
+        mod_dev_dbg!(
+            self.dev,
+            "Queue {}: Pushing job {}\n",
+            self.inner.id,
+            job.id
+        );
         job.push();
 
-        mod_dev_dbg!(self.dev, "Queue: Adding {} out_syncs\n", out_syncs.len());
+        mod_dev_dbg!(
+            self.dev,
+            "Queue {}: Adding {} out_syncs\n",
+            self.inner.id,
+            out_syncs.len()
+        );
         for mut sync in out_syncs {
             if let Some(chain) = sync.chain_fence.take() {
                 sync.syncobj
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 5e4084e3d0dd0f..e3023721e1e107 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -379,7 +379,7 @@ impl Job::ver {
             Some(work.get_fence())
         } else {
             pr_err!(
-                "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?}) ev={:#x?}\n",
+                "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?}) ev={:#x?} cur={:#x?} slot {:?}\n",
                 inner.free_slots(),
                 self.event_count,
                 inner.free_space(),
@@ -388,6 +388,8 @@ impl Job::ver {
                 inner.last_submitted,
                 inner.last_completed,
                 inner.event.as_ref().map(|a| a.1),
+                inner.event.as_ref().map(|a| a.0.current()),
+                inner.event.as_ref().map(|a| a.0.slot()),
             );
             None
         }
@@ -440,6 +442,16 @@ impl Job::ver {
         inner.pending.reserve(command_count, GFP_KERNEL)?;
 
         inner.last_submitted = inner.event.as_ref().map(|e| e.1);
+        mod_dev_dbg!(
+            inner.dev,
+            "WorkQueue: submitting {} cmds at {:#x?}, lc {:#x?}, cur {:#x?}, pending {}, events {}\n",
+            self.pending.len(),
+            inner.last_submitted,
+            inner.last_completed,
+            inner.event.as_ref().map(|a| a.0.current()),
+            inner.pending.len(),
+            self.event_count,
+        );
 
         for mut command in self.pending.drain(..) {
             command.set_wptr(wptr);
@@ -515,6 +527,13 @@ impl Drop for Job::ver {
         mod_pr_debug!("WorkQueue: Dropping Job\n");
         let mut inner = self.wq.inner.lock();
 
+        if !self.committed {
+            pr_info!(
+                "WorkQueue: Dropping uncommitted job with {} events\n",
+                self.event_count
+            );
+        }
+
         if self.committed && !self.submitted {
             let pipe_type = inner.pipe_type;
             let event = inner.event.as_mut().expect("Job lost its event");
@@ -733,7 +752,12 @@ impl WorkQueue::ver {
 
         let ev = &inner.event.as_ref().unwrap();
 
-        mod_pr_debug!("WorkQueue({:?}): New job\n", inner.pipe_type);
+        mod_pr_debug!(
+            "WorkQueue({:?}): New job at value {:#x?} slot {}\n",
+            inner.pipe_type,
+            ev.1,
+            ev.0.slot()
+        );
         Ok(Job::ver {
             wq: self.clone(),
             event_info: QueueEventInfo::ver {
@@ -877,9 +901,12 @@ impl WorkQueue for WorkQueue::ver {
         }
 
         mod_pr_debug!(
-            "WorkQueue({:?}): Completed {} commands\n",
+            "WorkQueue({:?}): Completed {} commands, left pending {}, ls {:#x?}, lc {:#x?}\n",
             inner.pipe_type,
-            completed_commands
+            completed_commands,
+            inner.pending.len(),
+            inner.last_submitted,
+            inner.last_completed,
         );
 
         if let Some(i) = completed.last() {

From 7b3511e127136f47d233ce350e88a8364281685d Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 22:00:58 +0900
Subject: [PATCH 1009/1027] drm/asahi: workqueue: Fix "Cannot submit, but queue
 is empty?" bug

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/workqueue.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index e3023721e1e107..867be80665f7a6 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -441,7 +441,7 @@ impl Job::ver {
 
         inner.pending.reserve(command_count, GFP_KERNEL)?;
 
-        inner.last_submitted = inner.event.as_ref().map(|e| e.1);
+        inner.last_submitted = Some(self.event_info.value);
         mod_dev_dbg!(
             inner.dev,
             "WorkQueue: submitting {} cmds at {:#x?}, lc {:#x?}, cur {:#x?}, pending {}, events {}\n",

From eb6ed04fc41f06b94bc5f9217ad6d7b45d86a7f6 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 25 Sep 2024 00:11:23 +0900
Subject: [PATCH 1010/1027] drm/asahi: Clean up jobs in a workqueue

This eliminates a potential deadlock under load and improves the fence
signaling situation (for when we have a shrinker).

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/gpu.rs       |  29 -------
 drivers/gpu/drm/asahi/workqueue.rs | 121 ++++++++++++++++-------------
 2 files changed, 67 insertions(+), 83 deletions(-)

diff --git a/drivers/gpu/drm/asahi/gpu.rs b/drivers/gpu/drm/asahi/gpu.rs
index ca27ce9c0f4c9d..6d31cc77a4ac0f 100644
--- a/drivers/gpu/drm/asahi/gpu.rs
+++ b/drivers/gpu/drm/asahi/gpu.rs
@@ -207,8 +207,6 @@ pub(crate) struct GpuManager {
     event_manager: Arc<event::EventManager>,
     buffer_mgr: buffer::BufferManager::ver,
     ids: SequenceIDs,
-    #[pin]
-    garbage_work: Mutex<Vec<Box<dyn workqueue::GenSubmittedWork>>>,
     #[allow(clippy::vec_box)]
     #[pin]
     garbage_contexts: Mutex<Vec<Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>>>,
@@ -274,8 +272,6 @@ pub(crate) trait GpuManager: Send + Sync {
     fn get_cfg(&self) -> &'static hw::HwConfig;
     /// Get the dynamic GPU configuration for this SoC.
     fn get_dyncfg(&self) -> &hw::DynConfig;
-    /// Register completed work as garbage
-    fn add_completed_work(&self, work: Vec<Box<dyn workqueue::GenSubmittedWork>>);
     /// Register an unused context as garbage
     fn free_context(&self, data: Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>);
     /// Check whether the GPU is crashed
@@ -718,7 +714,6 @@ impl GpuManager::ver {
                 pipes,
                 buffer_mgr,
                 ids: Default::default(),
-                garbage_work <- Mutex::new_named(Vec::new(), c_str!("garbage_work")),
                 garbage_contexts <- Mutex::new_named(Vec::new(), c_str!("garbage_contexts")),
             }),
             GFP_KERNEL,
@@ -1155,12 +1150,6 @@ impl GpuManager for GpuManager::ver {
     }
 
     fn alloc(&self) -> Guard<'_, KernelAllocators, MutexBackend> {
-        /*
-         * TODO: This should be done in a workqueue or something.
-         * Clean up completed jobs
-         */
-        self.garbage_work.lock().clear();
-
         /* Clean up idle contexts */
         let mut garbage_ctx = Vec::new();
         core::mem::swap(&mut *self.garbage_contexts.lock(), &mut garbage_ctx);
@@ -1484,24 +1473,6 @@ impl GpuManager for GpuManager::ver {
         &self.dyncfg
     }
 
-    fn add_completed_work(&self, work: Vec<Box<dyn workqueue::GenSubmittedWork>>) {
-        let mut garbage = self.garbage_work.lock();
-
-        if garbage.reserve(work.len(), GFP_KERNEL).is_err() {
-            dev_err!(
-                self.dev,
-                "Failed to reserve space for completed work, deadlock possible.\n"
-            );
-            return;
-        }
-
-        for i in work {
-            garbage
-                .push(i, GFP_KERNEL)
-                .expect("push() failed after reserve()");
-        }
-    }
-
     fn free_context(&self, ctx: Box<fw::types::GpuObject<fw::workqueue::GpuContextData>>) {
         let mut garbage = self.garbage_contexts.lock();
 
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 867be80665f7a6..46b170b1ce71d1 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -34,6 +34,7 @@ use kernel::{
         Arc, Mutex,
     },
     uapi,
+    workqueue::{self, impl_has_work, new_work, Work, WorkItem},
 };
 
 const DEBUG_CLASS: DebugFlags = DebugFlags::WorkQueue;
@@ -173,6 +174,32 @@ pub(crate) trait GenSubmittedWork: Send + Sync {
     fn get_fence(&self) -> dma_fence::Fence;
 }
 
+#[pin_data]
+struct SubmittedWorkContainer {
+    #[pin]
+    work: Work<Self>,
+    inner: Box<dyn GenSubmittedWork>,
+}
+
+impl_has_work! {
+    impl HasWork<Self> for SubmittedWorkContainer { self.work }
+}
+
+impl WorkItem for SubmittedWorkContainer {
+    type Pointer = Pin<Box<SubmittedWorkContainer>>;
+
+    fn run(this: Pin<Box<SubmittedWorkContainer>>) {
+        mod_pr_debug!("WorkQueue: Freeing command @ {:?}\n", this.inner.gpu_va());
+    }
+}
+
+impl SubmittedWorkContainer {
+    fn inner_mut(self: Pin<&mut Self>) -> &mut Box<dyn GenSubmittedWork> {
+        // SAFETY: inner does not require structural pinning.
+        unsafe { &mut self.get_unchecked_mut().inner }
+    }
+}
+
 impl<O: OpaqueGpuObject, C: FnOnce(&mut O, Option<WorkError>) + Send + Sync> GenSubmittedWork
     for SubmittedWork<O, C>
 {
@@ -221,7 +248,7 @@ struct WorkQueueInner {
     pipe_type: PipeType,
     size: u32,
     wptr: u32,
-    pending: Vec<Box<dyn GenSubmittedWork>>,
+    pending: Vec<Pin<Box<SubmittedWorkContainer>>>,
     last_token: Option<event::Token>,
     pending_jobs: usize,
     last_submitted: Option<event::EventValue>,
@@ -270,7 +297,7 @@ pub(crate) struct Job {
     wq: Arc<WorkQueue::ver>,
     event_info: QueueEventInfo::ver,
     start_value: EventValue,
-    pending: Vec<Box<dyn GenSubmittedWork>>,
+    pending: Vec<Pin<Box<SubmittedWorkContainer>>>,
     committed: bool,
     submitted: bool,
     event_count: usize,
@@ -319,17 +346,23 @@ impl Job::ver {
             return Err(EINVAL);
         }
 
+        let fence = self.fence.clone();
+        let value = self.event_info.value.next();
+
         self.pending.push(
-            Box::new(
-                SubmittedWork::<_, _> {
-                    object: command,
-                    value: self.event_info.value.next(),
-                    error: None,
-                    callback: Some(callback),
-                    wptr: 0,
-                    vm_slot,
-                    fence: self.fence.clone(),
-                },
+            Box::try_pin_init(
+                try_pin_init!(SubmittedWorkContainer {
+                    work <- new_work!("SubmittedWorkWrapper::work"),
+                    inner: Box::new(SubmittedWork::<_, _> {
+                        object: command,
+                        value,
+                        error: None,
+                        callback: Some(callback),
+                        wptr: 0,
+                        vm_slot,
+                        fence,
+                    }, GFP_KERNEL)?
+                }),
                 GFP_KERNEL,
             )?,
             GFP_KERNEL,
@@ -376,7 +409,7 @@ impl Job::ver {
         if inner.free_slots() > self.event_count && inner.free_space() > self.pending.len() {
             None
         } else if let Some(work) = inner.pending.first() {
-            Some(work.get_fence())
+            Some(work.inner.get_fence())
         } else {
             pr_err!(
                 "WorkQueue: Cannot submit, but queue is empty? {} > {}, {} > {} (pend={} ls={:#x?} lc={:#x?}) ev={:#x?} cur={:#x?} slot {:?}\n",
@@ -454,11 +487,11 @@ impl Job::ver {
         );
 
         for mut command in self.pending.drain(..) {
-            command.set_wptr(wptr);
+            command.as_mut().inner_mut().set_wptr(wptr);
 
             let next_wptr = (wptr + 1) % inner.size;
             assert!(inner.doneptr() != next_wptr);
-            inner.info.ring[wptr as usize] = command.gpu_va().get();
+            inner.info.ring[wptr as usize] = command.inner.gpu_va().get();
             wptr = next_wptr;
 
             // Cannot fail, since we did a reserve(1) above
@@ -863,11 +896,11 @@ impl WorkQueue for WorkQueue::ver {
         let mut completed_commands: usize = 0;
 
         for cmd in inner.pending.iter() {
-            if cmd.value() <= value {
+            if cmd.inner.value() <= value {
                 mod_pr_debug!(
                     "WorkQueue({:?}): Command at value {:#x?} complete\n",
                     inner.pipe_type,
-                    cmd.value()
+                    cmd.inner.value()
                 );
                 completed_commands += 1;
             } else {
@@ -879,25 +912,17 @@ impl WorkQueue for WorkQueue::ver {
             return inner.pending.is_empty();
         }
 
-        let mut completed = Vec::new();
-
-        if completed.reserve(completed_commands, GFP_KERNEL).is_err() {
-            pr_crit!(
-                "WorkQueue({:?}): Failed to allocate space for {} completed commands\n",
-                inner.pipe_type,
-                completed_commands
-            );
-        }
-
+        let last_wptr = inner.pending[completed_commands - 1].inner.wptr();
         let pipe_type = inner.pipe_type;
 
-        for cmd in inner.pending.drain(..completed_commands) {
-            if completed.push(cmd, GFP_KERNEL).is_err() {
-                pr_crit!(
-                    "WorkQueue({:?}): Failed to signal a completed command\n",
-                    pipe_type,
-                );
-            }
+        for mut cmd in inner.pending.drain(..completed_commands) {
+            mod_pr_debug!(
+                "WorkQueue({:?}): Queueing command @ {:?} for cleanup\n",
+                pipe_type,
+                cmd.inner.gpu_va()
+            );
+            cmd.as_mut().inner_mut().complete();
+            workqueue::system().enqueue(cmd);
         }
 
         mod_pr_debug!(
@@ -909,12 +934,10 @@ impl WorkQueue for WorkQueue::ver {
             inner.last_completed,
         );
 
-        if let Some(i) = completed.last() {
-            inner
-                .info
-                .state
-                .with(|raw, _inner| raw.cpu_freeptr.store(i.wptr(), Ordering::Release));
-        }
+        inner
+            .info
+            .state
+            .with(|raw, _inner| raw.cpu_freeptr.store(last_wptr, Ordering::Release));
 
         let empty = inner.pending.is_empty();
         if empty && inner.pending_jobs == 0 {
@@ -923,16 +946,6 @@ impl WorkQueue for WorkQueue::ver {
             inner.last_completed = None;
         }
 
-        let dev = inner.dev.clone();
-        core::mem::drop(inner);
-
-        for cmd in completed.iter_mut() {
-            cmd.complete();
-        }
-
-        let gpu = &dev.data().gpu;
-        gpu.add_completed_work(completed);
-
         empty
     }
 
@@ -961,8 +974,8 @@ impl WorkQueue for WorkQueue::ver {
         );
 
         for cmd in inner.pending.iter_mut() {
-            if cmd.value() <= value {
-                cmd.mark_error(error);
+            if cmd.inner.value() <= value {
+                cmd.as_mut().inner_mut().mark_error(error);
             } else {
                 break;
             }
@@ -1003,8 +1016,8 @@ impl WorkQueue for WorkQueue::ver {
         core::mem::drop(inner);
 
         for mut cmd in cmds {
-            cmd.mark_error(error);
-            cmd.complete();
+            cmd.as_mut().inner_mut().mark_error(error);
+            cmd.as_mut().inner_mut().complete();
         }
     }
 }

From fd7ebb2cdbdd10f62ca085a527fcfdd6d8f316fa Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Tue, 24 Sep 2024 22:18:36 +0900
Subject: [PATCH 1011/1027] drm/asahi: Add robust_isolation kernel parameter

This only allows binding one VM context at once, which serializes GPU
usage between VMs and therefore prevents one faulting VM from affecting
others.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/asahi.rs     |  5 ++++
 drivers/gpu/drm/asahi/mmu.rs       | 12 +++++++++
 drivers/gpu/drm/asahi/slotalloc.rs | 41 ++++++++++++++++++++----------
 3 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/drivers/gpu/drm/asahi/asahi.rs b/drivers/gpu/drm/asahi/asahi.rs
index 1382e73177cc67..ed5cfad363ed51 100644
--- a/drivers/gpu/drm/asahi/asahi.rs
+++ b/drivers/gpu/drm/asahi/asahi.rs
@@ -48,5 +48,10 @@ module_platform_driver! {
             permissions: 0o644,
             description: "Initial TVB size in blocks",
         },
+        robust_isolation: bool {
+            default: false,
+            permissions: 0o644,
+            description: "Fully isolate GPU contexts (limits performance)",
+        },
     },
 }
diff --git a/drivers/gpu/drm/asahi/mmu.rs b/drivers/gpu/drm/asahi/mmu.rs
index 3a97a2d5304254..a7deecb071f19b 100644
--- a/drivers/gpu/drm/asahi/mmu.rs
+++ b/drivers/gpu/drm/asahi/mmu.rs
@@ -12,6 +12,7 @@
 
 use core::fmt::Debug;
 use core::mem::size_of;
+use core::num::NonZeroUsize;
 use core::ops::Range;
 use core::ptr::NonNull;
 use core::sync::atomic::{fence, AtomicU32, AtomicU64, AtomicU8, Ordering};
@@ -1459,6 +1460,17 @@ impl Uat {
         if binding.binding.is_none() {
             assert_eq!(binding.active_users, 0);
 
+            let isolation = {
+                let lock = crate::THIS_MODULE.kernel_param_lock();
+                *crate::robust_isolation.read(&lock)
+            };
+
+            self.slots.set_limit(if isolation {
+                NonZeroUsize::new(1)
+            } else {
+                None
+            });
+
             let slot = self.slots.get(binding.bind_token)?;
             if slot.changed() {
                 mod_pr_debug!("Vm Bind [{}]: bind_token={:?}\n", vm.id, slot.token(),);
diff --git a/drivers/gpu/drm/asahi/slotalloc.rs b/drivers/gpu/drm/asahi/slotalloc.rs
index 635800cc7fe562..9f73283dc71621 100644
--- a/drivers/gpu/drm/asahi/slotalloc.rs
+++ b/drivers/gpu/drm/asahi/slotalloc.rs
@@ -15,6 +15,7 @@
 //! of serious system contention most allocation requests will be immediately fulfilled from the
 //! previous slot without doing an LRU scan.
 
+use core::num::NonZeroUsize;
 use core::ops::{Deref, DerefMut};
 use kernel::{
     alloc::{flags::*, vec_ext::VecExt},
@@ -108,6 +109,7 @@ struct SlotAllocatorInner<T: SlotItem> {
     slots: Vec<Option<Entry<T>>>,
     get_count: u64,
     drop_count: u64,
+    slot_limit: usize,
 }
 
 /// A single slot allocator instance.
@@ -156,6 +158,7 @@ impl<T: SlotItem> SlotAllocator<T> {
             slots,
             get_count: 0,
             drop_count: 0,
+            slot_limit: usize::MAX,
         };
 
         let alloc = Arc::pin_init(
@@ -177,6 +180,13 @@ impl<T: SlotItem> SlotAllocator<T> {
         cb(&mut inner.data)
     }
 
+    /// Set the slot limit for this allocator. New bindings will not use slots above
+    /// this threshold.
+    pub(crate) fn set_limit(&self, limit: Option<NonZeroUsize>) {
+        let mut inner = self.0.inner.lock();
+        inner.slot_limit = limit.unwrap_or(NonZeroUsize::MAX).get();
+    }
+
     /// Gets a fresh slot, optionally reusing a previous allocation if a `SlotToken` is provided.
     ///
     /// Blocks if no slots are free.
@@ -200,18 +210,20 @@ impl<T: SlotItem> SlotAllocator<T> {
         let mut inner = self.0.inner.lock();
 
         if let Some(token) = token {
-            let slot = &mut inner.slots[token.slot as usize];
-            if slot.is_some() {
-                let count = slot.as_ref().unwrap().get_time;
-                if count == token.time {
-                    let mut guard = Guard {
-                        item: Some(slot.take().unwrap().item),
-                        token,
-                        changed: false,
-                        alloc: self.0.clone(),
-                    };
-                    cb(&mut inner.data, &mut guard)?;
-                    return Ok(guard);
+            if (token.slot as usize) < inner.slot_limit {
+                let slot = &mut inner.slots[token.slot as usize];
+                if slot.is_some() {
+                    let count = slot.as_ref().unwrap().get_time;
+                    if count == token.time {
+                        let mut guard = Guard {
+                            item: Some(slot.take().unwrap().item),
+                            token,
+                            changed: false,
+                            alloc: self.0.clone(),
+                        };
+                        cb(&mut inner.data, &mut guard)?;
+                        return Ok(guard);
+                    }
                 }
             }
         }
@@ -222,6 +234,9 @@ impl<T: SlotItem> SlotAllocator<T> {
             let mut oldest_slot = 0u32;
 
             for (i, slot) in inner.slots.iter().enumerate() {
+                if i >= inner.slot_limit {
+                    break;
+                }
                 if let Some(slot) = slot.as_ref() {
                     if slot.drop_time < oldest_time {
                         oldest_slot = i as u32;
@@ -231,7 +246,7 @@ impl<T: SlotItem> SlotAllocator<T> {
             }
 
             if oldest_time == u64::MAX {
-                if first {
+                if first && inner.slot_limit == usize::MAX {
                     pr_warn!(
                         "{}: out of slots, blocking\n",
                         core::any::type_name::<Self>()

From ca2678c2ac1c29b10b26d243c5180879f125bcd9 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 25 Sep 2024 02:36:09 +0900
Subject: [PATCH 1012/1027] fixup! drm/asahi: Add the Asahi driver for Apple
 AGX GPUs

---
 drivers/gpu/drm/asahi/fw/compute.rs    |  6 ++++--
 drivers/gpu/drm/asahi/queue/compute.rs | 10 ++++++----
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/compute.rs b/drivers/gpu/drm/asahi/fw/compute.rs
index 860b9034bfb44c..740f3f2f4dcb9d 100644
--- a/drivers/gpu/drm/asahi/fw/compute.rs
+++ b/drivers/gpu/drm/asahi/fw/compute.rs
@@ -85,9 +85,11 @@ pub(crate) mod raw {
         pub(crate) unk_2d4: u32,
         pub(crate) unk_2d8: u8,
         #[ver(V >= V13_0B4)]
-        pub(crate) unk_ts: U64,
+        pub(crate) context_store_req: U64,
         #[ver(V >= V13_0B4)]
-        pub(crate) unk_2e1: Array<0x1c, u8>,
+        pub(crate) context_store_compl: U64,
+        #[ver(V >= V13_0B4)]
+        pub(crate) unk_2e9: Array<0x14, u8>,
         #[ver(V >= V13_0B4)]
         pub(crate) unk_flag: U32,
         #[ver(V >= V13_0B4)]
diff --git a/drivers/gpu/drm/asahi/queue/compute.rs b/drivers/gpu/drm/asahi/queue/compute.rs
index 20ae88b8e5e52d..68f2d586752cf0 100644
--- a/drivers/gpu/drm/asahi/queue/compute.rs
+++ b/drivers/gpu/drm/asahi/queue/compute.rs
@@ -191,7 +191,7 @@ impl super::QueueInner::ver {
                                 work_queue: ev_comp.info_ptr,
                                 unk_24: U64(0),
                                 #[ver(V >= V13_0B4)]
-                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                unk_ts: inner_weak_ptr!(ptr, context_store_req),
                                 uuid,
                                 unk_30_padding: 0,
                             })?;
@@ -215,7 +215,7 @@ impl super::QueueInner::ver {
                                 work_queue: ev_comp.info_ptr,
                                 unk_24: U64(0),
                                 #[ver(V >= V13_0B4)]
-                                unk_ts: inner_weak_ptr!(ptr, unk_ts),
+                                unk_ts: inner_weak_ptr!(ptr, context_store_req),
                                 uuid,
                                 unk_30_padding: 0,
                             })?;
@@ -378,9 +378,11 @@ impl super::QueueInner::ver {
                     unk_2d4: 0,
                     unk_2d8: 0,
                     #[ver(V >= V13_0B4)]
-                    unk_ts: U64(0),
+                    context_store_req: U64(0),
                     #[ver(V >= V13_0B4)]
-                    unk_2e1: Default::default(),
+                    context_store_compl: U64(0),
+                    #[ver(V >= V13_0B4)]
+                    unk_2e9: Default::default(),
                     #[ver(V >= V13_0B4)]
                     unk_flag: U32(0),
                     #[ver(V >= V13_0B4)]

From ea4f38d46dc1d4c67feb1a6892512fa0c9ceb60c Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Wed, 25 Sep 2024 02:55:58 +0900
Subject: [PATCH 1013/1027] drm/asahi: HACK: Disable compute preemption for now

Possibly because we don't have support in the helper program, this is
broken and causes channel errors. Hack in high priority for now, which
works around it.

Use debug_flags 0x1000000000000 to re-enable for testing.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/fw/channels.rs  |  2 +-
 drivers/gpu/drm/asahi/fw/workqueue.rs |  9 ++++++++-
 drivers/gpu/drm/asahi/workqueue.rs    | 10 +++++++++-
 3 files changed, 18 insertions(+), 3 deletions(-)

diff --git a/drivers/gpu/drm/asahi/fw/channels.rs b/drivers/gpu/drm/asahi/fw/channels.rs
index cf1f1ec4eddd77..c1a7ec82aad1e2 100644
--- a/drivers/gpu/drm/asahi/fw/channels.rs
+++ b/drivers/gpu/drm/asahi/fw/channels.rs
@@ -113,7 +113,7 @@ impl TxChannelState for FwCtlChannelState {
     }
 }
 
-#[derive(Debug, Copy, Clone, Default)]
+#[derive(Debug, Copy, Clone, Default, PartialEq, Eq)]
 #[repr(u32)]
 pub(crate) enum PipeType {
     #[default]
diff --git a/drivers/gpu/drm/asahi/fw/workqueue.rs b/drivers/gpu/drm/asahi/fw/workqueue.rs
index 1b55d8cb6ca273..a53312854ab356 100644
--- a/drivers/gpu/drm/asahi/fw/workqueue.rs
+++ b/drivers/gpu/drm/asahi/fw/workqueue.rs
@@ -98,7 +98,14 @@ pub(crate) mod raw {
 
     #[derive(Debug, Clone, Copy)]
     #[repr(C)]
-    pub(crate) struct Priority(u32, u32, U64, u32, u32, u32);
+    pub(crate) struct Priority(
+        pub(crate) u32,
+        pub(crate) u32,
+        pub(crate) U64,
+        pub(crate) u32,
+        pub(crate) u32,
+        pub(crate) u32,
+    );
 
     pub(crate) const PRIORITY: [Priority; 4] = [
         Priority(0, 0, U64(0xffff_ffff_ffff_0000), 1, 0, 1),
diff --git a/drivers/gpu/drm/asahi/workqueue.rs b/drivers/gpu/drm/asahi/workqueue.rs
index 46b170b1ce71d1..c91cb62c3ec834 100644
--- a/drivers/gpu/drm/asahi/workqueue.rs
+++ b/drivers/gpu/drm/asahi/workqueue.rs
@@ -662,6 +662,14 @@ impl WorkQueue::ver {
         let gpu_buf = alloc.private.array_empty_tagged(0x2c18, b"GPBF")?;
         let mut state = alloc.shared.new_default::<RingState>()?;
         let ring = alloc.shared.array_empty(size as usize)?;
+        let mut prio = *raw::PRIORITY.get(priority as usize).ok_or(EINVAL)?;
+
+        if pipe_type == PipeType::Compute && !debug_enabled(DebugFlags::Debug0) {
+            // Hack to disable compute preemption until we fix it
+            prio.0 = 0;
+            prio.5 = 1;
+        }
+
         let inner = WorkQueueInner::ver {
             dev: dev.into(),
             event_manager,
@@ -694,7 +702,7 @@ impl WorkQueue::ver {
                         gpu_rptr2: Default::default(),
                         gpu_rptr3: Default::default(),
                         event_id: AtomicI32::new(-1),
-                        priority: *raw::PRIORITY.get(priority as usize).ok_or(EINVAL)?,
+                        priority: prio,
                         unk_4c: -1,
                         uuid: id as u32,
                         unk_54: -1,

From ce5529bb22b541a56296a5dd663eddfe00a4f1a3 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Thu, 3 Oct 2024 22:46:54 +0900
Subject: [PATCH 1014/1027] drm/asahi: Align kernel range to buffer::PAGE_SIZE

We only require alignment to the UAT page size from userspace, but
internally we need more, so just align it if userspace gives us lower
alignment.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs | 11 +++++++++--
 drivers/gpu/drm/asahi/util.rs | 20 ++++++++++++++++++++
 2 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 5baf5f54631a9a..5d1bcb4b04f874 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -9,7 +9,10 @@
 
 use crate::debug::*;
 use crate::driver::AsahiDevice;
-use crate::{alloc, buffer, driver, gem, hw, mmu, queue, util::RangeExt};
+use crate::{
+    alloc, buffer, driver, gem, hw, mmu, queue,
+    util::{align, align_down, RangeExt},
+};
 use core::mem::MaybeUninit;
 use core::ops::Range;
 use kernel::dma_fence::RawDmaFence;
@@ -319,7 +322,11 @@ impl File {
             return Err(EINVAL);
         }
 
-        let kernel_half_size = (kernel_range.range() >> 1) & !(mmu::UAT_PGMSK as u64);
+        // Align to buffer::PAGE_SIZE so the allocators are happy
+        let kernel_range = align(kernel_range.start, buffer::PAGE_SIZE as u64)
+            ..align_down(kernel_range.end, buffer::PAGE_SIZE as u64);
+
+        let kernel_half_size = align_down(kernel_range.range() >> 1, buffer::PAGE_SIZE as u64);
         let kernel_gpu_range = kernel_range.start..(kernel_range.start + kernel_half_size);
         let kernel_gpufw_range = kernel_gpu_range.end..kernel_range.end;
 
diff --git a/drivers/gpu/drm/asahi/util.rs b/drivers/gpu/drm/asahi/util.rs
index ad1c61920d5ee4..74f71568b90592 100644
--- a/drivers/gpu/drm/asahi/util.rs
+++ b/drivers/gpu/drm/asahi/util.rs
@@ -25,6 +25,26 @@ where
     (a + b - one) & !(b - one)
 }
 
+/// Aligns an integer type down to a power of two.
+pub(crate) fn align_down<T>(a: T, b: T) -> T
+where
+    T: Copy
+        + Default
+        + BitAnd<Output = T>
+        + Not<Output = T>
+        + Sub<Output = T>
+        + Div<Output = T>
+        + core::cmp::PartialEq,
+{
+    let def: T = Default::default();
+    #[allow(clippy::eq_op)]
+    let one: T = !def / !def;
+
+    assert!((b & (b - one)) == def);
+
+    a & !(b - one)
+}
+
 /// Integer division rounding up.
 pub(crate) fn div_ceil<T>(a: T, b: T) -> T
 where

From 95034212f3ca17c8e1794195f46bb47450e9425e Mon Sep 17 00:00:00 2001
From: Qais Yousef <qyousef@layalina.io>
Date: Sun, 28 Jul 2024 20:26:59 +0100
Subject: [PATCH 1015/1027] cpufreq: sched/schedutil: Remove LATENCY_MULTIPLIER

The current LATENCY_MULTIPLIER which has been around for nearly 20 years
causes rate_limit_us to be always in ms range.

On M1 mac mini I get 50 and 56us transition latency, but due to the 1000
multiplier we end up setting rate_limit_us to 50 and 56ms, which gets
capped into 2ms and was 10ms before e13aa799c2a6 ("cpufreq: Change
default transition delay to 2ms")

On Intel I5 system transition latency is 20us but due to the multiplier
we end up with 20ms that again is capped to 2ms.

Given how good modern hardware and how modern workloads require systems
to be more responsive to cater for sudden changes in workload (tasks
sleeping/wakeup/migrating, uclamp causing a sudden boost or cap) and
that 2ms is quarter of the time of 120Hz refresh rate system, drop the
old logic in favour of providing 50% headroom.

	rate_limit_us = 1.5 * latency.

I considered not adding any headroom which could mean that we can end up
with infinite back-to-back requests.

I also considered providing a constant headroom (e.g: 100us) assuming
that any h/w or f/w dealing with the request shouldn't require a large
headroom when transition_latency is actually high.

But for both cases I wasn't sure if h/w or f/w can end up being
overwhelmed dealing with the freq requests in a potentially busy system.
So I opted for providing 50% breathing room.

This is expected to impact schedutil only as the other user,
dbs_governor, takes the max(2*tick, transition_delay_us) and the former
was at least 2ms on 1ms TICK, which is equivalent to the max_delay_us
before applying this patch. For systems with TICK of 4ms, this value
would have almost always ended up with 8ms sampling rate.

For systems that report 0 transition latency, we still default to
returning 1ms as transition delay.

This helps in eliminating a source of latency for applying requests as
mentioned in [1]. For example if we have a 1ms tick, most systems will
miss sending an update at tick when updating the util_avg for a task/CPU
(rate_limit_us will be 2ms for most systems).

[1] https://lore.kernel.org/lkml/20240724212255.mfr2ybiv2j2uqek7@airbuntu/

Signed-off-by: Qais Yousef <qyousef@layalina.io>
---
 drivers/cpufreq/cpufreq.c | 27 ++++-----------------------
 include/linux/cpufreq.h   |  6 ------
 2 files changed, 4 insertions(+), 29 deletions(-)

diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index 04fc786dd2c096..f98c9438760c97 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -575,30 +575,11 @@ unsigned int cpufreq_policy_transition_delay_us(struct cpufreq_policy *policy)
 		return policy->transition_delay_us;
 
 	latency = policy->cpuinfo.transition_latency / NSEC_PER_USEC;
-	if (latency) {
-		unsigned int max_delay_us = 2 * MSEC_PER_SEC;
+	if (latency)
+		/* Give a 50% breathing room between updates */
+		return latency + (latency >> 1);
 
-		/*
-		 * If the platform already has high transition_latency, use it
-		 * as-is.
-		 */
-		if (latency > max_delay_us)
-			return latency;
-
-		/*
-		 * For platforms that can change the frequency very fast (< 2
-		 * us), the above formula gives a decent transition delay. But
-		 * for platforms where transition_latency is in milliseconds, it
-		 * ends up giving unrealistic values.
-		 *
-		 * Cap the default transition delay to 2 ms, which seems to be
-		 * a reasonable amount of time after which we should reevaluate
-		 * the frequency.
-		 */
-		return min(latency * LATENCY_MULTIPLIER, max_delay_us);
-	}
-
-	return LATENCY_MULTIPLIER;
+	return USEC_PER_MSEC;
 }
 EXPORT_SYMBOL_GPL(cpufreq_policy_transition_delay_us);
 
diff --git a/include/linux/cpufreq.h b/include/linux/cpufreq.h
index aabec598f79afa..7fe0981a7e4674 100644
--- a/include/linux/cpufreq.h
+++ b/include/linux/cpufreq.h
@@ -577,12 +577,6 @@ static inline unsigned long cpufreq_scale(unsigned long old, u_int div,
 #define CPUFREQ_POLICY_POWERSAVE	(1)
 #define CPUFREQ_POLICY_PERFORMANCE	(2)
 
-/*
- * The polling frequency depends on the capability of the processor. Default
- * polling frequency is 1000 times the transition latency of the processor.
- */
-#define LATENCY_MULTIPLIER		(1000)
-
 struct cpufreq_governor {
 	char	name[CPUFREQ_NAME_LEN];
 	int	(*init)(struct cpufreq_policy *policy);

From 3070d3e85dae491e49002d3177d17259e11c7fd0 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Draszik?= <andre.draszik@linaro.org>
Date: Thu, 8 Aug 2024 09:11:51 +0100
Subject: [PATCH 1016/1027] tty: serial: samsung_tty: drop unused argument to
 irq handlers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The 'irq' argument is not used in any of the callees, we can just drop
it and simplify the code.

No functional changes.

Reviewed-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20240808-samsung-tty-cleanup-v3-1-494412f49f4b@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/tty/serial/samsung_tty.c | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index dc35eb77d2ef34..1c6d0ffe564967 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -855,7 +855,7 @@ static irqreturn_t s3c24xx_serial_rx_chars_pio(void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static irqreturn_t s3c24xx_serial_rx_irq(int irq, void *dev_id)
+static irqreturn_t s3c24xx_serial_rx_irq(void *dev_id)
 {
 	struct s3c24xx_uart_port *ourport = dev_id;
 
@@ -928,7 +928,7 @@ static void s3c24xx_serial_tx_chars(struct s3c24xx_uart_port *ourport)
 		s3c24xx_serial_stop_tx(port);
 }
 
-static irqreturn_t s3c24xx_serial_tx_irq(int irq, void *id)
+static irqreturn_t s3c24xx_serial_tx_irq(void *id)
 {
 	struct s3c24xx_uart_port *ourport = id;
 	struct uart_port *port = &ourport->port;
@@ -950,11 +950,11 @@ static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id)
 	irqreturn_t ret = IRQ_HANDLED;
 
 	if (pend & S3C64XX_UINTM_RXD_MSK) {
-		ret = s3c24xx_serial_rx_irq(irq, id);
+		ret = s3c24xx_serial_rx_irq(id);
 		wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK);
 	}
 	if (pend & S3C64XX_UINTM_TXD_MSK) {
-		ret = s3c24xx_serial_tx_irq(irq, id);
+		ret = s3c24xx_serial_tx_irq(id);
 		wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK);
 	}
 	return ret;
@@ -971,11 +971,11 @@ static irqreturn_t apple_serial_handle_irq(int irq, void *id)
 	if (pend & (APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO)) {
 		wr_regl(port, S3C2410_UTRSTAT,
 			APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO);
-		ret = s3c24xx_serial_rx_irq(irq, id);
+		ret = s3c24xx_serial_rx_irq(id);
 	}
 	if (pend & APPLE_S5L_UTRSTAT_TXTHRESH) {
 		wr_regl(port, S3C2410_UTRSTAT, APPLE_S5L_UTRSTAT_TXTHRESH);
-		ret = s3c24xx_serial_tx_irq(irq, id);
+		ret = s3c24xx_serial_tx_irq(id);
 	}
 
 	return ret;

From 6554efd70e07b1736c76ea3fc6ad9911e3e63a93 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Draszik?= <andre.draszik@linaro.org>
Date: Thu, 8 Aug 2024 09:11:52 +0100
Subject: [PATCH 1017/1027] tty: serial: samsung_tty: cast the interrupt's void
 *id just once
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The interrupt handler routines and helpers are casting the 'void *'
pointer to 'struct exynos_uart_port *' all over the place.

There is no need for that, we can do the casting once and keep passing
the 'struct exynos_uart_port *', simplifying the code and saving a few
lines of code.

No functional changes.

Reviewed-by: Tudor Ambarus <tudor.ambarus@linaro.org>
Signed-off-by: André Draszik <andre.draszik@linaro.org>
Reviewed-by: Jiri Slaby <jirislaby@kernel.org>
Link: https://lore.kernel.org/r/20240808-samsung-tty-cleanup-v3-2-494412f49f4b@linaro.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/tty/serial/samsung_tty.c | 29 ++++++++++++-----------------
 1 file changed, 12 insertions(+), 17 deletions(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 1c6d0ffe564967..c4f2ac9518aa2a 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -707,9 +707,8 @@ static void enable_rx_pio(struct s3c24xx_uart_port *ourport)
 
 static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport);
 
-static irqreturn_t s3c24xx_serial_rx_chars_dma(void *dev_id)
+static irqreturn_t s3c24xx_serial_rx_chars_dma(struct s3c24xx_uart_port *ourport)
 {
-	struct s3c24xx_uart_port *ourport = dev_id;
 	struct uart_port *port = &ourport->port;
 	struct s3c24xx_uart_dma *dma = ourport->dma;
 	struct tty_struct *tty = tty_port_tty_get(&ourport->port.state->port);
@@ -843,9 +842,8 @@ static void s3c24xx_serial_rx_drain_fifo(struct s3c24xx_uart_port *ourport)
 	tty_flip_buffer_push(&port->state->port);
 }
 
-static irqreturn_t s3c24xx_serial_rx_chars_pio(void *dev_id)
+static irqreturn_t s3c24xx_serial_rx_chars_pio(struct s3c24xx_uart_port *ourport)
 {
-	struct s3c24xx_uart_port *ourport = dev_id;
 	struct uart_port *port = &ourport->port;
 
 	uart_port_lock(port);
@@ -855,13 +853,11 @@ static irqreturn_t s3c24xx_serial_rx_chars_pio(void *dev_id)
 	return IRQ_HANDLED;
 }
 
-static irqreturn_t s3c24xx_serial_rx_irq(void *dev_id)
+static irqreturn_t s3c24xx_serial_rx_irq(struct s3c24xx_uart_port *ourport)
 {
-	struct s3c24xx_uart_port *ourport = dev_id;
-
 	if (ourport->dma && ourport->dma->rx_chan)
-		return s3c24xx_serial_rx_chars_dma(dev_id);
-	return s3c24xx_serial_rx_chars_pio(dev_id);
+		return s3c24xx_serial_rx_chars_dma(ourport);
+	return s3c24xx_serial_rx_chars_pio(ourport);
 }
 
 static void s3c24xx_serial_tx_chars(struct s3c24xx_uart_port *ourport)
@@ -928,9 +924,8 @@ static void s3c24xx_serial_tx_chars(struct s3c24xx_uart_port *ourport)
 		s3c24xx_serial_stop_tx(port);
 }
 
-static irqreturn_t s3c24xx_serial_tx_irq(void *id)
+static irqreturn_t s3c24xx_serial_tx_irq(struct s3c24xx_uart_port *ourport)
 {
-	struct s3c24xx_uart_port *ourport = id;
 	struct uart_port *port = &ourport->port;
 
 	uart_port_lock(port);
@@ -944,17 +939,17 @@ static irqreturn_t s3c24xx_serial_tx_irq(void *id)
 /* interrupt handler for s3c64xx and later SoC's.*/
 static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id)
 {
-	const struct s3c24xx_uart_port *ourport = id;
+	struct s3c24xx_uart_port *ourport = id;
 	const struct uart_port *port = &ourport->port;
 	u32 pend = rd_regl(port, S3C64XX_UINTP);
 	irqreturn_t ret = IRQ_HANDLED;
 
 	if (pend & S3C64XX_UINTM_RXD_MSK) {
-		ret = s3c24xx_serial_rx_irq(id);
+		ret = s3c24xx_serial_rx_irq(ourport);
 		wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_RXD_MSK);
 	}
 	if (pend & S3C64XX_UINTM_TXD_MSK) {
-		ret = s3c24xx_serial_tx_irq(id);
+		ret = s3c24xx_serial_tx_irq(ourport);
 		wr_regl(port, S3C64XX_UINTP, S3C64XX_UINTM_TXD_MSK);
 	}
 	return ret;
@@ -963,7 +958,7 @@ static irqreturn_t s3c64xx_serial_handle_irq(int irq, void *id)
 /* interrupt handler for Apple SoC's.*/
 static irqreturn_t apple_serial_handle_irq(int irq, void *id)
 {
-	const struct s3c24xx_uart_port *ourport = id;
+	struct s3c24xx_uart_port *ourport = id;
 	const struct uart_port *port = &ourport->port;
 	u32 pend = rd_regl(port, S3C2410_UTRSTAT);
 	irqreturn_t ret = IRQ_NONE;
@@ -971,11 +966,11 @@ static irqreturn_t apple_serial_handle_irq(int irq, void *id)
 	if (pend & (APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO)) {
 		wr_regl(port, S3C2410_UTRSTAT,
 			APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO);
-		ret = s3c24xx_serial_rx_irq(id);
+		ret = s3c24xx_serial_rx_irq(ourport);
 	}
 	if (pend & APPLE_S5L_UTRSTAT_TXTHRESH) {
 		wr_regl(port, S3C2410_UTRSTAT, APPLE_S5L_UTRSTAT_TXTHRESH);
-		ret = s3c24xx_serial_tx_irq(id);
+		ret = s3c24xx_serial_tx_irq(ourport);
 	}
 
 	return ret;

From d36ade80a809e65648c7e67e1b2577ed8e16e359 Mon Sep 17 00:00:00 2001
From: Nick Chan <towinchenmi@gmail.com>
Date: Wed, 11 Sep 2024 13:02:11 +0800
Subject: [PATCH 1018/1027] tty: serial: samsung: Use bit manipulation macros
 for APPLE_S5L_*

New entries using BIT() will be added soon, so change the existing ones to
use bit manipulation macros including BIT() and GENMASK() for
consistency.

Suggested-by: Krzysztof Kozlowski <krzk@kernel.org>
Tested-by: Janne Grunau <j@jannau.net>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Nick Chan <towinchenmi@gmail.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20240911050741.14477-2-towinchenmi@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 include/linux/serial_s3c.h | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/include/linux/serial_s3c.h b/include/linux/serial_s3c.h
index 1672cf0810ef54..2a934e20ca4b75 100644
--- a/include/linux/serial_s3c.h
+++ b/include/linux/serial_s3c.h
@@ -249,9 +249,9 @@
 #define APPLE_S5L_UCON_RXTO_ENA		9
 #define APPLE_S5L_UCON_RXTHRESH_ENA	12
 #define APPLE_S5L_UCON_TXTHRESH_ENA	13
-#define APPLE_S5L_UCON_RXTO_ENA_MSK	(1 << APPLE_S5L_UCON_RXTO_ENA)
-#define APPLE_S5L_UCON_RXTHRESH_ENA_MSK	(1 << APPLE_S5L_UCON_RXTHRESH_ENA)
-#define APPLE_S5L_UCON_TXTHRESH_ENA_MSK	(1 << APPLE_S5L_UCON_TXTHRESH_ENA)
+#define APPLE_S5L_UCON_RXTO_ENA_MSK	BIT(APPLE_S5L_UCON_RXTO_ENA)
+#define APPLE_S5L_UCON_RXTHRESH_ENA_MSK	BIT(APPLE_S5L_UCON_RXTHRESH_ENA)
+#define APPLE_S5L_UCON_TXTHRESH_ENA_MSK	BIT(APPLE_S5L_UCON_TXTHRESH_ENA)
 
 #define APPLE_S5L_UCON_DEFAULT		(S3C2410_UCON_TXIRQMODE | \
 					 S3C2410_UCON_RXIRQMODE | \
@@ -260,10 +260,10 @@
 					 APPLE_S5L_UCON_RXTHRESH_ENA_MSK | \
 					 APPLE_S5L_UCON_TXTHRESH_ENA_MSK)
 
-#define APPLE_S5L_UTRSTAT_RXTHRESH	(1<<4)
-#define APPLE_S5L_UTRSTAT_TXTHRESH	(1<<5)
-#define APPLE_S5L_UTRSTAT_RXTO		(1<<9)
-#define APPLE_S5L_UTRSTAT_ALL_FLAGS	(0x3f0)
+#define APPLE_S5L_UTRSTAT_RXTHRESH	BIT(4)
+#define APPLE_S5L_UTRSTAT_TXTHRESH	BIT(5)
+#define APPLE_S5L_UTRSTAT_RXTO		BIT(9)
+#define APPLE_S5L_UTRSTAT_ALL_FLAGS	GENMASK(9, 4)
 
 #ifndef __ASSEMBLY__
 

From 08e500b30f6aa7d7f40ce156e702c3adfc126f1f Mon Sep 17 00:00:00 2001
From: Nick Chan <towinchenmi@gmail.com>
Date: Wed, 11 Sep 2024 13:02:12 +0800
Subject: [PATCH 1019/1027] tty: serial: samsung: Fix A7-A11 serial earlycon
 SError

Apple's earlier SoCs, like A7-A11, requires 32-bit writes for the serial
port. Otherwise, a SError happens when writing to UTXH (+0x20). This only
manifested in earlycon as reg-io-width in the device tree is consulted
for normal serial writes.

Change the iotype of the port to UPIO_MEM32, to allow the serial port to
function on A7-A11 SoCs. This change does not appear to affect Apple M1 and
above.

Reviewed-by: Krzysztof Kozlowski <krzk@kernel.org>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Tested-by: Janne Grunau <j@jannau.net>
Signed-off-by: Nick Chan <towinchenmi@gmail.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20240911050741.14477-3-towinchenmi@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/tty/serial/samsung_tty.c | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index c4f2ac9518aa2a..3fdec06322acdb 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -2536,7 +2536,7 @@ static const struct s3c24xx_serial_drv_data s5l_serial_drv_data = {
 		.name		= "Apple S5L UART",
 		.type		= TYPE_APPLE_S5L,
 		.port_type	= PORT_8250,
-		.iotype		= UPIO_MEM,
+		.iotype		= UPIO_MEM32,
 		.fifosize	= 16,
 		.rx_fifomask	= S3C2410_UFSTAT_RXMASK,
 		.rx_fifoshift	= S3C2410_UFSTAT_RXSHIFT,
@@ -2822,6 +2822,9 @@ OF_EARLYCON_DECLARE(gs101, "google,gs101-uart", gs101_early_console_setup);
 static int __init apple_s5l_early_console_setup(struct earlycon_device *device,
 						const char *opt)
 {
+	/* Apple A7-A11 requires MMIO32 register accesses. */
+	device->port.iotype = UPIO_MEM32;
+
 	/* Close enough to S3C2410 for earlycon... */
 	device->port.private_data = &s3c2410_early_console_data;
 

From fe090b9b8ddc945793ac27beb86452e90904a20e Mon Sep 17 00:00:00 2001
From: Nick Chan <towinchenmi@gmail.com>
Date: Wed, 11 Sep 2024 13:02:13 +0800
Subject: [PATCH 1020/1027] tty: serial: samsung: Fix serial rx on Apple A7-A9

Apple's older A7-A9 SoCs seems to use bit 3 in UTRSTAT as RXTO, which is
enabled by bit 11 in UCON.

Access these bits in addition to the original RXTO and RXTO enable bits,
to allow serial rx to function on A7-A9 SoCs. This change does not
appear to affect the A10 SoC and up.

Tested-by: Janne Grunau <j@jannau.net>
Reviewed-by: Neal Gompa <neal@gompa.dev>
Signed-off-by: Nick Chan <towinchenmi@gmail.com>
Reviewed-by: Andi Shyti <andi.shyti@kernel.org>
Link: https://lore.kernel.org/r/20240911050741.14477-4-towinchenmi@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
---
 drivers/tty/serial/samsung_tty.c | 17 ++++++++++++-----
 include/linux/serial_s3c.h       | 18 +++++++++++-------
 2 files changed, 23 insertions(+), 12 deletions(-)

diff --git a/drivers/tty/serial/samsung_tty.c b/drivers/tty/serial/samsung_tty.c
index 3fdec06322acdb..0d184ee2f9cec0 100644
--- a/drivers/tty/serial/samsung_tty.c
+++ b/drivers/tty/serial/samsung_tty.c
@@ -550,6 +550,7 @@ static void s3c24xx_serial_stop_rx(struct uart_port *port)
 		case TYPE_APPLE_S5L:
 			s3c24xx_clear_bit(port, APPLE_S5L_UCON_RXTHRESH_ENA, S3C2410_UCON);
 			s3c24xx_clear_bit(port, APPLE_S5L_UCON_RXTO_ENA, S3C2410_UCON);
+			s3c24xx_clear_bit(port, APPLE_S5L_UCON_RXTO_LEGACY_ENA, S3C2410_UCON);
 			break;
 		default:
 			disable_irq_nosync(ourport->rx_irq);
@@ -963,9 +964,11 @@ static irqreturn_t apple_serial_handle_irq(int irq, void *id)
 	u32 pend = rd_regl(port, S3C2410_UTRSTAT);
 	irqreturn_t ret = IRQ_NONE;
 
-	if (pend & (APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO)) {
+	if (pend & (APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO |
+		APPLE_S5L_UTRSTAT_RXTO_LEGACY)) {
 		wr_regl(port, S3C2410_UTRSTAT,
-			APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO);
+			APPLE_S5L_UTRSTAT_RXTHRESH | APPLE_S5L_UTRSTAT_RXTO |
+			APPLE_S5L_UTRSTAT_RXTO_LEGACY);
 		ret = s3c24xx_serial_rx_irq(ourport);
 	}
 	if (pend & APPLE_S5L_UTRSTAT_TXTHRESH) {
@@ -1190,7 +1193,8 @@ static void apple_s5l_serial_shutdown(struct uart_port *port)
 	ucon = rd_regl(port, S3C2410_UCON);
 	ucon &= ~(APPLE_S5L_UCON_TXTHRESH_ENA_MSK |
 		  APPLE_S5L_UCON_RXTHRESH_ENA_MSK |
-		  APPLE_S5L_UCON_RXTO_ENA_MSK);
+		  APPLE_S5L_UCON_RXTO_ENA_MSK |
+		  APPLE_S5L_UCON_RXTO_LEGACY_ENA_MSK);
 	wr_regl(port, S3C2410_UCON, ucon);
 
 	wr_regl(port, S3C2410_UTRSTAT, APPLE_S5L_UTRSTAT_ALL_FLAGS);
@@ -1287,6 +1291,7 @@ static int apple_s5l_serial_startup(struct uart_port *port)
 	/* Enable Rx Interrupt */
 	s3c24xx_set_bit(port, APPLE_S5L_UCON_RXTHRESH_ENA, S3C2410_UCON);
 	s3c24xx_set_bit(port, APPLE_S5L_UCON_RXTO_ENA, S3C2410_UCON);
+	s3c24xx_set_bit(port, APPLE_S5L_UCON_RXTO_LEGACY_ENA, S3C2410_UCON);
 
 	return ret;
 }
@@ -2143,13 +2148,15 @@ static int s3c24xx_serial_resume_noirq(struct device *dev)
 
 			ucon &= ~(APPLE_S5L_UCON_TXTHRESH_ENA_MSK |
 				  APPLE_S5L_UCON_RXTHRESH_ENA_MSK |
-				  APPLE_S5L_UCON_RXTO_ENA_MSK);
+				  APPLE_S5L_UCON_RXTO_ENA_MSK |
+				  APPLE_S5L_UCON_RXTO_LEGACY_ENA_MSK);
 
 			if (ourport->tx_enabled)
 				ucon |= APPLE_S5L_UCON_TXTHRESH_ENA_MSK;
 			if (ourport->rx_enabled)
 				ucon |= APPLE_S5L_UCON_RXTHRESH_ENA_MSK |
-					APPLE_S5L_UCON_RXTO_ENA_MSK;
+					APPLE_S5L_UCON_RXTO_ENA_MSK |
+					APPLE_S5L_UCON_RXTO_LEGACY_ENA_MSK;
 
 			wr_regl(port, S3C2410_UCON, ucon);
 
diff --git a/include/linux/serial_s3c.h b/include/linux/serial_s3c.h
index 2a934e20ca4b75..102aa33d956c47 100644
--- a/include/linux/serial_s3c.h
+++ b/include/linux/serial_s3c.h
@@ -246,24 +246,28 @@
 				 S5PV210_UFCON_TXTRIG4 |	\
 				 S5PV210_UFCON_RXTRIG4)
 
-#define APPLE_S5L_UCON_RXTO_ENA		9
-#define APPLE_S5L_UCON_RXTHRESH_ENA	12
-#define APPLE_S5L_UCON_TXTHRESH_ENA	13
-#define APPLE_S5L_UCON_RXTO_ENA_MSK	BIT(APPLE_S5L_UCON_RXTO_ENA)
-#define APPLE_S5L_UCON_RXTHRESH_ENA_MSK	BIT(APPLE_S5L_UCON_RXTHRESH_ENA)
-#define APPLE_S5L_UCON_TXTHRESH_ENA_MSK	BIT(APPLE_S5L_UCON_TXTHRESH_ENA)
+#define APPLE_S5L_UCON_RXTO_ENA			9
+#define APPLE_S5L_UCON_RXTO_LEGACY_ENA		11
+#define APPLE_S5L_UCON_RXTHRESH_ENA		12
+#define APPLE_S5L_UCON_TXTHRESH_ENA		13
+#define APPLE_S5L_UCON_RXTO_ENA_MSK		BIT(APPLE_S5L_UCON_RXTO_ENA)
+#define APPLE_S5L_UCON_RXTO_LEGACY_ENA_MSK	BIT(APPLE_S5L_UCON_RXTO_LEGACY_ENA)
+#define APPLE_S5L_UCON_RXTHRESH_ENA_MSK		BIT(APPLE_S5L_UCON_RXTHRESH_ENA)
+#define APPLE_S5L_UCON_TXTHRESH_ENA_MSK		BIT(APPLE_S5L_UCON_TXTHRESH_ENA)
 
 #define APPLE_S5L_UCON_DEFAULT		(S3C2410_UCON_TXIRQMODE | \
 					 S3C2410_UCON_RXIRQMODE | \
 					 S3C2410_UCON_RXFIFO_TOI)
 #define APPLE_S5L_UCON_MASK		(APPLE_S5L_UCON_RXTO_ENA_MSK | \
+					 APPLE_S5L_UCON_RXTO_LEGACY_ENA_MSK | \
 					 APPLE_S5L_UCON_RXTHRESH_ENA_MSK | \
 					 APPLE_S5L_UCON_TXTHRESH_ENA_MSK)
 
+#define APPLE_S5L_UTRSTAT_RXTO_LEGACY	BIT(3)
 #define APPLE_S5L_UTRSTAT_RXTHRESH	BIT(4)
 #define APPLE_S5L_UTRSTAT_TXTHRESH	BIT(5)
 #define APPLE_S5L_UTRSTAT_RXTO		BIT(9)
-#define APPLE_S5L_UTRSTAT_ALL_FLAGS	GENMASK(9, 4)
+#define APPLE_S5L_UTRSTAT_ALL_FLAGS	GENMASK(9, 3)
 
 #ifndef __ASSEMBLY__
 

From ab2f3f3738d9acdd261d331f724d5a559b08a8c5 Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Sun, 7 Nov 2021 11:21:19 +0100
Subject: [PATCH 1021/1027] dt-bindings: usb: Add Apple dwc3 bindings

Apple Silicon SoCs such as the M1 have multiple USB controllers based on
the Synopsys DesignWare USB3 controller.
References to the ATC PHY required for SuperSpeed are left out for now
until support has been upstreamed as well.

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 .../devicetree/bindings/usb/apple,dwc3.yaml   | 64 +++++++++++++++++++
 1 file changed, 64 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/usb/apple,dwc3.yaml

diff --git a/Documentation/devicetree/bindings/usb/apple,dwc3.yaml b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
new file mode 100644
index 00000000000000..fb3b3489e6b263
--- /dev/null
+++ b/Documentation/devicetree/bindings/usb/apple,dwc3.yaml
@@ -0,0 +1,64 @@
+# SPDX-License-Identifier: GPL-2.0
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/usb/apple,dwc3.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Apple Silicon DWC3 USB controller
+
+maintainers:
+  - Sven Peter <sven@svenpeter.dev>
+
+description:
+  On Apple Silicon SoCs such as the M1 each Type-C port has a corresponding
+  USB controller based on the Synopsys DesignWare USB3 controller.
+
+  The common content of this binding is defined in snps,dwc3.yaml.
+
+allOf:
+  - $ref: snps,dwc3.yaml#
+
+select:
+  properties:
+    compatible:
+      contains:
+        const: apple,dwc3
+  required:
+    - compatible
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,t8103-dwc3
+          - apple,t6000-dwc3
+      - const: apple,dwc3
+      - const: snps,dwc3
+
+  reg:
+    maxItems: 1
+
+  interrupts:
+    maxItems: 1
+
+unevaluatedProperties: false
+
+required:
+  - compatible
+  - reg
+  - interrupts
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/apple-aic.h>
+    #include <dt-bindings/interrupt-controller/irq.h>
+
+    usb@82280000 {
+      compatible = "apple,t8103-dwc3", "apple,dwc3", "snps,dwc3";
+      reg = <0x82280000 0x10000>;
+      interrupts = <AIC_IRQ 777 IRQ_TYPE_LEVEL_HIGH>;
+
+      dr_mode = "otg";
+      usb-role-switch;
+      role-switch-default-mode = "host";
+    };

From 82b5992ab89626e25487bf9d65e6dab5f90d566d Mon Sep 17 00:00:00 2001
From: Sven Peter <sven@svenpeter.dev>
Date: Wed, 30 Nov 2022 22:10:59 +0100
Subject: [PATCH 1022/1027] usb: dwc3: Add support for Apple DWC3

As mad as it sounds, the dwc3 controller present on the Apple M1 must be
reset and reinitialized whenever a device is unplugged from the root
port or when the PHY mode is changed.

This is required for at least the following reasons:

  - The USB2 D+/D- lines are connected through a stateful eUSB2 repeater
    which in turn is controlled by a variant of the TI TPS6598x USB PD
    chip. When the USB PD controller detects a hotplug event it resets
    the eUSB2 repeater. Afterwards, no new device is recognized before
    the DWC3 core and PHY are reset as well because the eUSB2 repeater
    and the PHY/dwc3 block disagree about the current state.

  - It's possible to completely break the dwc3 controller by switching
    it to device mode and unplugging the cable at just the wrong time.
    If this happens dwc3 behaves as if no device is connected.
    CORESOFTRESET will also never clear after it has been set. The only
    workaround is to trigger a hard reset of the entire dwc3 core with
    its external reset line.

  - Whenever the PHY mode is changed (to e.g. transition to DisplayPort
    alternate mode or USB4) dwc3 has to be shutdown and reinitialized.
    Otherwise the Type-C port will not be useable until the entire SoC
    has been reset.

All of this can be easily worked around by respecting transitions to
USB_ROLE_NONE and making sure the external reset line is asserted when
switching roles.

Signed-off-by: Sven Peter <sven@svenpeter.dev>
---
 drivers/usb/dwc3/core.c | 58 ++++++++++++++++++++++++++++++++++++++---
 drivers/usb/dwc3/core.h |  3 +++
 drivers/usb/dwc3/drd.c  | 11 +++++++-
 3 files changed, 67 insertions(+), 5 deletions(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 427e5660f87c24..2b128f111bc6bd 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -143,6 +143,9 @@ void dwc3_set_prtcap(struct dwc3 *dwc, u32 mode)
 	dwc->current_dr_role = mode;
 }
 
+static void dwc3_core_exit(struct dwc3 *dwc);
+static int dwc3_core_init_for_resume(struct dwc3 *dwc);
+
 static void __dwc3_set_mode(struct work_struct *work)
 {
 	struct dwc3 *dwc = work_to_dwc(work);
@@ -162,7 +165,7 @@ static void __dwc3_set_mode(struct work_struct *work)
 	if (dwc->current_dr_role == DWC3_GCTL_PRTCAP_OTG)
 		dwc3_otg_update(dwc, 0);
 
-	if (!desired_dr_role)
+	if (!desired_dr_role && !dwc->role_switch_reset_quirk)
 		goto out;
 
 	if (desired_dr_role == dwc->current_dr_role)
@@ -190,13 +193,32 @@ static void __dwc3_set_mode(struct work_struct *work)
 		break;
 	}
 
+	if (dwc->role_switch_reset_quirk) {
+		if (dwc->current_dr_role) {
+			dwc->current_dr_role = 0;
+			dwc3_core_exit(dwc);
+		}
+
+		if (desired_dr_role) {
+			ret = dwc3_core_init_for_resume(dwc);
+			if (ret) {
+				dev_err(dwc->dev,
+				    "failed to reinitialize core\n");
+				goto out;
+			}
+		} else {
+			goto out;
+		}
+	}
+
 	/*
 	 * When current_dr_role is not set, there's no role switching.
 	 * Only perform GCTL.CoreSoftReset when there's DRD role switching.
 	 */
-	if (dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
+	if (dwc->role_switch_reset_quirk ||
+		(dwc->current_dr_role && ((DWC3_IP_IS(DWC3) ||
 			DWC3_VER_IS_PRIOR(DWC31, 190A)) &&
-			desired_dr_role != DWC3_GCTL_PRTCAP_OTG)) {
+			desired_dr_role != DWC3_GCTL_PRTCAP_OTG))) {
 		reg = dwc3_readl(dwc->regs, DWC3_GCTL);
 		reg |= DWC3_GCTL_CORESOFTRESET;
 		dwc3_writel(dwc->regs, DWC3_GCTL, reg);
@@ -1594,6 +1616,18 @@ static int dwc3_core_init_mode(struct dwc3 *dwc)
 		ret = dwc3_drd_init(dwc);
 		if (ret)
 			return dev_err_probe(dev, ret, "failed to initialize dual-role\n");
+
+		/*
+		 * If the role switch reset quirk is required the first role
+		 * switch notification will initialize the core such that we
+		 * have to shut it down here. Make sure that the __dwc3_set_mode
+		 * queued by dwc3_drd_init has completed before since it
+		 * may still try to access MMIO.
+		 */
+		if (dwc->role_switch_reset_quirk) {
+			flush_work(&dwc->drd_work);
+			dwc3_core_exit(dwc);
+		}
 		break;
 	default:
 		dev_err(dev, "Unsupported mode of operation %d\n", dwc->dr_mode);
@@ -2171,6 +2205,22 @@ static int dwc3_probe(struct platform_device *pdev)
 	if (ret)
 		goto err_put_psy;
 
+	if (dev->of_node) {
+		if (of_device_is_compatible(dev->of_node, "apple,dwc3")) {
+			if (!IS_ENABLED(CONFIG_USB_ROLE_SWITCH) ||
+			    !IS_ENABLED(CONFIG_USB_DWC3_DUAL_ROLE)) {
+				dev_err(dev,
+				    "Apple DWC3 requires role switch support.\n"
+				    );
+				ret = -EINVAL;
+				goto err_put_psy;
+			}
+
+			dwc->dr_mode = USB_DR_MODE_OTG;
+			dwc->role_switch_reset_quirk = true;
+		}
+	}
+
 	ret = reset_control_deassert(dwc->reset);
 	if (ret)
 		goto err_put_psy;
@@ -2310,7 +2360,6 @@ static void dwc3_remove(struct platform_device *pdev)
 		power_supply_put(dwc->usb_psy);
 }
 
-#ifdef CONFIG_PM
 static int dwc3_core_init_for_resume(struct dwc3 *dwc)
 {
 	int ret;
@@ -2337,6 +2386,7 @@ static int dwc3_core_init_for_resume(struct dwc3 *dwc)
 	return ret;
 }
 
+#ifdef CONFIG_PM
 static int dwc3_suspend_common(struct dwc3 *dwc, pm_message_t msg)
 {
 	u32 reg;
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index eab81dfdcc3502..b801517a1d857f 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -1152,6 +1152,7 @@ struct dwc3_scratchpad_array {
  * @suspended: set to track suspend event due to U3/L2.
  * @susphy_state: state of DWC3_GUSB2PHYCFG_SUSPHY + DWC3_GUSB3PIPECTL_SUSPHY
  *		  before PM suspend.
+ * @role_switch_reset_quirk: set to force reinitialization after any role switch
  * @imod_interval: set the interrupt moderation interval in 250ns
  *			increments or 0 to disable.
  * @max_cfg_eps: current max number of IN eps used across all USB configs.
@@ -1386,6 +1387,8 @@ struct dwc3 {
 	unsigned		suspended:1;
 	unsigned		susphy_state:1;
 
+	unsigned		role_switch_reset_quirk:1;
+
 	u16			imod_interval;
 
 	int			max_cfg_eps;
diff --git a/drivers/usb/dwc3/drd.c b/drivers/usb/dwc3/drd.c
index d76ae676783cf3..cc2ce23de0fe8d 100644
--- a/drivers/usb/dwc3/drd.c
+++ b/drivers/usb/dwc3/drd.c
@@ -464,6 +464,9 @@ static int dwc3_usb_role_switch_set(struct usb_role_switch *sw,
 		break;
 	}
 
+	if (dwc->role_switch_reset_quirk && role == USB_ROLE_NONE)
+		mode = 0;
+
 	dwc3_set_mode(dwc, mode);
 	return 0;
 }
@@ -492,6 +495,10 @@ static enum usb_role dwc3_usb_role_switch_get(struct usb_role_switch *sw)
 			role = USB_ROLE_DEVICE;
 		break;
 	}
+
+	if (dwc->role_switch_reset_quirk && !dwc->current_dr_role)
+		role = USB_ROLE_NONE;
+
 	spin_unlock_irqrestore(&dwc->lock, flags);
 	return role;
 }
@@ -502,7 +509,9 @@ static int dwc3_setup_role_switch(struct dwc3 *dwc)
 	u32 mode;
 
 	dwc->role_switch_default_mode = usb_get_role_switch_default_mode(dwc->dev);
-	if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
+	if (dwc->role_switch_reset_quirk) {
+		mode = 0;
+	} else if (dwc->role_switch_default_mode == USB_DR_MODE_HOST) {
 		mode = DWC3_GCTL_PRTCAP_HOST;
 	} else {
 		dwc->role_switch_default_mode = USB_DR_MODE_PERIPHERAL;

From 6d1a2452b6fe8feef1ffe2f66082bee8c16a9dd7 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Thu, 30 May 2024 17:44:10 +0200
Subject: [PATCH 1023/1027] usb: dwc3: apple: Do not use host-vbus-glitches
 workaround

It results in SErrors during init presumedly because MMIO accesses fail
while certain parts are shutdown.

Fixes: a6ba1e453174 ("usb: dwc3: apply snps,host-vbus-glitches workaround unconditionally")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/usb/dwc3/host.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index e0533cee6870ba..4001272786254a 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -135,7 +135,8 @@ int dwc3_host_init(struct dwc3 *dwc)
 	 * Some platforms need to power off all Root hub ports immediately after DWC3 set to host
 	 * mode to avoid VBUS glitch happen when xhci get reset later.
 	 */
-	dwc3_power_off_all_roothub_ports(dwc);
+	if (!dwc->role_switch_reset_quirk)
+		dwc3_power_off_all_roothub_ports(dwc);
 
 	irq = dwc3_host_get_irq(dwc);
 	if (irq < 0)

From f59caa2c556572f00ab344377e2bb7872e897b12 Mon Sep 17 00:00:00 2001
From: Janne Grunau <j@jannau.net>
Date: Sat, 2 Nov 2024 19:13:57 +0100
Subject: [PATCH 1024/1027] usb: dwc3: core: Allow phy suspend during init for
 role_switch_reset_quirk

Disabling phy suspend during init breaks USB3 on Apple silicon systems.
The pipehandler lock in atc-phy will not be acked breaking USB3 but not
always USB2 and produces following log message
| phy-apple-atc b03000000.phy: pipehandler lock not acked, this type-c port is probably dead until the next reboot.

Fixes: 6d735722063a9 ("usb: dwc3: core: Prevent phy suspend during init")
Signed-off-by: Janne Grunau <j@jannau.net>
---
 drivers/usb/dwc3/core.c | 3 +++
 drivers/usb/dwc3/host.c | 3 ++-
 2 files changed, 5 insertions(+), 1 deletion(-)

diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 2b128f111bc6bd..f7ad39ab399c81 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -1377,6 +1377,9 @@ static int dwc3_core_init(struct dwc3 *dwc)
 	if (ret)
 		goto err_exit_phy;
 
+	if (dwc->role_switch_reset_quirk)
+		dwc3_enable_susphy(dwc, true);
+
 	dwc3_core_setup_global_control(dwc);
 	dwc3_core_num_eps(dwc);
 
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 4001272786254a..298164a5c094be 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -221,7 +221,8 @@ void dwc3_host_exit(struct dwc3 *dwc)
 	if (dwc->sys_wakeup)
 		device_init_wakeup(&dwc->xhci->dev, false);
 
-	dwc3_enable_susphy(dwc, false);
+	if (!dwc->role_switch_reset_quirk)
+		dwc3_enable_susphy(dwc, false);
 	platform_device_unregister(dwc->xhci);
 	dwc->xhci = NULL;
 }

From 0549a3e9ada6e5771f4271f37b9010e69c69e840 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 3 Nov 2024 20:56:58 +0900
Subject: [PATCH 1025/1027] drm/asahi: Implement missing ASAHI_BIND_OP_UNBIND

Trivial now that we have GPUVM.

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/file.rs | 62 ++++++++++++++++++++++++++++++++++-
 1 file changed, 61 insertions(+), 1 deletion(-)

diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index 5d1bcb4b04f874..d2e28fd1d11a9b 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -519,7 +519,7 @@ impl File {
 
         match data.op {
             uapi::drm_asahi_bind_op_ASAHI_BIND_OP_BIND => Self::do_gem_bind(device, data, file),
-            uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND => Err(ENOTSUPP),
+            uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND => Self::do_gem_unbind(device, data, file),
             uapi::drm_asahi_bind_op_ASAHI_BIND_OP_UNBIND_ALL => {
                 Self::do_gem_unbind_all(device, data, file)
             }
@@ -609,6 +609,66 @@ impl File {
         Ok(0)
     }
 
+    pub(crate) fn do_gem_unbind(
+        _device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_gem_bind,
+        file: &DrmFile,
+    ) -> Result<u32> {
+        if data.offset != 0 || data.flags != 0 || data.handle != 0 {
+            cls_pr_debug!(Errors, "gem_unbind: offset/flags/handle not zero\n");
+            return Err(EINVAL);
+        }
+
+        if (data.addr | data.range) as usize & mmu::UAT_PGMSK != 0 {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Addr/range/offset not page aligned: {:#x} {:#x}\n",
+                data.addr,
+                data.range
+            );
+            return Err(EINVAL); // Must be page aligned
+        }
+
+        let start = data.addr;
+        let end = data.addr.checked_add(data.range).ok_or(EINVAL)?;
+        let range = start..end;
+
+        if !VM_USER_RANGE.is_superset(range.clone()) {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Invalid unmap range {:#x}..{:#x} (not contained in user range)\n",
+                start,
+                end
+            );
+            return Err(EINVAL); // Invalid map range
+        }
+
+        let guard = file
+            .inner()
+            .vms()
+            .get(data.vm_id.try_into()?)
+            .ok_or(ENOENT)?;
+
+        // Clone it immediately so we aren't holding the XArray lock
+        let vm = guard.borrow().vm.clone();
+        let kernel_range = guard.borrow().kernel_range.clone();
+        core::mem::drop(guard);
+
+        if kernel_range.overlaps(range.clone()) {
+            cls_pr_debug!(
+                Errors,
+                "gem_bind: Invalid unmap range {:#x}..{:#x} (intrudes in kernel range)\n",
+                start,
+                end
+            );
+            return Err(EINVAL);
+        }
+
+        vm.unmap_range(range.start, range.range())?;
+
+        Ok(0)
+    }
+
     pub(crate) fn unbind_gem_object(file: &DrmFile, bo: &gem::Object) -> Result {
         let mut index = 0;
         loop {

From 493ec371cddc1ae70b97429b0d0fe671e21cc5a8 Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 3 Nov 2024 21:14:52 +0900
Subject: [PATCH 1026/1027] rust: Add time_namespace helpers

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 rust/helpers/helpers.c        | 1 +
 rust/helpers/time_namespace.c | 7 +++++++
 2 files changed, 8 insertions(+)
 create mode 100644 rust/helpers/time_namespace.c

diff --git a/rust/helpers/helpers.c b/rust/helpers/helpers.c
index a2cc0e26eca70a..ad3147a72a05ac 100644
--- a/rust/helpers/helpers.c
+++ b/rust/helpers/helpers.c
@@ -34,6 +34,7 @@
 #include "slab.c"
 #include "spinlock.c"
 #include "task.c"
+#include "time_namespace.c"
 #include "timekeeping.c"
 #include "uaccess.c"
 #include "wait.c"
diff --git a/rust/helpers/time_namespace.c b/rust/helpers/time_namespace.c
new file mode 100644
index 00000000000000..9010e8efbcfe0d
--- /dev/null
+++ b/rust/helpers/time_namespace.c
@@ -0,0 +1,7 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <linux/time_namespace.h>
+
+void rust_helper_timens_add_monotonic(struct timespec64 *ts) {
+	timens_add_monotonic(ts);
+}

From 57a2096e69681a8d8cbfbbfa6158ab22c1e0535a Mon Sep 17 00:00:00 2001
From: Asahi Lina <lina@asahilina.net>
Date: Sun, 3 Nov 2024 21:18:08 +0900
Subject: [PATCH 1027/1027] drm/asahi: Implement ASAHI_GET_TIME

Signed-off-by: Asahi Lina <lina@asahilina.net>
---
 drivers/gpu/drm/asahi/driver.rs |  2 ++
 drivers/gpu/drm/asahi/file.rs   | 33 +++++++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/drivers/gpu/drm/asahi/driver.rs b/drivers/gpu/drm/asahi/driver.rs
index f99494265234a7..80ea12df7810ed 100644
--- a/drivers/gpu/drm/asahi/driver.rs
+++ b/drivers/gpu/drm/asahi/driver.rs
@@ -77,6 +77,8 @@ impl drv::Driver for AsahiDriver {
             ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::queue_destroy),
         (ASAHI_SUBMIT,          drm_asahi_submit,
             ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::submit),
+        (ASAHI_GET_TIME,        drm_asahi_get_time,
+            ioctl::AUTH | ioctl::RENDER_ALLOW, crate::file::File::get_time),
     }
 }
 
diff --git a/drivers/gpu/drm/asahi/file.rs b/drivers/gpu/drm/asahi/file.rs
index d2e28fd1d11a9b..5c50e2aa41c33c 100644
--- a/drivers/gpu/drm/asahi/file.rs
+++ b/drivers/gpu/drm/asahi/file.rs
@@ -925,6 +925,39 @@ impl File {
             Ok(_) => Ok(0),
         }
     }
+
+    /// IOCTL: get_time: Get the current GPU timer value.
+    pub(crate) fn get_time(
+        device: &AsahiDevice,
+        data: &mut uapi::drm_asahi_get_time,
+        file: &DrmFile,
+    ) -> Result<u32> {
+
+        if data.extensions != 0 || data.flags != 0 {
+            cls_pr_debug!(Errors, "get_time: Unexpected extensions or flags\n");
+            return Err(EINVAL);
+        }
+
+        let mut tp: kernel::bindings::timespec64 = Default::default();
+        let mut gputime: u64 = 0;
+
+        // TODO: bindings
+        // SAFETY: These functions are safe to call as long as the argument pointer is valid
+        unsafe {
+            core::arch::asm!(
+                "mrs {x}, CNTPCT_EL0",
+                x = out(reg) gputime
+            );
+            kernel::bindings::ktime_get_raw_ts64(&mut tp);
+            kernel::bindings::timens_add_monotonic(&mut tp);
+        }
+
+        data.gpu_timestamp = gputime;
+        data.tv_sec = tp.tv_sec;
+        data.tv_nsec = tp.tv_nsec;
+
+        Ok(0)
+    }
 }
 
 impl Drop for File {