devicetree.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH 0/5] Driver for Apple ISP and cameras.
@ 2025-02-19  9:26 Sasha Finkelstein via B4 Relay
  2025-02-19  9:26 ` [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties Sasha Finkelstein via B4 Relay
                   ` (4 more replies)
  0 siblings, 5 replies; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:26 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein, Asahi Lina, Eileen Yoon,
	Janne Grunau

Hi.

This series adds support for the camera and ISP system present
on Apple devices using M-series chips. This is a "simple" camera
and does not need any special userspace handling, everything
is handled by the firmware running on an ASC coprocessor.

Patches 1 and 2 add support for special handling neccesary for the
ISP power domains. The rest add the driver itself.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
Asahi Lina (1):
      pmdomain: apple: Add force-disable/force-reset

Eileen Yoon (2):
      media: apple: Add Apple ISP driver
      arm64: dts: apple: Add ISP nodes

Sasha Finkelstein (2):
      dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties
      media: dt-bindings: Add Apple ISP

 .../devicetree/bindings/media/apple,isp.yaml       | 151 ++++
 .../bindings/power/apple,pmgr-pwrstate.yaml        |  10 +
 MAINTAINERS                                        |   2 +
 arch/arm64/boot/dts/apple/isp-common.dtsi          |  45 ++
 arch/arm64/boot/dts/apple/isp-imx248.dtsi          |  62 ++
 arch/arm64/boot/dts/apple/isp-imx364.dtsi          |  78 ++
 arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi     | 101 +++
 arch/arm64/boot/dts/apple/isp-imx558.dtsi          | 102 +++
 arch/arm64/boot/dts/apple/t600x-die0.dtsi          |  48 ++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi     |   6 +
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi          |  81 ++
 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/t8103-pmgr.dtsi          | 118 +++
 arch/arm64/boot/dts/apple/t8103.dtsi               |  50 ++
 arch/arm64/boot/dts/apple/t8112-j413.dts           |   7 +
 arch/arm64/boot/dts/apple/t8112-j493.dts           |   6 +
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi          | 118 +++
 arch/arm64/boot/dts/apple/t8112.dtsi               |  50 ++
 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/Kconfig           |  16 +
 drivers/media/platform/apple/isp/Makefile          |   3 +
 drivers/media/platform/apple/isp/isp-cam.c         | 414 ++++++++++
 drivers/media/platform/apple/isp/isp-cam.h         |  21 +
 drivers/media/platform/apple/isp/isp-cmd.c         | 635 +++++++++++++++
 drivers/media/platform/apple/isp/isp-cmd.h         | 692 ++++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.c         | 586 ++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.h         | 284 +++++++
 drivers/media/platform/apple/isp/isp-fw.c          | 770 ++++++++++++++++++
 drivers/media/platform/apple/isp/isp-fw.h          |  24 +
 drivers/media/platform/apple/isp/isp-iommu.c       | 251 ++++++
 drivers/media/platform/apple/isp/isp-iommu.h       |  20 +
 drivers/media/platform/apple/isp/isp-ipc.c         | 258 ++++++
 drivers/media/platform/apple/isp/isp-ipc.h         |  25 +
 drivers/media/platform/apple/isp/isp-regs.h        |  56 ++
 drivers/media/platform/apple/isp/isp-v4l2.c        | 900 +++++++++++++++++++++
 drivers/media/platform/apple/isp/isp-v4l2.h        |  16 +
 drivers/pmdomain/apple/pmgr-pwrstate.c             |  43 +-
 43 files changed, 6077 insertions(+), 6 deletions(-)
---
base-commit: 2408a807bfc3f738850ef5ad5e3fd59d66168996
change-id: 20250218-isp-5da1dbc7d472



^ permalink raw reply	[flat|nested] 21+ messages in thread

