Skip to content

M6 audio: DART owned streams, ADMAC apertures, CIO reset, AOP audio and macaudio for the T8152 Mac mini - #201

Closed
joshuaswarren wants to merge 32 commits into
omacom:integration/aurorafrom
joshuaswarren:m6-audio-2
Closed

joshuaswarren wants to merge 32 commits into
omacom:integration/aurorafrom
joshuaswarren:m6-audio-2

Conversation

@joshuaswarren

@joshuaswarren joshuaswarren commented Oct 24, 2026 •

Copy link
Copy Markdown

Summary

The audio side of the Apple M6 (T8152) Mac mini after the plumbing of #194: the DART owned-streams change, the per-channel ADMAC apertures, the CIO reset driver, the AOP audio driver variant and the macaudio card for the J873, the mailbox support the AOP and ACIO need, and the device-tree nodes. Twelve commits.

This series now enables the internal speaker on the J873 (commit 30). It stays a draft until the review of commit 30 is done. The T8152 speaker link carries 24-bit samples, and the earlier limiter and integer output commits assumed 2^30. Commit 27 fixes the scale. Commits 28 and 29 add a 100 Hz high-pass and a measured ceiling. The messages of the limiter commit (43f2664) and the plain-PCM commit (68c6401) still carry the old 2^30 wording. The scale commit says so. Before this PR is merged, ask the maintainers to squash, or rewrite those messages.

Commit 30 sets enable_speakers = true and safe_vol = 0 in macaudio_j873_cfg. Before it, the speaker link was skipped unless the please_blow_up_my_speakers module parameter was set.

The first twelve commits of this branch are #191 (the M6 base) and #194 (audio plumbing). Only the last seventeen are new here: the diff of this PR shows the other twelve too until they merge, then I rebase. Review the last seventeen.

It is not needed for the release candidate, and there is no rush to review or merge it. Sound does not reach a codec or speaker from this branch yet (see Blocked).

Commits

# Commit Notes
13 iommu/apple-dart: add owned-streams and inherited-roots for T8152 a DART shared with firmware: Linux owns only the listed streams, never resets or acknowledges the others; the exception handler and the probe drain mask pending exceptions with the owned bitmap
14 dma: apple-admac: T8152 channels have per-channel 16 KiB apertures TX n at 0x40000 + n * 0x4000, RX n at 0x80000 + n * 0x4000, selected by a flag set only for T8152
15 reset: apple-cio: add apple,t8152-cio-reset request at reg + 0, done at reg + 4, one bit per cluster, 4 resets, child of the PMGR syscon
16 ASoC: apple: t8140-aop-audio: add the T8152 variant Rust; no lpai/hpai/tap, no source ring, no fabric raise; the speaker front-end and back-end accept one or two channels
17 ASoC: apple: macaudio: add apple,j873-macaudio j873 configuration, Speaker prefix for the mono amplifier; speakers stay disabled in the configuration
18 soc: apple: mailbox: add the T8152 ACIO counted-FIFO mailbox identical driver patch to the commit in #197 (same patch-id). The apple,t8152-acio-mailbox binding text is only in #197, so this layer needs #197 first for that compatible; the second copy of the driver patch becomes empty when both land
19 soc: apple: mailbox: add the T8152 ASCWrap v8 AOP mailbox compatible apple,t8152-ascwrap-v8-mailbox on the same hardware data
20 dt-bindings: sound: cirrus,cs42l84: add the DAI common properties the codec node carries sound-name-prefix
21 arm64: dts: apple: t8152-j873g: add the audio I2C codecs
22 dt-bindings: soc: apple: aop: no power domain on T8152 required only for T8140
23 arm64: dts: apple: t8152-j873g: add the AOP audio chain AOP, its mailbox, ADMAC nodes, the shared DART
24 ASoC: apple: t8140-aop-audio: limit the T8152 speaker with a thermal model in-kernel limiter in the speaker copy path, KUnit suite of four tests; see the section below
25 ASoC: apple: t8140-aop-audio: give the T8152 speaker plain PCM the T8152 amplifier takes the limited word as a plain 32-bit integer, not float32 bits (3 lines); found by a microphone run on the M6 with the previous commit
26 ASoC: apple: macaudio: give the J873 speaker link a rising RX edge the speaker amplifier stays in software shutdown with the default falling receive edge; a platform field sets the speaker link format only (10 lines, macaudio.c); inert until enable_speakers is true
27 ASoC: apple: t8140-aop-audio: scale the T8152 speaker words to 24 bits arithmetic shift right by 7 of the limited word, so full scale is 2^23 as the amplifier link wraps; code only in t8140_aop_audio.rs; measured, see the next section
28 ASoC: apple: t8140-aop-audio: high-pass the T8152 speaker at 100 Hz a filter in the speaker copy path, KUnit tests including no latch of the clamped state
29 ASoC: apple: t8140-aop-audio: set the T8152 speaker ceiling CEILING_Q30 = 83,800,000 with its measured basis
30 ASoC: apple: macaudio: enable the J873 speaker enable_speakers = true, safe_vol = 0, comments updated

Measured, on an M6 Mac mini (out-of-tree builds of the same changes, before this series was rebased)

  • The AOP boots, the audio service has speaker, jack output and jack input devices and no lpai/hpai/tap.
  • With the T8152 ADMAC apertures the jack stream (S24, 48 kHz stereo) is consumed in real time: a DMA report every 125.00 ms for 48000-byte periods.
  • The speaker stream drains at half the frame rate in stereo and at 48 kHz in mono (24000-byte periods, one report per 124.67 ms), with no hang and no DART fault. aplay -c 1 on the speaker PCM was refused before the back-end change and played after it.
  • The AOP serves audio with a node that has no power-domains.

Speaker series (commits 27 to 30), measured on the M6 (25 Oct)

MEASURED:

  • With the scale fix, a low-level tone came out 17.0 dB above the previous low-level anchor. The shift predicts 17.1 dB. So the 128x shift and the 2^23 scale hold at low level.
  • Tones at 440 Hz, 1, 2 and 4 kHz were clean. There was no sign of buzz or rub from 440 Hz to 4000 Hz.
  • At the ceiling, 440 Hz was 26.7 dB over the room. Its second, third and fourth harmonics were 33, 41 and 45 dB down.
  • With the high-pass in the path, 440 Hz was 1.2 dB higher and 200 Hz was 0.35 dB lower than without it. That is inside the 1.9 dB repeatability of this setup. The filter added no distortion at 440 Hz. The limiter state was unity at both stream starts.

NOT measured:

  • The absolute output level and the amplifier output voltage. Both come from the model.
  • The speaker cone excursion at low frequencies. Nothing below 200 Hz was played at the ceiling. The 100 Hz high-pass is insurance, reasoned and not measured. 100 Hz and 60 Hz were not played.
  • Music. Only tones and one noise burst were played.
  • A bad TDM clock line appears once per stream start. It is benign, and not understood further.

Limits of the run: there was no earlier multi-frequency reference, and a dead band is excluded only at the four tones tested.

Not measured

  • Sound reaching the jack codec, the headset or the speaker; capture; suspend and resume.
  • The CIO reset driver on hardware, and that ACIO n maps to reset cluster n (inferred).
  • A foreign DART exception: none was seen, so the handler masking is read, not exercised. A foreign stream that latches an exception keeps a level interrupt asserted; the handler returns IRQ_NONE and the line could be disabled as spurious.
  • This branch was not booted.

Blocked

  • Commit 30 enables the speaker. It needs one review before this PR leaves draft, because it sets speaker levels on hardware.

Compile and check receipt

arm64 clang, W=1, CONFIG_RUST=y, 16K pages, integration/aurora at 00e601f plus the 26 commits of this branch: iommu/apple-dart.o, dma/apple-admac.o, reset/reset-apple-cio.o, soc/apple/aop.o, soc/apple/mailbox.o, apple/t8152-j873g.dtb, sound/soc/apple/t8140_aop_audio.o and sound/soc/apple/macaudio.o all rc 0 with 0 warnings; the only warning in the Rust build is an unused method in rust/kernel/dma_fence.rs, which these commits do not touch. dtcheck (dtschema 2026.9): the board file gives 0 messages; the 72 binding examples give only the existing apple,dcp-typec-routes message.

