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>; + }; 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 diff --git a/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi b/arch/arm64/boot/dts/apple/t8132-j713-pmgr.dtsi index d5d29a55e8fc5..66b69f0f6320c 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"; }; @@ -385,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"; }; @@ -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-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, 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>; 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"; 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-cam.c b/drivers/media/platform/apple/isp/isp-cam.c index 004a7fa13d019..1881d22482961 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->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) + 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]; @@ -558,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); @@ -568,6 +589,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; } @@ -578,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; } @@ -955,14 +980,34 @@ 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; + /* + * 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); + 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; } + 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 new file mode 100644 index 0000000000000..dbddb079a0f57 --- /dev/null +++ b/drivers/media/platform/apple/isp/isp-drv-test.c @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * 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 +#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); + +/* + * 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->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_firmware_lifetime), + {} +}; + +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 93a21e901ff6a..a2390964a13d8 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) @@ -175,6 +212,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 +618,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) { @@ -558,12 +673,23 @@ 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); - err = apple_isp_detect_camera(isp); - if (err) { - dev_err(dev, "failed to detect camera: %d\n", err); - goto free_surface; + if (!isp->hw->deferred_start) { + 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); @@ -889,6 +1015,74 @@ 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. 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, + .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, + .coproc_status = ISP_COPROC_STATUS_T8140, + .coproc_quiesce = ISP_COPROC_QUIESCE_T8140, + .boot_mode = 1, + .shared_size = 0xdec00000, + .fw_restore = true, + .deferred_start = 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 }, @@ -899,6 +1093,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); @@ -939,8 +1134,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); @@ -972,6 +1167,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", @@ -980,9 +1192,14 @@ 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); 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..738590fec3f35 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; @@ -187,11 +196,27 @@ 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; 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; @@ -205,10 +230,27 @@ 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 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 + * probe, or at the first stream with deferred_start. */ bool resident_fw; + + /* + * 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 deferred_start; }; enum isp_sensor_id { @@ -330,6 +372,7 @@ struct apple_isp { void __iomem *gpio; void __iomem *mbox2; void __iomem *wdt; + void __iomem *dapf; struct hrtimer wdt_timer; bool wdt_running; @@ -347,11 +390,19 @@ 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; 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]; @@ -424,6 +475,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__) @@ -443,7 +496,8 @@ enum { 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->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 22dbf99a4c3ec..28727ef24fbb6 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 @@ -53,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) { @@ -391,10 +397,47 @@ 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) { + /* 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); } @@ -403,10 +446,18 @@ 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; + 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); @@ -837,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; - } + int res, ret; - /* Wait for ISP_GPIO_0 to 0xf7fbdff9 -> 0x8042006 */ isp_gpio_write32(isp, ISP_GPIO_0, 0xf7fbdff9); - 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"); @@ -971,6 +1022,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) @@ -982,6 +1036,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; } @@ -1039,7 +1095,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 + * 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. @@ -1085,7 +1142,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) { diff --git a/drivers/media/platform/apple/isp/isp-regs.h b/drivers/media/platform/apple/isp/isp-regs.h index f2f6f1c9489f4..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 @@ -40,6 +44,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 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;