* [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties
  2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:26 ` Sasha Finkelstein via B4 Relay
  2025-02-19  9:34   ` Krzysztof Kozlowski
  2025-02-19  9:26 ` [PATCH 2/5] pmdomain: apple: Add force-disable/force-reset Sasha Finkelstein via B4 Relay
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:26 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein

From: Sasha Finkelstein <fnkl.kernel@gmail.com>

Add properties to set disable/reset bits when powering down
certain domains

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 .../devicetree/bindings/power/apple,pmgr-pwrstate.yaml         | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
index 59a6af735a2167b7edd9e0491da238f21effe316..9c8f5385bee5aa1bac8c3e44963713299160521e 100644
--- a/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
+++ b/Documentation/devicetree/bindings/power/apple,pmgr-pwrstate.yaml
@@ -62,6 +62,16 @@ properties:
       Forces this power domain to always be powered up.
     type: boolean
 
+  apple,force-disable:
+    description:
+      Use the disable bit when turning the power off
+    type: boolean
+
+  apple,force-reset:
+    description:
+      Use the reset bit when turning the power off
+    type: boolean
+
   apple,min-state:
     description:
       Specifies the minimum power state for auto-PM.

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 2/5] pmdomain: apple: Add force-disable/force-reset
  2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
  2025-02-19  9:26 ` [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:26 ` Sasha Finkelstein via B4 Relay
  2025-02-19  9:26 ` [PATCH 3/5] media: dt-bindings: Add Apple ISP Sasha Finkelstein via B4 Relay
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:26 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein, Asahi Lina

From: Asahi Lina <lina@asahilina.net>

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>
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 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 9467235110f4654e00ab96c25e160e125ef0f3e5..3236d854265c9489cca7affadf4448e8444dd839 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,

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
  2025-02-19  9:26 ` [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties Sasha Finkelstein via B4 Relay
  2025-02-19  9:26 ` [PATCH 2/5] pmdomain: apple: Add force-disable/force-reset Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:26 ` Sasha Finkelstein via B4 Relay
  2025-02-19  9:37   ` Krzysztof Kozlowski
  2025-02-19  9:27 ` [PATCH 4/5] media: apple: Add Apple ISP driver Sasha Finkelstein via B4 Relay
  2025-02-19  9:27 ` [PATCH 5/5] arm64: dts: apple: Add ISP nodes Sasha Finkelstein via B4 Relay
  4 siblings, 1 reply; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:26 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein

From: Sasha Finkelstein <fnkl.kernel@gmail.com>

Add bindings for the ISP used with the webcam in Apple
ARM laptops.

Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 .../devicetree/bindings/media/apple,isp.yaml       | 151 +++++++++++++++++++++
 MAINTAINERS                                        |   1 +
 2 files changed, 152 insertions(+)

diff --git a/Documentation/devicetree/bindings/media/apple,isp.yaml b/Documentation/devicetree/bindings/media/apple,isp.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..67d536b61851af30fcc5bc452a761138876c6b18
--- /dev/null
+++ b/Documentation/devicetree/bindings/media/apple,isp.yaml
@@ -0,0 +1,151 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/media/apple,isp.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: An ISP block used in Apple products
+
+maintainers:
+  - Sasha Finkelstein <fnkl.kernel@gmail.com>
+
+description:
+  The ISP in charge of webcams on ARM Apple laptops
+
+properties:
+  compatible:
+    items:
+      - enum:
+          - apple,t8103-isp
+          - apple,t8112-isp
+          - apple,t6000-isp
+          - apple,t6020-isp
+      - const: apple,isp
+
+  reg:
+    items:
+      - description: ASC coprocessor control
+      - description: Peripheral to host mailbox
+      - description: General-purpose ASC IO registers
+      - description: Host to peripheral mailbox
+
+  reg-names:
+    items:
+      - const: coproc
+      - const: mbox
+      - const: gpio
+      - const: mbox2
+
+  iommus:
+    description: All 3 must be kept in sync
+    minItems: 3
+    maxItems: 3
+
+  interrupts:
+    maxItems: 1
+
+  power-domains:
+    minItems: 1
+    maxItems: 20
+    description: All necessary power domains. Driver will enable them in order
+
+  memory-region:
+    maxItems: 1
+
+  apple,dart-vm-size:
+    description: Supported device memory range
+    $ref: /schemas/types.yaml#/definitions/uint64
+
+  apple,platform-id:
+    description: Platform id for firmware
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  apple,temporal-filter:
+    description: Whether temporal filter should be enabled in firmware
+    $ref: /schemas/types.yaml#/definitions/uint32
+
+  sensor-presets:
+    additionalProperties: false
+
+    patternProperties:
+      '^preset[0-9]+$':
+        type: object
+
+        additionalProperties: false
+
+        properties:
+          apple,config-index:
+            description: Firmware config index
+            $ref: /schemas/types.yaml#/definitions/uint32
+
+          apple,input-size:
+            $ref: /schemas/types.yaml#/definitions/uint32-array
+            minItems: 2
+            maxItems: 2
+            description: Raw sensor size
+
+          apple,output-size:
+            $ref: /schemas/types.yaml#/definitions/uint32-array
+            minItems: 2
+            maxItems: 2
+            description: Cropped and scaled image size
+
+          apple,crop:
+            $ref: /schemas/types.yaml#/definitions/uint32-array
+            minItems: 4
+            maxItems: 4
+            description: Area to crop
+
+        required:
+          - apple,config-index
+          - apple,input-size
+          - apple,output-size
+          - apple,crop
+
+required:
+  - compatible
+  - reg
+  - iommus
+  - interrupts
+  - power-domains
+  - memory-region
+  - apple,dart-vm-size
+  - apple,platform-id
+  - apple,temporal-filter
+
+additionalProperties: false
+
+examples:
+  - |
+    #include <dt-bindings/interrupt-controller/apple-aic.h>
+    isp: isp@2a000000 {
+        compatible = "apple,t8103-isp", "apple,isp";
+        reg = <0x2a000000 0x2000000>,
+              <0x2c104000 0x100>,
+              <0x2c104170 0x100>,
+              <0x2c1043f0 0x100>;
+        reg-names = "coproc", "mbox", "gpio", "mbox2";
+        iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+        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>;
+        memory-region = <&isp_heap>;
+        apple,dart-vm-size = <0x0 0xa0000000>;
+        apple,platform-id = <1>;
+        apple,temporal-filter = <0>;
+
+        sensor-presets {
+            preset0 {
+                apple,config-index = <0>;
+                apple,input-size = <1296 736>;
+                apple,output-size = <1280 720>;
+                apple,crop = <8 8 1280 720>;
+            };
+        };
+    };
+...
diff --git a/MAINTAINERS b/MAINTAINERS
index efee40ea589f70bc5e4a390072a4543234616743..dea7239ee0f5464b31efed5a2e0e5e602bcb6439 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2228,6 +2228,7 @@ F:	Documentation/devicetree/bindings/interrupt-controller/apple,*
 F:	Documentation/devicetree/bindings/iommu/apple,dart.yaml
 F:	Documentation/devicetree/bindings/iommu/apple,sart.yaml
 F:	Documentation/devicetree/bindings/mailbox/apple,mailbox.yaml
+F:	Documentation/devicetree/bindings/media/apple,isp.yaml
 F:	Documentation/devicetree/bindings/net/bluetooth/brcm,bcm4377-bluetooth.yaml
 F:	Documentation/devicetree/bindings/nvme/apple,nvme-ans.yaml
 F:	Documentation/devicetree/bindings/nvmem/apple,efuses.yaml

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 4/5] media: apple: Add Apple ISP driver
  2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
                   ` (2 preceding siblings ...)
  2025-02-19  9:26 ` [PATCH 3/5] media: dt-bindings: Add Apple ISP Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:27 ` Sasha Finkelstein via B4 Relay
  2025-02-19 11:34   ` Janne Grunau
  2025-02-19  9:27 ` [PATCH 5/5] arm64: dts: apple: Add ISP nodes Sasha Finkelstein via B4 Relay
  4 siblings, 1 reply; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:27 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein, Eileen Yoon, Asahi Lina,
	Janne Grunau

From: Eileen Yoon <eyn@gmx.com>

This is the ISP and camera module present on certain Apple laptops

Signed-off-by: Eileen Yoon <eyn@gmx.com>
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Co-developed-by: Asahi Lina <lina@asahilina.net>
Signed-off-by: Asahi Lina <lina@asahilina.net>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Co-developed-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 MAINTAINERS                                  |   1 +
 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/Kconfig     |  16 +
 drivers/media/platform/apple/isp/Makefile    |   3 +
 drivers/media/platform/apple/isp/isp-cam.c   | 414 ++++++++++++
 drivers/media/platform/apple/isp/isp-cam.h   |  21 +
 drivers/media/platform/apple/isp/isp-cmd.c   | 635 +++++++++++++++++++
 drivers/media/platform/apple/isp/isp-cmd.h   | 692 ++++++++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.c   | 586 +++++++++++++++++
 drivers/media/platform/apple/isp/isp-drv.h   | 284 +++++++++
 drivers/media/platform/apple/isp/isp-fw.c    | 770 +++++++++++++++++++++++
 drivers/media/platform/apple/isp/isp-fw.h    |  24 +
 drivers/media/platform/apple/isp/isp-iommu.c | 251 ++++++++
 drivers/media/platform/apple/isp/isp-iommu.h |  20 +
 drivers/media/platform/apple/isp/isp-ipc.c   | 258 ++++++++
 drivers/media/platform/apple/isp/isp-ipc.h   |  25 +
 drivers/media/platform/apple/isp/isp-regs.h  |  56 ++
 drivers/media/platform/apple/isp/isp-v4l2.c  | 900 +++++++++++++++++++++++++++
 drivers/media/platform/apple/isp/isp-v4l2.h  |  16 +
 22 files changed, 4982 insertions(+)

diff --git a/MAINTAINERS b/MAINTAINERS
index dea7239ee0f5464b31efed5a2e0e5e602bcb6439..60517f7dcee14fc942dd3f77ed5d58eae394f7fa 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2248,6 +2248,7 @@ F:	drivers/i2c/busses/i2c-pasemi-platform.c
 F:	drivers/iommu/apple-dart.c
 F:	drivers/iommu/io-pgtable-dart.c
 F:	drivers/irqchip/irq-apple-aic.c
+F:	drivers/media/platform/apple/*
 F:	drivers/nvme/host/apple.c
 F:	drivers/nvmem/apple-efuses.c
 F:	drivers/pinctrl/pinctrl-apple-gpio.c
diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
index 85d2627776b6a424fbd392187669535c4159ec97..ba75cfdb57f710cca086136e4524d3e1bc1910ac 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 ace4e34483ddce6c3361479989086145dd495f29..e59e4259064bf04b718ea8d128031af859a13d2e 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 0000000000000000000000000000000000000000..f16508bff5242a0bc433bf8a1d8e3f29737d20d1
--- /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 0000000000000000000000000000000000000000..d8fe985b0e6c377de6c77d30a3a796c40f3da116
--- /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/Kconfig b/drivers/media/platform/apple/isp/Kconfig
new file mode 100644
index 0000000000000000000000000000000000000000..8e94962990031304d51cdd7cd6190b05b05b40bb
--- /dev/null
+++ b/drivers/media/platform/apple/isp/Kconfig
@@ -0,0 +1,16 @@
+# 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
+	help
+	  Say Y here to enable support for the ISP and cameras persent
+	  in Apple ARM laptops.
+
+	  To compile this driver as a module, choose M here. The module will be
+	  called apple_isp
diff --git a/drivers/media/platform/apple/isp/Makefile b/drivers/media/platform/apple/isp/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..4649f32987f025a639945a37d774d4ecdc83b02a
--- /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 0000000000000000000000000000000000000000..bdf46666987ec61a432d384ae0bd4173230aa82c
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cam.c
@@ -0,0 +1,414 @@
+// 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"
+
+#define ISP_MAX_PRESETS 32
+
+struct isp_setfile {
+	u32 version;
+	u32 magic;
+	const char *path;
+	size_t size;
+};
+
+static const struct isp_setfile isp_setfiles[] = {
+	[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},
+};
+
+struct isp_version {
+	u32 version;
+	enum isp_sensor_id id;
+};
+
+static const struct isp_version isp_versions[] = {
+	{0x248, ISP_IMX248_1820_01},
+	{0x343, ISP_IMX343_5221_02},
+	{0x354, ISP_IMX354_9251_02},
+	{0x356, ISP_IMX356_4820_01},
+	{0x364, ISP_IMX364_8720_01},
+	{0x372, ISP_IMX372_3820_01},
+	{0x405, ISP_IMX405_9720_01},
+	{0x414, ISP_IMX414_2520_01},
+	{0x503, ISP_IMX503_7820_01},
+	{0x505, ISP_IMX505_3921_01},
+	{0x514, ISP_IMX514_2820_01},
+	{0x558, ISP_IMX558_1921_01},
+	{0x603, ISP_IMX603_7920_01},
+	{0x613, ISP_IMX613_4920_01},
+	{0x614, ISP_IMX614_2921_01},
+	{0x633, ISP_IMX633_3622_01},
+	{0x703, ISP_IMX703_7721_01},
+	{0x713, ISP_IMX713_4721_01},
+	{0x714, ISP_IMX714_2022_01},
+	{0x772, ISP_IMX772_3721_01},
+	{0x814, ISP_IMX814_2123_01},
+	{0x853, ISP_IMX853_7622_01},
+	{0x913, ISP_IMX913_7523_01},
+	{0xd56, ISP_VD56G0_6221_01},
+};
+
+static int isp_ch_get_sensor_id(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	int i;
+
+	/* TODO need more datapoints to figure out the sub-versions
+	 * Defaulting to 1st release for now, the calib files aren't too different.
+	 */
+	for (i = 0; i < ARRAY_SIZE(isp_versions); i++) {
+		if (isp_versions[i].version == fmt->version) {
+			fmt->id = isp_versions[i].id;
+			return 0;
+		}
+	}
+
+	dev_err(isp->dev, "invalid sensor version: 0x%x\n",
+		fmt->version);
+
+	return -EINVAL;
+}
+
+static int isp_ch_cache_sensor_info(struct apple_isp *isp, u32 ch)
+{
+	struct isp_format *fmt = isp_get_format(isp, ch);
+	struct cmd_ch_info *args; /* Too big to allocate on stack */
+	int err = 0;
+
+	args = kzalloc(sizeof(*args), GFP_KERNEL);
+	if (!args)
+		return -ENOMEM;
+
+	err = apple_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;
+
+	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_IMX364_8720_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_detect_camera(struct apple_isp *isp)
+{
+	int err;
+	struct cmd_config_get args;
+
+	memset(&args, 0, sizeof(args));
+
+	err = apple_isp_cmd_config_get(isp, &args);
+	if (err)
+		return err;
+
+	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;
+
+	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)
+{
+	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_cmd_flicker_sensor_set(isp, 0);
+
+	apple_isp_cmd_ch_stop(isp, 0);
+	apple_isp_cmd_ch_buffer_return(isp, isp->current_ch);
+
+	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;
+	}
+
+	memcpy(isp->data_surf->virt, (void *)fw->data, setfile->size);
+	release_firmware(fw);
+
+	return apple_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;
+
+	apple_isp_cmd_flicker_sensor_set(isp, 0);
+
+	err = isp_ch_load_setfile(isp, ch);
+	if (err) {
+		dev_err(isp->dev, "calibration data not loaded: %d\n",
+			err);
+		return err;
+	}
+
+	if (isp->hw->lpdp) {
+		err = apple_isp_cmd_ch_lpdp_hs_receiver_tuning_set(isp, ch, 1, 15);
+		if (err)
+			return err;
+	}
+
+	err = apple_isp_cmd_ch_sbs_enable(isp, ch, 1);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_camera_config_select(isp, ch, fmt->preset->index);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_buffer_recycle_mode_set(
+		isp, ch, CISP_BUFFER_RECYCLE_MODE_EMPTY_ONLY);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_buffer_recycle_start(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_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 = apple_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;
+
+	err = apple_isp_cmd_ch_preview_stream_set(isp, ch, 1);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_cnr_start(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_mbnr_enable(isp, ch, 0, ISP_MBNR_MODE_ENABLE, 1);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_apple_ch_ae_fd_scene_metering_config_set(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_apple_ch_ae_metering_mode_set(isp, ch, 3);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_ae_stability_set(isp, ch, 32);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_ae_stability_to_stable_set(isp, ch, 20);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_sif_pixel_format_set(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_ae_frame_rate_max_set(isp, ch, ISP_FRAME_RATE_DEN);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_ae_frame_rate_min_set(isp, ch, ISP_FRAME_RATE_DEN2);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_apple_ch_temporal_filter_start(isp, ch, isp->temporal_filter);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_apple_ch_motion_history_start(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_apple_ch_temporal_filter_enable(isp, ch);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_ch_buffer_pool_config_set(isp, ch, CISP_POOL_TYPE_META);
+	if (err)
+		return err;
+
+	err = apple_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) {
+		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 apple_isp_cmd_ch_start(isp, 0);
+}
+
+void apple_isp_stop_capture(struct apple_isp *isp)
+{
+	apple_isp_cmd_ch_stop(isp, 0);
+	apple_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 0000000000000000000000000000000000000000..2372cf2e012bebd775f7d4d45836bb6d06c54b5c
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cam.h
@@ -0,0 +1,21 @@
+/* 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
+#define ISP_FRAME_RATE_DEN2 3840
+
+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 0000000000000000000000000000000000000000..5b678023b9b9c9497606c1cbb406bc99121785d6
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cmd.c
@@ -0,0 +1,635 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include "isp-cmd.h"
+#include "isp-drv.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, 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))
+#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)
+{
+	struct isp_channel *chan = isp->chan_io;
+	struct isp_message *req = &chan->req;
+	u64 opcode;
+	int err;
+
+	req->arg0 = isp->cmd_iova;
+	req->arg1 = insize;
+	req->arg2 = outsize;
+
+	memcpy(isp->cmd_virt, args, insize);
+	err = apple_isp_ipc_chan_send(isp, chan, timeout);
+	if (err) {
+		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)
+{
+	int err;
+
+	err = cisp_send(isp, args, insize, outsize, CISP_TIMEOUT);
+	if (err)
+		return err;
+
+	memcpy(args, isp->cmd_virt, outsize);
+	return 0;
+}
+
+int apple_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 apple_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 apple_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 apple_isp_cmd_suspend(struct apple_isp *isp)
+{
+	struct cmd_suspend args = {
+		.opcode = CISP_OPCODE(CISP_CMD_SUSPEND),
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr,
+				   u32 size)
+{
+	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,
+			.addr = addr,
+			.size = size,
+		};
+		return CISP_SEND_IN(isp, args);
+	}
+	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 apple_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 apple_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(isp->hw->scl1 ? CISP_CMD_CH_CROP_SCL1_SET
+				      : CISP_CMD_CH_CROP_SET),
+		.chan = chan,
+		.x1 = x1,
+		.y1 = y1,
+		.x2 = x2,
+		.y2 = y2,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int apple_isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
+				 u32 height, u32 strides[3], u32 colorspace, u32 format)
+{
+	struct cmd_ch_output_config_set args = {
+		.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,
+		.padding_rows = 0,
+		.unk_h0 = height,
+		.compress = 0,
+		.unk_w2 = width,
+	};
+	memcpy(args.strides, strides, sizeof(args.strides));
+	return CISP_SEND_IN(isp, args);
+}
+
+int apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 = ISP_MAX_BUFFERS,
+		.meta_size0 = isp->hw->meta_size,
+		.meta_size1 = isp->hw->meta_size,
+		.unk0 = 0,
+		.unk1 = 0,
+		.unk2 = 0,
+		.data_blocks = 1,
+		.compress = 0,
+	};
+	return CISP_SEND_INOUT(isp, args);
+}
+
+int apple_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 apple_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 = arg,
+	};
+	return CISP_SEND_IN(isp, args);
+}
+
+int apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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 apple_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);
+}
+
+int apple_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 apple_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 apple_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
new file mode 100644
index 0000000000000000000000000000000000000000..75e7295f783c998dbc7775216cff94beba440b36
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-cmd.h
@@ -0,0 +1,692 @@
+/* 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_POWER_DOWN				     0x0010
+#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_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
+#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_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
+#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_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_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 {
+	u64 opcode;
+	u32 mode;
+} __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;
+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_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;
+	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);
+
+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);
+
+struct cmd_flicker_sensor_set {
+	u64 opcode;
+	u32 mode;
+} __packed;
+static_assert(sizeof(struct cmd_flicker_sensor_set) == 0xc);
+
+int apple_isp_cmd_start(struct apple_isp *isp, u32 mode);
+int apple_isp_cmd_stop(struct apple_isp *isp, u32 mode);
+int apple_isp_cmd_power_down(struct apple_isp *isp);
+int apple_isp_cmd_suspend(struct apple_isp *isp);
+int apple_isp_cmd_print_enable(struct apple_isp *isp, u32 enable);
+int apple_isp_cmd_trace_enable(struct apple_isp *isp, u32 enable);
+int apple_isp_cmd_config_get(struct apple_isp *isp, struct cmd_config_get *args);
+int apple_isp_cmd_set_isp_pmu_base(struct apple_isp *isp, u64 pmu_base);
+int apple_isp_cmd_set_dsid_clr_req_base(struct apple_isp *isp, u64 dsid_clr_base,
+					u32 dsid_clr_range);
+int apple_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 apple_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 apple_isp_cmd_fid_enter(struct apple_isp *isp);
+int apple_isp_cmd_fid_exit(struct apple_isp *isp);
+int apple_isp_cmd_flicker_sensor_set(struct apple_isp *isp, u32 mode);
+
+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;  /* 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; /* 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; /* 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; /* 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);
+
+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_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);
+
+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_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;
+} __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 strides[3];
+	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);
+
+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 apple_isp_cmd_ch_start(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_stop(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_info_get(struct apple_isp *isp, u32 chan,
+			      struct cmd_ch_info *args);
+int apple_isp_cmd_ch_camera_config_get(struct apple_isp *isp, u32 chan, u32 preset,
+				       struct cmd_ch_camera_config *args);
+int apple_isp_cmd_ch_camera_config_current_get(struct apple_isp *isp, u32 chan,
+					       struct cmd_ch_camera_config *args);
+int apple_isp_cmd_ch_camera_config_select(struct apple_isp *isp, u32 chan,
+					  u32 preset);
+int apple_isp_cmd_ch_set_file_load(struct apple_isp *isp, u32 chan, u64 addr,
+				   u32 size);
+int apple_isp_cmd_ch_buffer_return(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_sbs_enable(struct apple_isp *isp, u32 chan, u32 enable);
+int apple_isp_cmd_ch_crop_set(struct apple_isp *isp, u32 chan, u32 x1, u32 y1, u32 x2,
+			      u32 y2);
+int apple_isp_cmd_ch_output_config_set(struct apple_isp *isp, u32 chan, u32 width,
+				       u32 height, u32 strides[3], u32 colorspace, u32 format);
+int apple_isp_cmd_ch_preview_stream_set(struct apple_isp *isp, u32 chan, u32 stream);
+int apple_isp_cmd_ch_als_disable(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_cnr_start(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_mbnr_enable(struct apple_isp *isp, u32 chan, u32 use_case,
+				 u32 mode, u32 enable_chroma);
+int apple_isp_cmd_ch_sif_pixel_format_set(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_lpdp_hs_receiver_tuning_set(struct apple_isp *isp, u32 chan,
+						 u32 unk1, u32 unk2);
+
+int apple_isp_cmd_ch_property_read(struct apple_isp *isp, u32 chan, u32 prop, u32 *val);
+int apple_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;
+	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;
+	u64 unk0;
+	u64 unk1;
+	u64 unk2;
+	u32 zero[0x19];
+	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 apple_isp_cmd_ch_buffer_recycle_mode_set(struct apple_isp *isp, u32 chan,
+					     u32 mode);
+int apple_isp_cmd_ch_buffer_recycle_start(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_ch_buffer_pool_config_set(struct apple_isp *isp, u32 chan,
+					    u16 type);
+int apple_isp_cmd_ch_buffer_pool_config_get(struct apple_isp *isp, u32 chan,
+					    u16 type);
+int apple_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 apple_isp_cmd_apple_ch_temporal_filter_start(struct apple_isp *isp, u32 chan, u32 arg);
+int apple_isp_cmd_apple_ch_temporal_filter_stop(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_apple_ch_motion_history_start(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_apple_ch_motion_history_stop(struct apple_isp *isp, u32 chan);
+int apple_isp_cmd_apple_ch_temporal_filter_enable(struct apple_isp *isp, u32 chan);
+int apple_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 apple_isp_cmd_ch_ae_stability_set(struct apple_isp *isp, u32 chan, u32 stability);
+int apple_isp_cmd_ch_ae_stability_to_stable_set(struct apple_isp *isp, u32 chan,
+						u32 stability);
+int apple_isp_cmd_ch_ae_frame_rate_max_get(struct apple_isp *isp, u32 chan,
+					   struct cmd_ch_ae_frame_rate_max_get *args);
+int apple_isp_cmd_ch_ae_frame_rate_max_set(struct apple_isp *isp, u32 chan,
+					   u32 framerate);
+int apple_isp_cmd_ch_ae_frame_rate_min_set(struct apple_isp *isp, u32 chan,
+					   u32 framerate);
+int apple_isp_cmd_apple_ch_ae_fd_scene_metering_config_set(struct apple_isp *isp,
+							   u32 chan);
+int apple_isp_cmd_apple_ch_ae_metering_mode_set(struct apple_isp *isp, u32 chan,
+						u32 mode);
+int apple_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 apple_isp_cmd_ch_semantic_video_enable(struct apple_isp *isp, u32 chan,
+				     u32 enable);
+int apple_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 0000000000000000000000000000000000000000..b0c73b4f43d73f4ee29093fe62ed1d39ccfa33dd
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-drv.c
@@ -0,0 +1,586 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Apple Image Signal Processor driver
+ *
+ * Copyright (C) 2023 The Asahi Linux Contributors
+ */
+
+#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-fw.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);
+	}
+}
+
+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++) {
+		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);
+			return PTR_ERR(isp->pd_dev[i]);
+		}
+
+		isp->pd_link[i] =
+			device_link_add(dev, isp->pd_dev[i], flags);
+
+		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;
+	phys_addr_t heap_base;
+	size_t heap_size;
+	u64 vm_size;
+	int err;
+	int size;
+	struct device_node *mem_node;
+	const __be32 *maps, *end;
+
+	isp->domain = iommu_get_domain_for_dev(isp->dev);
+	if (!isp->domain)
+		return -ENODEV;
+	isp->shift = __ffs(isp->domain->pgsize_bitmap);
+
+	mem_node = of_parse_phandle(dev->of_node, "memory-region", 0);
+	if (!mem_node) {
+		dev_err(dev, "No memory-region found for heap\n");
+		return -ENODEV;
+	}
+
+	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);
+	}
+
+	isp->fw.heap_top = heap_base + heap_size;
+
+	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, isp->fw.heap_top,
+		    vm_size - (heap_base & 0xffffffff));
+
+	return 0;
+}
+
+static void apple_isp_free_iommu(struct apple_isp *isp)
+{
+	drm_mm_takedown(&isp->iovad);
+}
+
+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);
+		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 isp_preset *preset;
+	int err = 0;
+
+	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;
+	}
+
+	isp->num_presets = of_get_child_count(np);
+	if (!isp->num_presets) {
+		dev_err(dev, "no sensor presets found\n");
+		return -EINVAL;
+	}
+
+	isp->presets = devm_kzalloc(
+		dev, sizeof(*isp->presets) * isp->num_presets, GFP_KERNEL);
+	if (!isp->presets)
+		return -ENOMEM;
+
+	preset = isp->presets;
+	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");
+			return err;
+		}
+
+		err = isp_of_read_coord(dev, child, "apple,input-size",
+					&preset->input_dim);
+		if (err)
+			return err;
+		err = isp_of_read_coord(dev, child, "apple,output-size",
+					&preset->output_dim);
+		if (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");
+			return 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++;
+	}
+
+	return 0;
+}
+
+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;
+	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;
+
+	isp->dev = dev;
+	isp->hw = of_device_get_match_data(dev);
+	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) {
+		dev_err(dev, "failed to get 'apple,platform-id' property: %d\n",
+			err);
+		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");
+		return err;
+	}
+
+	err = apple_isp_attach_genpd(isp);
+	if (err) {
+		dev_err(dev, "failed to attach power domains\n");
+		return err;
+	}
+
+	isp->coproc = devm_platform_ioremap_resource_byname(pdev, "coproc");
+	if (IS_ERR(isp->coproc)) {
+		err = PTR_ERR(isp->coproc);
+		goto detach_genpd;
+	}
+
+	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;
+	}
+
+	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;
+		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->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");
+		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;
+	}
+
+	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_surface;
+	}
+
+	err = apple_isp_setup_video(isp);
+	if (err) {
+		dev_err(dev, "failed to register video device: %d\n", err);
+		goto free_surface;
+	}
+
+	return 0;
+
+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);
+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_firmware_surface(isp);
+	apple_isp_free_iommu(isp);
+	destroy_workqueue(isp->wq);
+	apple_isp_detach_genpd(isp);
+}
+
+static const struct apple_isp_hw apple_isp_hw_t8103 = {
+	.gen = ISP_GEN_T8103,
+	.pmu_base = 0x23b704000,
+
+	.dsid_count = 4,
+	.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,
+
+	.scl1 = false,
+	.lpdp = false,
+	.meta_size = ISP_META_SIZE_T8103,
+};
+
+static const struct apple_isp_hw apple_isp_hw_t6000 = {
+	.gen = ISP_GEN_T8103,
+	.pmu_base = 0x28e584000,
+
+	.dsid_count = 1,
+	.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,
+
+	.scl1 = false,
+	.lpdp = false,
+	.meta_size = ISP_META_SIZE_T8103,
+};
+
+static const struct apple_isp_hw apple_isp_hw_t8112 = {
+	.gen = ISP_GEN_T8112,
+	.pmu_base = 0x23b704000,
+
+	.dsid_count = 1,
+	.dsid_clr_base0 = 0x200f14000,
+	.dsid_clr_range0 = 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,
+
+	.scl1 = false,
+	.lpdp = false,
+	.meta_size = ISP_META_SIZE_T8112,
+};
+
+static const struct apple_isp_hw apple_isp_hw_t6020 = {
+	.gen = ISP_GEN_T8112,
+	.pmu_base = 0x290284000,
+
+	.dsid_count = 1,
+	.dsid_clr_base0 = 0x200f14000,
+	.dsid_clr_range0 = 0x1000,
+
+	.clock_scratch = 0x28e3d10a8,
+	.clock_base = 0x0,
+	.clock_bit = 0x0,
+	.clock_size = 0x8,
+	.bandwidth_scratch = 0x28e3d1200,
+	.bandwidth_base = 0x0,
+	.bandwidth_bit = 0x0,
+	.bandwidth_size = 0x8,
+
+	.scl1 = true,
+	.lpdp = true,
+	.meta_size = ISP_META_SIZE_T8112,
+};
+
+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 },
+	{},
+};
+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;
+}
+
+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	= {
+		.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");
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 0000000000000000000000000000000000000000..b76ad4f56328c0454ad8fa249c82471fbae02d3a
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-drv.h
@@ -0,0 +1,284 @@
+/* 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_DEVICE_NAME "apple-isp"
+#define APPLE_ISP_CARD_NAME "FaceTime HD Camera"
+
+#define ISP_MAX_CHANNELS      6
+#define ISP_IPC_MESSAGE_SIZE  64
+#define ISP_IPC_FLAG_ACK      0x1
+#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,
+};
+
+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;
+	u64 size;
+	u64 type;
+	u32 num_pages;
+	struct page **pages;
+	struct sg_table sgt;
+	dma_addr_t iova;
+	void *virt;
+	refcount_t refcount;
+	bool gc;
+	bool submitted;
+};
+
+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;
+	void *virt;
+	u32 doorbell;
+	u32 cursor;
+	struct mutex lock;
+	struct isp_message req;
+	struct isp_message rsp;
+	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 {
+	enum isp_generation gen;
+	u64 pmu_base;
+
+	int dsid_count;
+	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 meta_size;
+	bool scl1;
+	bool lpdp;
+};
+
+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;
+	struct isp_preset *preset;
+	unsigned int num_planes;
+	u32 strides[VB2_MAX_PLANES];
+	size_t plane_size[VB2_MAX_PLANES];
+	size_t total_size;
+};
+
+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;
+	int num_presets;
+
+	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;
+	bool pds_active;
+
+	int irq;
+
+	void __iomem *coproc;
+	void __iomem *mbox;
+	void __iomem *gpio;
+	void __iomem *mbox2;
+
+	struct iommu_domain *domain;
+	unsigned long shift;
+	struct drm_mm iovad;
+	struct mutex iovad_lock;
+
+	struct isp_firmware {
+		u64 heap_top;
+	} fw;
+
+	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 isp_surf *meta_surfs[ISP_MAX_BUFFERS];
+	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;
+	void *cmd_virt;
+
+	unsigned long state;
+	spinlock_t buf_lock;
+	struct list_head bufs_pending;
+	struct list_head bufs_submitted;
+};
+
+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];
+};
+
+#define to_isp_buffer(x) container_of((x), struct isp_buffer, vb)
+
+enum {
+	ISP_STATE_STREAMING,
+	ISP_STATE_LOGGING,
+	ISP_STATE_SLEEPING,
+};
+
+#define isp_dbg(isp, fmt, ...) \
+	dev_dbg((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__)
+
+#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 0000000000000000000000000000000000000000..ffdfcf460fedcf2bcf94118a7b27d9e49010274f
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-fw.c
@@ -0,0 +1,770 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/* Copyright 2023 Eileen Yoon <eyn@gmx.com> */
+
+#include "isp-fw.h"
+
+#include <linux/delay.h>
+#include <linux/io.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"
+#include "isp-v4l2.h"
+
+#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_COPROC_IN_WFI      0x3
+
+static inline u32 isp_coproc_read32(struct apple_isp *isp, u32 reg)
+{
+	return readl(isp->coproc + reg);
+}
+
+static inline void isp_coproc_write32(struct apple_isp *isp, u32 reg, u32 val)
+{
+	writel(val, isp->coproc + 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);
+}
+
+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_resume_and_get(isp->pd_dev[i]);
+		if (ret) {
+			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;
+}
+
+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)
+{
+	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;
+	u64 shared_base;
+	u64 shared_size;
+	u64 extra_iova;
+	u64 extra_size;
+	u32 platform_id;
+	u32 pad_40;
+	u64 logbuf_addr;
+	u64 logbuf_size;
+	u64 logbuf_entsize;
+	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) == 0x180);
+
+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 = apple_isp_ipc_tm_handle,
+};
+
+static const struct isp_chan_ops sm_ops = {
+	.handle = apple_isp_ipc_sm_handle,
+};
+
+static const struct isp_chan_ops bt_ops = {
+	.handle = apple_isp_ipc_bt_handle,
+};
+
+static irqreturn_t apple_isp_isr(int irq, void *dev)
+{
+	struct apple_isp *isp = dev;
+
+	isp_mbox2_write32(isp, ISP_MBOX2_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_all(&isp->wait);
+
+	apple_isp_ipc_chan_handle(isp, isp->chan_sm);
+	wake_up_all(&isp->wait); /* Some commands depend on sm */
+
+	apple_isp_ipc_chan_handle(isp, isp->chan_tm);
+
+	apple_isp_ipc_chan_handle(isp, isp->chan_bt);
+	wake_up_all(&isp->wait);
+
+	return IRQ_HANDLED;
+}
+
+static void isp_disable_irq(struct apple_isp *isp)
+{
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0);
+	free_irq(isp->irq, isp);
+	isp_gpio_write32(isp, ISP_GPIO_1, 0xfeedbabe);
+}
+
+static int isp_enable_irq(struct apple_isp *isp)
+{
+	int err;
+
+	err = request_threaded_irq(isp->irq, apple_isp_isr,
+				   apple_isp_isr_thread, 0, "apple-isp", isp);
+	if (err) {
+		isp_err(isp, "failed to request IRQ#%u (%d)\n", isp->irq, err);
+		return err;
+	}
+
+	isp_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0xf);
+
+	return 0;
+}
+
+static int isp_reset_coproc(struct apple_isp *isp)
+{
+	int retries;
+	u32 status;
+	u32 val;
+
+	isp_coproc_write32(isp, ISP_COPROC_EDPRCR, 0x2);
+
+	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);
+	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 < 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) {
+			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_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;
+	u32 val;
+
+	err = apple_isp_power_up_domains(isp);
+	if (err)
+		return err;
+
+
+	isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1);
+
+	err = isp_reset_coproc(isp);
+	if (err)
+		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);
+	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_mbox_write32(isp, ISP_MBOX_IRQ_ENABLE, 0x0);
+
+	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x0);
+	isp_coproc_write32(isp, ISP_COPROC_CONTROL, 0x10);
+
+	/* Wait for ISP_GPIO_7 to 0x0 -> 0x8042006 */
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		val = isp_gpio_read32(isp, ISP_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;
+}
+
+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 = apple_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 = apple_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");
+		apple_isp_free_surface(isp, isp->ipc_surf);
+		return -ENOMEM;
+	}
+
+	return 0;
+}
+
+void apple_isp_free_firmware_surface(struct apple_isp *isp)
+{
+	apple_isp_free_surface(isp, isp->data_surf);
+	apple_isp_free_surface(isp, isp->ipc_surf);
+}
+
+static void isp_firmware_shutdown_stage2(struct apple_isp *isp)
+{
+	apple_isp_free_surface(isp, isp->extra_surf);
+}
+
+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 val, num_ipc_chans, args_offset, extra_size;
+
+	num_ipc_chans = isp_gpio_read32(isp, ISP_GPIO_0);
+	args_offset = isp_gpio_read32(isp, ISP_GPIO_1);
+	extra_size = isp_gpio_read32(isp, ISP_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->extra_surf = apple_isp_alloc_surface_vmap(isp, extra_size);
+	if (!isp->extra_surf) {
+		isp_err(isp, "failed to alloc surface for extra heap\n");
+		return -ENOMEM;
+	}
+
+	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;
+	args.ipc_size = isp->ipc_surf->size;
+	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->platform_id;
+	args.unk5 = 0x40;
+	args.unk7 = 0x1;
+	args.unk_iova1 = args_iova + sizeof(args) - 0xc;
+	args.unk9 = 0x3;
+	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);
+	dma_wmb();
+
+	/* 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++) {
+		val = isp_gpio_read32(isp, ISP_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_extra;
+	}
+
+	return 0;
+
+free_extra:
+	apple_isp_free_surface(isp, isp->extra_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)
+{
+	struct isp_channel *chan;
+	int i;
+
+	for (i = 0; i < isp->num_ipc_chans; i++) {
+		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)
+{
+	u64 table_iova = isp_gpio_read32(isp, ISP_GPIO_0) |
+			 ((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);
+	if (!isp->ipc_chans)
+		goto out;
+
+	for (int i = 0; i < isp->num_ipc_chans; i++) {
+		struct isp_chan_desc 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;
+
+		memcpy(&desc, desc_virt, 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->virt =
+			apple_isp_ipc_translate(isp, desc.iova, chan->size);
+		chan->cursor = 0;
+		mutex_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)) {
+			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)
+{
+	struct isp_channel *chan;
+	struct isp_message msg;
+	void *msg_virt;
+	int err, retries, i, j;
+	u32 val;
+
+	err = isp_fill_channel_info(isp);
+	if (err)
+		return err;
+
+	/* Mask the command channels to prepare for submission */
+	for (i = 0; i < isp->num_ipc_chans; i++) {
+		chan = isp->ipc_chans[i];
+		if (chan->type != ISP_IPC_CHAN_TYPE_COMMAND)
+			continue;
+		for (j = 0; j < chan->num; j++) {
+			msg_virt = chan->virt + (j * sizeof(msg));
+
+			memset(&msg, 0, sizeof(msg));
+			msg.arg0 = ISP_IPC_FLAG_ACK;
+			memcpy(msg_virt, &msg, sizeof(msg));
+		}
+	}
+	dma_wmb();
+
+	/* 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++) {
+		val = isp_gpio_read32(isp, ISP_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;
+	int res;
+	u32 val;
+
+	isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9);
+
+	res = apple_isp_cmd_suspend(isp);
+	if (res) {
+		isp_err(isp, "isp_cmd_suspend() failed\n");
+		return res;
+	}
+
+	for (retries = 0; retries < ISP_FIRMWARE_MAX_TRIES; retries++) {
+		val = isp_gpio_read32(isp, ISP_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 = apple_isp_cmd_print_enable(isp, 1);
+	if (err)
+		return err;
+
+	err = apple_isp_cmd_set_isp_pmu_base(isp, isp->hw->pmu_base);
+	if (err)
+		return err;
+
+	if (isp->hw->dsid_count == 1) {
+		err = apple_isp_cmd_set_dsid_clr_req_base(
+			isp, isp->hw->dsid_clr_base0, isp->hw->dsid_clr_range0);
+		if (err)
+			return err;
+	} else {
+		err = apple_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 = apple_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 = apple_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;
+
+	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);
+		apple_isp_free_surface(isp, surf);
+	}
+}
+
+static int isp_firmware_boot(struct apple_isp *isp)
+{
+	int err;
+
+	err = isp_firmware_boot_stage1(isp);
+	if (err) {
+		isp_err(isp, "failed firmware boot stage 1: %d\n", err);
+		goto garbage_collect;
+	}
+
+	err = isp_firmware_boot_stage2(isp);
+	if (err) {
+		isp_err(isp, "failed firmware boot stage 2: %d\n", err);
+		goto shutdown_stage1;
+	}
+
+	err = isp_firmware_boot_stage3(isp);
+	if (err) {
+		isp_err(isp, "failed firmware boot stage 3: %d\n", err);
+		goto shutdown_stage2;
+	}
+
+	err = isp_enable_irq(isp);
+	if (err) {
+		isp_err(isp, "failed to enable interrupts: %d\n", err);
+		goto shutdown_stage3;
+	}
+
+	err = isp_start_command_processor(isp);
+	if (err) {
+		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) {
+		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 0000000000000000000000000000000000000000..b986141cf2a9a474b9e5ef0abada0d51dbfcdd91
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-fw.h
@@ -0,0 +1,24 @@
+/* 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_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);
+
+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
new file mode 100644
index 0000000000000000000000000000000000000000..79d3a9691542b0301e3e02ea5313d64a4e68be7d
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-iommu.c
@@ -0,0 +1,251 @@
+// 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"
+
+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 | __GFP_ZERO);
+		if (surf->pages[i] == NULL)
+			goto free_pages;
+	}
+
+	return 0;
+
+free_pages:
+	isp_surf_free_pages(surf);
+	return -ENOMEM;
+}
+
+int apple_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) {
+		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);
+	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) {
+		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 | IOMMU_CACHE);
+	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 *__apple_isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc)
+{
+	struct isp_surf *surf;
+	int err;
+
+	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) {
+		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) {
+		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) {
+		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 *apple_isp_alloc_surface_vmap(struct apple_isp *isp, u64 size)
+{
+	struct isp_surf *surf;
+	int err;
+
+	surf = __apple_isp_alloc_surface(isp, size, false);
+	if (!surf)
+		return NULL;
+
+	err = apple_isp_surf_vmap(isp, surf);
+	if (err) {
+		dev_err(isp->dev, "failed to vmap iova 0x%llx - 0x%llx\n",
+			surf->iova, surf->iova + surf->size);
+		apple_isp_free_surface(isp, surf);
+		return NULL;
+	}
+
+	return surf;
+}
+
+void apple_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);
+	}
+}
+
+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;
+
+	surf->mm = NULL;
+	surf->size = size;
+
+	err = isp_surf_reserve_iova(isp, surf);
+	if (err) {
+		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 | IOMMU_CACHE);
+	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);
+	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 0000000000000000000000000000000000000000..4a4afe7ccd0c8c61fb614fdcb8871335e71f3b2c
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-iommu.h
@@ -0,0 +1,20 @@
+/* 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"
+
+struct isp_surf *__apple_isp_alloc_surface(struct apple_isp *isp, u64 size, bool gc);
+#define isp_alloc_surface(isp, size)	(__apple_isp_alloc_surface(isp, size, false))
+#define isp_alloc_surface_gc(isp, size) (__apple_isp_alloc_surface(isp, size, true))
+struct isp_surf *apple_isp_alloc_surface_vmap(struct apple_isp *isp, u64 size);
+int apple_isp_surf_vmap(struct apple_isp *isp, struct isp_surf *surf);
+void apple_isp_free_surface(struct apple_isp *isp, struct isp_surf *surf);
+
+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 0000000000000000000000000000000000000000..e32c16d97556be4aab4571d531ed16b12636db78
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-ipc.c
@@ -0,0 +1,258 @@
+// 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"
+#include "isp-fw.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 void *chan_msg_virt(struct isp_channel *chan, u32 index)
+{
+	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)
+{
+	memcpy(msg, chan_msg_virt(chan, index), 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)
+{
+	u64 *p0;
+
+	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,
+				  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) {
+		dev_err(isp->dev, "%s: handler failed: %d)\n", chan->name, err);
+		return err;
+	}
+
+	chan_write_msg(isp, chan, &chan->rsp);
+
+	isp_mbox2_write32(isp, ISP_MBOX2_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 apple_isp_ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	int err = 0;
+
+	mutex_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)
+			break;
+	}
+	mutex_unlock(&chan->lock);
+
+	return err;
+}
+
+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);
+		return true;
+	}
+	return false;
+}
+
+int apple_isp_ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
+			    unsigned long timeout)
+{
+	long t;
+
+	chan_write_msg(isp, chan, &chan->req);
+	dma_wmb();
+
+	isp_mbox2_write32(isp, ISP_MBOX2_IRQ_DOORBELL, chan->doorbell);
+
+	if (!timeout)
+		return 0;
+
+	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",
+			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 apple_isp_ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan)
+{
+	struct isp_message *rsp = &chan->rsp;
+
+	rsp->arg0 = ISP_IPC_FLAG_ACK;
+	rsp->arg1 = 0x0;
+	rsp->arg2 = 0x0;
+
+	return 0;
+}
+
+int apple_isp_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;
+
+		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;
+		}
+		surf->type = req->arg2;
+
+		rsp->arg0 = surf->iova | ISP_IPC_FLAG_ACK;
+		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" */
+			if (surf->size == 0xc000)
+				isp->bt_surf = surf;
+			break;
+		default:
+			/* skip vmap */
+			return 0;
+		}
+
+		err = apple_isp_surf_vmap(isp, surf);
+		if (err)
+			isp_err(isp, "failed to vmap iova=0x%llx size=0x%llx\n",
+				surf->iova, surf->size);
+	} 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
+		 */
+		rsp->arg0 = req->arg0 | ISP_IPC_FLAG_ACK;
+		rsp->arg1 = 0x0;
+		rsp->arg2 = 0x0;
+	}
+
+	return 0;
+}
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 0000000000000000000000000000000000000000..e3418a4eb6f73764642ec97aa65bc725a4247568
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-ipc.h
@@ -0,0 +1,25 @@
+/* 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 apple_isp_ipc_chan_handle(struct apple_isp *isp, struct isp_channel *chan);
+int apple_isp_ipc_chan_send(struct apple_isp *isp, struct isp_channel *chan,
+			    unsigned long timeout);
+
+int apple_isp_ipc_tm_handle(struct apple_isp *isp, struct isp_channel *chan);
+int apple_isp_ipc_sm_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 0000000000000000000000000000000000000000..c040dc19954382caf66912189c9e132209461858
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-regs.h
@@ -0,0 +1,56 @@
+/* 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_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
+#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 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
+#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->mbox + reg);
+}
+
+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__ */
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 0000000000000000000000000000000000000000..5e82e8b9cb480bb2a61ca620a563e7d25a1b0324
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-v4l2.c
@@ -0,0 +1,900 @@
+// 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>
+#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-fw.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_STRIDE_ALIGNMENT 64
+
+static bool multiplanar;
+module_param(multiplanar, bool, 0644);
+MODULE_PARM_DESC(multiplanar, "Enable multiplanar API");
+
+struct isp_buflist_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_buflist_buffer) == 0x40);
+
+struct isp_buflist {
+	u64 type;
+	u64 num_buffers;
+	struct isp_buflist_buffer buffers[];
+};
+
+int apple_isp_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;
+	struct isp_surf *meta;
+	struct isp_buflist_buffer *bufd;
+	enum vb2_buffer_state state;
+	u32 count;
+	int err = 0;
+
+	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++) {
+		bufd = &bl->buffers[i];
+
+		if (bufd->pool_type == 0) {
+			for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) {
+				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]) {
+					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, *tmp;
+	struct isp_surf *meta;
+	unsigned long flags;
+	size_t offset;
+	int err;
+
+	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++) {
+		meta = isp->meta_surfs[i];
+
+		if (meta->submitted)
+			continue;
+
+		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))) {
+		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++) {
+			bufd->iovas[j] = buf->surfs[0].iova + offset;
+			bufd->flags[j] = 0x40000000;
+			offset += fmt->plane_size[j];
+		}
+
+		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);
+
+	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 = apple_isp_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(
+				buf, tmp, &isp->bufs_submitted, link) {
+				if (bufd->iovas[0] == buf->surfs[0].iova) {
+					list_move_tail(&buf->link,
+						       &isp->bufs_pending);
+				}
+			}
+			for (int j = 0; j < ARRAY_SIZE(isp->meta_surfs); j++) {
+				meta = isp->meta_surfs[j];
+				if (bufd->iovas[0] == meta->iova)
+					meta->submitted = false;
+			}
+		}
+
+		spin_unlock_irqrestore(&isp->buf_lock, flags);
+	}
+
+	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);
+
+	/* This is not strictly necessary 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;
+
+		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]);
+}
+
+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);
+	struct sg_table *sgt;
+	unsigned int i;
+	int err;
+
+	for (i = 0; i < vb->num_planes; i++) {
+		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->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->bufs_pending);
+	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->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)
+		isp_submit_buffers(isp);
+}
+
+static int apple_isp_start_streaming(struct apple_isp *isp)
+{
+	int err;
+
+	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 apple_isp_stop_streaming(struct apple_isp *isp)
+{
+	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,
+	.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,
+};
+
+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;
+
+	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
+ */
+static int isp_vidioc_querycap(struct file *file, void *priv,
+			       struct v4l2_capability *cap)
+{
+	strscpy(cap->card, APPLE_ISP_CARD_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)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (f->index >= ISP_MAX_PIX_FORMATS)
+		return -EINVAL;
+
+	switch (f->index) {
+	case 0:
+		f->pixelformat = V4L2_PIX_FMT_NV12;
+		break;
+	case 1:
+		if (!isp->multiplanar)
+			return -EINVAL;
+		f->pixelformat = V4L2_PIX_FMT_NV12M;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int isp_vidioc_enum_framesizes(struct file *file, void *fh,
+				      struct v4l2_frmsizeenum *f)
+{
+	struct apple_isp *isp = video_drvdata(file);
+
+	if (f->index >= isp->num_presets)
+		return -EINVAL;
+
+	if ((f->pixel_format != V4L2_PIX_FMT_NV12) &&
+	    (f->pixel_format != V4L2_PIX_FMT_NV12M))
+		return -EINVAL;
+
+	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 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)
+{
+	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;
+	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_get_mp_pix_format(struct apple_isp *isp,
+					 struct v4l2_format *f,
+					 struct isp_format *fmt)
+{
+	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++) {
+		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;
+	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);
+	struct isp_format *fmt = isp_get_current_format(isp);
+
+	isp_get_sp_pix_format(isp, f, fmt);
+
+	return 0;
+}
+
+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;
+
+	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);
+
+	isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+
+	return 0;
+}
+
+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;
+
+	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;
+}
+
+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_get_mp_pix_format(isp, f, fmt);
+
+	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);
+	struct isp_format *fmt = isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
+
+	if (!isp->multiplanar)
+		return -ENOTTY;
+
+	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);
+
+	isp->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
+
+	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);
+	struct isp_format fmt = *isp_get_current_format(isp);
+	struct isp_preset *preset;
+	int err;
+
+	if (!isp->multiplanar)
+		return -ENOTTY;
+
+	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;
+}
+
+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 != V4L2_BUF_TYPE_VIDEO_CAPTURE &&
+	    (!isp->multiplanar ||
+	     a->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE))
+		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 != 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. */
+	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 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_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,
+	.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;
+	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;
+	}
+
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		isp->meta_surfs[i] =
+			apple_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;
+	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 = multiplanar;
+
+	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;
+	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;
+	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);
+surf_cleanup:
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		if (isp->meta_surfs[i])
+			apple_isp_free_surface(isp, isp->meta_surfs[i]);
+		isp->meta_surfs[i] = NULL;
+	}
+
+	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);
+	for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) {
+		if (isp->meta_surfs[i])
+			apple_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
new file mode 100644
index 0000000000000000000000000000000000000000..e41533cd54f79fbb8b991920750a8bb04a09ac1a
--- /dev/null
+++ b/drivers/media/platform/apple/isp/isp-v4l2.h
@@ -0,0 +1,16 @@
+/* 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);
+int apple_isp_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__ */

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* [PATCH 5/5] arm64: dts: apple: Add ISP nodes
  2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
                   ` (3 preceding siblings ...)
  2025-02-19  9:27 ` [PATCH 4/5] media: apple: Add Apple ISP driver Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:27 ` Sasha Finkelstein via B4 Relay
  4 siblings, 0 replies; 21+ messages in thread
From: Sasha Finkelstein via B4 Relay @ 2025-02-19  9:27 UTC (permalink / raw)
  To: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Sasha Finkelstein, Eileen Yoon, Janne Grunau,
	Asahi Lina

From: Eileen Yoon <eyn@gmx.com>

Adds device tree entries for the ISP and camera sensors

Signed-off-by: Eileen Yoon <eyn@gmx.com>
Co-developed-by: Janne Grunau <j@jannau.net>
Signed-off-by: Janne Grunau <j@jannau.net>
Co-developed-by: Hector Martin <marcan@marcan.st>
Signed-off-by: Hector Martin <marcan@marcan.st>
Co-developed-by: Asahi Lina <lina@asahilina.net>
Signed-off-by: Asahi Lina <lina@asahilina.net>
Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
---
 arch/arm64/boot/dts/apple/isp-common.dtsi      |  45 ++++++++++
 arch/arm64/boot/dts/apple/isp-imx248.dtsi      |  62 +++++++++++++
 arch/arm64/boot/dts/apple/isp-imx364.dtsi      |  78 ++++++++++++++++
 arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi | 101 +++++++++++++++++++++
 arch/arm64/boot/dts/apple/isp-imx558.dtsi      | 102 +++++++++++++++++++++
 arch/arm64/boot/dts/apple/t600x-die0.dtsi      |  48 ++++++++++
 arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi |   6 ++
 arch/arm64/boot/dts/apple/t600x-pmgr.dtsi      |  81 +++++++++++++++++
 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/t8103-pmgr.dtsi      | 118 +++++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8103.dtsi           |  50 +++++++++++
 arch/arm64/boot/dts/apple/t8112-j413.dts       |   7 ++
 arch/arm64/boot/dts/apple/t8112-j493.dts       |   6 ++
 arch/arm64/boot/dts/apple/t8112-pmgr.dtsi      | 118 +++++++++++++++++++++++++
 arch/arm64/boot/dts/apple/t8112.dtsi           |  50 +++++++++++
 18 files changed, 896 insertions(+)

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 0000000000000000000000000000000000000000..beaaab950bef70d7521666ba093f10a57cf1b910
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-common.dtsi
@@ -0,0 +1,45 @@
+// 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 {
+			/* Filled in by bootloder */
+			reg = <0 0 0 0>;
+			no-map;
+		};
+	};
+};
+
+&isp {
+	memory-region = <&isp_heap>;
+	status = "okay";
+};
+
+&isp_dart0 {
+	status = "okay";
+};
+
+&isp_dart1 {
+	status = "okay";
+};
+
+&isp_dart2 {
+	status = "okay";
+};
+
+&ps_isp_sys {
+	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 0000000000000000000000000000000000000000..1d8a87b8058cfc8d63ccef5874aa7c2c318f9654
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx248.dtsi
@@ -0,0 +1,62 @@
+// 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>;
+		};
+
+		/* 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>;
+		};
+	};
+};
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 0000000000000000000000000000000000000000..dc52fb22126ffb7370fb4bb9e9a9e04ee3c1b9ae
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx364.dtsi
@@ -0,0 +1,78 @@
+// 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 0000000000000000000000000000000000000000..83a86a937f41a4589419b051d33f3fae728ffe6e
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx558-cfg0.dtsi
@@ -0,0 +1,101 @@
+// 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 0000000000000000000000000000000000000000..6eae34a3c2ee4a5dc02c7871462c4cebfc0a9be2
--- /dev/null
+++ b/arch/arm64/boot/dts/apple/isp-imx558.dtsi
@@ -0,0 +1,102 @@
+// 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>;
+		};
+
+		/* 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>;
+		};
+	};
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-die0.dtsi b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
index b1c875e692c8fb9c0af46a23568a7b0cd720141b..019484360804ecdd0313638a0694a61693a5729a 100644
--- a/arch/arm64/boot/dts/apple/t600x-die0.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-die0.dtsi
@@ -53,6 +53,54 @@ wdt: watchdog@2922b0000 {
 		interrupts = <AIC_IRQ 0 631 IRQ_TYPE_LEVEL_HIGH>;
 	};
 
+	isp: isp@384000000 {
+		compatible = "apple,t6000-isp", "apple,isp";
+		reg = <0x3 0x84000000 0x0 0x2000000>,
+			<0x3 0x86104000 0x0 0x100>,
+			<0x3 0x86104170 0x0 0x100>,
+			<0x3 0x861043f0 0x0 0x100>;
+		reg-names = "coproc", "mbox", "gpio", "mbox2";
+		iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+		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";
+	};
+
+	isp_dart0: iommu@3860e8000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860e8000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
+	isp_dart1: iommu@3860f4000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860f4000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
+	isp_dart2: iommu@3860fc000 {
+		compatible = "apple,t6000-dart";
+		reg = <0x3 0x860fc000 0x0 0x4000>;
+		interrupt-parent = <&aic>;
+		interrupts = <AIC_IRQ 0 543 IRQ_TYPE_LEVEL_HIGH>;
+		#iommu-cells = <1>;
+		power-domains = <&ps_isp_sys>;
+		status = "disabled";
+	};
+
 	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 2e471dfe43cf885c1234d36bf0e0acfdc4904621..416e6ec213c8238f0d3c15298d5e44cb13abaabf 100644
--- a/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-j314-j316.dtsi
@@ -119,3 +119,9 @@ sdhci0: mmc@0,0 {
 &fpwm0 {
 	status = "okay";
 };
+
+#include "isp-imx558.dtsi"
+
+&isp {
+	apple,platform-id = <3>;
+};
diff --git a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
index 0bd44753b76a0c111dc12e6e900bf2f3c07a07ff..1a24e0d20c040fc6225326cd665cf1d1902b7813 100644
--- a/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t600x-pmgr.dtsi
@@ -1368,6 +1368,7 @@ DIE_NODE(ps_isp_sys): power-controller@3a8 {
 		#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 {
@@ -1456,6 +1457,86 @@ DIE_NODE(ps_venc_me1): power-controller@8020 {
 		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) {
diff --git a/arch/arm64/boot/dts/apple/t8103-j293.dts b/arch/arm64/boot/dts/apple/t8103-j293.dts
index 56b0c67bfcda321b60c621de092643017693ff91..cef284f347363d416d375a68dacc7221739f2d75 100644
--- a/arch/arm64/boot/dts/apple/t8103-j293.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j293.dts
@@ -49,3 +49,9 @@ &i2c4 {
 &fpwm1 {
 	status = "okay";
 };
+
+#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 97a4344d8dca685708aff136af92a1b316f3c3dd..b6899af595f47eaae3e5ff24ba18490dc81377c8 100644
--- a/arch/arm64/boot/dts/apple/t8103-j313.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j313.dts
@@ -41,3 +41,9 @@ &wifi0 {
 &fpwm1 {
 	status = "okay";
 };
+
+#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 58c8e43789b4861544e20c717124ede3327be010..e54393b4e83c12a233067683c03e9ada2d1fba70 100644
--- a/arch/arm64/boot/dts/apple/t8103-j456.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j456.dts
@@ -75,3 +75,9 @@ &pcie0_dart_1 {
 &pcie0_dart_2 {
 	status = "okay";
 };
+
+#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 152f95fd49a2118093396838fbd8b6bd1b518f81..7a825eee01f5600fe5eb9a11d59e64d3939edac6 100644
--- a/arch/arm64/boot/dts/apple/t8103-j457.dts
+++ b/arch/arm64/boot/dts/apple/t8103-j457.dts
@@ -48,3 +48,9 @@ ethernet0: ethernet@0,0 {
 &pcie0_dart_2 {
 	status = "okay";
 };
+
+#include "isp-imx364.dtsi"
+
+&isp {
+	apple,platform-id = <2>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
index 9645861a858c1a7c46c25a614c2cc4b03083bf46..5761cb260b7f1c1a0657aa99137e451892610ec3 100644
--- a/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103-pmgr.dtsi
@@ -805,6 +805,7 @@ ps_isp_sys: power-controller@400 {
 		#reset-cells = <0>;
 		label = "isp_sys";
 		power-domains = <&ps_rmx>;
+		status = "disabled";
 	};
 
 	ps_venc_sys: power-controller@408 {
@@ -1003,6 +1004,123 @@ ps_disp0_cpu0: power-controller@10018 {
 		apple,always-on; /* TODO: figure out if we can enable PM here */
 		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 9b0dad6b618444ac6b1c9735c50cccfc3965f947..420fda4a8dba70992c7a55c9ef56142ed39ad68f 100644