Known edges

  • A foreign DART stream that latches an exception keeps a shared level interrupt asserted. The handler returns IRQ_NONE, so after enough unhandled counts the kernel could disable the line, and real faults of owned streams with it. No foreign exception was seen on the M6.
  • apple,inherited-roots and apple,owned-streams are meant for the T8152 DART only; the schema does not enforce it.
  • The CIO reset deassert does not clear a done bit that firmware may have left set before the request, so a stale bit would end the poll at once. Not measured, and there is no consumer yet.
  • checkpatch warns that device-tree binding docs should be a separate patch on five commits that carry a driver and its binding together.

Review

Two independent reviews, both GREEN on the first eleven commits at 4b2beadf5d49b05a6d2ef8b704d4f1c4bf12815e, and both GREEN on the twelfth (the limiter) at 43f266453804e1843362c697c2dc4cec5ee09e5b, both GREEN on the thirteenth (T8152 speaker plain PCM) at 68c6401203afb55e7a60706fbca7c55f57350422, and both GREEN on the fourteenth (J873 speaker link rising RX edge) at 444f88a8eff09c01aa345b41654005b57f48307e, the head of this PR. The first review found one defect in the DART exception handler, which is fixed in this head. The second review recomputed dtcheck (0 messages) and the admac, PSD and DMA-channel arithmetic against the device tree.

Speaker thermal limiter (commit 43f2664, t8140_aop_audio.rs only)

Correction (25 Oct): all figures in this section are in the model's units, which assume a full scale of 2^30. The amplifier's real full scale is 2^23, so they are 128 times too optimistic for the words this series sends. See the hold note at the top.

In-kernel feed-forward limiter in the T8152 speaker copy path (the speaker front-end has no mmap flags, so every sample passes through it). The T8152 AOP has no speaker sense tap, so the sense-based userspace daemon cannot run; this limiter uses these thermal model numbers (11.9 V per full scale, 4.6 ohm, coil 40 C/W with 3.4 s, magnet 60 C/W with 210 s, ambient 50 C, limit 140 C): sustained limit 0.9 W (full-scale square wave 30.78 W, so minimum gain -15.43 dB at 2 % margin), immediate attack per 128-word block, 20 s release, idle cooling between streams, fixed point (i64 only). A hard ceiling 20 dB below the sustained level is a const, removed by a rebuild only, for the first audible tests.

Receipts: host reference model plus integer model, 66 checks pass (worst-case sine, square, noise and DC step from four start temperatures stay below 140 C; closed-loop equilibrium 139.89 C); KUnit suite snd_soc_t8140_aop_audio_thermal 4/4 on an arm64 qemu (TCG) kernel at this head, 0 fail 0 skip; compile CONFIG_RUST+KUNIT 16K pages rc 0, 0 warnings; two independent GREEN reviews by hash.

NOT measured: the real amplifier output voltage per full scale, the real speaker resistance and thermal constants (the model uses these numbers as given), the loudest speaker step (distorted on the M6, unexplained) and absolute sound level (the speaker is still disabled in the configuration), the plant 25 % worse than these numbers with the amplifier above the card's gain cap reaches 229.7 C in simulation (no margin by design beyond the 3 dB of the amplifier gain cap and the 2 % power margin). enable_speakers stays false in this series. Not needed for the release candidate.

MEASURED on the M6 with the plain-PCM commit (limiter build, microphone 12 to 18 inches away): a 440 Hz tone at the quietest step is detected 38 dB above the room at 440 Hz, with harmonics below -30 dB relative to the fundamental; the next step, 10 dB up, measures 9 dB louder. Before that commit the same tone gave a steady sound whose strongest component was 880 Hz, at the same level for two steps 10 dB apart. The amplifier itself still needs its configuration and a clock-before-unmute fix in a separate driver change. With the macaudio commit (26) and no register writes from user space the amplifier leaves software shutdown: the quietest step is 39 dB above the room with harmonics below -29 dB relative to the fundamental, and the step 10 dB up is 7 dB louder. NOT measured: absolute level; the loudest step is distorted and unexplained.

The T8152 GPIO block has the same layout as the T8140 one. Match
"apple,t8152-pinctrl" with the T8140 hardware data.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
T8152 has a SART with a new register layout: the per-entry config,
physical address and size registers sit at 0x00, 0x60 and 0xc0. Add the
operations for it and match "apple,t8152-sart". The binding lists the
new compatible as a stand-alone entry.

Measured on an M6 Mac mini with an earlier form of this change: the
machine boots and runs its root file system from NVMe, which uses this
SART. This version is compile-checked only.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
On T8152 the queue setup registers (admin queue, IO queues, the linear
submission queue control) live at different offsets, and a few of them
accept writes but read back zero. Add a register translation for that
layout, skip the read-back checks that cannot work, and describe the
controller with a new hardware data block matched by
"apple,t8152-nvme-ans2".

The ANS is a virtual PMGR device on this SoC and has no reset line, so
the hardware data block carries a no_reset_control flag. Only T8152 skips
the reset lookup. Every other SoC still requires a reset control and
fails the probe without one, as before.

The binding gets the new compatible. On T8152 the resets, power-domains
and power-domain-names properties are not required, and the controller
has two register regions (the NVMMU registers live in the first one).

Measured on an M6 Mac mini with an earlier form of this change: the
machine boots and runs its root file system from NVMe. This version is
compile-checked only.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
On T8152 the ATC USB blocks (and the GPU) are behind a per-slot service
request in the power state director, on top of the normal PMGR power
states. Expose each slot, or a group of slots that must come up together,
as a generic power domain. Power-on writes the slot request and waits for
the acknowledge; power-off requests state 6 in reverse order.

Add the devicetree binding.

When a power-on request times out, the slot that timed out is put back to idle with the slots before it, because its acknowledge was not seen but it may still start.

When a group power-off fails, every slot is put back to its active state, including a slot whose off request timed out and may still finish stopping, because genpd keeps the domain marked on after a failed power-off and would skip power_on at the next resume.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The fixed host ports on T8152 need only the USB2 part of the PHY. Add a
driver that applies the bootloader-supplied calibration, handles host and
device mode, and leaves the USB3 and USB4 lanes alone.

Add the devicetree binding.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
A hub with the Apple ID 05ac:800f sits behind the front ports of the M6
Mac mini. Add that ID to the hub quirk table with
HUB_QUIRK_DISABLE_AUTOSUSPEND so the kernel keeps it powered.

The match is on the vendor and product ID alone. Every hub with this ID
loses autosuspend, not only the one on this machine. The constant is
named after the ID, USB_PRODUCT_APPLE_HUB_800F, because the ID is
Apple's and the part behind it was not identified from the descriptor.

This quirk was not measured on its own: it only keeps the hub awake.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Add a peripherals fragment to the M6 Mac mini board file: the always-on
GPIO controller with the reset hog for the front-port USB hub, and the
four PSD service slots for the USB ports.

The NVMe controller, its mailbox and its address filter are not described
here. The NVMe node needs a mailbox driver change that is not in this
tree yet.

The fragment holds only addresses, interrupt numbers and wiring. The boot
loader adds the register tunables that some blocks need. The PMGR power
domains are not described yet, so nodes that need them are left out.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Add the DART of the Apple M6 (T8152), a third-generation DART whose
topology is fixed by the hardware. Its streams are described by static
parameters instead of being probed, and it takes no MMIO access until the
first use, because the block can be power gated behind the ATC USB
domains.

The binding requires a second register region (the TLB invalidate
doorbell) for the T8152 compatible only.

The change adds:
- the apple,t8152-dart compatible and its hardware description;
- static topology and "first use" initialisation of every DART;
- the Gen3 TLB invalidate and the fault handler;
- the reset path for DARTs that the boot loader leaves locked.

