From 6602a37f73c66ad60b81d0117a40bdd0419ce08e Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:06 -0400 Subject: [PATCH 01/11] dt-bindings: iommu: apple,dart: Add the address filter registers Apple DARTs have address filter (DAPF) registers next to their translation registers. They restrict the physical addresses the DART lets its masters reach, and lose their contents whenever the DART's power domain is off. Allow them as an optional second register range named "dapf", so that a device whose coprocessor reaches fixed MMIO windows through the DART can program them after every power-up, finding them on the DART it already references. Signed-off-by: Erik Bourget --- .../devicetree/bindings/iommu/apple,dart.yaml | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/iommu/apple,dart.yaml b/Documentation/devicetree/bindings/iommu/apple,dart.yaml index 350e077e779b4..8fb92757fe7e5 100644 --- a/Documentation/devicetree/bindings/iommu/apple,dart.yaml +++ b/Documentation/devicetree/bindings/iommu/apple,dart.yaml @@ -65,7 +65,18 @@ properties: error-mask policy rather than changing it for these streams. reg: - maxItems: 1 + minItems: 1 + items: + - description: DART registers + - description: + Address filter (DAPF) registers, which restrict the physical + addresses the DART lets its masters reach + + reg-names: + minItems: 1 + items: + - const: dart + - const: dapf interrupts: maxItems: 1 @@ -157,6 +168,17 @@ required: - '#iommu-cells' - interrupts +allOf: + - if: + properties: + reg: + minItems: 2 + required: + - reg + then: + required: + - reg-names + additionalProperties: false examples: @@ -189,3 +211,12 @@ examples: master2 { iommus = <&dart2a 0>, <&dart2b 1>; }; + + - |+ + dart3: iommu@8e8c0000 { + compatible = "apple,t8132-dart", "apple,t8110-dart"; + reg = <0x8e8c0000 0xc000>, <0x8e8d0000 0x4000>; + reg-names = "dart", "dapf"; + interrupts = <1 387 4>; + #iommu-cells = <1>; + }; From d069e026bc14a9e1b74065e0ad553a418aca43c1 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:06 -0400 Subject: [PATCH 02/11] dt-bindings: media: Add the Apple T8132 ISP The T8132 (M4) ISP has the T6041's register layout and firmware memory model: the bootloader loads the firmware's text and data segments, described as reserved memory at the addresses the firmware runs them at. It uses the same fourteen ISP power domains as the T8140, plus two camera sensor power states. Describe it in its own schema, as the T6041 and T8140 are, since its power-domain list differs from both. Signed-off-by: Erik Bourget --- .../bindings/media/apple,t8132-isp.yaml | 201 ++++++++++++++++++ MAINTAINERS | 1 + 2 files changed, 202 insertions(+) create mode 100644 Documentation/devicetree/bindings/media/apple,t8132-isp.yaml diff --git a/Documentation/devicetree/bindings/media/apple,t8132-isp.yaml b/Documentation/devicetree/bindings/media/apple,t8132-isp.yaml new file mode 100644 index 0000000000000..3861d13a0bde9 --- /dev/null +++ b/Documentation/devicetree/bindings/media/apple,t8132-isp.yaml @@ -0,0 +1,201 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/media/apple,t8132-isp.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: Apple T8132 Image Signal Processor + +maintainers: + - Erik Bourget + +description: + The T8132 ISP controls the attached camera sensor and processes its frames + through firmware the bootloader loads. The firmware segments are described + as reserved memory mapped at the addresses the firmware runs them at. The + host exchanges commands and image buffers through three DART instances; the + first one also has the address filter registers. + +select: + properties: + compatible: + contains: + const: apple,t8132-isp + required: + - compatible + +properties: + compatible: + items: + - const: apple,t8132-isp + - const: apple,isp + + reg: + items: + - description: Coprocessor registers up to its control block + - description: Mailbox interrupt registers + - description: Firmware handshake registers + - description: Mailbox doorbell registers + + reg-names: + items: + - const: coproc + - const: mbox + - const: gpio + - const: mbox2 + + interrupts: + maxItems: 1 + + iommus: + items: + - description: First ISP DART stream; this DART has the address filter bank + - description: Second ISP DART stream + - description: Third ISP DART stream + + power-domains: + items: + - description: ISP system domain + - description: ISP CPU domain + - description: ISP CPU core 0 domain + - description: ISP CPU core 1 domain + - description: ISP FE domain + - description: ISP DPRX domain + - description: ISP secure domain + - description: ISP STS0 domain + - description: ISP STS1 domain + - description: ISP Pearl domain + - description: ISP BE domain + - description: ISP CLR domain + - description: ISP CVD domain + - description: ISP CVM domain + - description: First camera sensor domain + - description: Second camera sensor domain + + memory-region: + items: + - description: Firmware text loaded by the bootloader + - description: Firmware data loaded by the bootloader + - description: Firmware main memory window; host allocations follow it + + memory-region-names: + items: + - const: text + - const: data + - const: heap + + apple,dart-vm-size: + $ref: /schemas/types.yaml#/definitions/uint64 + description: + Size in bytes of the host allocation aperture used by the ISP driver. + + apple,platform-id: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Platform identifier passed in the firmware boot descriptor. + + apple,cam-connections-scheme: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Camera connection layout of the board passed in the firmware boot + descriptor. + + apple,temporal-filter: + $ref: /schemas/types.yaml#/definitions/uint32 + description: + Firmware temporal-filter strength selected for the attached sensor. + + sensor-presets: + type: object + description: + Sensor firmware presets and the host output geometry associated with them. + patternProperties: + "^preset[0-9]+$": + type: object + properties: + apple,config-index: + $ref: /schemas/types.yaml#/definitions/uint32 + description: Sensor configuration index understood by the firmware + apple,input-size: + $ref: /schemas/types.yaml#/definitions/uint32-array + items: + - description: Input width in pixels + - description: Input height in pixels + description: Sensor input dimensions + apple,output-size: + $ref: /schemas/types.yaml#/definitions/uint32-array + items: + - description: Output width in pixels + - description: Output height in pixels + description: Delivered frame dimensions + apple,crop: + $ref: /schemas/types.yaml#/definitions/uint32-array + items: + - description: Horizontal crop origin in pixels + - description: Vertical crop origin in pixels + - description: Crop width in pixels + - description: Crop height in pixels + description: Cropped sensor rectangle + required: + - apple,config-index + - apple,input-size + - apple,output-size + - apple,crop + additionalProperties: false + additionalProperties: false + +required: + - compatible + - reg + - reg-names + - interrupts + - iommus + - power-domains + - memory-region + - memory-region-names + - apple,dart-vm-size + - apple,platform-id + - sensor-presets + +additionalProperties: false + +examples: + - | + #include + #include + + soc { + #address-cells = <2>; + #size-cells = <2>; + + isp@48c000000 { + compatible = "apple,t8132-isp", "apple,isp"; + reg = <0x4 0x8c000000 0x0 0x1604000>, + <0x4 0x8e924000 0x0 0x40>, + <0x4 0x8e9241d0 0x0 0x20>, + <0x4 0x8e924464 0x0 0x10>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + interrupt-parent = <&aic>; + interrupts = ; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + power-domains = <&ps_isp_sys>, <&ps_isp_cpu>, + <&ps_isp_cpu_core0>, <&ps_isp_cpu_core1>, + <&ps_isp_fe>, <&ps_isp_dprx>, <&ps_isp_secure>, + <&ps_isp_sts0>, <&ps_isp_sts1>, <&ps_isp_pearl>, + <&ps_isp_be>, <&ps_isp_clr>, <&ps_isp_cvd>, + <&ps_isp_cvm>, <&ps_ispsens0>, <&ps_ispsens1>; + memory-region = <&isp_fw_text>, <&isp_fw_data>, <&isp_heap>; + memory-region-names = "text", "data", "heap"; + apple,dart-vm-size = /bits/ 64 <0xdec00000>; + apple,platform-id = <0x17>; + + sensor-presets { + preset0 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + }; + }; + }; diff --git a/MAINTAINERS b/MAINTAINERS index e0a0070243ae6..4ed4b3dc08ea8 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2097,6 +2097,7 @@ F: Documentation/devicetree/bindings/iio/imu/apple,aop-motion.yaml F: Documentation/devicetree/bindings/iio/light/apple,t8140-aop-als.yaml F: Documentation/devicetree/bindings/input/apple,dockchannel-hid.yaml F: Documentation/devicetree/bindings/mailbox/apple,ascwrap-v6.yaml +F: Documentation/devicetree/bindings/media/apple,t8132-isp.yaml F: Documentation/devicetree/bindings/media/apple,t8140-isp.yaml F: Documentation/devicetree/bindings/mux/apple,display-crossbar.yaml F: Documentation/devicetree/bindings/reserved-memory/apple,asc-mem.yaml From 47d8f59220ddf676a1d55edaf3877dd0b94dc82d Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:06 -0400 Subject: [PATCH 03/11] arm64: dts: apple: t8132: Add the ISP power states The ISP's own power states are children of ISP_SYS at PMGR + 0x4000, in the ADT's order: CPU, CPU cores 0 and 1, FE, DPRX, secure, STS0, STS1, Pearl, BE, CLR, CVD and CVM, with the parents the ADT gives them. The camera sensor power states ISPSENS0 and ISPSENS1 are at 0x188 and 0x190; the ADT lists both among the ISP's power gates. Add them disabled, like ISP_SYS, and let the J713 policy enable them. They have no consumer yet, so they stay off. Signed-off-by: Erik Bourget --- .../arm64/boot/dts/apple/t8132-j713-pmgr.dtsi | 60 +++++++ arch/arm64/boot/dts/apple/t8132-pmgr.dtsi | 149 ++++++++++++++++++ 2 files changed, 209 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi index d5d29a55e8fc5..b39d9124c4206 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi @@ -63,6 +63,14 @@ status = "disabled"; }; +&ps_ispsens0 { + status = "okay"; +}; + +&ps_ispsens1 { + status = "okay"; +}; + &ps_pmgr_soc_ocla { status = "disabled"; }; @@ -430,6 +438,58 @@ status = "disabled"; }; +&ps_isp_cpu { + status = "okay"; +}; + +&ps_isp_cpu_core0 { + status = "okay"; +}; + +&ps_isp_cpu_core1 { + status = "okay"; +}; + +&ps_isp_fe { + status = "okay"; +}; + +&ps_isp_dprx { + status = "okay"; +}; + +&ps_isp_secure { + status = "okay"; +}; + +&ps_isp_sts0 { + status = "okay"; +}; + +&ps_isp_sts1 { + status = "okay"; +}; + +&ps_isp_pearl { + status = "okay"; +}; + +&ps_isp_be { + status = "okay"; +}; + +&ps_isp_clr { + status = "okay"; +}; + +&ps_isp_cvd { + status = "okay"; +}; + +&ps_isp_cvm { + status = "okay"; +}; + &ps_venc_dma { status = "disabled"; }; diff --git a/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi index 5e31d6ce69e15..0dafc59196507 100644 --- a/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-pmgr.dtsi @@ -120,6 +120,24 @@ label = "pmgr_soc_ocla"; }; + ps_ispsens0: power-controller@188 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x188 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens0"; + status = "disabled"; + }; + + ps_ispsens1: power-controller@190 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x190 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "ispsens1"; + status = "disabled"; + }; + ps_ap_tmm: power-controller@1b8 { compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x1b8 4>; @@ -885,6 +903,137 @@ apple,always-on; }; + /* The ISP's own power states, children of ISP_SYS */ + ps_isp_cpu: power-controller@4000 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4000 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cpu"; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + ps_isp_cpu_core0: power-controller@4008 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4008 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cpu_core0"; + power-domains = <&ps_isp_cpu>; + status = "disabled"; + }; + + ps_isp_cpu_core1: power-controller@4010 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4010 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cpu_core1"; + power-domains = <&ps_isp_cpu>; + status = "disabled"; + }; + + ps_isp_fe: power-controller@4018 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4018 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_fe"; + power-domains = <&ps_isp_sys>; + status = "disabled"; + }; + + ps_isp_dprx: power-controller@4020 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4020 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_dprx"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_secure: power-controller@4028 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4028 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_secure"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_sts0: power-controller@4030 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4030 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_sts0"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_sts1: power-controller@4038 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4038 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_sts1"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_pearl: power-controller@4040 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4040 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_pearl"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_be: power-controller@4048 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4048 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_be"; + power-domains = <&ps_isp_fe>; + status = "disabled"; + }; + + ps_isp_clr: power-controller@4050 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4050 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_clr"; + power-domains = <&ps_isp_be>; + status = "disabled"; + }; + + ps_isp_cvd: power-controller@4058 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4058 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cvd"; + power-domains = <&ps_isp_be>; + status = "disabled"; + }; + + ps_isp_cvm: power-controller@4060 { + compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; + reg = <0x4060 4>; + #power-domain-cells = <0>; + #reset-cells = <0>; + label = "isp_cvm"; + power-domains = <&ps_isp_cvd>; + status = "disabled"; + }; + ps_venc_dma: power-controller@8000 { compatible = "apple,t8132-pmgr-pwrstate", "apple,t8103-pmgr-pwrstate"; reg = <0x8000 4>; From 41dc2841b379df68b33738480b0a85958fcefbb8 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:06 -0400 Subject: [PATCH 04/11] arm64: dts: apple: t8132: Add the ISP and its DARTs Describe the ISP from the ADT: the coprocessor, mailbox, handshake and doorbell registers at the T6041's offsets, the mailbox interrupt, and three DART instances on one interrupt, which remap streams 1, 2, 3, 6, 7, 10 and 11 to stream 0. The first instance also has the address filter registers. The DART's 0xe0000000 window above the firmware's 20 MiB main memory is the host's allocation aperture. Everything stays disabled until a board describes its firmware memory. Signed-off-by: Erik Bourget --- arch/arm64/boot/dts/apple/t8132.dtsi | 61 ++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/arch/arm64/boot/dts/apple/t8132.dtsi b/arch/arm64/boot/dts/apple/t8132.dtsi index 7a26e67f37a41..6ee608983784e 100644 --- a/arch/arm64/boot/dts/apple/t8132.dtsi +++ b/arch/arm64/boot/dts/apple/t8132.dtsi @@ -1204,6 +1204,67 @@ }; + /* + * ADT isp and dart-isp: three DART instances on one interrupt; streams 1, + * 2, 3, 6, 7, 10 and 11 are remapped to stream 0. The first instance also + * has the address filter registers. + */ + isp: isp@48c000000 { + compatible = "apple,t8132-isp", "apple,isp"; + reg = <0x4 0x8c000000 0x0 0x1604000>, + <0x4 0x8e924000 0x0 0x40>, + <0x4 0x8e9241d0 0x0 0x20>, + <0x4 0x8e924464 0x0 0x10>; + reg-names = "coproc", "mbox", "gpio", "mbox2"; + interrupt-parent = <&aic>; + interrupts = ; + iommus = <&isp_dart0 0>, <&isp_dart1 0>, <&isp_dart2 0>; + power-domains = <&ps_isp_sys>, <&ps_isp_cpu>, + <&ps_isp_cpu_core0>, <&ps_isp_cpu_core1>, + <&ps_isp_fe>, <&ps_isp_dprx>, <&ps_isp_secure>, + <&ps_isp_sts0>, <&ps_isp_sts1>, <&ps_isp_pearl>, + <&ps_isp_be>, <&ps_isp_clr>, <&ps_isp_cvd>, + <&ps_isp_cvm>, <&ps_ispsens0>, <&ps_ispsens1>; + /* the DART's 0xe0000000 window above the firmware's 20 MiB */ + apple,dart-vm-size = /bits/ 64 <0xdec00000>; + status = "disabled"; + }; + + isp_dart0: iommu@48e8c0000 { + compatible = "apple,t8132-dart", "apple,t8110-dart"; + reg = <0x4 0x8e8c0000 0x0 0xc000>, + <0x4 0x8e8d0000 0x0 0x4000>; + reg-names = "dart", "dapf"; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <2 0>, <3 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; + }; + + isp_dart1: iommu@48e8e0000 { + compatible = "apple,t8132-dart", "apple,t8110-dart"; + reg = <0x4 0x8e8e0000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <2 0>, <3 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; + }; + + isp_dart2: iommu@48e900000 { + compatible = "apple,t8132-dart", "apple,t8110-dart"; + reg = <0x4 0x8e900000 0x0 0xc000>; + #iommu-cells = <1>; + interrupt-parent = <&aic>; + interrupts = ; + power-domains = <&ps_isp_sys>; + apple,remap-streams = <1 0>, <2 0>, <3 0>, <6 0>, <7 0>, <10 0>, <11 0>; + status = "disabled"; + }; + dart_apcie0: iommu@490000000 { status = "disabled"; compatible = "apple,t8132-dart", "apple,t8110-dart"; From 7e4f610ba21b0055e9c28db7cac7f63349d881b2 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:07 -0400 Subject: [PATCH 05/11] media: apple: isp: Program the DART address filter On T8132 the ADT gives the first ISP DART address filter windows for the fixed MMIO windows the ISP's coprocessor reaches, among them the ISP power states and the DSID broadcast-clear windows. The filter loses its windows whenever ISP_SYS is off and powers up with arbitrary contents. The camera streams with the filter cleared and with it left as it powers up, but program it as the ADT describes rather than leave it undefined. Take an optional per-SoC window list, find the filter registers on the first iommus DART, and program and verify the windows after every power-up, before the coprocessor starts. The windows lie inside the fw_mmio windows that the driver already maps into that DART for the firmware, so the list belongs next to fw_mmio; the DART driver itself has no use for them. Two KUnit cases check that every SoC's windows lie inside its fw_mmio windows, and the register image the writer produces. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/Kconfig | 6 +- .../media/platform/apple/isp/isp-drv-test.c | 99 +++++++++++++++++++ drivers/media/platform/apple/isp/isp-drv.c | 82 +++++++++++++++ drivers/media/platform/apple/isp/isp-drv.h | 20 ++++ drivers/media/platform/apple/isp/isp-fw.c | 6 ++ drivers/media/platform/apple/isp/isp-regs.h | 14 +++ 6 files changed, 224 insertions(+), 3 deletions(-) create mode 100644 drivers/media/platform/apple/isp/isp-drv-test.c diff --git a/drivers/media/platform/apple/isp/Kconfig b/drivers/media/platform/apple/isp/Kconfig index d5c6ca690476a..3967e51d472cf 100644 --- a/drivers/media/platform/apple/isp/Kconfig +++ b/drivers/media/platform/apple/isp/Kconfig @@ -28,9 +28,9 @@ config VIDEO_APPLE_ISP_KUNIT_TEST default KUNIT_ALL_TESTS help Builds unit tests for queue setup, P010 layouts and buffer identity, - firmware sensor geometry, the capture command stream and shared - memory retirement. The command tests stub the IPC transport and - need no hardware. Only useful for kernel developers running KUnit. + firmware sensor geometry, the capture command stream, shared memory + retirement and the per-SoC DART address filter windows. The command + tests stub the IPC transport and need no hardware. Only useful for kernel developers running KUnit. If unsure, say N. diff --git a/drivers/media/platform/apple/isp/isp-drv-test.c b/drivers/media/platform/apple/isp/isp-drv-test.c new file mode 100644 index 0000000000000..a31cf2bfa899f --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-drv-test.c @@ -0,0 +1,99 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * KUnit tests for the DART address filter windows of the Apple ISP driver, + * built into isp-drv.c. They need no hardware. + */ + +#include +#include +#include + +/* Every address filter window must lie inside a window the firmware is mapped. */ +static void isp_test_dapf_in_fw_mmio(struct kunit *test) +{ + const struct of_device_id *match; + + for (match = apple_isp_of_match; match->compatible[0]; match++) { + const struct apple_isp_hw *hw = match->data; + + for (unsigned int i = 0; i < hw->num_dapf; i++) { + const struct isp_dapf_window *w = &hw->dapf[i]; + bool inside = false; + + KUNIT_EXPECT_LE_MSG(test, w->start, w->end, "%s window %u", + match->compatible, i); + for (unsigned int j = 0; j < hw->num_fw_mmio; j++) { + const struct isp_mmio_window *m = &hw->fw_mmio[j]; + + if (w->start >= m->base && w->end < m->base + m->size) + inside = true; + } + KUNIT_EXPECT_TRUE_MSG(test, inside, + "%s window %u 0x%llx-0x%llx is not in fw_mmio", + match->compatible, i, w->start, w->end); + } + } +} + +#define ISP_TEST_DAPF_FILL 0xa5 + +/* + * Each window fills its own 0x40-byte slot with exactly these words, and + * leaves every other byte alone. The bank is plain memory here: the driver + * is arm64 only, where the MMIO accessors work on it unchanged. + */ +static void isp_test_dapf_program(struct kunit *test) +{ + static const struct isp_dapf_window windows[] = { + { 0x380704000, 0x380704063 }, + { 0x382824000, 0x382824004 }, + }; + static const struct apple_isp_hw hw = { + .dapf = windows, + .num_dapf = ARRAY_SIZE(windows), + }; + size_t size = (ARRAY_SIZE(windows) + 1) * DAPF_WINDOW_STRIDE; + u8 expect[DAPF_WINDOW_STRIDE]; + struct apple_isp *isp; + u8 *bank; + + isp = kunit_kzalloc(test, sizeof(*isp), GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, isp); + isp->dev = kunit_device_register(test, "apple-isp-dapf-test"); + KUNIT_ASSERT_NOT_ERR_OR_NULL(test, isp->dev); + bank = kunit_kmalloc(test, size, GFP_KERNEL); + KUNIT_ASSERT_NOT_NULL(test, bank); + memset(bank, ISP_TEST_DAPF_FILL, size); + isp->hw = &hw; + isp->dapf = (__force void __iomem *)bank; + + KUNIT_ASSERT_EQ(test, apple_isp_program_dapf(isp), 0); + + for (unsigned int i = 0; i < ARRAY_SIZE(windows); i++) { + memset(expect, ISP_TEST_DAPF_FILL, sizeof(expect)); + put_unaligned_le32(0x31, &expect[0x00]); + put_unaligned_le32(0, &expect[0x04]); + put_unaligned_le64(windows[i].start, &expect[0x08]); + put_unaligned_le64(windows[i].end, &expect[0x10]); + put_unaligned_le32(1, &expect[0x20]); + KUNIT_EXPECT_MEMEQ_MSG(test, bank + i * DAPF_WINDOW_STRIDE, expect, + sizeof(expect), "window %u", i); + } + + /* The slot after the last window is untouched. */ + KUNIT_EXPECT_NULL(test, memchr_inv(bank + ARRAY_SIZE(windows) * DAPF_WINDOW_STRIDE, + ISP_TEST_DAPF_FILL, DAPF_WINDOW_STRIDE)); +} + +static struct kunit_case isp_dapf_test_cases[] = { + KUNIT_CASE(isp_test_dapf_in_fw_mmio), + KUNIT_CASE(isp_test_dapf_program), + {} +}; + +static struct kunit_suite isp_dapf_test_suite = { + .name = "apple-isp-dapf", + .test_cases = isp_dapf_test_cases, +}; + +kunit_test_suite(isp_dapf_test_suite); diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 93a21e901ff6a..85d884952bf73 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -175,6 +175,78 @@ static int isp_of_region_iova(struct device *dev, const char *name, return 0; } +/* + * The firmware reaches its fixed MMIO windows through the first DART, whose + * address filter the ADT describes for them. Its registers are on that DART. + */ +static int apple_isp_map_dapf(struct apple_isp *isp) +{ + struct device *dev = isp->dev; + /* The first iommus entry, whatever its #iommu-cells */ + struct device_node *dart __free(device_node) = + of_parse_phandle(dev->of_node, "iommus", 0); + struct resource res; + int idx, err; + + if (!dart) + return dev_err_probe(dev, -ENODEV, "no ISP DART\n"); + + /* DAPF_* describe the T8110 DART's address filter */ + if (!of_device_is_compatible(dart, "apple,t8110-dart")) + return dev_err_probe(dev, -ENODEV, + "%pOF has no known address filter\n", dart); + + idx = of_property_match_string(dart, "reg-names", "dapf"); + if (idx < 0) + return dev_err_probe(dev, idx, + "%pOF has no address filter registers\n", dart); + + err = of_address_to_resource(dart, idx, &res); + if (err) + return dev_err_probe(dev, err, "%pOF: bad address filter registers\n", + dart); + + if (resource_size(&res) < isp->hw->num_dapf * DAPF_WINDOW_STRIDE) + return dev_err_probe(dev, -EINVAL, "%pOF: address filter too small\n", + dart); + + isp->dapf = devm_ioremap_resource(dev, &res); + return PTR_ERR_OR_ZERO(isp->dapf); +} + +/* Program the address filter windows, then check that they took. */ +int apple_isp_program_dapf(struct apple_isp *isp) +{ + const struct apple_isp_hw *hw = isp->hw; + void __iomem *w; + unsigned int i; + + for (i = 0; i < hw->num_dapf; i++) { + w = isp->dapf + i * DAPF_WINDOW_STRIDE; + writel(0, w + DAPF_CTRL1); + writeq(hw->dapf[i].start, w + DAPF_START); + writeq(hw->dapf[i].end, w + DAPF_END); + writel(DAPF_CTRL0_ISP, w + DAPF_CTRL0); + writel(DAPF_CTRL2_ISP, w + DAPF_CTRL2); + } + + /* Only bits 7:0 of CTRL0 and bits 63:2 of END read back. */ + for (i = 0; i < hw->num_dapf; i++) { + w = isp->dapf + i * DAPF_WINDOW_STRIDE; + if (readl(w + DAPF_CTRL1) || + readq(w + DAPF_START) != hw->dapf[i].start || + (readq(w + DAPF_END) | 3) != (hw->dapf[i].end | 3) || + (readl(w + DAPF_CTRL0) & 0xff) != DAPF_CTRL0_ISP || + readl(w + DAPF_CTRL2) != DAPF_CTRL2_ISP) { + dev_err(isp->dev, "DAPF window %u (0x%llx-0x%llx) failed readback\n", + i, hw->dapf[i].start, hw->dapf[i].end); + return -EIO; + } + } + + return 0; +} + static int apple_isp_init_iommu(struct apple_isp *isp) { struct device *dev = isp->dev; @@ -509,6 +581,12 @@ static int apple_isp_probe(struct platform_device *pdev) } } + if (isp->hw->num_dapf) { + err = apple_isp_map_dapf(isp); + if (err) + goto detach_genpd; + } + /* Capture watchdog, where the ISP gates its frames on one */ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "wdt"); if (res) { @@ -986,3 +1064,7 @@ module_platform_driver(apple_isp_driver); MODULE_AUTHOR("Eileen Yoon "); MODULE_DESCRIPTION("Apple ISP driver"); MODULE_LICENSE("GPL v2"); + +#if IS_ENABLED(CONFIG_VIDEO_APPLE_ISP_KUNIT_TEST) +#include "isp-drv-test.c" +#endif diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 30425f6cf483d..70c647fcea5a6 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -137,6 +137,15 @@ struct isp_mmio_window { u64 size; }; +/* + * A DART address filter window. Both bounds are inclusive and word + * granular, as the hardware takes them. + */ +struct isp_dapf_window { + u64 start; + u64 end; +}; + struct isp_preset { u32 index; struct coord input_dim; @@ -192,6 +201,14 @@ struct apple_isp_hw { const struct isp_mmio_window *fw_mmio; unsigned int num_fw_mmio; + /* + * The windows the ADT gives the first DART's address filter, inside + * the fw_mmio windows. The filter loses them with ISP_SYS, so they + * are programmed after every power-up. + */ + const struct isp_dapf_window *dapf; + unsigned int num_dapf; + /* ISP_GPIO_6 boot mode */ u32 boot_mode; @@ -330,6 +347,7 @@ struct apple_isp { void __iomem *gpio; void __iomem *mbox2; void __iomem *wdt; + void __iomem *dapf; struct hrtimer wdt_timer; bool wdt_running; @@ -424,6 +442,8 @@ enum { ISP_STATE_SLEEPING, }; +int apple_isp_program_dapf(struct apple_isp *isp); + #define isp_dbg(isp, fmt, ...) \ dev_dbg((isp)->dev, "[%s] " fmt, __func__, ##__VA_ARGS__) diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 22dbf99a4c3ec..d892841470ad0 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -407,6 +407,12 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) if (err < 0) return err; + if (isp->dapf) { + err = apple_isp_program_dapf(isp); + if (err) + goto shutdown; + } + /* ISP17a and T6040 have no clock enable word */ if (isp->hw->gen != ISP_GEN_T8140 && isp->hw->gen != ISP_GEN_T6040) isp_gpio_write32(isp, ISP_GPIO_CLOCK_EN, 0x1); diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index f2f6f1c9489f4..f300440568c5e 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -40,6 +40,20 @@ #define ISP_COPROC_RESET_ACK_0_T8140 0x1600818 #define ISP_COPROC_RESET_ACK_1_T8140 0x160081c +/* + * Address filter (DAPF) registers of a T8110-type DART: an array of windows, + * each with its inclusive start and end and three control words. Every ISP + * window takes the control values its ADT dapf-instance entry gives. + */ +#define DAPF_WINDOW_STRIDE 0x40 +#define DAPF_CTRL0 0x00 +#define DAPF_CTRL0_ISP 0x31 +#define DAPF_CTRL1 0x04 +#define DAPF_START 0x08 +#define DAPF_END 0x10 +#define DAPF_CTRL2 0x20 +#define DAPF_CTRL2_ISP 0x1 + #define ISP_MBOX_IRQ_INTERRUPT 0x00 #define ISP_MBOX_IRQ_ENABLE 0x04 #define ISP_MBOX_IRQ_ENABLE_T6031 0x08 From 6d95eef30f957d73e06f12b9bee6f9b21cf5c630 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:07 -0400 Subject: [PATCH 06/11] media: apple: isp: Let resident firmware start at the first stream Resident firmware is started at probe and runs while the driver is bound, so the ISP stays powered from boot even when the camera is never used. On the J713 (T8132) that costs about 100 mW, at idle and in suspend-to-idle. The video device can be registered without the firmware: its formats come from the device tree presets. Let a SoC defer the firmware start and the camera detection to the first stream start; from then on the firmware stays resident as before. Opening the device or listing its formats does not start it, as desktop services open every video node at login. On the J713 the deferred start, from power-up to a detected camera, adds about 0.2 s to the first VIDIOC_STREAMON. Deferring moves what detection checks to that first stream: an unsupported sensor, unexpected metadata sizes or a missing channel now fail it instead of probe, and leave the camera unusable until the next boot, like any failed resident start. Presets the firmware lacks can no longer be dropped from the formats userspace has already seen, so a stream on one is refused with -EINVAL instead. For the same reason a deferred start keeps the device tree presets: the IMX958 configuration discovery and P010 preview, which add formats at detection, stay with starts at probe. A KUnit case checks that only resident firmware defers its start. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/isp-cam.c | 33 ++++++++++++++++++- .../media/platform/apple/isp/isp-drv-test.c | 31 +++++++++++++++-- drivers/media/platform/apple/isp/isp-drv.c | 14 ++++---- drivers/media/platform/apple/isp/isp-drv.h | 18 ++++++++-- drivers/media/platform/apple/isp/isp-fw.c | 5 +-- 5 files changed, 87 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 004a7fa13d019..9b6d7a0f069f0 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -209,6 +209,23 @@ static int isp_check_presets(struct apple_isp *isp, u32 num_fw_presets) if (!num_fw_presets) return 0; + /* + * A deferred start detects the camera after the presets were + * advertised: keep them, and refuse a stream on one the firmware + * lacks instead. + */ + if (isp->hw->resident_fw_deferred) { + isp->num_fw_presets = num_fw_presets; + for (int i = 0; i < isp->num_presets; i++) + if (isp->presets[i].index >= num_fw_presets) + dev_warn(isp->dev, + "%ux%u preset: config %u, firmware has %u\n", + isp->presets[i].output_dim.x, + isp->presets[i].output_dim.y, + isp->presets[i].index, num_fw_presets); + return 0; + } + for (int i = 0; i < isp->num_presets; i++) { struct isp_preset *preset = &isp->presets[i]; @@ -568,6 +585,8 @@ int apple_isp_detect_camera(struct apple_isp *isp) if (isp->hw->resident_fw) { if (err) apple_isp_firmware_halt(isp); + else + isp->camera_detected = true; return err; } @@ -955,14 +974,26 @@ static int isp_configure_capture(struct apple_isp *isp) int apple_isp_start_camera(struct apple_isp *isp) { + struct isp_format *fmt = isp_get_current_format(isp); int err; - err = apple_isp_firmware_boot(isp); + /* A deferred start boots the firmware and detects the camera now. */ + if (isp->hw->resident_fw_deferred && !isp->camera_detected) + err = apple_isp_detect_camera(isp); + else + err = apple_isp_firmware_boot(isp); if (err < 0) { dev_err(isp->dev, "failed to boot firmware: %d\n", err); return err; } + if (isp->num_fw_presets && fmt->preset->index >= isp->num_fw_presets) { + dev_err(isp->dev, "preset config %u: firmware has %u\n", + fmt->preset->index, isp->num_fw_presets); + apple_isp_firmware_shutdown(isp); + return -EINVAL; + } + err = isp_configure_capture(isp); if (err) { dev_err(isp->dev, "failed to configure capture: %d\n", err); diff --git a/drivers/media/platform/apple/isp/isp-drv-test.c b/drivers/media/platform/apple/isp/isp-drv-test.c index a31cf2bfa899f..ca86f58968ad1 100644 --- a/drivers/media/platform/apple/isp/isp-drv-test.c +++ b/drivers/media/platform/apple/isp/isp-drv-test.c @@ -1,7 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * KUnit tests for the DART address filter windows of the Apple ISP driver, - * built into isp-drv.c. They need no hardware. + * KUnit tests for the per-SoC data of the Apple ISP driver, built into + * isp-drv.c: the DART address filter windows and the firmware start. They + * need no hardware. */ #include @@ -97,3 +98,29 @@ static struct kunit_suite isp_dapf_test_suite = { }; kunit_test_suite(isp_dapf_test_suite); + +/* Only resident firmware may defer its start: it is detected once. */ +static void isp_test_deferred_is_resident(struct kunit *test) +{ + const struct of_device_id *match; + + for (match = apple_isp_of_match; match->compatible[0]; match++) { + const struct apple_isp_hw *hw = match->data; + + if (hw->resident_fw_deferred) + KUNIT_EXPECT_TRUE_MSG(test, hw->resident_fw, "%s", + match->compatible); + } +} + +static struct kunit_case isp_hw_test_cases[] = { + KUNIT_CASE(isp_test_deferred_is_resident), + {} +}; + +static struct kunit_suite isp_hw_test_suite = { + .name = "apple-isp-hw", + .test_cases = isp_hw_test_cases, +}; + +kunit_test_suite(isp_hw_test_suite); diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 85d884952bf73..b751d9b0bb778 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -638,10 +638,12 @@ static int apple_isp_probe(struct platform_device *pdev) 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; + if (!isp->hw->resident_fw_deferred) { + 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); @@ -1017,8 +1019,8 @@ static __maybe_unused int apple_isp_freeze(struct device *dev) /* * Hibernation hands the system from one kernel to another, and * firmware left running would go on using the memory of the kernel - * that started it. Stop it cleanly instead; the camera is then - * unavailable until the next system boot. + * that started it. Stop it cleanly instead; if it ran, the camera is + * then unavailable until the next system boot. */ if (isp->hw->resident_fw) { mutex_lock(&isp->video_lock); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 70c647fcea5a6..1e938dcd55844 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -222,10 +222,17 @@ struct apple_isp_hw { bool meta_exposure; /* - * The firmware can be started only once per system boot, so it is - * started at probe and kept running while the driver is bound. + * The firmware can be started only once per system boot, so once + * started it is kept running while the driver is bound. It starts at + * probe, or at the first stream with resident_fw_deferred. */ bool resident_fw; + + /* + * With resident_fw: start the firmware, and detect the camera, at the + * first stream instead of at probe, so the ISP stays off until then. + */ + bool resident_fw_deferred; }; enum isp_sensor_id { @@ -365,6 +372,10 @@ struct apple_isp { } fw; /* changes under video_lock once the video device is registered */ enum isp_fw_state fw_state; + /* the camera was detected; until then a deferred start is pending */ + bool camera_detected; + /* configurations the firmware has, once a deferred start detected them */ + u32 num_fw_presets; struct isp_surf *ipc_surf; struct isp_surf *extra_surf; @@ -463,7 +474,8 @@ int apple_isp_program_dapf(struct apple_isp *isp); static inline bool isp_supports_p010(struct apple_isp *isp) { - return isp->hw->gen == ISP_GEN_T6040 && + /* Its config discovery would change formats already advertised. */ + return isp->hw->gen == ISP_GEN_T6040 && !isp->hw->resident_fw_deferred && isp_get_current_format(isp)->version == 0x958; } diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index d892841470ad0..45ce027b58344 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -1045,7 +1045,8 @@ static void isp_firmware_shutdown(struct apple_isp *isp) * Resident firmware (hw->resident_fw) is loaded by the bootloader and * does not start again once it has been stopped: after CISP_CMD_SUSPEND, * a coprocessor reset and a power cycle of its domains it never completes - * the first handshake. It is booted once, at probe, and runs until the + * the first handshake. It is booted once, at probe or with + * resident_fw_deferred at the first stream, and runs until the * driver is unbound, probe fails or the system hibernates, also across * suspend; after it has stopped the device stays unusable until the next * system boot. @@ -1091,7 +1092,7 @@ int apple_isp_firmware_boot(struct apple_isp *isp) return 0; } -/* The end of a stream or of the camera detection at probe */ +/* The end of a stream or of the camera detection */ void apple_isp_firmware_shutdown(struct apple_isp *isp) { if (!isp->hw->resident_fw) { From df3431c6e60f32d522df67362080bf00a8c74cff Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:07 -0400 Subject: [PATCH 07/11] media: apple: isp: Add T8132 support The T8132 (M4) ISP is T6040's: the same coprocessor layout, firmware interface and boot path, with the bootloader's firmware described as reserved memory. It differs in: - two AMCCs (ADT mcc amcc_aperture_count), so two DSID broadcast-clear windows, passed one per AMCC without padding slots; - no PMP_CTRL_SET: T8132's PMP scratch offsets are not established, and the camera streams without the command. The firmware's MMIO windows still cover those PMP registers; - the firmware's MMIO windows are also opened in the DART's address filter. The firmware is resident as on T6040 and T8140. A second start after its domains were cycled fails on T8132 too: the ISP CPU cores then do not reach their active power state. So it runs while the driver is bound, and T8132 defers its start to the first stream, keeping the ISP off until the camera is used. T6040 and T8140 have not been tested with the deferred start and keep starting at probe. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/isp-drv.c | 64 ++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index b751d9b0bb778..10fbce92979ca 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -969,6 +969,69 @@ static const struct apple_isp_hw apple_isp_hw_t6040 = { .meta_exposure = true, }; +/* T8132: the firmware's MMIO windows, from the J713 ADT's DART address filter */ +static const struct isp_mmio_window apple_isp_fw_mmio_t8132[] = { + { 0x220004000, 0x1d4000 }, /* DSID broadcast-clear windows */ + { 0x222004000, 0x1d4000 }, + { 0x3803c0000, 0x28000 }, /* PMGR, with the PMP clock scratch */ + { 0x380704000, 0x4000 }, /* PMGR, ISP power states */ + { 0x380730000, 0x4000 }, + { 0x380e3c000, 0x4000 }, + { 0x382824000, 0x4000 }, /* PMP bandwidth scratch */ + { 0x39062c000, 0x20000 }, + { 0x501660000, 0x4000 }, +}; + +/* The ADT's DART address filter windows, in its order and with its ends */ +static const struct isp_dapf_window apple_isp_dapf_t8132[] = { + { 0x380704000, 0x380704063 }, /* PMGR, ISP power states */ + { 0x380730000, 0x380733fff }, + { 0x380e3c000, 0x380e3c003 }, + { 0x39062c000, 0x390648003 }, + { 0x501660000, 0x501660003 }, + { 0x220004000, 0x2200162fc }, /* DSID broadcast-clear windows */ + { 0x220044000, 0x2200562fc }, + { 0x2201c4000, 0x2201d62fc }, + { 0x222004000, 0x2220162fc }, + { 0x222044000, 0x2220562fc }, + { 0x2221c4000, 0x2221d62fc }, + { 0x3803c0000, 0x3803e4000 }, /* PMGR, with the PMP clock scratch */ + { 0x382824000, 0x382824004 }, /* PMP bandwidth scratch */ +}; + +/* + * T8132 runs T6040's coprocessor and firmware interface, booted the same + * way. Its two AMCCs (ADT mcc amcc_aperture_count) give two broadcast-clear + * windows, passed one per AMCC without padding slots. No PMP_CTRL_SET is + * sent: T8132's PMP scratch offsets are not established. + */ +static const struct apple_isp_hw apple_isp_hw_t8132 = { + .gen = ISP_GEN_T6040, + .fw_abi = ISP_FW_ABI_H17, + .pmu_base = 0x0, + + .dsid_count = 2, + .dsid_clr_base0 = 0x2201d4000, + .dsid_clr_base1 = 0x2221d4000, + .dsid_clr_range0 = 0x2fc, + + .mbox_irq_enable = ISP_MBOX_IRQ_ENABLE_T6031, + + .scl1 = true, + .lpdp = false, + .meta_size = ISP_META_SIZE_T8140, + .fw_mmio = apple_isp_fw_mmio_t8132, + .num_fw_mmio = ARRAY_SIZE(apple_isp_fw_mmio_t8132), + .dapf = apple_isp_dapf_t8132, + .num_dapf = ARRAY_SIZE(apple_isp_dapf_t8132), + + .coproc_control = ISP_COPROC_CONTROL_T8140, + .boot_mode = 1, + .shared_size = 0xdec00000, + .resident_fw = true, + .resident_fw_deferred = true, +}; + 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 }, @@ -979,6 +1042,7 @@ static const struct of_device_id apple_isp_of_match[] = { { .compatible = "apple,t6031-isp", .data = &apple_isp_hw_t6031 }, { .compatible = "apple,t8140-isp", .data = &apple_isp_hw_t8140 }, { .compatible = "apple,t6041-isp", .data = &apple_isp_hw_t6040 }, + { .compatible = "apple,t8132-isp", .data = &apple_isp_hw_t8132 }, {}, }; MODULE_DEVICE_TABLE(of, apple_isp_of_match); From 8295d7f0194ab7ab840d88ad80bc5889b6bbb252 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Wed, 28 Oct 2026 09:44:07 -0400 Subject: [PATCH 08/11] arm64: dts: apple: t8132-j713: Enable the camera Describe the ISP's firmware memory and the front IMX958's 1080p and 720p presets, configuration 5 as on the J616s. iBoot loads the ISP firmware outside the kernel's memory, at addresses that move with the boot firmware, so the firmware text and data are empty placeholders. The boot loader fills them from the ADT's isp segment-ranges, sets the platform ID from the ADT isp node's sensor-type, and enables the ISP and its DARTs. Without that the camera stays disabled. The ISP stays off until the first stream. From then on it stays powered until the driver is unbound, as on the other resident ISPs; on the J713 that costs about 100 mW at idle: 4.13 W against 4.03 W with the ISP off, mean battery power over 180 s with the display on. Signed-off-by: Erik Bourget --- .../arm64/boot/dts/apple/t8132-j713-pmgr.dtsi | 2 +- arch/arm64/boot/dts/apple/t8132-j713.dts | 47 +++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi index b39d9124c4206..66b69f0f6320c 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi +++ b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi @@ -393,7 +393,7 @@ apple,firmware-owned; }; -/* No camera driver yet; genpd powers the ISP down as an unused domain. */ +/* The camera powers the ISP from its first stream until it is unbound. */ &ps_isp_sys { status = "okay"; }; diff --git a/arch/arm64/boot/dts/apple/t8132-j713.dts b/arch/arm64/boot/dts/apple/t8132-j713.dts index 33a9fa372da65..d3fc7fb2c58de 100644 --- a/arch/arm64/boot/dts/apple/t8132-j713.dts +++ b/arch/arm64/boot/dts/apple/t8132-j713.dts @@ -127,6 +127,25 @@ }; }; + reserved-memory { + /* + * iBoot loads the ISP firmware outside the kernel's memory, at + * addresses that move with the boot firmware. The boot loader + * fills these placeholders from the ADT's isp segment-ranges: reg, + * and iommu-addresses where the firmware runs them, from + * 0x100_0000_0000 on. The driver checks that the coprocessor's + * reset vector is this text before it starts the firmware. + */ + isp_fw_text: isp-firmware-text { }; + + isp_fw_data: isp-firmware-data { }; + + /* the firmware's 20 MiB main memory, the driver allocates above it */ + isp_heap: isp-heap { + iommu-addresses = <&isp 0x100 0x0 0x0 0x1400000>; + }; + }; + /* * The boot NOR runs from a PMU rail that is on whenever the SoC is * and that no OS switches. The ADT gives the flash no supply. @@ -138,6 +157,34 @@ }; }; +/* + * The boot loader enables the ISP and its DARTs once it has filled the + * firmware placeholders and set apple,platform-id, the ADT isp node's + * sensor-type (the J713 ADT has no cam-connections-scheme). + */ +&isp { + memory-region = <&isp_fw_text>, <&isp_fw_data>, <&isp_heap>; + memory-region-names = "text", "data", "heap"; + + /* The front camera, an IMX958: configuration 5, as on the J616s. */ + sensor-presets { + /* 1920x1080 */ + preset0 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1920 1080>; + apple,crop = <0 0 1920 1080>; + }; + /* 1280x720 */ + preset1 { + apple,config-index = <5>; + apple,input-size = <1920 2160>; + apple,output-size = <1280 720>; + apple,crop = <0 0 1280 720>; + }; + }; +}; + /* * m1n1 programs the AOP DART's DAPF and reserves the firmware segments and * the DAPF windows in memory-region when /chosen asks for the AOP's handoff, From 1ffdbc03d9177356fbb8a26930e50429652cdba7 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Fri, 30 Oct 2026 15:00:03 -0400 Subject: [PATCH 09/11] media: apple: isp: Optionally restore the firmware data before each start Firmware that the boot loader loads changes its data segment while it runs, and does not start again from that state. Add hw->fw_restore: probe keeps a copy of the data segment, taken before the firmware first runs, and every start puts it back and clears the firmware main memory above it. Stopping the firmware puts it back too, also after a start that failed, and a shutdown stops a stream and such firmware, so that a rebound driver or a kexec'd kernel finds the firmware as it was loaded. No SoC uses it yet. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/isp-drv.c | 64 ++++++++++++++++++++++ drivers/media/platform/apple/isp/isp-drv.h | 12 ++++ drivers/media/platform/apple/isp/isp-fw.c | 25 +++++++++ 3 files changed, 101 insertions(+) diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index 10fbce92979ca..b9ede98069103 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -5,10 +5,12 @@ * Copyright (C) 2023 The Asahi Linux Contributors */ +#include #include #include #include #include +#include #include #include #include @@ -20,6 +22,41 @@ #include "isp-regs.h" #include "isp-v4l2.h" +static void apple_isp_kvfree(void *data) +{ + kvfree(data); +} + +/* + * Keep a copy of the firmware data segment as the boot loader loaded it + * (hw->fw_restore). This must run before the firmware first starts. + */ +static int apple_isp_copy_fw_data(struct apple_isp *isp) +{ + struct device *dev = isp->dev; + struct resource res; + int err; + + err = of_reserved_mem_region_to_resource_byname(dev->of_node, "data", &res); + if (err) + return err; + + isp->fw_data_size = resource_size(&res); + isp->fw_data = devm_memremap(dev, res.start, isp->fw_data_size, MEMREMAP_WB); + if (IS_ERR(isp->fw_data)) + return PTR_ERR(isp->fw_data); + + isp->fw_data_copy = kvmalloc(isp->fw_data_size, GFP_KERNEL); + if (!isp->fw_data_copy) + return -ENOMEM; + err = devm_add_action_or_reset(dev, apple_isp_kvfree, isp->fw_data_copy); + if (err) + return err; + + memcpy(isp->fw_data_copy, isp->fw_data, isp->fw_data_size); + return 0; +} + static void apple_isp_detach_genpd(struct apple_isp *isp) { if (isp->pd_count <= 1) @@ -636,6 +673,15 @@ static int apple_isp_probe(struct platform_device *pdev) goto free_iommu; } + if (isp->hw->fw_restore) { + err = apple_isp_copy_fw_data(isp); + if (err) { + dev_err(dev, "failed to copy the firmware data: %d\n", err); + apple_isp_free_firmware_surface(isp); + goto free_iommu; + } + } + pm_runtime_enable(dev); if (!isp->hw->resident_fw_deferred) { @@ -1116,6 +1162,23 @@ static const struct dev_pm_ops apple_isp_pm_ops = { RUNTIME_PM_OPS(apple_isp_runtime_suspend, apple_isp_runtime_resume, NULL) }; +/* + * Stop firmware that starts again (hw->fw_restore) before a reboot or a + * kexec, so that the next kernel finds its data segment as loaded. + */ +static void apple_isp_shutdown(struct platform_device *pdev) +{ + struct apple_isp *isp = platform_get_drvdata(pdev); + + if (!isp || !isp->hw->fw_restore) + return; + /* A stream that still runs is stopped cleanly first, as for hibernation */ + apple_isp_video_suspend(isp); + mutex_lock(&isp->video_lock); + apple_isp_firmware_halt(isp); + mutex_unlock(&isp->video_lock); +} + static struct platform_driver apple_isp_driver = { .driver = { .name = "apple-isp", @@ -1124,6 +1187,7 @@ static struct platform_driver apple_isp_driver = { }, .probe = apple_isp_probe, .remove = apple_isp_remove, + .shutdown = apple_isp_shutdown, }; module_platform_driver(apple_isp_driver); diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 1e938dcd55844..2616d78338f22 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -221,6 +221,14 @@ struct apple_isp_hw { /* metadata buffers carry the frame's exposure report (H17 firmware) */ bool meta_exposure; + /* + * The firmware leaves its data segment changed when it stops, and + * does not start from it again. Keep a copy of the segment as the + * boot loader loaded it, taken at probe before the firmware first + * runs, and put it back whenever the firmware has stopped. + */ + bool fw_restore; + /* * The firmware can be started only once per system boot, so once * started it is kept running while the driver is bound. It starts at @@ -381,6 +389,10 @@ struct apple_isp { struct isp_surf *extra_surf; struct isp_surf *data_surf; struct isp_surf *main_tail_surf; + /* With hw->fw_restore: the firmware data segment and its copy */ + void *fw_data; + void *fw_data_copy; + size_t fw_data_size; struct isp_surf *log_surf; struct isp_surf *bt_surf; struct isp_surf *meta_surfs[ISP_MAX_BUFFERS]; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 45ce027b58344..149f90096c4f3 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -391,6 +392,23 @@ static int isp_reset_coproc(struct apple_isp *isp) return 0; } +/* + * With hw->fw_restore, put the firmware memory back as the boot loader left + * it: the data segment from its copy, and the main memory above it cleared. + */ +static void isp_restore_fw_memory(struct apple_isp *isp) +{ + struct isp_surf *tail = isp->main_tail_surf; + + if (!isp->fw_data_copy) + return; + + memcpy(isp->fw_data, isp->fw_data_copy, isp->fw_data_size); + if (tail) + for (u32 i = 0; i < tail->num_pages; i++) + clear_highpage(tail->pages[i]); +} + static void isp_firmware_shutdown_stage1(struct apple_isp *isp) { isp_coproc_write32(isp, isp_coproc_control(isp), 0x0); @@ -403,6 +421,8 @@ static int isp_firmware_boot_stage1(struct apple_isp *isp) int err; // u32 val; + isp_restore_fw_memory(isp); + err = apple_isp_power_up_domains(isp); if (err < 0) return err; @@ -977,6 +997,9 @@ static void isp_firmware_stop(struct apple_isp *isp) isp_firmware_shutdown_stage3(isp); isp_firmware_shutdown_stage2(isp); isp_collect_gc_surface(isp); + + /* Also leave it as loaded for a rebound driver or another kernel. */ + isp_restore_fw_memory(isp); } static int isp_firmware_boot(struct apple_isp *isp) @@ -988,6 +1011,8 @@ static int isp_firmware_boot(struct apple_isp *isp) /* Stage 1 stops the coprocessor and powers it down itself. */ isp_err(isp, "failed firmware boot stage 1: %d\n", err); isp_collect_gc_surface(isp); + /* The firmware may have run: leave its memory as loaded. */ + isp_restore_fw_memory(isp); return err; } From b15632556a9733c94a754ed62c752e9dcf6fabd1 Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Fri, 30 Oct 2026 15:00:03 -0400 Subject: [PATCH 10/11] media: apple: isp: Power restartable firmware down completely Firmware that is started again after it stopped (hw->fw_restore) has to be stopped for good. SUSPEND leaves the camera module powered, with its light on, and it stays so even across a reboot. Send STOP before POWER_DOWN instead, which also powers the module down; POWER_DOWN follows even if STOP failed. This replaces the unused STOP sequence that was kept under #if 0. Some coprocessors also need a quiesce before their domains go down. Its CPU core domains otherwise stay busy after the power-down and never run again, so the next start of the firmware fails. Add hw->coproc_quiesce, the register whose bit 25 requests the quiesce and reads back once it has happened, and hw->coproc_status. With a quiesce register, the power-down of a coprocessor that was started waits for it to reach WFI, stops it, quiesces it, and only then powers its domains down. A start that fails after the coprocessor was started thus also leaves it ready for the next one. No SoC uses them yet. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/isp-drv.h | 8 ++++ drivers/media/platform/apple/isp/isp-fw.c | 53 +++++++++++++++------ drivers/media/platform/apple/isp/isp-regs.h | 4 ++ 3 files changed, 51 insertions(+), 14 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 2616d78338f22..039f51de6a883 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -196,6 +196,14 @@ struct apple_isp_hw { bool mbox_irq_route; /* coprocessor control register, 0 for ISP_COPROC_CONTROL */ u32 coproc_control; + /* coprocessor status register, 0 for ISP_COPROC_STATUS */ + u32 coproc_status; + /* + * coprocessor quiesce register, 0 for none. The CPU core domains of + * a coprocessor that stopped only power down cleanly once it has + * quiesced; otherwise they stay busy and never run again. + */ + u32 coproc_quiesce; /* windows mapped 1:1 for the firmware */ const struct isp_mmio_window *fw_mmio; diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 149f90096c4f3..130b0d9b8581d 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -54,6 +54,11 @@ static inline u32 isp_coproc_control(struct apple_isp *isp) return isp->hw->coproc_control ?: ISP_COPROC_CONTROL; } +static inline u32 isp_coproc_status(struct apple_isp *isp) +{ + return isp->hw->coproc_status ?: ISP_COPROC_STATUS; +} + /* Wait for the firmware to write @expected to a GPIO word. */ static int isp_gpio_wait(struct apple_isp *isp, u32 reg, u32 expected) { @@ -411,8 +416,28 @@ static void isp_restore_fw_memory(struct apple_isp *isp) static void isp_firmware_shutdown_stage1(struct apple_isp *isp) { + /* Only a coprocessor that was started has to settle and quiesce */ + bool started = isp->hw->coproc_quiesce && + isp_coproc_read32(isp, isp_coproc_control(isp)); + u32 val; + + /* A coprocessor that powered down waits in WFI */ + if (started && + readl_poll_timeout(isp->coproc + isp_coproc_status(isp), val, + val & ISP_COPROC_IN_WFI, 100, 200 * USEC_PER_MSEC)) + dev_warn(isp->dev, "coproc not in WFI before power-down (status 0x%x)\n", + val); + isp_coproc_write32(isp, isp_coproc_control(isp), 0x0); + if (started) { + /* The request bit alone asks for it, and reads back once done */ + isp_coproc_write32(isp, isp->hw->coproc_quiesce, ISP_COPROC_QUIESCED); + if (readl_poll_timeout(isp->coproc + isp->hw->coproc_quiesce, val, + val & ISP_COPROC_QUIESCED, 10, 50 * USEC_PER_MSEC)) + dev_warn(isp->dev, "coproc did not quiesce (0x%x)\n", val); + } + apple_isp_power_down_domains(isp); } @@ -863,26 +888,26 @@ static int isp_firmware_boot_stage3(struct apple_isp *isp) static int isp_stop_command_processor(struct apple_isp *isp) { -#if 0 - int res = isp_cmd_stop(isp, 0); - if (res) { - isp_err(isp, "isp_cmd_stop() failed\n"); - return res; - } - - /* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ - isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); + int res, ret; - isp_cmd_power_down(isp); -#else isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); - int res = isp_cmd_suspend(isp); + /* + * Firmware that starts again (hw->fw_restore) is stopped for good: + * STOP also powers the camera module down, which SUSPEND leaves on, + * even across a reboot. POWER_DOWN follows even if STOP failed. + */ + if (isp->hw->fw_restore) { + res = isp_cmd_stop(isp, 0); + ret = isp_cmd_power_down(isp); + res = res ?: ret; + } else { + res = isp_cmd_suspend(isp); + } if (res) { - isp_err(isp, "isp_cmd_suspend() failed\n"); + isp_err(isp, "failed to stop the firmware: %d\n", res); return res; } -#endif if (isp_gpio_wait(isp, ISP_GPIO_0, 0x8042006)) { isp_err(isp, "never received magic number from firmware\n"); diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index f300440568c5e..b7f9b2fb87faa 100644 --- a/drivers/media/platform/apple/isp/isp-regs.h +++ b/drivers/media/platform/apple/isp/isp-regs.h @@ -24,6 +24,10 @@ #define ISP_COPROC_IRQ_MASK_5 0x1400a14 #define ISP_COPROC_CONTROL_T8140 0x1600044 +#define ISP_COPROC_STATUS_T8140 0x1600048 +#define ISP_COPROC_QUIESCE_T8140 0x1600040 +/* Requests the quiesce when set, and reads back once it has happened */ +#define ISP_COPROC_QUIESCED BIT(25) /* * ISP17a (T8140) reset. The fourth fabric word is at 0xa50, off the 0x100 From 140a8213fdb77a6a0440cb607c28e450baa6dc7b Mon Sep 17 00:00:00 2001 From: Erik Bourget Date: Fri, 30 Oct 2026 15:00:03 -0400 Subject: [PATCH 11/11] media: apple: isp: Power the T8132 ISP down between streams T8132 firmware starts again after it stopped, from its data segment as loaded, once the coprocessor was quiesced before the power-down. Stop keeping it resident: restore its data before each start, quiesce it before each power-down, and power the ISP down after every stream, as the older SoCs do. The camera then costs nothing when it is not in use, not only until its first use, and can be used again after the firmware stopped (for example across hibernation). The deferred start no longer needs resident firmware, so it becomes deferred_start: the camera is detected at the first stream, which with firmware that starts again boots it a second time to stream. A deferred detection that fails is reported to the stream instead of deferring a probe. Metadata buffers are reset for each stream of firmware that starts again, as for resident firmware: none survives the restart. Signed-off-by: Erik Bourget --- drivers/media/platform/apple/isp/isp-cam.c | 28 ++++++++++++++----- .../media/platform/apple/isp/isp-drv-test.c | 19 +++++++++---- drivers/media/platform/apple/isp/isp-drv.c | 13 ++++++--- drivers/media/platform/apple/isp/isp-drv.h | 12 ++++---- drivers/media/platform/apple/isp/isp-fw.c | 2 +- drivers/media/platform/apple/isp/isp-v4l2.c | 9 +++--- 6 files changed, 57 insertions(+), 26 deletions(-) diff --git a/drivers/media/platform/apple/isp/isp-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 9b6d7a0f069f0..1881d22482961 100644 --- a/drivers/media/platform/apple/isp/isp-cam.c +++ b/drivers/media/platform/apple/isp/isp-cam.c @@ -214,7 +214,7 @@ static int isp_check_presets(struct apple_isp *isp, u32 num_fw_presets) * advertised: keep them, and refuse a stream on one the firmware * lacks instead. */ - if (isp->hw->resident_fw_deferred) { + if (isp->hw->deferred_start) { isp->num_fw_presets = num_fw_presets; for (int i = 0; i < isp->num_presets; i++) if (isp->presets[i].index >= num_fw_presets) @@ -575,8 +575,12 @@ int apple_isp_detect_camera(struct apple_isp *isp) dev_err(isp->dev, "failed to boot firmware for initial sensor detection: %d\n", err); - /* Resident firmware would fail a deferred retry the same way. */ - return isp->hw->resident_fw ? err : -EPROBE_DEFER; + /* + * Resident firmware would fail a deferred retry the same way, + * and a deferred start is not a probe to defer. + */ + return isp->hw->resident_fw || isp->hw->deferred_start ? + err : -EPROBE_DEFER; } err = isp_detect_camera(isp); @@ -597,6 +601,8 @@ int apple_isp_detect_camera(struct apple_isp *isp) apple_isp_firmware_shutdown(isp); + if (!err) + isp->camera_detected = true; return err; } @@ -977,11 +983,19 @@ int apple_isp_start_camera(struct apple_isp *isp) struct isp_format *fmt = isp_get_current_format(isp); int err; - /* A deferred start boots the firmware and detects the camera now. */ - if (isp->hw->resident_fw_deferred && !isp->camera_detected) + /* + * A deferred start detects the camera now. Resident firmware is then + * left running; firmware that starts again is stopped after the + * detection like at probe, and booted again here. + */ + if (isp->hw->deferred_start && !isp->camera_detected) { err = apple_isp_detect_camera(isp); - else - err = apple_isp_firmware_boot(isp); + if (err < 0) { + dev_err(isp->dev, "failed to detect the camera: %d\n", err); + return err; + } + } + err = apple_isp_firmware_boot(isp); if (err < 0) { dev_err(isp->dev, "failed to boot firmware: %d\n", err); return err; diff --git a/drivers/media/platform/apple/isp/isp-drv-test.c b/drivers/media/platform/apple/isp/isp-drv-test.c index ca86f58968ad1..dbddb079a0f57 100644 --- a/drivers/media/platform/apple/isp/isp-drv-test.c +++ b/drivers/media/platform/apple/isp/isp-drv-test.c @@ -99,22 +99,31 @@ static struct kunit_suite isp_dapf_test_suite = { kunit_test_suite(isp_dapf_test_suite); -/* Only resident firmware may defer its start: it is detected once. */ -static void isp_test_deferred_is_resident(struct kunit *test) +/* + * A deferred start detects the camera once: its firmware must keep running + * or start again. Firmware that starts again is not resident, and only it + * is quiesced before the power-down. + */ +static void isp_test_firmware_lifetime(struct kunit *test) { const struct of_device_id *match; for (match = apple_isp_of_match; match->compatible[0]; match++) { const struct apple_isp_hw *hw = match->data; - if (hw->resident_fw_deferred) - KUNIT_EXPECT_TRUE_MSG(test, hw->resident_fw, "%s", + if (hw->deferred_start) + KUNIT_EXPECT_TRUE_MSG(test, hw->resident_fw || hw->fw_restore, + "%s", match->compatible); + KUNIT_EXPECT_FALSE_MSG(test, hw->resident_fw && hw->fw_restore, "%s", + match->compatible); + if (hw->coproc_quiesce) + KUNIT_EXPECT_TRUE_MSG(test, hw->fw_restore, "%s", match->compatible); } } static struct kunit_case isp_hw_test_cases[] = { - KUNIT_CASE(isp_test_deferred_is_resident), + KUNIT_CASE(isp_test_firmware_lifetime), {} }; diff --git a/drivers/media/platform/apple/isp/isp-drv.c b/drivers/media/platform/apple/isp/isp-drv.c index b9ede98069103..a2390964a13d8 100644 --- a/drivers/media/platform/apple/isp/isp-drv.c +++ b/drivers/media/platform/apple/isp/isp-drv.c @@ -684,7 +684,7 @@ static int apple_isp_probe(struct platform_device *pdev) pm_runtime_enable(dev); - if (!isp->hw->resident_fw_deferred) { + if (!isp->hw->deferred_start) { err = apple_isp_detect_camera(isp); if (err) { dev_err(dev, "failed to detect camera: %d\n", err); @@ -1049,7 +1049,10 @@ static const struct isp_dapf_window apple_isp_dapf_t8132[] = { * T8132 runs T6040's coprocessor and firmware interface, booted the same * way. Its two AMCCs (ADT mcc amcc_aperture_count) give two broadcast-clear * windows, passed one per AMCC without padding slots. No PMP_CTRL_SET is - * sent: T8132's PMP scratch offsets are not established. + * sent: T8132's PMP scratch offsets are not established. Its firmware + * starts again from its data segment as loaded once the coprocessor was + * quiesced before the power-down, so the ISP is powered only while the + * camera is in use. */ static const struct apple_isp_hw apple_isp_hw_t8132 = { .gen = ISP_GEN_T6040, @@ -1072,10 +1075,12 @@ static const struct apple_isp_hw apple_isp_hw_t8132 = { .num_dapf = ARRAY_SIZE(apple_isp_dapf_t8132), .coproc_control = ISP_COPROC_CONTROL_T8140, + .coproc_status = ISP_COPROC_STATUS_T8140, + .coproc_quiesce = ISP_COPROC_QUIESCE_T8140, .boot_mode = 1, .shared_size = 0xdec00000, - .resident_fw = true, - .resident_fw_deferred = true, + .fw_restore = true, + .deferred_start = true, }; static const struct of_device_id apple_isp_of_match[] = { diff --git a/drivers/media/platform/apple/isp/isp-drv.h b/drivers/media/platform/apple/isp/isp-drv.h index 039f51de6a883..738590fec3f35 100644 --- a/drivers/media/platform/apple/isp/isp-drv.h +++ b/drivers/media/platform/apple/isp/isp-drv.h @@ -240,15 +240,17 @@ struct apple_isp_hw { /* * The firmware can be started only once per system boot, so once * started it is kept running while the driver is bound. It starts at - * probe, or at the first stream with resident_fw_deferred. + * probe, or at the first stream with deferred_start. */ bool resident_fw; /* - * With resident_fw: start the firmware, and detect the camera, at the - * first stream instead of at probe, so the ISP stays off until then. + * Start the firmware, and detect the camera, at the first stream + * instead of at probe, so the ISP stays off until then. The firmware + * must be resident or start again (fw_restore): the camera is + * detected only once. */ - bool resident_fw_deferred; + bool deferred_start; }; enum isp_sensor_id { @@ -495,7 +497,7 @@ int apple_isp_program_dapf(struct apple_isp *isp); static inline bool isp_supports_p010(struct apple_isp *isp) { /* Its config discovery would change formats already advertised. */ - return isp->hw->gen == ISP_GEN_T6040 && !isp->hw->resident_fw_deferred && + return isp->hw->gen == ISP_GEN_T6040 && !isp->hw->deferred_start && isp_get_current_format(isp)->version == 0x958; } diff --git a/drivers/media/platform/apple/isp/isp-fw.c b/drivers/media/platform/apple/isp/isp-fw.c index 130b0d9b8581d..28727ef24fbb6 100644 --- a/drivers/media/platform/apple/isp/isp-fw.c +++ b/drivers/media/platform/apple/isp/isp-fw.c @@ -1096,7 +1096,7 @@ static void isp_firmware_shutdown(struct apple_isp *isp) * does not start again once it has been stopped: after CISP_CMD_SUSPEND, * a coprocessor reset and a power cycle of its domains it never completes * the first handshake. It is booted once, at probe or with - * resident_fw_deferred at the first stream, and runs until the + * deferred_start at the first stream, and runs until the * driver is unbound, probe fails or the system hibernates, also across * suspend; after it has stopped the device stays unusable until the next * system boot. diff --git a/drivers/media/platform/apple/isp/isp-v4l2.c b/drivers/media/platform/apple/isp/isp-v4l2.c index 24506ed526a34..81964ab1916c9 100644 --- a/drivers/media/platform/apple/isp/isp-v4l2.c +++ b/drivers/media/platform/apple/isp/isp-v4l2.c @@ -531,11 +531,12 @@ static int apple_isp_start_streaming(struct apple_isp *isp) } /* - * Resident firmware was not restarted, but the channel's pools are - * configured again for every stream. Metadata buffers still marked - * as submitted to the previous pools would never reach the new ones. + * The channel's pools are configured again for every stream, by + * resident firmware that kept running or by firmware that started + * afresh. Metadata buffers still marked as submitted to the previous + * pools, or to firmware that stopped, would never reach the new ones. */ - if (isp->hw->resident_fw) { + if (isp->hw->resident_fw || isp->hw->fw_restore) { spin_lock_irqsave(&isp->buf_lock, flags); for (int i = 0; i < ARRAY_SIZE(isp->meta_surfs); i++) isp->meta_surfs[i]->submitted = false;