--- a/arch/arm64/boot/dts/apple/t8103.dtsi
+++ b/arch/arm64/boot/dts/apple/t8103.dtsi
@@ -356,6 +356,56 @@ cpufreq_p: performance-controller@211e20000 {
 			#performance-domain-cells = <0>;
 		};
 
+		isp: isp@22a000000 {
+			compatible = "apple,t8103-isp", "apple,isp";
+			reg = <0x2 0x2a000000 0x0 0x2000000>,
+				<0x2 0x2c104000 0x0 0x100>,
+				<0x2 0x2c104170 0x0 0x100>,
+				<0x2 0x2c1043f0 0x0 0x100>;
+			reg-names = "coproc", "mbox", "gpio", "mbox2";
+			iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+			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";
+		};
+
+		isp_dart0: iommu@22c0e8000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0e8000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			#iommu-cells = <1>;
+			status = "disabled";
+		};
+
+		isp_dart1: iommu@22c0f4000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0f4000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_isp_sys>;
+			status = "disabled";
+		};
+
+		isp_dart2: iommu@22c0fc000 {
+			compatible = "apple,t8103-dart";
+			reg = <0x2 0x2c0fc000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 251 IRQ_TYPE_LEVEL_HIGH>;
+			#iommu-cells = <1>;
+			power-domains = <&ps_isp_sys>;
+			status = "disabled";
+		};
+
 		sio_dart: iommu@235004000 {
 			compatible = "apple,t8103-dart";
 			reg = <0x2 0x35004000 0x0 0x4000>;
diff --git a/arch/arm64/boot/dts/apple/t8112-j413.dts b/arch/arm64/boot/dts/apple/t8112-j413.dts
index 6f69658623bf89ce73e3486bce504f1f5f8003f3..94bf097ff2a343cd6446b97e3391f9faee2fc5f4 100644
--- a/arch/arm64/boot/dts/apple/t8112-j413.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j413.dts
@@ -78,3 +78,10 @@ &i2c4 {
 &fpwm1 {
 	status = "okay";
 };
+
+#include "isp-imx558-cfg0.dtsi"
+
+&isp {
+	apple,platform-id = <14>;
+	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 0ad908349f55406783942735a2e9dad54cda00ec..5632bfb939ef8ff4f746c8e61ae0a87b82c646cb 100644
--- a/arch/arm64/boot/dts/apple/t8112-j493.dts
+++ b/arch/arm64/boot/dts/apple/t8112-j493.dts
@@ -67,3 +67,9 @@ &i2c4 {
 &fpwm1 {
 	status = "okay";
 };
+
+#include "isp-imx248.dtsi"
+
+&isp {
+	apple,platform-id = <6>;
+};
diff --git a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
index 7c050c6f2707a1d8128c09b4f58076a1c035adac..7154615b757215a2294f8f1559657ade951aab6c 100644
--- a/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112-pmgr.dtsi
@@ -818,6 +818,7 @@ ps_isp_sys: power-controller@438 {
 		#reset-cells = <0>;
 		label = "isp_sys";
 		power-domains = <&ps_rmx1>;
+		status = "disabled";
 	};
 
 	ps_venc_sys: power-controller@440 {
@@ -964,6 +965,123 @@ ps_sep: power-controller@c00 {
 		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 1666e6ab250bc0be9b8318e3c8fc903ccd3f3760..ed1852ac346b0f74d84619147feb7e624e355630 100644
--- a/arch/arm64/boot/dts/apple/t8112.dtsi
+++ b/arch/arm64/boot/dts/apple/t8112.dtsi
@@ -379,6 +379,56 @@ cpufreq_p: cpufreq@211e20000 {
 			#performance-domain-cells = <0>;
 		};
 
+		isp: isp@22a000000 {
+			compatible = "apple,t8112-isp", "apple,isp";
+			reg = <0x2 0x2a000000 0x0 0x2000000>,
+				<0x2 0x2c4c4000 0x0 0x100>,
+				<0x2 0x2c4c41b0 0x0 0x100>,
+				<0x2 0x2c4c4430 0x0 0x100>;
+			reg-names = "coproc", "mbox", "gpio", "mbox2";
+			iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>;
+			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";
+		};
+
+		isp_dart0: iommu@22c4a8000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4a8000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			#iommu-cells = <1>;
+			status = "disabled";
+		};
+
+		isp_dart1: iommu@22c4b4000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4b4000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			#iommu-cells = <1>;
+			status = "disabled";
+		};
+
+		isp_dart2: iommu@22c4bc000 {
+			compatible = "apple,t8110-dart";
+			reg = <0x2 0x2c4bc000 0x0 0x4000>;
+			interrupt-parent = <&aic>;
+			interrupts = <AIC_IRQ 274 IRQ_TYPE_LEVEL_HIGH>;
+			power-domains = <&ps_isp_sys>;
+			#iommu-cells = <1>;
+			status = "disabled";
+		};
+
 		sio_dart: iommu@235004000 {
 			compatible = "apple,t8110-dart";
 			reg = <0x2 0x35004000 0x0 0x4000>;

-- 
2.48.1



^ permalink raw reply related	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties
  2025-02-19  9:26 ` [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:34   ` Krzysztof Kozlowski
  2025-02-19  9:43     ` Sasha Finkelstein
  0 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-02-19  9:34 UTC (permalink / raw)
  To: fnkl.kernel, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx

On 19/02/2025 10:26, Sasha Finkelstein via B4 Relay wrote:
> From: Sasha Finkelstein <fnkl.kernel@gmail.com>
> 
> Add properties to set disable/reset bits when powering down
> certain domains


Please explain why and what problem are you solving. This looks too
close to SW policy or really arbitrary choice. Background would be useful.

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19  9:26 ` [PATCH 3/5] media: dt-bindings: Add Apple ISP Sasha Finkelstein via B4 Relay
@ 2025-02-19  9:37   ` Krzysztof Kozlowski
  2025-02-19  9:54     ` Sasha Finkelstein
  0 siblings, 1 reply; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-02-19  9:37 UTC (permalink / raw)
  To: fnkl.kernel, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam
  Cc: asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx

On 19/02/2025 10:26, Sasha Finkelstein via B4 Relay wrote:
> +  reg-names:
> +    items:
> +      - const: coproc
> +      - const: mbox
> +      - const: gpio
> +      - const: mbox2
> +
> +  iommus:
> +    description: All 3 must be kept in sync
> +    minItems: 3


Drop minItems

> +    maxItems: 3
> +
> +  interrupts:
> +    maxItems: 1
> +
> +  power-domains:
> +    minItems: 1
> +    maxItems: 20
> +    description: All necessary power domains. Driver will enable them in order
> +
> +  memory-region:
> +    maxItems: 1
> +
> +  apple,dart-vm-size:
> +    description: Supported device memory range
> +    $ref: /schemas/types.yaml#/definitions/uint64


That's deducible from comaptible.

> +
> +  apple,platform-id:
> +    description: Platform id for firmware
> +    $ref: /schemas/types.yaml#/definitions/uint32


No, use firmware-name.

> +
> +  apple,temporal-filter:
> +    description: Whether temporal filter should be enabled in firmware
> +    $ref: /schemas/types.yaml#/definitions/uint32

And why is this not enabled always? Why this is board specific?


You miss here ports or port. ISP usually gets signal from some camera or
other block.


> +
> +  sensor-presets:
> +    additionalProperties: false
> +
> +    patternProperties:
> +      '^preset[0-9]+$':
> +        type: object
> +
> +        additionalProperties: false
> +
> +        properties:
> +          apple,config-index:
> +            description: Firmware config index
> +            $ref: /schemas/types.yaml#/definitions/uint32


No duplicated indices. You have reg for this, assuming this is index.

> +
> +          apple,input-size:
> +            $ref: /schemas/types.yaml#/definitions/uint32-array
> +            minItems: 2
> +            maxItems: 2
> +            description: Raw sensor size
> +
> +          apple,output-size:
> +            $ref: /schemas/types.yaml#/definitions/uint32-array
> +            minItems: 2
> +            maxItems: 2
> +            description: Cropped and scaled image size
> +
> +          apple,crop:
> +            $ref: /schemas/types.yaml#/definitions/uint32-array
> +            minItems: 4
> +            maxItems: 4
> +            description: Area to crop


All these do not look like hardware properties but rather configuration
of sensor which should be done runtime by OS, not by DT.

Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties
  2025-02-19  9:34   ` Krzysztof Kozlowski
@ 2025-02-19  9:43     ` Sasha Finkelstein
  2025-02-21 21:41       ` Rob Herring
  0 siblings, 1 reply; 21+ messages in thread
From: Sasha Finkelstein @ 2025-02-19  9:43 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx

On Wed, 19 Feb 2025 at 10:34, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> On 19/02/2025 10:26, Sasha Finkelstein via B4 Relay wrote:
> > From: Sasha Finkelstein <fnkl.kernel@gmail.com>
> >
> > Add properties to set disable/reset bits when powering down
> > certain domains
>
>
> Please explain why and what problem are you solving. This looks too
> close to SW policy or really arbitrary choice. Background would be useful.

The ISP block has some weird requirements where some of it's power domains
will not power down correctly without using the "force disable" or "force reset"
pmgr feature. Basically a hardware quirk.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19  9:37   ` Krzysztof Kozlowski
@ 2025-02-19  9:54     ` Sasha Finkelstein
  2025-02-19 10:53       ` Laurent Pinchart
  2025-02-23  8:58       ` Krzysztof Kozlowski
  0 siblings, 2 replies; 21+ messages in thread
From: Sasha Finkelstein @ 2025-02-19  9:54 UTC (permalink / raw)
  To: Krzysztof Kozlowski
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx

On Wed, 19 Feb 2025 at 10:37, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > +
> > +  apple,platform-id:
> > +    description: Platform id for firmware
> > +    $ref: /schemas/types.yaml#/definitions/uint32
>
>
> No, use firmware-name.

Not sure how is firmware-name an appropriate field, fw-name is a string
that references a firmware file, while this field is an id that is sent to the
coprocessor firmware in order to identify the platform.

> > +  apple,temporal-filter:
> > +    description: Whether temporal filter should be enabled in firmware
> > +    $ref: /schemas/types.yaml#/definitions/uint32
>
> And why is this not enabled always? Why this is board specific?

Not every board has support for this feature.

> You miss here ports or port. ISP usually gets signal from some camera or
> other block.

For complex cameras - yes, but this is closer to a UVC camera connected
via a bespoke protocol. We do not need to deal with the sensor access,
all of it is managed by the coprocessor firmware.

> > +        properties:
> > +          apple,config-index:
> > +            description: Firmware config index
> > +            $ref: /schemas/types.yaml#/definitions/uint32
>
>
> No duplicated indices. You have reg for this, assuming this is index.

There are duplicated indices, see isp-imx248.dtsi in patch 5 for an example.

> All these do not look like hardware properties but rather configuration
> of sensor which should be done runtime by OS, not by DT.

Those are board-specific and not discoverable via the ISP protocol.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19  9:54     ` Sasha Finkelstein
@ 2025-02-19 10:53       ` Laurent Pinchart
  2025-02-19 11:05         ` Sasha Finkelstein
  2025-02-19 11:57         ` Janne Grunau
  2025-02-23  8:58       ` Krzysztof Kozlowski
  1 sibling, 2 replies; 21+ messages in thread
From: Laurent Pinchart @ 2025-02-19 10:53 UTC (permalink / raw)
  To: Sasha Finkelstein
  Cc: Krzysztof Kozlowski, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, asahi, linux-arm-kernel,
	devicetree, linux-kernel, linux-pm, linux-media, imx

On Wed, Feb 19, 2025 at 10:54:31AM +0100, Sasha Finkelstein wrote:
> On Wed, 19 Feb 2025 at 10:37, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > +
> > > +  apple,platform-id:
> > > +    description: Platform id for firmware
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> >
> >
> > No, use firmware-name.
> 
> Not sure how is firmware-name an appropriate field, fw-name is a string
> that references a firmware file, while this field is an id that is sent to the
> coprocessor firmware in order to identify the platform.
> 
> > > +  apple,temporal-filter:
> > > +    description: Whether temporal filter should be enabled in firmware
> > > +    $ref: /schemas/types.yaml#/definitions/uint32
> >
> > And why is this not enabled always? Why this is board specific?
> 
> Not every board has support for this feature.
> 
> > You miss here ports or port. ISP usually gets signal from some camera or
> > other block.
> 
> For complex cameras - yes, but this is closer to a UVC camera connected
> via a bespoke protocol. We do not need to deal with the sensor access,
> all of it is managed by the coprocessor firmware.
> 
> > > +        properties:
> > > +          apple,config-index:
> > > +            description: Firmware config index
> > > +            $ref: /schemas/types.yaml#/definitions/uint32
> >
> >
> > No duplicated indices. You have reg for this, assuming this is index.
> 
> There are duplicated indices, see isp-imx248.dtsi in patch 5 for an example.
> 
> > All these do not look like hardware properties but rather configuration
> > of sensor which should be done runtime by OS, not by DT.
> 
> Those are board-specific and not discoverable via the ISP protocol.

But they are settable through the ISP protocol, aren't they ? For
instance, looking at isp-imx248.dtsi, the first four entries are

	/* 1280x720 */
	preset0 {
		apple,config-index = <0>;
		apple,input-size = <1296 736>;
		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>;
	};

But I may be interested in capturing a 640x480 frame with cropping only
and without scaling, with

input-size = 1296x736
output-size = 640x480
crop = (328,128)/640x480

Or I may want my cropped frame to be located in the upper-left corner:

input-size = 1296x736
output-size = 640x480
crop = (8,8)/640x480

If I set those parameters through the ISP protocol, won't it work ?

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19 10:53       ` Laurent Pinchart
@ 2025-02-19 11:05         ` Sasha Finkelstein
  2025-02-19 13:40           ` Laurent Pinchart
  2025-02-19 15:36           ` Asahi Lina
  2025-02-19 11:57         ` Janne Grunau
  1 sibling, 2 replies; 21+ messages in thread
From: Sasha Finkelstein @ 2025-02-19 11:05 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Krzysztof Kozlowski, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, asahi, linux-arm-kernel,
	devicetree, linux-kernel, linux-pm, linux-media, imx

On Wed, 19 Feb 2025 at 11:53, Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
> >
> > Those are board-specific and not discoverable via the ISP protocol.
>
> But they are settable through the ISP protocol, aren't they ? For
> instance, looking at isp-imx248.dtsi, the first four entries are
>
>         /* 1280x720 */
>         preset0 {
>                 apple,config-index = <0>;
>                 apple,input-size = <1296 736>;
>                 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>;
>         };
>
> But I may be interested in capturing a 640x480 frame with cropping only
> and without scaling, with
>
> input-size = 1296x736
> output-size = 640x480
> crop = (328,128)/640x480
>
> Or I may want my cropped frame to be located in the upper-left corner:
>
> input-size = 1296x736
> output-size = 640x480
> crop = (8,8)/640x480
>
> If I set those parameters through the ISP protocol, won't it work ?
>
> --
> Regards,
>
> Laurent Pinchart

For cropping - you do not want to change those parameters, the sensor
is partially occluded, and the crop area is specified in such a way
to not expose those pixels. As for scaling - we can expose only the 1:1
scale and let userspace deal with it, but it appears that it expects
the other common output sizes to exist.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 4/5] media: apple: Add Apple ISP driver
  2025-02-19  9:27 ` [PATCH 4/5] media: apple: Add Apple ISP driver Sasha Finkelstein via B4 Relay
@ 2025-02-19 11:34   ` Janne Grunau
  2025-02-23  8:48     ` Sasha Finkelstein
  0 siblings, 1 reply; 21+ messages in thread
From: Janne Grunau @ 2025-02-19 11:34 UTC (permalink / raw)
  To: fnkl.kernel
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Eileen Yoon, Asahi Lina

Hej,

On Wed, Feb 19, 2025 at 10:27:00AM +0100, Sasha Finkelstein via B4 Relay wrote:
> From: Eileen Yoon <eyn@gmx.com>
> 
> This is the ISP and camera module present on certain Apple laptops
> 
> Signed-off-by: Eileen Yoon <eyn@gmx.com>
> Co-developed-by: Hector Martin <marcan@marcan.st>
> Signed-off-by: Hector Martin <marcan@marcan.st>
> Co-developed-by: Asahi Lina <lina@asahilina.net>
> Signed-off-by: Asahi Lina <lina@asahilina.net>
> Co-developed-by: Janne Grunau <j@jannau.net>
> Signed-off-by: Janne Grunau <j@jannau.net>
> Co-developed-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
> Signed-off-by: Sasha Finkelstein <fnkl.kernel@gmail.com>
> ---
>  MAINTAINERS                                  |   1 +
>  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/Kconfig     |  16 +
>  drivers/media/platform/apple/isp/Makefile    |   3 +
>  drivers/media/platform/apple/isp/isp-cam.c   | 414 ++++++++++++
>  drivers/media/platform/apple/isp/isp-cam.h   |  21 +
>  drivers/media/platform/apple/isp/isp-cmd.c   | 635 +++++++++++++++++++
>  drivers/media/platform/apple/isp/isp-cmd.h   | 692 ++++++++++++++++++++
>  drivers/media/platform/apple/isp/isp-drv.c   | 586 +++++++++++++++++
>  drivers/media/platform/apple/isp/isp-drv.h   | 284 +++++++++
>  drivers/media/platform/apple/isp/isp-fw.c    | 770 +++++++++++++++++++++++
>  drivers/media/platform/apple/isp/isp-fw.h    |  24 +
>  drivers/media/platform/apple/isp/isp-iommu.c | 251 ++++++++
>  drivers/media/platform/apple/isp/isp-iommu.h |  20 +
>  drivers/media/platform/apple/isp/isp-ipc.c   | 258 ++++++++
>  drivers/media/platform/apple/isp/isp-ipc.h   |  25 +
>  drivers/media/platform/apple/isp/isp-regs.h  |  56 ++
>  drivers/media/platform/apple/isp/isp-v4l2.c  | 900 +++++++++++++++++++++++++++
>  drivers/media/platform/apple/isp/isp-v4l2.h  |  16 +
>  22 files changed, 4982 insertions(+)

quick partial review

> diff --git a/MAINTAINERS b/MAINTAINERS
> index dea7239ee0f5464b31efed5a2e0e5e602bcb6439..60517f7dcee14fc942dd3f77ed5d58eae394f7fa 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2248,6 +2248,7 @@ F:	drivers/i2c/busses/i2c-pasemi-platform.c
>  F:	drivers/iommu/apple-dart.c
>  F:	drivers/iommu/io-pgtable-dart.c
>  F:	drivers/irqchip/irq-apple-aic.c
> +F:	drivers/media/platform/apple/*
>  F:	drivers/nvme/host/apple.c
>  F:	drivers/nvmem/apple-efuses.c
>  F:	drivers/pinctrl/pinctrl-apple-gpio.c
> diff --git a/drivers/media/platform/Kconfig b/drivers/media/platform/Kconfig
> index 85d2627776b6a424fbd392187669535c4159ec97..ba75cfdb57f710cca086136e4524d3e1bc1910ac 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 ace4e34483ddce6c3361479989086145dd495f29..e59e4259064bf04b718ea8d128031af859a13d2e 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 0000000000000000000000000000000000000000..f16508bff5242a0bc433bf8a1d8e3f29737d20d1
> --- /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 0000000000000000000000000000000000000000..d8fe985b0e6c377de6c77d30a3a796c40f3da116
> --- /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/Kconfig b/drivers/media/platform/apple/isp/Kconfig
> new file mode 100644
> index 0000000000000000000000000000000000000000..8e94962990031304d51cdd7cd6190b05b05b40bb
> --- /dev/null
> +++ b/drivers/media/platform/apple/isp/Kconfig
> @@ -0,0 +1,16 @@
> +# 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

missing dependency on DRM for drm_mm. I don't remember why iova's top
down allocation was a problem but if we can avoid drm_mm that would be
an option as well.

> +	help
> +	  Say Y here to enable support for the ISP and cameras persent
> +	  in Apple ARM laptops.
> +
> +	  To compile this driver as a module, choose M here. The module will be
> +	  called apple_isp
> diff --git a/drivers/media/platform/apple/isp/Makefile b/drivers/media/platform/apple/isp/Makefile
> new file mode 100644
> index 0000000000000000000000000000000000000000..4649f32987f025a639945a37d774d4ecdc83b02a
> --- /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-drv.c b/drivers/media/platform/apple/isp/isp-drv.c
> new file mode 100644
> index 0000000000000000000000000000000000000000..b0c73b4f43d73f4ee29093fe62ed1d39ccfa33dd
> --- /dev/null
> +++ b/drivers/media/platform/apple/isp/isp-drv.c
> @@ -0,0 +1,586 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Apple Image Signal Processor driver
> + *
> + * Copyright (C) 2023 The Asahi Linux Contributors
> + */
> +
> +#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-fw.h"
> +#include "isp-iommu.h"
> +#include "isp-v4l2.h"
> +

...

> +static int apple_isp_init_iommu(struct apple_isp *isp)
> +{
> +	struct device *dev = isp->dev;
> +	phys_addr_t heap_base;
> +	size_t heap_size;
> +	u64 vm_size;
> +	int err;
> +	int size;
> +	struct device_node *mem_node;
> +	const __be32 *maps, *end;
> +
> +	isp->domain = iommu_get_domain_for_dev(isp->dev);
> +	if (!isp->domain)
> +		return -ENODEV;
> +	isp->shift = __ffs(isp->domain->pgsize_bitmap);
> +
> +	mem_node = of_parse_phandle(dev->of_node, "memory-region", 0);
> +	if (!mem_node) {
> +		dev_err(dev, "No memory-region found for heap\n");
> +		return -ENODEV;
> +	}
> +
> +	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);
> +	}

The hand-rolled reserved memory parsing looks like it can be replaced
with of_iommu_get_resv_region();
> +

> +	isp->fw.heap_top = heap_base + heap_size;
> +
> +	err = of_property_read_u64(dev->of_node, "apple,dart-vm-size",
> +				   &vm_size);

This is not necessary and can be inferred from
isp->domain->geometry.aperture_{start,end}.

> +	if (err) {
> +		dev_err(dev, "failed to read 'apple,dart-vm-size': %d\n", err);
> +		return err;
> +	}
> +
> +	drm_mm_init(&isp->iovad, isp->fw.heap_top,
> +		    vm_size - (heap_base & 0xffffffff));

drm_mm probably should be replaced with something else. As I wrote above
I don't understand / remember what the problem was with relying on the
DMA api and iova. I can't imagine that the firmware cares about top-down
vs. bottom-up allocation. I could image it was related to
"apple,dart-vm-size" and iova's top down allocation only allocated
outside of the device's aperture. That should be solved by our current
downstream handling of the "vm-base" and "vm-size" properties from
Apple's devicetree.

ciao Janne

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19 10:53       ` Laurent Pinchart
  2025-02-19 11:05         ` Sasha Finkelstein
@ 2025-02-19 11:57         ` Janne Grunau
  2025-02-19 13:44           ` Laurent Pinchart
  1 sibling, 1 reply; 21+ messages in thread
From: Janne Grunau @ 2025-02-19 11:57 UTC (permalink / raw)
  To: Laurent Pinchart
  Cc: Sasha Finkelstein, Krzysztof Kozlowski, Sven Peter,
	Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hector Martin, Ulf Hansson, Mauro Carvalho Chehab, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, asahi,
	linux-arm-kernel, devicetree, linux-kernel, linux-pm, linux-media,
	imx

On Wed, Feb 19, 2025 at 12:53:26PM +0200, Laurent Pinchart wrote:
> On Wed, Feb 19, 2025 at 10:54:31AM +0100, Sasha Finkelstein wrote:
> > On Wed, 19 Feb 2025 at 10:37, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > > +
> > > > +  apple,platform-id:
> > > > +    description: Platform id for firmware
> > > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > >
> > >
> > > No, use firmware-name.
> > 
> > Not sure how is firmware-name an appropriate field, fw-name is a string
> > that references a firmware file, while this field is an id that is sent to the
> > coprocessor firmware in order to identify the platform.
> > 
> > > > +  apple,temporal-filter:
> > > > +    description: Whether temporal filter should be enabled in firmware
> > > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > >
> > > And why is this not enabled always? Why this is board specific?
> > 
> > Not every board has support for this feature.
> > 
> > > You miss here ports or port. ISP usually gets signal from some camera or
> > > other block.
> > 
> > For complex cameras - yes, but this is closer to a UVC camera connected
> > via a bespoke protocol. We do not need to deal with the sensor access,
> > all of it is managed by the coprocessor firmware.
> > 
> > > > +        properties:
> > > > +          apple,config-index:
> > > > +            description: Firmware config index
> > > > +            $ref: /schemas/types.yaml#/definitions/uint32
> > >
> > >
> > > No duplicated indices. You have reg for this, assuming this is index.
> > 
> > There are duplicated indices, see isp-imx248.dtsi in patch 5 for an example.
> > 
> > > All these do not look like hardware properties but rather configuration
> > > of sensor which should be done runtime by OS, not by DT.
> > 
> > Those are board-specific and not discoverable via the ISP protocol.
> 
> But they are settable through the ISP protocol, aren't they ? For
> instance, looking at isp-imx248.dtsi, the first four entries are
> 
> 	/* 1280x720 */
> 	preset0 {
> 		apple,config-index = <0>;
> 		apple,input-size = <1296 736>;
> 		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>;
> 	};
> 
> But I may be interested in capturing a 640x480 frame with cropping only
> and without scaling, with
> 
> input-size = 1296x736
> output-size = 640x480
> crop = (328,128)/640x480
> 
> Or I may want my cropped frame to be located in the upper-left corner:
> 
> input-size = 1296x736
> output-size = 640x480
> crop = (8,8)/640x480
> 
> If I set those parameters through the ISP protocol, won't it work ?

If my memory serves me right the presets wre added as workaround for
userspace not handling V4L2_FRMSIZE_TYPE_STEPWISE well (or at all) and
the added complexity of handling the qadratic sensor with partially
occluded or outside of the usable lens diameter corners.

It is a simplified description of the hardware to make it useable for
most software which is expected simple uvc cameras.

There are still two common issues in user space software related to this
driver:
- software expects width == linesize
- resolution selection is based frame height, i.e. it prefers 1080x1920
  over 1920x1080 on devices with quadratic sensor.

ciao Janne

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19 11:05         ` Sasha Finkelstein
@ 2025-02-19 13:40           ` Laurent Pinchart
  2025-02-19 15:36           ` Asahi Lina
  1 sibling, 0 replies; 21+ messages in thread
From: Laurent Pinchart @ 2025-02-19 13:40 UTC (permalink / raw)
  To: Sasha Finkelstein
  Cc: Krzysztof Kozlowski, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, asahi, linux-arm-kernel,
	devicetree, linux-kernel, linux-pm, linux-media, imx

On Wed, Feb 19, 2025 at 12:05:29PM +0100, Sasha Finkelstein wrote:
> On Wed, 19 Feb 2025 at 11:53, Laurent Pinchart wrote:
> > >
> > > Those are board-specific and not discoverable via the ISP protocol.
> >
> > But they are settable through the ISP protocol, aren't they ? For
> > instance, looking at isp-imx248.dtsi, the first four entries are
> >
> >         /* 1280x720 */
> >         preset0 {
> >                 apple,config-index = <0>;
> >                 apple,input-size = <1296 736>;
> >                 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>;
> >         };
> >
> > But I may be interested in capturing a 640x480 frame with cropping only
> > and without scaling, with
> >
> > input-size = 1296x736
> > output-size = 640x480
> > crop = (328,128)/640x480
> >
> > Or I may want my cropped frame to be located in the upper-left corner:
> >
> > input-size = 1296x736
> > output-size = 640x480
> > crop = (8,8)/640x480
> >
> > If I set those parameters through the ISP protocol, won't it work ?
> 
> For cropping - you do not want to change those parameters, the sensor
> is partially occluded, and the crop area is specified in such a way
> to not expose those pixels.

Surely cropping *more* wouldn't be an issue from that point of view ?
The visible area is a device-specific property, so that could be
specified in DT, instead of presets. The problem isn't limited to this
device, so ideally the DT binding for this feature should be
standardized.

In general, I'd say that even occluded pixels should be exposed to
userspace, should an application want to capture them. If you think of
fisheye lenses, for instance, it's common to have a round image captured
from a rectangular sensor, and use a dewarping engine (it could just be
GPU shaders) to un-distord it. Restricting capture to rectangles of
fully visible pixels would result in loss of information. For this
particular device I don't think that would really be a use case, so I'm
not opposed to the driver setting restrictions on crop rectangles.

> As for scaling - we can expose only the 1:1
> scale and let userspace deal with it, but it appears that it expects
> the other common output sizes to exist.

Can the driver synthesize the list of scaled-down resolutions, instead
of specifying them in DT ?

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19 11:57         ` Janne Grunau
@ 2025-02-19 13:44           ` Laurent Pinchart
  0 siblings, 0 replies; 21+ messages in thread
From: Laurent Pinchart @ 2025-02-19 13:44 UTC (permalink / raw)
  To: Janne Grunau
  Cc: Sasha Finkelstein, Krzysztof Kozlowski, Sven Peter,
	Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
	Hector Martin, Ulf Hansson, Mauro Carvalho Chehab, Shawn Guo,
	Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, asahi,
	linux-arm-kernel, devicetree, linux-kernel, linux-pm, linux-media,
	imx

On Wed, Feb 19, 2025 at 12:57:37PM +0100, Janne Grunau wrote:
> On Wed, Feb 19, 2025 at 12:53:26PM +0200, Laurent Pinchart wrote:
> > On Wed, Feb 19, 2025 at 10:54:31AM +0100, Sasha Finkelstein wrote:
> > > On Wed, 19 Feb 2025 at 10:37, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > > > > +
> > > > > +  apple,platform-id:
> > > > > +    description: Platform id for firmware
> > > > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > >
> > > >
> > > > No, use firmware-name.
> > > 
> > > Not sure how is firmware-name an appropriate field, fw-name is a string
> > > that references a firmware file, while this field is an id that is sent to the
> > > coprocessor firmware in order to identify the platform.
> > > 
> > > > > +  apple,temporal-filter:
> > > > > +    description: Whether temporal filter should be enabled in firmware
> > > > > +    $ref: /schemas/types.yaml#/definitions/uint32
> > > >
> > > > And why is this not enabled always? Why this is board specific?
> > > 
> > > Not every board has support for this feature.
> > > 
> > > > You miss here ports or port. ISP usually gets signal from some camera or
> > > > other block.
> > > 
> > > For complex cameras - yes, but this is closer to a UVC camera connected
> > > via a bespoke protocol. We do not need to deal with the sensor access,
> > > all of it is managed by the coprocessor firmware.
> > > 
> > > > > +        properties:
> > > > > +          apple,config-index:
> > > > > +            description: Firmware config index
> > > > > +            $ref: /schemas/types.yaml#/definitions/uint32
> > > >
> > > >
> > > > No duplicated indices. You have reg for this, assuming this is index.
> > > 
> > > There are duplicated indices, see isp-imx248.dtsi in patch 5 for an example.
> > > 
> > > > All these do not look like hardware properties but rather configuration
> > > > of sensor which should be done runtime by OS, not by DT.
> > > 
> > > Those are board-specific and not discoverable via the ISP protocol.
> > 
> > But they are settable through the ISP protocol, aren't they ? For
> > instance, looking at isp-imx248.dtsi, the first four entries are
> > 
> > 	/* 1280x720 */
> > 	preset0 {
> > 		apple,config-index = <0>;
> > 		apple,input-size = <1296 736>;
> > 		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>;
> > 	};
> > 
> > But I may be interested in capturing a 640x480 frame with cropping only
> > and without scaling, with
> > 
> > input-size = 1296x736
> > output-size = 640x480
> > crop = (328,128)/640x480
> > 
> > Or I may want my cropped frame to be located in the upper-left corner:
> > 
> > input-size = 1296x736
> > output-size = 640x480
> > crop = (8,8)/640x480
> > 
> > If I set those parameters through the ISP protocol, won't it work ?
> 
> If my memory serves me right the presets wre added as workaround for
> userspace not handling V4L2_FRMSIZE_TYPE_STEPWISE well (or at all) and
> the added complexity of handling the qadratic sensor with partially
> occluded or outside of the usable lens diameter corners.
> 
> It is a simplified description of the hardware to make it useable for
> most software which is expected simple uvc cameras.

I understand that. Ideally userspace should be fixed, but in the
meantime, I'm fine with the driver exposing a set of presets. They
should however not be listed in DT, but computed by the driver based on
properties that describe the device (such as, for instance, the full
sensor resolution, or the visible area). Those properties could come
from DT, or be hardcoded in the driver on a per-compatible basis.

> There are still two common issues in user space software related to this
> driver:
> - software expects width == linesize
> - resolution selection is based frame height, i.e. it prefers 1080x1920
>   over 1920x1080 on devices with quadratic sensor.

I share your pain, having to fix userspace when doing kernel work isn't
always fun :-)

-- 
Regards,

Laurent Pinchart

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19 11:05         ` Sasha Finkelstein
  2025-02-19 13:40           ` Laurent Pinchart
@ 2025-02-19 15:36           ` Asahi Lina
  1 sibling, 0 replies; 21+ messages in thread
From: Asahi Lina @ 2025-02-19 15:36 UTC (permalink / raw)
  To: Sasha Finkelstein, Laurent Pinchart
  Cc: Krzysztof Kozlowski, Sven Peter, Alyssa Rosenzweig, Rob Herring,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, asahi, linux-arm-kernel,
	devicetree, linux-kernel, linux-pm, linux-media, imx

On February 19, 2025 12:05:29 PM GMT+01:00, Sasha Finkelstein <fnkl.kernel@gmail.com> wrote:
>On Wed, 19 Feb 2025 at 11:53, Laurent Pinchart
><laurent.pinchart@ideasonboard.com> wrote:
>> >
>> > Those are board-specific and not discoverable via the ISP protocol.
>>
>> But they are settable through the ISP protocol, aren't they ? For
>> instance, looking at isp-imx248.dtsi, the first four entries are
>>
>>         /* 1280x720 */
>>         preset0 {
>>                 apple,config-index = <0>;
>>                 apple,input-size = <1296 736>;
>>                 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>;
>>         };
>>
>> But I may be interested in capturing a 640x480 frame with cropping only
>> and without scaling, with
>>
>> input-size = 1296x736
>> output-size = 640x480
>> crop = (328,128)/640x480
>>
>> Or I may want my cropped frame to be located in the upper-left corner:
>>
>> input-size = 1296x736
>> output-size = 640x480
>> crop = (8,8)/640x480
>>
>> If I set those parameters through the ISP protocol, won't it work ?
>>
>> --
>> Regards,
>>
>> Laurent Pinchart
>
>For cropping - you do not want to change those parameters, the sensor
>is partially occluded, and the crop area is specified in such a way
>to not expose those pixels. As for scaling - we can expose only the 1:1
>scale and let userspace deal with it, but it appears that it expects
>the other common output sizes to exist.
>
>

The square sensor with the hack is the IMX558. The configs there are actually a workaround for missing ANE support.

What the driver is supposed to be doing is using the higher numbered presets, which crop internally and exclude the occluded area. But those on machines with that sensor end up requiring ANE integration for denoising and ISP crashes without it.

Config #0 doesn't use ANE, so the crops are re-creations of the other configs so it behaves the same minus the denoising.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties
  2025-02-19  9:43     ` Sasha Finkelstein
@ 2025-02-21 21:41       ` Rob Herring
  0 siblings, 0 replies; 21+ messages in thread
From: Rob Herring @ 2025-02-21 21:41 UTC (permalink / raw)
  To: Sasha Finkelstein
  Cc: Krzysztof Kozlowski, Sven Peter, Alyssa Rosenzweig,
	Krzysztof Kozlowski, Conor Dooley, Hector Martin, Ulf Hansson,
	Mauro Carvalho Chehab, Shawn Guo, Sascha Hauer,
	Pengutronix Kernel Team, Fabio Estevam, asahi, linux-arm-kernel,
	devicetree, linux-kernel, linux-pm, linux-media, imx

On Wed, Feb 19, 2025 at 10:43:17AM +0100, Sasha Finkelstein wrote:
> On Wed, 19 Feb 2025 at 10:34, Krzysztof Kozlowski <krzk@kernel.org> wrote:
> > On 19/02/2025 10:26, Sasha Finkelstein via B4 Relay wrote:
> > > From: Sasha Finkelstein <fnkl.kernel@gmail.com>
> > >
> > > Add properties to set disable/reset bits when powering down
> > > certain domains
> >
> >
> > Please explain why and what problem are you solving. This looks too
> > close to SW policy or really arbitrary choice. Background would be useful.
> 
> The ISP block has some weird requirements where some of it's power domains
> will not power down correctly without using the "force disable" or "force reset"
> pmgr feature. Basically a hardware quirk.

Add that to the commit message.

Rob

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 4/5] media: apple: Add Apple ISP driver
  2025-02-19 11:34   ` Janne Grunau
@ 2025-02-23  8:48     ` Sasha Finkelstein
  2025-02-23  9:03       ` Janne Grunau
  0 siblings, 1 reply; 21+ messages in thread
From: Sasha Finkelstein @ 2025-02-23  8:48 UTC (permalink / raw)
  To: Janne Grunau
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Eileen Yoon, Asahi Lina

On Wed, 19 Feb 2025 at 12:34, Janne Grunau <j@jannau.net> wrote:
> > +     while (maps < end) {
> > +             maps++;
> > +             maps = of_translate_dma_region(dev->of_node, maps, &heap_base,
> > +                                            &heap_size);
> > +     }
>
> The hand-rolled reserved memory parsing looks like it can be replaced
> with of_iommu_get_resv_region();

I have looked into it, and `of_iommu_get_resv_region` does the wrong
thing. We fill out `reg`, and it grabs that instead of `iommu-addresses`.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 3/5] media: dt-bindings: Add Apple ISP
  2025-02-19  9:54     ` Sasha Finkelstein
  2025-02-19 10:53       ` Laurent Pinchart
@ 2025-02-23  8:58       ` Krzysztof Kozlowski
  1 sibling, 0 replies; 21+ messages in thread
From: Krzysztof Kozlowski @ 2025-02-23  8:58 UTC (permalink / raw)
  To: Sasha Finkelstein
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx

On 19/02/2025 10:54, Sasha Finkelstein wrote:
> On Wed, 19 Feb 2025 at 10:37, Krzysztof Kozlowski <krzk@kernel.org> wrote:
>>> +
>>> +  apple,platform-id:
>>> +    description: Platform id for firmware
>>> +    $ref: /schemas/types.yaml#/definitions/uint32
>>
>>
>> No, use firmware-name.
> 
> Not sure how is firmware-name an appropriate field, fw-name is a string
> that references a firmware file, while this field is an id that is sent to the
> coprocessor firmware in order to identify the platform.

You get advice how you explain the hardware. Why do you need to identify
the platform? Compatible already tells that. Who identifies the
platform? For what purpose?


> 
>>> +  apple,temporal-filter:
>>> +    description: Whether temporal filter should be enabled in firmware
>>> +    $ref: /schemas/types.yaml#/definitions/uint32
>>
>> And why is this not enabled always? Why this is board specific?
> 
> Not every board has support for this feature.

Board for the same SoC, right?  It's fine then, but you should explain
all this in binding description.


> 
>> You miss here ports or port. ISP usually gets signal from some camera or
>> other block.
> 
> For complex cameras - yes, but this is closer to a UVC camera connected
> via a bespoke protocol. We do not need to deal with the sensor access,
> all of it is managed by the coprocessor firmware.

And there is nothing more in this pipeline, like some other processing
units? It's fine then, but you should explain all this in binding
description.

> 
>>> +        properties:
>>> +          apple,config-index:
>>> +            description: Firmware config index
>>> +            $ref: /schemas/types.yaml#/definitions/uint32
>>
>>
>> No duplicated indices. You have reg for this, assuming this is index.
> 
> There are duplicated indices, see isp-imx248.dtsi in patch 5 for an example.

I still do no understand their point and description does not help me much.

> 
>> All these do not look like hardware properties but rather configuration
>> of sensor which should be done runtime by OS, not by DT.
> 
> Those are board-specific and not discoverable via the ISP protocol.

No, I rather suggest these should be runtime or deduced from the
compatible of the sensor. You know embed the sensor characteristics into
DT... or if not sensor then some user choice?


Best regards,
Krzysztof

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH 4/5] media: apple: Add Apple ISP driver
  2025-02-23  8:48     ` Sasha Finkelstein
@ 2025-02-23  9:03       ` Janne Grunau
  0 siblings, 0 replies; 21+ messages in thread
From: Janne Grunau @ 2025-02-23  9:03 UTC (permalink / raw)
  To: Sasha Finkelstein
  Cc: Sven Peter, Alyssa Rosenzweig, Rob Herring, Krzysztof Kozlowski,
	Conor Dooley, Hector Martin, Ulf Hansson, Mauro Carvalho Chehab,
	Shawn Guo, Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam,
	asahi, linux-arm-kernel, devicetree, linux-kernel, linux-pm,
	linux-media, imx, Eileen Yoon, Asahi Lina

On Sun, Feb 23, 2025 at 09:48:30AM +0100, Sasha Finkelstein wrote:
> On Wed, 19 Feb 2025 at 12:34, Janne Grunau <j@jannau.net> wrote:
> > > +     while (maps < end) {
> > > +             maps++;
> > > +             maps = of_translate_dma_region(dev->of_node, maps, &heap_base,
> > > +                                            &heap_size);
> > > +     }
> >
> > The hand-rolled reserved memory parsing looks like it can be replaced
> > with of_iommu_get_resv_region();
> 
> I have looked into it, and `of_iommu_get_resv_region` does the wrong
> thing. We fill out `reg`, and it grabs that instead of `iommu-addresses`.

Downstream commit 704ace01cd3c4 [0] ("iommu: Add IOMMU_RESV_TRANSLATED
for non 1:1 mapped reserved regions") adds device virtual addresses to
struct iommu_resv_region. Sorry for the added dependency but it is
required anyway for the isp to work.

ciao Janne

[0] https://github.com/AsahiLinux/linux/commit/704ace01cd3c423b1e2492f0777d9c4c0f3404d8

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2025-02-23  9:03 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-02-19  9:26 [PATCH 0/5] Driver for Apple ISP and cameras Sasha Finkelstein via B4 Relay
2025-02-19  9:26 ` [PATCH 1/5] dt-bindings: power: apple,pmgr-pwrstate: Add force-{disable,reset} properties Sasha Finkelstein via B4 Relay
2025-02-19  9:34   ` Krzysztof Kozlowski
2025-02-19  9:43     ` Sasha Finkelstein
2025-02-21 21:41       ` Rob Herring
2025-02-19  9:26 ` [PATCH 2/5] pmdomain: apple: Add force-disable/force-reset Sasha Finkelstein via B4 Relay
2025-02-19  9:26 ` [PATCH 3/5] media: dt-bindings: Add Apple ISP Sasha Finkelstein via B4 Relay
2025-02-19  9:37   ` Krzysztof Kozlowski
2025-02-19  9:54     ` Sasha Finkelstein
2025-02-19 10:53       ` Laurent Pinchart
2025-02-19 11:05         ` Sasha Finkelstein
2025-02-19 13:40           ` Laurent Pinchart
2025-02-19 15:36           ` Asahi Lina
2025-02-19 11:57         ` Janne Grunau
2025-02-19 13:44           ` Laurent Pinchart
2025-02-23  8:58       ` Krzysztof Kozlowski
2025-02-19  9:27 ` [PATCH 4/5] media: apple: Add Apple ISP driver Sasha Finkelstein via B4 Relay
2025-02-19 11:34   ` Janne Grunau
2025-02-23  8:48     ` Sasha Finkelstein
2025-02-23  9:03       ` Janne Grunau
2025-02-19  9:27 ` [PATCH 5/5] arm64: dts: apple: Add ISP nodes Sasha Finkelstein via B4 Relay

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).