A DART that the boot loader leaves locked is held powered for its
lifetime. That applies to Gen3 hardware only. The other generations keep
their behaviour. The first-use initialisation re-reads the parameters and fails if the
page size differs from the one assumed at probe. It runs under a spinlock, and
every function it calls is non-sleeping (register accesses and an atomic
poll).

Measured on an M6 Mac mini with the earlier form of this change: the
display controller DART attaches locked on every boot. This version is
rebased on current integration/aurora and was compile-checked only. The
USB DART paths need the T8152 PSD power domains and were not run on
their own.

The doorbell store and the TLBI of the Gen3 invalidate are arm64 instructions: they are built only with CONFIG_ARM64, and other architectures (COMPILE_TEST) get a stub that returns -EOPNOTSUPP. A locked Gen3 DART no longer takes the range flush through TLB_CMD, which Gen3 does not accept; it uses the full-stream Gen3 callback.

Gen3 does not take commands through TLB_CMD, so the unlock after a lock-on-error exception is not sent there: the DART logs once that this is not supported. The stale-exception drain of a reset does not read 0x704 on Gen3, like the fault handler.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Add the apple,t8152-dwc3 compatible to the Apple dwc3 glue. The ATC ports
of the M6 (T8152) controller never switch roles through the Type-C
controller. The glue starts the controller as a fixed host, with or
without a hub child, or as a fixed peripheral when the node sets
dr_mode = "peripheral".

The T8152 profile is USB2 only: it needs maximum-speed = "high-speed",
takes no reset line, and its USB3 PHY is optional. The calibration lists
come from the device tree (apple,tunable-* properties that the boot loader
fills in at runtime; the binding does not require them in the board file) and are applied to the register windows in a fixed order after
the core is reset. The pipe handler is set up for the selected role.

The apple,t8103-dwc3 paths keep their behaviour. The controller stays a
syscore device while it runs, as before: no separate suspend or resume
path is added for T8152.

The earlier form of this change ran on an M6 Mac mini USB2 front port.
This version is rebased on current integration/aurora and was
compile-checked only. SuperSpeed does not train yet and is not part of
this change.

A fixed peripheral does not require the host-only calibration lists at probe, since it never applies them. The binding lists exactly four iommus entries, two stream IDs on each of two DARTs.

If the T8152 USB2 PHY fails to initialise or power on during core start, the failure goes through the same shutdown as a failed core init, so the reset and syscore flags set earlier are cleared again.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Describe the power management blocks and the USB controllers of the Mac
mini (M6, J873) next to the peripherals fragment:
- the two PMGR blocks and their power domains, in t8152-j873-pmgr.dtsi;
- for each of the four USB ports (usb0, usb1, usb2 and usb3 of the SoC):
  two DART instances, the USB2 PHY and the dwc3 controller.

The register windows, interrupts and DART tunables are the values of the
Apple device tree of the machine. The boot loader adds the apple,tunable-*
lists of the USB2 PHY and the controller at runtime.

The bindings list the apple,t8152-pmgr and apple,t8152-pmgr-pwrstate
compatibles, and the dwc3 and USB2 PHY bindings take the tunable lists as
optional, because the boot loader adds them.

The fragment is a plain description of the hardware. The ports that the
change describes run USB2 only: SuperSpeed and USB4 are not part of it.

This commit compiles to a device tree blob with dtc without warnings. It
was not booted. The bring-up it comes from enumerated the front-port USB2
hub and a USB disk at 480 Mb/s.

The four PSD service slots get their PMGR domains (the aon and the port domain of their port) as parents, so genpd powers them before the slot request.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The T8152 audio ADMAC is the same evolved family as the T8140 one: the
vendor driver for it matches the evolved register map at every register we
compared and never writes the global 0x28/0x2c pair, so the new variant sets
evolved_layout and clears set_unk28.

On an M6 Mac mini the base-ns ADMAC binds and probes ("Audio DMA
Controller"). No data transfer is tested; the channel registers did not
answer a read-only probe made after boot.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Add apple,t8152-aop. Unlike T8140 it has no setup port: the CPU is started
through the wrapper and RTKit boots on the primary mailbox, with three AFK
endpoints (0x20 SPUApp, 0x21 wakehint, 0x22 aop-audio) announced with EPIC
sub-header version 2. The binding requires two mailboxes for T8140 and allows exactly one for T8152.

MEASURED on an M6 Mac mini, with an out-of-tree probe module: RTKit v12
boots on the primary mailbox without the setup port; all three services
start; 25 of 25 EPIC calls with the version 2 header were answered; the
speaker, codec-out and codec-in devices attach and report power state idle.
NOT measured: audio data transfer, the hpai/lpai/tap devices (they return
an error on this machine).
BLOCKED: this patch alone does not play audio, see the audio driver patch.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The T8152 SBR DART serves the audio DMA controllers together with firmware
and coprocessor streams (AOP, MTP) in one instance, and the bootloader
leaves its streams translating with page-table roots that hold firmware
mappings although its lock bit is clear. Resetting it as a whole would break
the firmware streams.

apple,inherited-roots makes the driver treat such a DART as locked: it is
never reset, TCR and TTBR are never written, and consumers publish into the
vacant slots of the inherited roots, the path already used for locked DARTs.
apple,owned-streams lists the streams that belong to Linux: consumer iommus
entries naming another stream are refused, latched exceptions of other
streams are not acknowledged, and the DART tunables are left as firmware
set them.

The interrupt handler applies the same filter as the probe-time drain:
the pending word is masked with the owned streams before the faults are
reported and before it is written back, so the exceptions of the streams
firmware owns are neither logged as faults nor acknowledged. If such a
stream raises an exception that stays latched, the shared interrupt line
stays asserted; no foreign exception was seen on the M6 (the error
registers were zero in every run).

MEASURED on an M6 Mac mini with an earlier form of this change (separate
compatibles instead of properties, on an older base): the shared DART
probes without a reset, the AOP firmware boots, the audio DMA channels
move data through it (the jack stream in real time at 48 kHz, the speaker
stream in real time as mono at 48 kHz) and the error registers stay zero.
NOT measured: this exact form on the current base, an exception raised by
a firmware-owned stream. BLOCKED: nothing for the DART itself; the SBR
DART device-tree node comes with the audio layer.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The T8152 audio DMA controller has no channel control blocks at 0x8000 and
0xc000 as the t8140 one does: a read there ends in a synchronous external
abort, and the first write to such a block hangs the machine with an
asynchronous SError. Instead every channel has its own 16 KiB aperture, the
TX channels from 0x40000 and the RX channels from 0x80000, at a 0x4000
stride. Add a per-variant flag for that layout and use it for the T8152
variant; the descriptor FIFO ports (0x10000 for TX, 0x14000 for RX) and the
global registers stay where they are.

MEASURED on an M6 Mac mini with /dev/mem reads after the first power
request to an AOP audio service: the global registers (+0x0, +0x94), the
FIFO port pages (0x10000, 0x14000) and 13 apertures each at 0x40000 and
0x80000 (stride 0x4000, 13 being the channel count of the instance) answer;
0x8000, 0xc000 and 0x74000 abort. Writes of the bus width, SRAM carveout,
FIFO control and interrupt mask registers at the start of an aperture read
back correctly.
The TX/RX assignment of the two halves comes from the vendor driver's
descriptor and aperture code (TX channel n at 0x40000 + n * 0x4000); with
the opposite assignment the channel starts but never takes a descriptor
(ring register stays empty, no transfer).
MEASURED with this layout: TX channel 5 (headphone jack) and TX channel 1
(speaker) accept their descriptors, the ring registers and residue move, a
report with a 24 MHz timestamp arrives for every completed descriptor and
the interrupt fires; the jack stream consumes data in real time at 48 kHz.
NOT measured: that the samples reach the codec (nothing was listened to).
BLOCKED: audio output also needs the AOP audio driver changes.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The M6 (T8152) CIO reconfigure is a request/done register pair in the PMGR
block: BIT(cluster) is written to the request register, BIT(cluster) is
polled in the done register (the vendor driver allows 10 s), and the done
bit is cleared by writing it back. The node is a sub-node of the PMGR syscon
whose reg is the offset of the request register; the reset id is the CIO
cluster (0..3, the cio-cluster property of the PMGR child).

The sequence follows the vendor power-management driver for this SoC.

NOT measured: no M6 boot has exercised this driver yet. Compile status: see
the pull request. BLOCKED: the controller node and the reset consumers
(ACIO) live in the Thunderbolt/USB4 device-tree layers.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
On T8152 (J873) the AOP audio service has only the speaker, jack output
and jack input devices: lpai, hpai and the speaker tap answer
kIOReturnNoDevice. There is no lpai microphone to raise the gated audio
fabric with, no source ring, and no AP-powered serializer leaves (the
audio PMGR leaves are virtual; the firmware powers them).

Match data now says whether the machine has lpai: apple,t8140-aop-audio
keeps everything as it was, apple,t8152-aop-audio skips the lpai/hpai
attach, the source ring, the raw sound card with its two capture PCMs,
the power-domain attach and the fabric raise, and treats the tap service
as absent. The speaker mute property is not requested (the firmware
answers every property above 302 with 0xe00002f0, unsupported; the
amplifier is muted through its own codec), the speaker front-end and
back-end accept one or two channels, and the properties 200/300/301/302
of a service are logged at debug level after pw0 and pwrd, so that the
stream format can be read from the log.

MEASURED on an M6 Mac mini: the AOP boots, the audio service has
speaker, jack output and jack input devices and no lpai/hpai/tap
(kIOReturnNoDevice); the audio fabric answers after the first power
request of any service; with the T8152 ADMAC apertures the jack stream
(S24, 48 kHz stereo) is consumed in real time (a DMA report every 125.00
ms for 48000-byte periods); the speaker stream drains at half the frame
rate in stereo and at 48 kHz in mono (24000-byte periods, one report per
124.67 ms), no hang, no DART fault; the speaker amplifier logs 'bad TDM
clock' once at stream start and not again while streaming. NOT measured:
sound reaching the jack codec/headset or the speaker (the speaker volume
stays locked at the lowest step and no speaker-safety profile exists),
capture paths, suspend/resume. BLOCKED: the amplifier unlock needs a
speaker-safety profile.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
AOP-fed like the J700, with the J873's SN012776 speaker amplifier; the
speakers stay disabled until the speaker path is verified on hardware,
the jack comes first. The front-end DAI names are those of the AOP audio
component.

In the single-woofer control fixup, the J873's amplifier controls are
looked up with the amp node's sound-name-prefix ("Speaker ") instead of
the empty prefix: with the empty prefix the lookup of 'Amp Gain Volume'
fails with -EINVAL, and "* " would match the cs42l84 jack codec instead.
No other model's prefix changes.

MEASURED on an M6 Mac mini: the AOP boots, the audio service has
speaker, jack output and jack input devices and no lpai/hpai/tap
(kIOReturnNoDevice); the audio fabric answers after the first power
request of any service; with the T8152 ADMAC apertures the jack stream
(S24, 48 kHz stereo) is consumed in real time (a DMA report every 125.00
ms for 48000-byte periods); the speaker stream drains at half the frame
rate in stereo and at 48 kHz in mono (24000-byte periods, one report per
124.67 ms), no hang, no DART fault; the speaker amplifier logs 'bad TDM
clock' once at stream start and not again while streaming. NOT measured:
sound reaching the jack codec/headset or the speaker (the speaker volume
stays locked at the lowest step and no speaker-safety profile exists),
capture paths, suspend/resume. BLOCKED: the amplifier unlock needs a
speaker-safety profile.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
apple,t8152-acio-mailbox has a different register interface than the
ASC mailboxes: the status word at +0x20 holds the receive occupancy in
bits 3:0 and the number of free transmit slots in bits 11:8, the receive
FIFO is at +0, the transmit FIFO at +0x10, and each 128-bit message is
moved with one paired 64-bit access. Reading or writing the classic ASC
offsets (control at +0x110, send at +0x800, receive at +0x830) from this
window would touch unrelated registers of the wrapper block.

Add a counted_fifo flag to the hardware table, test "transmit full" and
"receive empty" through two helpers that honour it, and move the message
read and write into helpers that use one load or store pair for the
counted layout (arm64 only; the entry is refused elsewhere). Every
other hardware table keeps its flags unchanged and its two separate
64-bit accesses.

Measured here: arm64 compile. Run on hardware in an older tree: this
layout and the paired access carried the RTKit boot of the ACIO
co-processor. Not measured: this patch on this base, the send-empty
interrupt path of this mailbox.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The always-on processor of the T8152 uses the same counted-FIFO post
office window as the ACIO co-processor (receive at +0, transmit at +0x10,
status at +0x20 with the receive occupancy and the free transmit slots),
as a plain 0x80-byte window without wrapper registers. Match it with the
existing hardware description under its own compatible and describe it
in the binding: two interrupts, send-empty and recv-not-empty, no IOMMU
and no DMA range of its own.

MEASURED on an M6 Mac mini: the AOP firmware boots through this mailbox
(RTKit protocol version 12 handshake, endpoints discovered, 25 of 25
EPIC calls answered, hours of audio-service use without a message
error). NOT measured: sleep. BLOCKED: needs the counted-FIFO mailbox
commit it builds on.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The codec is a DAI provider and sits on a machine driver's sound card
where its controls are named with a prefix (sound-name-prefix), as for
the speaker amplifier binding. Reference dai-common.yaml and evaluate
the remaining properties through it.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Add the two audio codecs of the Mac mini on their I2C buses: the cs42l84
jack codec at 0x4b on i2c1 and the SN012776 speaker amplifier at 0x38 on
i2c3, with the controllers' power slots, the 24 MHz reference clock and
the SIO pinctrl the codecs' reset and interrupt lines need. This is
probe wiring only: the codecs' audio path is the AOP, described by a
separate change.

MEASURED on an M6 Mac mini with this device tree: i2c1 0x4b binds
cs42l84 and i2c3 0x38 binds the SN012776 amplifier (tas2764 driver),
both through the I2C controller behind its power slot. NOT measured:
the polarity encoding of the reset GPIO flags (taken from the ADT, not
verified electrically) and sound.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The T8152 always-on processor is not behind a power domain: every PMGR
device of the AOP block and of the audio block is firmware-managed and
has no power state register of its own. Require power-domains only for
the T8140 and forbid it for the T8152.

MEASURED on an M6 Mac mini: the AOP firmware boots and serves audio
with an AOP node that has no power domain.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
Describe the audio path of the Mac mini: the SBR DART shared with
firmware (inherited roots, owned streams 0, 1, 40 and 42), the two
non-secure audio DMA controllers, the AOP mailbox, the AOP with its audio
service (speaker, jack output and jack input, no power domains since all
audio PMGR devices are firmware-managed) and the sound card with the
headphone jack and speaker links. The card driver keeps the speaker
disabled until a speaker-safety profile exists.

MEASURED on an M6 Mac mini with this device tree (the DART described by
separate compatibles and driver changes of an earlier form of the series):
the AOP boots and its audio service attaches the speaker, jack output and
jack input devices, the SBR DART is used without a reset, the audio DMA
channels run (the jack stream in real time at 48 kHz, the speaker stream
in real time as mono at 48 kHz), the jack codec and the speaker amplifier
probe on their I2C buses. NOT measured: this exact form with the DART
properties, sound at the headset or speaker, capture, suspend.
BLOCKED: needs the T8152 DART, ADMAC, mailbox, AOP, audio component and
macaudio driver changes of the same series.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 4b2bead

Scope: the eleven commits above the base layer (7d52ab5), read by diff: the shared T8152 DART (owned streams and inherited roots), the per-channel ADMAC apertures, the CIO reset driver, the T8152 AOP audio variant, the J873 macaudio card, the counted-FIFO mailbox and the ASCWrap v8 AOP mailbox compatible, the binding changes and the device-tree nodes. Findings and how they were closed: (1) the runtime Gen3 exception handler acknowledged and logged bits of streams the OS does not own; the head masks the pending word with the owned bitmap through one helper, before the report and before the write-1-to-clear, and the probe drain uses the same helper. (2) With apple,inherited-roots the DART is treated as locked, and every full-reset path (probe, remove, resume) and the attach and detach paths are guarded by that flag, so no existing translation or TTBR is cleared. (3) The counted-FIFO mailbox commit has the same patch-id as the one in the ACIO series that was reviewed separately. (4) The device-tree windows, interrupts, power-domain slot windows, DMA channel numbers and DAI indices were compared with the firmware device tree of the machine and match; the owned stream numbers and the AOP mailbox interrupt order were not independently checked. Residual points the commit text already states: a foreign stream that latches an exception keeps a level interrupt asserted and the handler returns IRQ_NONE, so the line may eventually be disabled as spurious; no such exception has been seen. The speaker front-end and back-end accept one channel on T8152 (the serializer takes one word per frame); the back-end half has a hardware observation, the front-end half is measured only through it. Not covered: any build, device-tree schema check or hardware run by this reviewer; Rust error paths were read for the T8152 probe, not traced in full. Merge needs a second non-author GREEN and CI.

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 4b2bead

Review of the 11 commits above 7d52ab5f (DART owned streams, ADMAC apertures, CIO reset, AOP audio T8152, macaudio J873, counted-FIFO mailbox, ASCWrap v8 mailbox, three binding changes, two board-file commits). Four notes below, none blocking.

Checked

  • Cumulative patch against the previous head of this branch. Diffing the two cumulative patches (7d52ab5f..ef54e0e0 against ..4b2beadf, index lines ignored) shows hunks only in drivers/iommu/apple-dart.c: a new apple_dart_owned_mask() helper, and both the Gen3 runtime handler and the probe drain now do pending &= apple_dart_owned_mask(dart, i) before logging and before the write-one-to-clear acknowledge. With no apple,owned-streams, the helper returns U32_MAX, so every other DART behaves as before.
  • DART parse. An owned stream id at or above num_streams fails the probe with -ERANGE; an empty property fails it; of_xlate refuses a stream that is not owned; apple,inherited-roots makes the DART count as locked and skips the tunables.
  • dtschema (2026.9) and dtc 1.7.2 on this head. t8152-j873g.dts preprocesses, compiles with 0 dtc warnings and validates with 0 messages, the new audio, AOP, DART and I2C codec nodes included. The examples of the 7 touched binding files give 0 messages. Of the 72 apple,*.yaml example files, the only message is the older apple,dcp-typec-routes one (typec0-dpin0).
  • Mailbox. The drivers/soc/apple/mailbox.c change of the counted-FIFO commit is identical, added and removed lines, to the mailbox.c section of Apple M6 (T8152) USB4/Thunderbolt host router, rear port ACIO3 (on top of #191) #197.
  • Board-file numbers. The ADMAC register sizes equal the aperture formula 0x40000 / 0x80000 + (ch / 2) * 0x4000: base-ns, 13 channels, the last RX aperture ends at 0xb4000, which is the reg size; leap-ns, 9 channels, 0xa4000. The dmas entries 2, 10, 11 and 3 are TX1, TX5, RX5 and RX1 under the channel 2n / 2n+1 rule. The PSD windows 0x303a10000 + (15 << 8) and + (17 << 8) are 0x303a10f00 and 0x303a11100. apple,owned-streams = <0 1 40 42> covers every iommus user (AOP 0, base-ns 42, leap-ns 40). sound-dai indexes 0 and 4 equal the T8140 J700 board. The speakers stay disabled in the card configuration.
  • AOP audio without lpai. snd_card_free is guarded by !card.is_null(), and the lid handler and microphone jack are created only inside has_lpai.
  • checkpatch (--no-tree). 0 errors in all 11 commits. Warnings: "DT binding docs and includes should be a separate patch" on five driver-plus-binding commits, and line-length warnings in the Rust file and the AOP-chain commit.
  • Commit text. No host names, home paths or internal names in the commit messages.

Notes

  1. Shared interrupt line. The runtime handler now returns IRQ_NONE for a latched exception of a stream that firmware owns. On a shared level-triggered line the kernel disables the interrupt after about 99,900 of 100,000 unhandled counts, which would also drop real faults of owned streams. The DART commit says the line stays asserted and that no foreign exception was seen. It is disclosed, not measured; keep it in the description.
  2. Binding dependency. The counted-FIFO commit adds the apple,t8152-acio-mailbox compatible to the driver, but the binding text for it is in Apple M6 (T8152) USB4/Thunderbolt host router, rear port ACIO3 (on top of #191) #197 and not in this branch (this branch documents only apple,t8152-ascwrap-v8-mailbox). State that this layer needs Apple M6 (T8152) USB4/Thunderbolt host router, rear port ACIO3 (on top of #191) #197 first, or add the hunk.
  3. CIO done bit. t8152_cio_deassert does not clear a done bit that firmware may have left set before it writes the request, so a stale bit would end the poll at once. Not measured, no consumer yet; clear it first if the vendor sequence allows.
  4. Binding wording. The descriptions of apple,inherited-roots and apple,owned-streams say "apple,t8152-dart only"; the schema does not enforce that.

Not checked

A kernel build (I rely on the author's clang W=1 CONFIG_RUST=y 16K-page results), a boot, audio on hardware, the CIO reset consumers (ACIO n to cluster n is inferred), and the single-channel speaker claim, which is taken from the commit text.

@greptile-apps

greptile-apps Bot commented Oct 24, 2026 •

Copy link
Copy Markdown

RetriggerView in GreptileConfidence Score: 5/5

[Medium impact] The changes since the previous review appear safe to merge, with no new actionable defects found.

Summary

The PR adds T8152 audio support through DART, ADMAC, mailbox, AOP audio, and the J873 sound card.

Changes since the previous review:

  • The J873 speaker plays mono audio through a limited 24-bit path.
  • The J873 USB2 ports start in their fixed host or device roles.
  • T8152 firmware and DMA blocks can share resources with Linux.
  • T8152 Mac minis can use the Apple NVMe controller.

No new actionable issues were found. The checks covered sample bounds, filter resets, mmap restrictions, and amplifier gain limits. No build or hardware tests were run during this review.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["Speaker PCM write"] --> B["Volume gain"]
  B --> C["100 Hz high-pass"]
  C --> D["Peak clamp"]
  D --> E["Ceiling and thermal gain"]
  E --> F["Shift right by 7"]
  F --> G["ADMAC / AOP"]
  G --> H["Speaker amplifier"]
Loading

Reviews (5) · Last reviewed commit: "ASoC: apple: macaudio: enable the J873 s..." · Reviewed by Greptile

…model

The T8152 AOP offers no speaker tap, so the speaker-protection daemon
that reads the amplifier's current and voltage sense cannot run on the
Mac mini. The vendor protects this speaker with a thermal model driven
by the signal alone; do the same in the speaker copy path, where no
stream can bypass it.

Every word, after the playback volume, goes through a fixed-point
version of the vendor's model: the power (x * 11.9 V)^2 / 4.6 ohm heats
the magnet (60 C/W, 210 s) towards ambient (50 C) and the coil (40 C/W,
3.4 s) towards the magnet, the two filters of the daemon's own model.
The gain falls over the last 20 C below the 140 C limit to the level at
which a full-scale square wave dissipates 98 % of the sustainable
(140 - 50) / (40 + 60) = 0.9 W, which is -15.43 dB. The attack is
immediate (one block of 128 words) and the release takes 20 s; the model
assumes the amplifier at the vendor's 11.9 V per full scale whatever its
gain setting. The speaker cools by the idle time between streams. A hard
amplitude ceiling 20 dB below the sustained level (about 9 mW worst case)
is built in for the first audible tests and is removed by a rebuild only.

MEASURED on the host: the integer model against the closed-form step
response of the two filters, a full-scale sine, square, noise and DC step
from four starting temperatures stay below 140 C, ten minutes of a
full-scale square wave settle at the sustained level with an equilibrium
temperature of 139.9 C, the release and the idle cooling follow the
model, and the kernel arithmetic (as a plain Rust test crate with
overflow checks) reproduces the host model bit for bit. NOT measured: the
real speaker (the vendor's resistance, thermal resistances and time
constants are used as they are, the amplifier output voltage per full
scale is not measured), anything audible on the machine, and the
kernel build of this change.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

New head 43f266453804e1843362c697c2dc4cec5ee09e5b: one commit added on top, ASoC: apple: t8140-aop-audio: limit the T8152 speaker with a thermal model (only sound/soc/apple/t8140_aop_audio.rs, +294/-5, with a KUnit suite of four tests). The earlier eleven commits are unchanged. Receipts: arm64 clang W=1, CONFIG_RUST=y, CONFIG_KUNIT=y, 16K pages: t8140_aop_audio.o, aop.o, macaudio.o, apple-dart.o, apple-admac.o, reset-apple-cio.o, mailbox.o and t8152-j873g.dtb rc 0, 0 warnings (the existing rust/kernel/dma_fence.rs dead-code warning only); the KUnit test symbols are in the object. NOT run: the KUnit suite (it needs a booted arm64 kernel with this tree), a boot, audio on hardware.

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 43f2664

Review of the one commit above 4b2bead (speaker thermal limiter, sound/soc/apple/t8140_aop_audio.rs only, +294/-5). The branch above that head is that commit alone; its patch-id equals the patch I reviewed earlier by hash, so what follows is that review. Algorithm: the model uses the vendor numbers recorded in the commit text (11.9 V per full scale, 4.6 ohm, coil 40 C/W with 3.4 s, magnet 60 C/W with 210 s, ambient 50 C, limit 140 C); I re-derived the constants: full-scale square power 11.9^2/4.6 = 30.78 W, sustainable (140-50)/(40+60) = 0.9 W, minimum gain 10 log10(0.98 * 0.9 / 30.78) = -15.43 dB, and the first-test ceiling 20 dB lower, amplitude 0.01693 (CEILING_M20_Q30 = 18174652 / 2^30), 0.2 V peak and about 9 mW as a square. I ran the author host test (66 checks) on the CT: all pass, including the ceiling check, the plant-equal and 25-percent-worse cases at the amplifier card cap, and the printed case that has no margin by design (a 25-percent-worse plant with the amplifier above the card cap). Integration: the limiter runs in the speaker copy op, the speaker front-end is created without MMAP flags (INTERLEAVED only), so every sample passes through the copy op and no stream can bypass it; the idle cooling runs in prepare; the state sits behind a spin lock; the arithmetic is fixed point and the commit says the kernel integer code reproduces the host model bit for bit as a plain Rust test crate with overflow checks. The 4 KUnit tests were not run (as the PR comment states). Not covered: the real speaker (resistance, thermal constants and the amplifier output per full scale are not measured, as the commit text says), a kernel boot, any audible test (the first audible test is a separate, human-present step with the build ceiling in place), and the Rust error paths beyond what the compile receipt shows. Merge needs a second non-author GREEN and CI.

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 43f2664

Review of the one commit above the previously reviewed head 4b2beadf ("limit the T8152 speaker with a thermal model", sound/soc/apple/t8140_aop_audio.rs only, +294 -5). The earlier 11 commits are unchanged; the compare against 4b2beadf shows this one commit. Four notes, none blocking.

Checked

  • The integer model, recomputed. I wrote a Python model of spk_thermal that follows the Rust line by line (i64 semantics, arithmetic shifts, an assertion on every intermediate against the i64 range) and ran the four KUnit scenarios: ten seconds of a full-scale square wave then two seconds of silence, six times ten seconds, the 60 s idle cooling from 139 C, and the first block at the ceiling. Every expected constant in the tests is reproduced exactly (152358436004836, 102381555896261, 152358436004836, 102381555896261, 206396213; the post-silence state 130024328395229, 101932305864922, 135521886534109, 102381555896261, 226457860; the cooled 99824966751432 and 99097783336276; the ceiling word 18174652), and no intermediate leaves the i64 range: the largest is 2^61, with full 32-bit input words at both the production ceiling and the 0 dB ceiling. The six-times-ten-seconds square wave ends at 139.10 C, below 140 C.
  • The constants. KCQ and KMQ equal 40 and 60 C/W times (11.9 V)^2 / 4.6 ohm in the model's units (315236 and 472854); AC and AM equal 1 - exp(-1 / (48000 * 3.4 s)) and 1 - exp(-1 / (48000 * 210 s)) in Q32 (26317 and 426); AC10 and AM10 are the same for 10 ms; REL is one 128-word block over 20 s; the 65 gain-table entries are 10^(-15.43 dB * k / 64 / 20) in Q30 (last entry 181746525); the temperature constants are 50, 120, 20 and 5 C times 2^40. The ceiling 18174652 is -20 dB below the sustained level (about 9 mW).
  • Placement. The speaker front-end is opened without mmap (hw.info = SNDRV_PCM_INFO_INTERLEAVED for FE_SPKR), so every word reaches the DMA buffer through asoc_pcm_copy; the copy chunk is 512 bytes, so the 128-word array and words[..count] cannot overrun; the limiter state is behind a spinlock taken for at most 128 words; the T8140 path (has_lpai) is unchanged.
  • Build and text. The commit text and the code comments carry no host names, home paths or internal names. The author's W=1 CONFIG_RUST=y CONFIG_KUNIT=y result is taken as stated; I did not build.

Notes

  1. With the -20 dB ceiling the thermal part never acts. In my model a full-scale square wave at the production ceiling heats the coil to 74 C after two minutes and the gain stays at 1.0. The first audible tests therefore exercise only the ceiling; the thermal model is checked by the KUnit and host numbers until the ceiling is removed.
  2. Any prepare resets the hysteresis. cool_idle runs on the prepare of every stream when there is no lpai, jack streams included, and cool() sets the hysteresis temperatures to the current ones. A jack stream opened while the speaker plays gives the speaker a small release step. Negligible at this ceiling.
  3. Idle cooling is a loop. It runs up to 120,000 steps (20 minutes) in prepare with the spinlock held; that is well under a millisecond, but a closed form would remove the loop.
  4. The model assumes one word per 1/48000 s. That is what the front-end enforces (48 kHz only, one word per frame as measured); a change of that rule changes the time constants.

Not checked

A kernel build, a KUnit run (the numbers above are reproduced by my model instead), the speaker, and anything audible.

@joshuaswarren

Copy link
Copy Markdown
Author

Status at head 43f266453804e1843362c697c2dc4cec5ee09e5b: two independent reviews GREEN at this exact head, Greptile pass with no comments on the earlier head, arm64 clang W=1 CONFIG_RUST=y CONFIG_KUNIT=y compile of the touched objects rc 0 with 0 warnings, device-tree schema check 0 messages. Known edges and the other reviewer notes are in the PR text and the review comments. Not done: a boot on hardware, running the KUnit suite, CI. Needs a maintainer's CI approval and merge. This PR needs #197 first for the apple,t8152-acio-mailbox binding text. Not needed for the release candidate.

@joshuaswarren

Copy link
Copy Markdown
Author

KUnit run at head 43f266453804e1843362c697c2dc4cec5ee09e5b: arm64 Image (CONFIG_RUST=y, CONFIG_KUNIT=y, 16K pages, audio driver built in) booted under qemu-system-aarch64 (virt, TCG, no root filesystem). All T8140 audio suites pass: names 2/2, parser 5/5, thermal 4/4, speaker 5/5, lid 7/7; also apple_aop_ring 17/17. 0 fail, 0 skip. Image sha256 354ffb7cb0437b6b416cae11f955c34f7ba234b2afe73ef9e3324cc9ad0ab778. This is emulation: it checks the test logic and constants, not behaviour on the M6 hardware.

The speaker copy path converts every sample to a float32 bit pattern,
the word format the T8140 speaker DSP consumes. The T8152 has no LPAI:
the AOP link hands the words to the amplifier unchanged, so the
amplifier played float bit patterns as 32-bit integers.

On the T8152, write the limited sample as a signed 32-bit integer
instead. The T8140 path is unchanged.

MEASURED: with the float words, a 440 Hz tone gave a steady 2.6 s sound
whose strongest component was 880 Hz, at the same level for two volume
steps 10 dB apart (room microphone, 12 to 18 inches).
NOT measured: the sound with this change, and the amplifier output
voltage per full scale.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

New head 68c6401203afb55e7a60706fbca7c55f57350422: one commit on top of the limiter, ASoC: apple: t8140-aop-audio: give the T8152 speaker plain PCM.

A microphone run on the M6 found that the limiter's output was still written as float32 bit patterns, which the T8152 amplifier plays as 32-bit integers. The commit writes the limited word as a plain integer on the T8152. The T8140 path is unchanged. It is 3 lines in one file.

Two reviews are GREEN at this head. The whole stack compiles with W=1 and Rust on, and the audio objects have 0 warnings. I did not rerun the KUnit suite, since no tested function changed. I have NOT measured the sound with this change; that needs the next run on the Mac mini.

The default serial link format of this card is I2S with an inverted bit
clock, which makes the tas2764 driver program its receiver to sample on
the falling edge. The SN012776 amplifier on the J873 does not take the
AOP's speaker stream that way: it reports a bad TDM clock and stays in
software shutdown.

Add a platform configuration field for the DAI format of the speaker
amplifier's link only and set it for the J873 to the default format
with the clock polarity changed (rising RX edge, frame polarity as
before). The headphone link and every other machine keep the default
format.

MEASURED: with the falling edge the amplifier status stays at software
shutdown for a 440 Hz tone, and the room microphone (12 to 18 inches)
hears nothing. Writing only the receiver edge bit of the amplifier's
first TDM register to the rising value, and nothing else, puts it in
the active state and the tone is heard 49 dB above the room at 440 Hz;
the other four registers tried alone do not. With this change and no
register writes from user space, the quietest volume step is 39 dB above
the room with harmonics below -29 dB relative to the fundamental, and
the step 10 dB up is 7 dB louder with harmonics below -21 dB.
NOT measured: the absolute sound level, the amplifier output voltage,
the loudest volume step (distorted, unexplained), the "bad TDM clock"
report that still appears once when a stream starts.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

New head 444f88a8eff09c01aa345b41654005b57f48307e: one more commit, ASoC: apple: macaudio: give the J873 speaker link a rising RX edge.

The J873 speaker amplifier stays in software shutdown with the card's default serial format. The commit sets a rising receive edge on the speaker link only. It adds 10 lines to macaudio.c, and the headphone link and every other machine keep the default.

Two reviews are GREEN at this head. The whole stack compiles with W=1 and Rust on, and the audio objects have 0 warnings. On the M6, with no register writes from user space, the quietest step is 39 dB above the room and the next step up is 7 dB louder. I have NOT measured the absolute level, and the loudest step is distorted and unexplained. The change does nothing until enable_speakers is true, and it stays false in this series.

@joshuaswarren

Copy link
Copy Markdown
Author

This series does not turn the internal speaker on. The speaker stays off in the J873 config, and the link is skipped unless the module parameter please_blow_up_my_speakers is set. The speaker commits only matter once it is turned on. That waits for open items: a gain cut at high levels that is unexplained, and the absolute level, which is not measured.

@joshuaswarren
joshuaswarren marked this pull request as draft October 25, 2026 11:35
@joshuaswarren

Copy link
Copy Markdown
Author

Hold: the T8152 speaker output scale is wrong. The link carries 24-bit samples, so words must be shifted to a full scale of 2^23. The thermal limiter and the integer output in this series assume 2^30, so the words are 128 times too large. Do not merge until the fix lands and is re-measured. The speaker stays disabled in the configuration.

@joshuaswarren

Copy link
Copy Markdown
Author

Review at 444f88a: my GREENs on the limiter commits (24, 25) do not stand as a safety statement.

A hardware peak test on the T8152 speaker path found that the amplifier path wraps at a word value of 2^23, not at 2^30. The limiter model in these commits assumes the I2S full scale is the word scale; every word written in the earlier tone tests was therefore about 128 times (42 dB) larger than the model believed. My GREENs checked the code against its stated model (the arithmetic, the tests, the bounds), not that scale, which was never measured, and the PR text already says the speaker stays disabled. Until a corrected scale lands (the author is preparing it), nothing in this PR should be read as "protects the speaker" or "safe level", the first audible test must not be run with the current module, and the speaker stays off for users. I will review the corrected scale by its exact head.

The amplifier on the T8152 takes the samples in the low 24 bits of
each 32-bit word of the AOP link: a word beyond 2^23 wraps. The
thermal limiter works in units of a 2^30 full scale and the copy path
wrote its words unscaled, so the amplifier saw every word 128 times
(42 dB) larger than the limiter's model, and a word at the limiter's
ceiling was already past the amplifier's full scale.

Write the limited word shifted down by seven bits, so that the
limiter's full scale is the amplifier's.

MEASURED: with the unscaled words, a 440 Hz tone followed the word
peak for peaks of 3.0e6, 5.0e6, 7.0e6 and 8.0e6 (30.9, 35.3, 38.3 and
39.7 dB above the room, third harmonic below -38 dB relative to the
tone), and at 8.8e6, 9.6e6 and 1.1e7 the tone fell by 10 dB and more
with the third harmonic at -4, +6 and -6 dB, the signature of a wrap
at 2^23 = 8388608 (room microphone, 12 to 18 inches).
NOT measured: the sound with this change, the absolute level, the
amplifier output voltage.

This does not enable the speaker: it stays disabled in the J873
configuration.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 394a040

Commit 27 (394a040, the only commit over 444f88a): one file, one hunk. After the limiter runs on the block, the copy loop writes the limited i32 word shifted right by 7 (arithmetic, sign kept), so the limiter's 2^30 full scale becomes 2^23 on the wire and the amplifier's 24-bit wrap cannot be reached by a word the limiter lets through. The limiter still runs before the shift. The code comment and the commit message say what was measured (wrap at 2^23 with the unscaled words, the three peaks where the tone fell) and what was not (the sound with this change, the absolute level, the amplifier output voltage), and that the speaker stays disabled. This does not remove the hold in the PR body: nothing here is a statement that the speaker is safe; the shifted module still has to be measured at the wire.

Not built by me (the author reports 27 of 27 check-commits and a clean clang W=1 build of the two files); not run.

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at 394a040, as a code review of commit 27 only. This is not a statement that the speaker is safe to run.

Checked:

  • The delta from 444f88a is one commit, one file, one hunk (t8140_aop_audio.rs, +6 -3). Its added and removed lines equal the reviewed patch exactly.
  • The change: after the limiter has processed the block, the copy path writes *w >> 7 of each limited word. The words are i32, so the shift is arithmetic and the sign is kept, and the limiter's 2^30 full scale becomes 2^23 on the wire, where the amplifier's 24-bit field wraps. The limiter is still applied before the shift.
  • I also compared the built modules (before and after): exactly one function differs, asoc_pcm_copy, by one added arithmetic shift of seven.
  • The commit message states what was measured (the wrap at 2^23 with the unscaled words, from room-microphone levels) and what was not (the sound with this change, the absolute level, the amplifier output voltage), and that the speaker stays disabled in the J873 configuration. It has no safety wording.

What this GREEN does not cover: the scale is inferred from the observed wrap, not measured at the speaker or at the wire. The earlier commits 24 and 25 still carry the 2^30 wording in their messages (the PR body says so). The hold stays until the shifted module is measured, and nothing in this series should be read as protecting the speaker.

Not checked: a build of this head, and any run on hardware.

The speaker limiter models the power of the speaker, not its cone
excursion, which for the same voltage is largest at low frequencies,
where the speaker radiates least.

Filter the words of the T8152 speaker path with a fixed second-order
Butterworth high-pass at 100 Hz (integer, direct form I, Q28
coefficients, 64-bit state), before the limiter, and hold its output to
the input's full scale (a step through a high-pass overshoots by up to
twice its size), so that the ceiling stays a peak ceiling.

The unit tests of the module run the filter on integer sines: the gain
at 50, 100, 200 and 440 Hz is 0.2425, 0.7071, 0.9701 and 0.9987 (the
ideal Butterworth values, within 0.01), and a constant input is blocked.

MEASURED (room microphone, 12 to 18 inches): with the filter in the path
and the speaker ceiling of the next commit, a 440 Hz tone is within 1.2
dB of its level without the filter (the repeatability of the setup is
1.9 dB) with its second to fourth harmonics at -33 dB or lower relative
to the tone, a 200 Hz tone is within 0.4 dB, and the limiter's gain is
unchanged.
NOT measured: the attenuation of the filter on the machine (100 and 60
Hz were not played, the speaker's cone excursion is not measured), the
absolute level.

This does not enable the speaker: it stays disabled in the J873
configuration.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The first audible tests ran with the ceiling 20 dB below the speaker's
sustained level (4 mW at the amplifier, by the limiter's model). Raise
it to 0.078 of full scale: 0.93 V peak and about 94 mW for a sine at an
amplifier gain of 15, by the model. A full-scale square wave at this
ceiling is 0.19 W against the model's sustained 0.9 W, so the thermal
gain never moves and the ceiling is a pure peak limit; a unit test
checks that.

The level is set from a measurement, not from the model: 6 dB below the
level at which the vendor's own driver plays a 440 Hz tone at its
maximum volume, settled over 20 s, measured with one microphone at one
place.

MEASURED (room microphone, 12 to 18 inches, a 440 Hz tone): the vendor's
driver at maximum volume is 5.4 dB louder in power than this ceiling
(its tone file was 1 dB lower in level than ours; two measurements of
the vendor's maximum, taken hours apart, differ by 1.9 dB). 20 s of the
tone at this ceiling are steady to 0.1 dB after the first 2 s. Tones of
440, 1000, 2000 and 4000 Hz at the ceiling have their second to fourth
harmonics at -34 dB or lower relative to the tone; a 200 Hz tone is
detected but its harmonics are below the room noise.
NOT measured: the absolute level, the amplifier output voltage, the
speaker's cone excursion, content other than tones and one noise burst
(the noise burst is 11.9 dB quieter than the vendor's driver at maximum
volume).

This does not enable the speaker: it stays disabled in the J873
configuration.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
The speaker of the J873 was left disabled until its path was verified
on hardware. It now plays: the amplifier takes the serial data on the
rising bit-clock edge, the words are scaled to its 24 bits, and the
speaker path has a ceiling set against the vendor's own maximum volume,
a fixed 100 Hz high-pass and a thermal model of the speaker.

The speaker is protected by that limiter, not by a user-space daemon
(the machine offers no sense stream for one): set safe_vol to 0, so the
volume lock for such a daemon is not created and the amplifier's volume
is not held at its lowest step.

MEASURED (room microphone, 12 to 18 inches): tones of 200 Hz and of
440 to 4000 Hz play at the ceiling with the second to fourth harmonics
of the 440 to 4000 Hz tones at -33 dB or lower relative to the tone; a
20 s tone at the ceiling is steady to 0.1 dB; the ceiling is 5.4 dB
below the vendor's own driver at its maximum volume for a 440 Hz tone
and 11.9 dB below it for a noise burst.
NOT measured: the absolute level, the amplifier output voltage, the
speaker's cone excursion at low frequencies (nothing below 200 Hz was
played at the ceiling), music. The amplifier reports a bad TDM clock
once at the start of each stream and plays on; that is not understood
further.

Not needed for the release candidate.

Signed-off-by: Joshua Warren <816217+joshuaswarren@users.noreply.github.com>
@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at e9d9aa0

The three commits over 394a040 (8108707 high-pass, df7f942 ceiling, e9d9aa0 enable) change exactly the lines of the reviewed patches: I compared the changed lines of each commit with its patch and they are identical (same checksum for all three). What I reviewed in those patches: the Q28 Butterworth coefficients recomputed independently and matching, no 64-bit overflow in the worst case, the clamped-output and unclamped-feedback fix (the latch I reproduced with the old code is gone), the 83.8M ceiling as a scale of full scale, and for the enable commit: safe_vol 0 only drops the volume-lock controls, the amplifier gain stays capped at 15 by macaudio_set_speaker(), and the speaker front end has no mmap so the limiter cannot be bypassed. Not checked: a build or a run.

@joshuaswarren

Copy link
Copy Markdown
Author

GREEN at e9d9aa0, as a code review of the series. This is not a statement that the speaker is safe to run.

Checked:

  • Commits up to 394a040 are the head I reviewed before (GREEN at 394a040).
  • The three new commits (high-pass at 100 Hz, speaker ceiling, enable the J873 speaker) have code bodies identical, line for line, to the three patch files I read earlier (sha256 prefixes 8a4b3cda, fa207604, 89b11b74): 143, 24 and 23 changed lines, 0 differences.
  • The last commit sets enable_speakers = true and safe_vol = 0, plus comments in the dtsi and in macaudio.c. It adds no other path.

Not checked: any run on the device. The absolute output level and the cone excursion are unmeasured, so the first run must use a low level with a person at the machine.

@joshuaswarren
joshuaswarren marked this pull request as ready for review October 25, 2026 20:10
@joshuaswarren

Copy link
Copy Markdown
Author

Note on the red checks of this PR (ci / package, ci / host tests (tools) and the ci / result that sums them). The audio commits are not the cause.

  • ci / package fails in modpost: __tracepoint_dwc3_readl, __traceiter_dwc3_readl and the two dwc3_writel symbols are undefined for drivers/usb/dwc3/dwc3-apple.ko. The T8152 USB2 controller commit (48cbf4a3, in the lower layers of this branch) makes the Apple glue driver call dwc3_readl and dwc3_writel from io.h. Those inline functions fire the dwc3 core tracepoints, and the core module does not export them.
  • ci / host tests (tools) fails on apple-dcp/run.sh, case 31 dwc3-teardown. That case reads the teardown functions of dwc3-apple.c, so it is the same driver. It passes on the base branch.

The commits of this PR that I added (27 to 30) change t8140_aop_audio.rs, macaudio.c and the J873 audio device tree include. The checks for compile, device trees, KUnit and commit format pass.

I think the module fix is one line each for the two tracepoints in drivers/usb/dwc3/trace.c (EXPORT_TRACEPOINT_SYMBOL_GPL). I have not built it. If you want, I will open it as a separate PR against the lower layer, with the build result.

The io.h dwc3_readl()/dwc3_writel() helpers reference the dwc3 tracepoints,
which the dwc3 core module never exports, so dwc3-apple.ko failed MODPOST in
the omarchy-pkgs package build:

  ERROR: modpost: "__tracepoint_dwc3_readl" [drivers/usb/dwc3/dwc3-apple.ko] undefined!

Read and write the 0xc680 window with plain readl()/writel() on the core
mapping, like the raw pipe-handler access in the same function.
dwc3_apple_core_start() and dwc3_usb_role_switch_set() now reference the
T8152_PIPE_* defines and the t8152_usb2, fixed_peripheral and tunable_regs
members, so the dwc3-teardown host unit stopped compiling (TAP case 31,
'not ok 31 - dwc3-teardown' in CI). Mirror the defines and extend the
fixture struct; the new fields stay false, leaving fixture behavior
unchanged. The dwc3-teardown unit compiles and passes with
cc -fsanitize=address,undefined on the host.
@joshuaswarren

Copy link
Copy Markdown
Author

Note

🤖 GPT-6.1 Terra responding on behalf of joshuaswarren

Replaced by #359, which adds the missing Signed-off-by trailers without rewriting this branch.

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.

1 participant