Skip to content

J713: enable the camera (T8132 ISP) - #531

Open
erikwb wants to merge 11 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/camera
Open

erikwb wants to merge 11 commits into
aurora-silicon:integration/aurorafrom
erikwb:j713/camera

Conversation

@erikwb

@erikwb erikwb commented Oct 28, 2026 •

Copy link
Copy Markdown

The J713 (MacBook Air 13" M4) camera works with this series: /dev/video0 streams NV12 at 1920x1080 and 1280x720, and the ISP is powered only while it streams: it is off from boot until the first stream, and again after every stream.

The T8132 ISP is T6040's ISP with a different power topology and two AMCCs, so the series reuses the T6040 path and adds what T8132 needs.

Patches

  1. dt-bindings: iommu: apple,dart: Add the address filter registers: an optional second reg range, dapf, for the DART's address filter (DAPF) registers. If a node has two ranges, reg-names is required.
  2. dt-bindings: media: Add the Apple T8132 ISP: its own schema, as for T6041 and T8140. It has T6041's register layout and firmware memory model, and T8140's fourteen ISP power domains plus two camera sensor power states.
  3. arm64: dts: apple: t8132: Add the ISP power states: the thirteen ISP power states below ISP_SYS (PMGR + 0x4000) and ISPSENS0/1. They are disabled in the SoC file like ps_isp_sys, and only the J713 policy enables them, so the other T8132 boards are unchanged.
  4. arm64: dts: apple: t8132: Add the ISP and its DARTs: the ISP and its three DARTs, disabled. The first DART carries the dapf range.
  5. media: apple: isp: Program the DART address filter: an optional per-SoC window list, programmed after every power-up and read back. Two KUnit cases cover it.
  6. media: apple: isp: Let resident firmware start at the first stream: a SoC can defer its resident firmware's start, and the camera detection, from probe to the first stream start. One KUnit case covers it.
  7. media: apple: isp: Add T8132 support: the hardware entry: T6040's interface, two DSID broadcast-clear windows (one per AMCC), no PMP_CTRL_SET, and resident firmware with the deferred start (patch 11 makes it restartable).
  8. arm64: dts: apple: t8132-j713: Enable the camera: the firmware segments as reserved memory (from the ADT's isp segment-ranges), the platform ID, the IMX958 presets, and the DARTs.
  9. media: apple: isp: Optionally restore the firmware data before each start: hw->fw_restore. Probe keeps a copy of the firmware's data segment, as the boot loader loaded it, and every start and stop puts it back. A shutdown stops a stream and the firmware, so a rebound driver or a kexec'd kernel finds the firmware as it was loaded.
  10. media: apple: isp: Power restartable firmware down completely: such firmware is stopped with STOP and POWER_DOWN rather than SUSPEND, which powers the camera module and its light down too. A coprocessor with a quiesce register (hw->coproc_quiesce) is quiesced after it reaches WFI and before its domains go down; without that its CPU cores stay busy and never start again.
  11. media: apple: isp: Power the T8132 ISP down between streams: T8132 uses both, and its firmware is no longer resident. The ISP powers down after every stream, as on the older SoCs. The deferred start no longer needs resident firmware, so resident_fw_deferred becomes deferred_start. A KUnit case checks the flag combinations.

Power

  • The ISP is off whenever the camera isn't streaming. Each stream start restores the firmware's data, powers the ISP up and starts the firmware. Each stream stop powers the camera module down, quiesces the coprocessor and turns all sixteen ISP domains off.
  • This saves the ~100 mW that resident firmware cost after first use. Before patch 11, the ISP stayed powered from the first stream until the driver was unbound. On the J713 that cost about 100 mW at idle and in suspend-to-idle: 4.13 W against 4.03 W with the ISP off, as the mean battery power over 180 s at 100 nits. After a stream all sixteen domains now read off, and the CPU core domains are back in their cold-boot state.
  • Nothing starts before the first stream (patch 6). The video device registers from the device tree presets without the firmware. Opening it or listing its formats doesn't start it, since desktop services open every video node at login. The first VIDIOC_STREAMON detects the camera, which boots the firmware once for detection and again to stream.
  • What moves to the first stream: an unsupported sensor, unexpected metadata sizes or a missing channel now fail the first stream instead of probe. The next stream tries again.
  • Presets stay as advertised. A deferred start can't drop presets the firmware lacks from formats userspace has already seen, so a stream on one is refused with -EINVAL. For the same reason it keeps the device-tree presets: the IMX958 configuration discovery and P010 preview from media: apple: isp: add IMX958 P010 previews and firmware config discovery #462 add formats at detection, so they stay with starts at probe. T6040 and T8140 are unchanged; they haven't been tested with the deferred start.
  • Without the driver, ISP_SYS stays on, because genpd keeps it on while the ISP node waits for its driver (sync_state).

The address filter

The ADT gives the first ISP DART address filter windows for the fixed MMIO windows the ISP's coprocessor reaches: the ISP power states, the DSID broadcast-clear windows and PMP registers. The filter loses them whenever ISP_SYS is off and powers up with arbitrary contents.

The camera also streams with the filter cleared, and with it left as it powers up. So this isn't required for the camera to work. The driver programs the windows as the ADT describes rather than leaving the filter undefined:

  • The windows already belong to the driver. They lie inside the fw_mmio windows it maps into the same DART for the firmware, so the per-SoC list sits next to fw_mmio. The DART driver itself has no use for them.
  • The DT stays honest. The registers are described on the DART they belong to, as a second reg. The ISP driver finds them through its iommus phandle and maps them non-posted, like the rest of the SoC's MMIO.

If you'd rather leave the filter to m1n1, or not touch it at all, patches 1 and 5 and the dapf range drop out cleanly.

Testing (J713)

  • Load: with the driver bound, /dev/video0 is present, and all sixteen ISP domains are off, also after listing formats. The power-domain devices are runtime-suspended.

  • Streams: NV12 1920x1080, then 1280x720, 30 frames each, with no errors. 720p scales the full 1080p field; it is not a crop. While streaming all sixteen domains are on, and /proc/iomem shows 48c000000.isp dapf.

  • Power-down and restart, one boot:

    • s2idle before any stream;
    • two streams;
    • a module unload and reload, then a stream;
    • s2idle after streams, then a stream;
    • s2idle during a 600-frame stream, which carries on after resume;
    • one more stream.

    Every stream delivered all of its frames. After each one all sixteen ISP domains were off with the CPU core domains in their cold-boot state, and the camera light was off. It came on only while streaming.

  • Reboot while streaming (systemctl reboot -ff, so the stream is still running when the kernel shuts the device down): the light goes off, and the next boot finds the ISP CPU core domains in their cold-boot state.

  • Base: rebased onto integration/aurora 41c4055 with the ISP driver's IOVA allocation without drm_mm (f729605) and the P010 timeout recovery (504cb5d). The commits are unchanged. On that base: four 30-frame streams (1080p, 720p, 1080p, and 1080p after a module reload) delivered every frame. All sixteen ISP domains were off after each one, with the CPU core domains in their cold-boot state. Across 222 s of s2idle, a 600-frame stream carried on and completed, and a stream after resume worked.

  • Filter: separately, the camera streams with the filter windows cleared and with them left as they power up.

  • Static checks:

    • checkpatch --strict: clean on all eleven commits, except the MAINTAINERS note for the new isp-drv-test.c;
    • dt_binding_check: clean for both bindings;
    • CHECK_DTBS + W=1: no new warnings on any of the six T8132 DTBs;
    • each commit builds the ISP driver with W=1. The only warning is the existing FORTIFY one in isp-ipc.c.
  • KUnit on arm64 QEMU: 33/33 on the new base, including the new apple-isp-dapf and apple-isp-hw cases.

Not covered

  • PMP scratch words: T8132's offsets are not established, so PMP_CTRL_SET is not sent. The camera streams without it.
  • IMX958 configuration discovery and P010 on T8132, which the deferred start skips.
  • Calibration file: the driver loads apple/isp_2224_01XX.dat, the IMX958 calibration file it already uses for T6040. The installer has no J713 entry yet, so that's a follow-up.

This change was developed and tested by Claude (Anthropic), working with me on the J713.

@MuBai-He

Copy link
Copy Markdown

Tested on a second J713: it works here too, including with the calibration file.

Setup. MacBook Air 13" M4 (J713), macOS 26.7.1 (25G241) alongside. This machine runs an Asahi-based 7.1.9 kernel rather than integration/aurora, so I built drivers/media/platform/apple/isp from 55733eb out of tree, unchanged, and applied this series' T8132/J713 nodes as a runtime overlay. The nodes differ only where the older kernel forces it: its DART driver has no apple,remap-streams, so the ISP lists streams 0–3, 6, 7, 10 and 11 of each DART itself, and the AIC is referenced by phandle.

  • Firmware segments: this unit's ADT segment-ranges match the reserved memory in this PR (TEXT 0x10000d30000 + 0xca4000, DATA 0x10001f40000 + 0x748000) on every boot I recorded, as do the DAPF window starts, sensor-type 0x17 and IRQs 369/387.
  • Streams: NV12 at 1920x1080 and 1280x720, 30 fps, with repeated open/close.
  • Calibration: with apple/isp_2224_01XX.dat installed, the blue cast goes away. The mean chroma of the same scene moved from U/V 149.6/109.9 to 127.5/129.1. On T8132 the driver loads it through the T6040 lens-shading fixup, and the firmware takes it with no errors.
  • s2idle while streaming: the stream resumes by itself. All 1500 frames arrived with contiguous sequence numbers and a 0.91 s gap across the suspend. Suspending while idle also works, as in your testing.
  • Loading at boot works, too (about 3.5 s in).

For the installer follow-up. On this machine the 2224 setfile is in /usr/sbin/appleh16camerad of the recoveryOS (arm64eBaseSystem.dmg, 25G241), 4K-aligned at 0x3ba000 with the usual header. The copy on the system volume is byte-identical. appleh13camerad doesn't have it.

  • aurora-installer's native path already reads appleh16camerad from the Recovery image, so the only missing piece looks like a cameraRegistry entry 0x2224_0106 / "2224_01XX" / 0x6ff4. The registry test also pins it to seven entries.
  • The recoveryOS appleh16camerad also holds 1921_01XX and 1922_02XX, byte-identical to appleh13camerad's, so scanning both daemons doesn't conflict.
  • The Python isp.py only scans appleh13camerad, so that path would need the second daemon as well.
  • For cross-checking, the 0x7000-byte extract (round_up(0x6ff4, 64), as isp.py cuts it) has SHA-256 dce21e48ccd35f54bad20bb2dbf043ed0842b1dadf3c5cc6cff8a2279f2aaf16.

Tested with help from Claude (Anthropic).

erikwb added 11 commits November 1, 2026 18:23
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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
Reserve the ISP firmware segments iBoot loads, from the ADT's isp
segment-ranges, and the firmware's main memory above them. Enable the
ISP with the J713's platform ID, the ADT isp node's sensor-type, and
the front IMX958's 1080p and 720p presets, configuration 5 as on the
J616s.

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 <erik@ebourget.net>
…tart

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 <erik@ebourget.net>
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 <erik@ebourget.net>
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 <erik@ebourget.net>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants