Repository navigation
soc: apple: aop, ASoC: J616s (MacBook Pro M4 Pro) audio: microphones, speakers and headphone jack - #406
Conversation
The T6040 (M4 Pro) AOP starts like the T6020 one, through RTKit over a single mailbox with patched boot arguments, not through the T8140 setup port. Its firmware's external code and log segments must be mapped into the AOP's DART again before the start, from /reserved-memory nodes the bootloader adds. Describe the node with its four register windows, as Apple's device tree lists them (the driver uses the SRAM and the ASC wrapper), and its audio, ambient light and lid angle service children. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
Describe the T6041 AOP's audio child, which the AOP binding leaves a bare object: the compatible, the ADMAC channels by name (the microphones' "dma", the speakers' "spkr" and "sense", and the optional headset jack's "cout" and "cin"), the chassis and machine names of the microphone card, and "#sound-dai-cells", whose values 3 and 4 select the AOP audio driver's speaker and headset jack back-ends. The schema is modelled on the T8140 one, and the AOP binding's audio child references it. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The T6040 (M4 Pro) AOP firmware takes the same boot arguments as on the T6020 (EC0p 0x100_0000_0000, nCal 0, alig 64, AOPt 0) and starts through RTKit over one mailbox, but two things differ: - Linux resets the AOP's DART, and no mapping the bootloader made survives that. The firmware fetches its external code segment (__ETEXT) at the EC0p IOVA and writes its log segment (__OS_LOG) at its physical address; without the latter it stops within a second on a storm of DART faults. The bootloader reserves both segments as /reserved-memory/aop-etext@<phys> and aop-oslog@<phys>. Map them into the AOP's DMA domain before the coprocessor starts: each node must be unique, page aligned and at most 1 MiB, each mapping is read back, and nothing is started if any check fails. The mappings are removed on unbind once the coprocessor is quiesced, and kept, like the other buffers, when the shutdown cannot be confirmed. - The firmware is shut down on unbind. When its start fails after the CPU was released, the driver tears down as usual and then clears the CPU run bit again, leaving the AOP halted as the bootloader left it. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
patch_bootargs() takes the offset and size of the firmware's boot argument table from the AOP's own SRAM and walks its records with unchecked slice indexing. A table or record length that does not fit, or a patched record longer than eight bytes, panics the kernel on an out-of-bounds slice. Check the table against the mapped region before reading it, and each record header and length against the table while walking it, and fail the probe with -EINVAL on a malformed table. A key missing from the table is still skipped, so firmware without one of them behaves as before. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The ambient light sensor's calibration blob (apple/aop-als-cal.bin) is extracted from macOS and is not installed on every system. Without it the driver fails to probe, although the sensor reports without it, only uncalibrated (seen on the M4 Pro as on earlier machines). Load the blob when present; otherwise warn that the readings are uncalibrated and carry on. The CT817 path, which gets its calibration over the setup port, is unchanged. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
Several paths of the AOP microphone PCM leak or race: - open requested the DMA channel and released it by hand on one error path only, and it powered the microphones on before the dmaengine PCM was set up, so a failed snd_dmaengine_pcm_open() left them on and leaked the channel. Release the channel through a scope guard until the PCM helper owns it, open the dmaengine PCM first, and close it again if the power-on request fails (ALSA does not call close after a failed open). - close asked the firmware to power down before the DMA channel was released; a failing or timed-out firmware call then returned early with the channel still running and its callbacks pending, and ALSA ignores close's error. Release the channel first, then power down. - there was no sync_stop, so DMA completion callbacks could still run after a stop; use snd_dmaengine_pcm_sync_stop(). - copy_str() overran the card's fixed-size name fields when a name did not fit; it now truncates and always NUL-terminates. - AudioSetDeviceProp<T> was Zeroable for every T; require T: Zeroable. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
On the T6040 (M4 Pro) the AOP firmware follows macOS's IOPAudioLPMicDevice configuration flow, which differs from the T6030 one this driver uses: - LPAI property 301 is the read-only stream description; writing the channel configuration there is refused with kIOReturnNotWritable. - The channel masks are set through property 300 instead, as a 12-byte record of the supported, enabled and history channel masks from the ADT node /product/audio (all 7 on the J616s). Add the apple,t6041-aop-audio compatible with a T6040 flow: after the usual device attaches, set the channel control (property 300), the PDM configuration and the high-frequency decimator configuration. As on macOS, a refused step is reported but not fatal, so the following steps still run and the card is still registered. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The tas2764 driver already takes an optional SDZ supply, which the binding does not describe. Apple machines drive the SDZ pins of all their amplifiers from one line: the J616s (MacBook Pro 16" M4 Pro) has six SN012776 on a single shutdown GPIO, modelled as a fixed regulator that every amp takes as its SDZ-supply. Document SDZ-supply, and apple,sdz-group-fail-closed: the amplifiers that share an SDZ supply and carry the property form a group, and an over-temperature or over-current fault on any of them turns the shared supply off for the whole group. A member must have the supply and must not have a shutdown GPIO of its own, which would bypass it. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The codec suspend pulled the SDZ line low and the resume raised it again. On boards where all amps share one SDZ supply (J616s: six SN012776 on one fixed regulator) the last suspend puts every amp into hardware shutdown, and the first resume puts every amp through a power-on reset. A few milliseconds after the register cache sync, soc_resume_deferred() unmutes the amps of a stream that was running at suspend, with the audio already flowing. After such a resume the woofer amps' I/V sense reports a negative average power (-0.012 to -0.021 W) for several seconds; speakersafetyd treats that as broken sense data and panics, the speaker volume lock times out, and the speakers cut out until the daemon restarts (2-3 times per resume). It never happens on cold boot or on any ordinary stream start: there the amps leave software shutdown, not a power-on reset that is milliseconds old. The power cycle also loses state the cache cannot restore: the Apple quirk writes to page 0xfd sit in the volatile range, so regcache_sync() never replays them, and after the first sleep the amps run without them until reboot. Leave SDZ (supply and per-amp GPIO) as it is across system sleep. The amp waits in software shutdown, as between streams, and keeps its registers. The cache-only window and the sync stay, so the bus is not touched while suspended and a supply the platform cuts anyway is still restored. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
Amps that share one SDZ supply and carry "apple,sdz-group-fail-closed" form a group (J616s: six SN012776 on one shutdown line). An over-temperature or over-current interrupt on any of them forces the shared supply off, which puts every amp of the group in hardware shutdown, and marks the group failed. From then on no amp of the group enables the supply, resumes, changes its power mode or starts a stream until the driver is reloaded. The supply's enable count cannot express this on its own: one amp's disable leaves a shared line up while the others hold it. The safeguards around it: - the group check and the supply enable are one step under the group lock, and so are marking the group failed and forcing the supply off, so an enable cannot slip in between; - a group member without a fault interrupt, or whose fault IRQ cannot be requested, fails its probe: it could never take the group down; - a group with a shutdown GPIO on an amp is refused, since that line would bypass the supply. An unreadable interrupt latch is not taken for a fault since commit f0e5882 ("ASoC: tas2764: do not report an unreadable interrupt latch as faults"), so an I2C error cannot shut the group down. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The J616s (MacBook Pro 16" M4 Pro) speaker card runs on the AOP audio component instead of the MCA. Add its compatible. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
On the J616s (MacBook Pro 16" M4 Pro) the AOP firmware owns the MCA and the audio power states for the internal speakers: its "spkr" service runs MCA cluster 0 (serializer ms00, 12 MHz crystal), a 250-bit frame at 48 kHz with six 32-bit playback slots and twelve 16-bit V/I sense slots. The base-ns ADMAC moves both directions (channel 0, TX0, the playback; channel 1, RX0, the sense) through AOP DART stream 8. Add an ASoC component for it next to the microphone card. The service has its own attachment, users and power states, each request confirmed by reading property 200 back: back-end startup attaches the service and raises pw1, which powers the audio fabric and the ADMAC in it; prepare requests pwrd (output flag 2), which runs the serializer clocks before the amps unmute; hw_free drops back to pw1 after they muted; the last shutdown goes idle. Every front-end holds its own power reference from before it requests its channel until after it releases it: DPCM starts the back-end before the front-end opens and shuts it down before the front-end closes, so without it the channel release would touch an unpowered ADMAC. The component has DPCM front-ends j616-pcm-0..2 (the names macaudio's J616 variant binds to; j616-pcm-1 plays six S24_LE channels, j616-pcm-2 captures the twelve S16_LE sense channels) and the spkr back-end (DAI index 3). Neither slot stream carries a frame marker: a channel lines up with its slots only when its DMA meets a serializer that starts afresh (Asahi's MCA driver resets the serializer on every trigger for this reason). Otherwise the sense pairs one amp's V with another amp's I, and speakersafetyd reads negative power and panics. So every start is aligned: - The driver owns the sense channel. A cyclic ring is armed on the stopped serializer before every pwrd, so ring offset 0 is slot 0. The sense front-end does not start a channel: the ring callback copies whole frames into its buffer while it runs, so a sense start during playback attaches at the next frame, and signals it once per period. - Every playback prepare starts a fresh serializer: a running one goes down to pw1 (the amps are muted first; ASoC unmutes them after the prepare), the sense ring is re-armed, then pwrd. The playback channel starts in the trigger afterwards. - The front-ends do not offer PAUSE or RESUME, which would restart the DMA where it stopped instead of on a frame boundary: after system sleep userspace prepares the stream again. - System sleep: the component suspend takes a running serializer down and stops the ring; resume re-arms the ring and raises pwrd again before ASoC unmutes the amps of the suspended stream. The component is registered when the audio node lists a "spkr" DMA channel; the microphone card is unchanged. The power sequence follows the aurora contributors' J616s control test (spkr attached, pw1, pwrd with output flag 2, pw1, idle). The component structure (the component context with the descriptor as its first field, the DAI tables and the dmaengine front-end helpers) is derived from t8140_aop_audio.rs by the aurora contributors. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
Locking the speaker volumes clips each "* Speaker Volume" to the safe value. snd_soc_limit_volume() stops at the first control it cannot clip, for instance on an I2C error, and leaves the rest at their old limit and value. The card still reported the volumes as locked. Limit the controls one at a time, so that one failure does not skip the others. If any of them fails while locking, mark the lock failed: stop every running playback stream of the card and refuse playback prepares until the driver is reloaded, since a volume above the safe value may be left behind. The stream stop shares one helper with the existing software-gain stop, which only stops the software-gain links prepared before the current lock epoch. The software-gain limit counts as failed when any control failed or none matched, as before. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
Going to sleep with music playing crackles briefly. The card's own suspend is in the right order: snd_soc_suspend() mutes the amps (tas2764 mute: ramp, then software shutdown) before it suspends the PCMs and the components. The noise comes earlier: the suspend core freezes userspace first, and the card suspends more than half a second later. The frozen sound server stops feeding a running playback stream, which plays out up to a period of stale buffer and stops on an xrun, into amps that are still unmuted at full volume: the frozen speakersafetyd's lease runs out only later. Take the speaker volume lock (the ceiling every lease timeout applies) from PM_SUSPEND_PREPARE, before the freeze, until PM_POST_SUSPEND. End the speakersafetyd lease at suspend-prepare as well, and don't report this lock as a lost lease. Otherwise the daemon's first ping after the resume gets -ETIMEDOUT, the notice for a lease it lost, and it restarts cold, forgetting its speaker temperatures. The lease clock (CLOCK_MONOTONIC) also stops across s2idle with the tick frozen, so a lease still running at the resume would unlock the volumes and lock them again when it expired, before the thawed daemon pinged. A ping between suspend-prepare and the freeze doesn't start a new lease. After the resume the volumes stay locked until the daemon's first ping, which unlocks them. Its thermal model measures time with CLOCK_MONOTONIC too, so across a sleep it assumes the speakers did not cool: continuing with it is the conservative choice. The PM notifier walks the card's controls, which exist only while the card is bound: register it at the end of late_probe and unregister it from the card's remove callback. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
J616s (MacBook Pro 16" M4 Pro, AID35): the AOP firmware runs MCA cluster 0, so the front-ends are the AOP audio driver's j616-pcm-0..2 and the back-end is its spkr DAI. One serializer carries all six SN012776 amps in a 250-bit frame at 12 MHz, so the secondary (speaker) front-end's bclk ratio is 250, and the amps take I2S with an inverted frame clock and a normal bit clock, as on the J713 (T8132: the same amp and frame). The protection settings are the J316's: amp gain cap 15 (18.5 dBV, the level macOS's speaker model assumes), the volume lock at safe_vol -20, and V/I sense. Unlocked, the speakers go up to 0 dB like on every other macaudio machine: speakersafetyd models the coil and magnet temperatures and lowers the volume itself. The AOP speaker and jack components sit on one OF node, and the front-ends' platform lookup by OF node adds every component it matches to a runtime: the speaker card would take the jack component (and call its PCM ops for its own streams), and a jack card would then fail to bind it. Add fe_cpu_is_platform to skip the front-end platform: the CPU DAI's component, which every runtime includes, is the PCM provider already. Other machines are unchanged. Add the apple,j616-macaudio variant with these settings, and a secondary_bclk_ratio next to the existing primary_bclk_ratio for a speaker front-end whose frame length the firmware sets. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
On the J616s (MacBook Pro 16" M4 Pro) the headphone jack's CS42L84 sits on a different AOP firmware stream than the speaker amps, with a different frame, and gets a sound card of its own, so that a codec that never answers costs the jack only. Add its compatible. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The driver only takes I2S, where each channel follows its own FSYNC edge and the frame must be 50 %. On machines whose serial port is run by the AOP firmware (the M4 Pro MacBook Pro's headphone jack on MCA cluster 2) the frame is a one-bit FSYNC pulse in a 64-bit frame with two 32-bit slots, data one bit clock after the rising edge: DSP_A. Take DSP_A with the same bus polarities as the I2S case (IB_NF, which for I2S is IB_IF): set the ASP's TDM mode, put channel 2 on the frame- start edge like channel 1, and start it one physical slot after channel 1 in hw_params. I2S keeps the register values it had. The TDM-mode semantics (slot start in bit clocks from the frame-start edge, the one-bit delay kept) are inferred from the I2S configuration; no datasheet is public. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The J616s 3.5 mm jack is two more services of the AOP firmware that runs the speakers: "cout" drives MCA cluster 2 (serializer ms02, mck2 from the 3.072 MHz NCO: a 64-bit frame at 48 kHz with two 32-bit slots, 24 valid bits) fed by base-ns ADMAC channel 4 (TX2), and "cin " brings the headset microphone back on the same serializer through channel 5 (RX2). These are the iop-audio-controller streams 'tuoc' and ' nic' of the machine's device tree. The firmware arbitrates the MCA and the audio power states between the speaker and jack streams. Each jack direction is a service of its own, with its own attachment, users and power states, like the speaker's: back-end startup raises pw1, prepare requests pwrd (output flag 2 for "cout", input flag 1 for "cin ", as the microphones use), hw_free drops back to pw1. Neither has a sense ring, so the service's ring becomes optional, and a jack service's prepare only raises pwrd when its serializer is not running yet. The jack's 8- and 4-byte frames need whole 16-byte ADMAC bursts per period, so its front-ends constrain the period size to them. The jack is a second component, "t6040-aop-jack", for a sound card of its own: front-end j616-jack-pcm-0 (playback on "cout", 2 channels, capture on "cin", 1 channel, S24_LE at 48 kHz), j616-jack-pcm-1 (only the stream widget macaudio's fixed routes need; it refuses to open) and the "jack" back-end. A component binds to one card only, and a card binds only once all of its codecs have probed: on its own card, a jack codec that never answers costs the jack, not the speakers. Both components sit on the audio node, and ASoC resolves a sound-dai there by asking each component in turn. The jack component answers argument 4 through of_xlate_dai_name and defers every other argument, and it registers before the speaker component, so neither card can probe into a half-registered state. A jack registration failure is logged and leaves the speakers alone. The jack component is registered when the audio node lists the "cout" and "cin" DMA channels. System sleep takes both jack services down with the streams suspended, as for the speaker: suspend drops a running service to pw1 and notes that it ran, resume brings it back to pwrd before ASoC unmutes the codec. Both services are resumed even if the first fails; the first error is returned. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
snd_soc_jack_add_pins() links the pins it is given into the jack's pin list through their own list heads. macaudio passes every card the same static array. That works with one card per machine, but the J616s headphone jack becomes a card of its own next to the speaker card. With two cards, the second card to probe re-links the shared pins into its own jack. The first card's pin list then runs into the second card's list head, and its next jack report dereferences garbage as a pin name. On the J616s that oopsed the CS42L84 interrupt thread at boot and left jack detection dead until the next boot. Copy the pins into the card's own data and keep the template const. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
On the J616s the jack's CS42L84 sits on a different AOP firmware stream than the speaker amps, with a different frame: the jack's stream on MCA cluster 2 is a 64-SCLK frame with a one-SCLK FSYNC pulse, two 32-bit slots and data launched on the rising SCLK edge, which is DSP_A with an inverted bit clock (as on the J700), while the amps take I2S with an inverted frame clock on a 250-SCLK frame. Give the jack a card of its own, "apple,j616-macaudio-jack": the AOP audio driver's jack component provides its front-ends (j616-jack-pcm-0 and -1) and back-end, and the primary front-end's 64 bit-clock ratio gives the codec its 3.072 MHz. A card binds only once all of its codecs have probed, so as a link of the speaker card a codec that never answers would take the speakers down with it; on its own card it costs the jack only. Like the speaker card, it takes the front-ends' CPU component as their PCM provider, since the AOP speaker and jack components share one OF node. The jack component has no PCM2, so the fixed route PCM2 RX <- Speaker Sense Capture fails and the card does not register: snd-soc-macaudio sound-jack: ASoC: Failed to add route Speaker Sense Capture -> PCM2 RX(*) Drop the route whenever the sense link is dropped. Cards with MCA front-ends lose a route to an unused widget. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
The CS42L84 DAC volume goes up to +12 dB, and nothing limits it on the jack card. Add an optional headphone volume ceiling to the machine configuration, set through snd_soc_limit_volume() on "Jack DAC Playback Volume" before the card registers, so userspace sees the capped range from the start, and the codec refuses writes above it since commit eaefdaa ("ASoC: cs42l84: honour the platform volume limit"). The card fails to bind if the limit does not apply, as with the speaker limits. Cap the J616s jack at -6 dB. Above 0 dB the DAC adds only digital gain, which clips a full-scale signal. The 6 dB below that are margin: the analog full scale of this board's headphone output has not been measured, and macOS adapts it to the headphone impedance it detects, which this driver does not do. Signed-off-by: Jonathan Goyvaerts <jonathan.goyvaerts@gmail.com> Assisted-by: LLM
|
Tested the headphone jack on a second J616s (MacBook Pro 16" M4 Pro, macOS 26.6.2 firmware, own m4-loader rev 15 build). Headset: Apple EarPods (3.5 mm, with microphone). Kernel: 7.1.12-m4.27 =
Not tested: a jack stream re-prepared after an XRUN (left/right slot alignment). |
|
Thanks, this is a big piece and most of it is in good shape. We've reviewed it, built it with our universal config (Fedora base + asahi.config), and booted it on the Neo and on our J616s. We're holding the merge for one reason, and we're working on that part ourselves. Why it's on hold. On our J616s the AOP doesn't come up: What we checked so far
Fixups we're adding on top (separate commits from us, your commits stay as they are)
Follow-ups, not blockers: aop_spkr.rs repeats a lot of t8140_aop_audio.rs (we'd like a shared helper later), a failed The userspace PRs (alsa-ucm-conf-asahi #5, asahi-audio #8) wait for this, since they need the cards. |
RyanTheTide
left a comment
There was a problem hiding this comment.
Update: the AOP now comes up on our boot chain, so we're merging this with our follow-ups (ryan/j616s-audio-406).
AOP firmware regions. m1n1 now reserves the AOP's two DRAM segments (__ETEXT at its remap address, __OS_LOG at its physical address) from the ADT segment-ranges. Each gets an apple,asc-mem node with iommu-addresses, plus a memory-region on the AOP node, the same way m1n1 does it for DCP and ISP (aurora-silicon/m1n1#41). Our follow-up to aop.rs takes the regions from memory-region, and the IOMMU core maps them. The node-name lookup of aop-etext/aop-oslog and the raw iommu_map() are gone. This means your m4-loader needs to write the same memory-region and iommu-addresses, or the AOP stops at "No firmware memory-region from the bootloader".
Tested on our J616s with integration/aurora + this series + our follow-ups (7.3.0-aurora-73s3, universal config, 0 warnings):
- The AOP boots with no errors and no DART faults.
- Three cards come up: J616 HPAI, J616 and Jack. All six amps bind.
- The microphones record on all three channels.
- Your UCM (alsa-ucm-conf-asahi#5) parses, and every control it sets exists.
- The speaker PCM stays closed because our machine has no speakersafetyd config for the J616 yet. That's the fail-closed lock doing its job, so the speakers themselves aren't tested here.
- One thing we noticed: the HPAI capture PCM advertises every sample format but always delivers float, so an S32 capture is garbage. We'll look at that separately.
Neo and M1 Pro: no regressions, with speakerguardd/speakersafetyd fine across s2idle and the new suspend lock lines as expected.
Thanks for the big series.
J616s audio: #406 review follow-ups
|
A correction to our approval above. We wrote that the speaker PCM stayed closed because our J616s had no speakersafetyd config. That was wrong, and your speaker side was fine. The open failed because our test opened With alsa-ucm-conf-asahi#5 installed, the verb routes the speakers and the PCM opens as S24_LE, 6 channels, 48 kHz. We added a J616 speakersafetyd profile: speakersafetyd's j316.conf values with your channel map (aurora-silicon/fedora-aurora#23). With that, our J616s (7.3.0-aurora-73v, which carries this series and our follow-ups) passes all of these:
One thing to compare on your machine: our left woofer 1 reads about half the current of the other woofers at the same drive, and its voltage reads the same as theirs. Its group shares the gain with left woofer 2, which gets the same signal in the J616 graph, so the protection holds. Still, it would help to know whether yours does the same. |
|
A follow-up on the left woofer 1 point above. It isn't the sense slot as such. On our J316 (M1 Pro), slot 0 is left woofer 1 as well, and in a session where all four woofers got the same program they read the same V*I power: 0.674, 0.663, 0.666 and 0.686 W at peak. On our J616s we swapped the pairings in a test config. LW1's current times LW2's voltage reads 0.012 W, and LW2's current times LW1's voltage reads 0.024 W, the same as the other woofers. So only LW1's current reads low, by about half. If your machine reads its four woofers alike, then it's something on ours. |
|
Checked the left woofer 1 question on our J616s (7.3.0-m4.33, which carries this series' audio code, speakersafetyd 2.0.1 with our j616.conf: is/vs 2i/2i+1, is_scale 3.75, vs_scale 14, volumes unlocked by the daemon). Band noise at -20 dBFS RMS on one channel of hw:AppleJ616,1 at a time, 6 s each (woofers 150-1500 Hz, tweeters 2-8 kHz), power read from speakersafetyd's per-speaker debug output, two runs:
Only the driven speaker showed power, with no negative power, no gain reduction and no kernel messages. Our four woofers read alike (within 3%), so the low LW1 current looks specific to your machine. We didn't read V and I separately, only their product, but a halved LW1 current would halve its V*I, and it doesn't here. |
|
Our J616s reads left woofer 1 like the others too, so it looks specific to your unit. This is from our speaker bring-up captures (7.1 kernels with the same tas2764/macaudio code, our own checker reading the V and I sense channels separately, not speakersafetyd's product). A 43 Hz tone at −40 dB (10.51 mV rms commanded) went to one amp at a time:
A halved LW1 current would read about 6.6 Ω here. With pink noise at −40 dB on all six, the four woofers each read 3.1–3.2 mA at 11.2–12.0 mV. |
Audio for the MacBook Pro 16" M4 Pro (J616s, T6040): the AOP driver and its audio and ambient light services on the T6040 (microphones, ambient light and lid angle sensors), the six internal speakers with the SN012776 amp protection they need, and the 3.5 mm headphone jack. It's one series because each part builds on the one before: the speakers on the AOP audio component, the jack on the speaker component's file and machine driver changes. The userspace side goes out with it: the J616s graphs in asahi-audio and the UCM in alsa-ucm-conf-asahi (links below).
Depends on: nothing open. Merged: the T6041 ADMAC (#379), which carries the microphone, speaker and jack DMA channels; the t604x bindings (#372); and two commits of #368 the speakers rely on at run time ("ASoC: tas2764: do not report an unreadable interrupt latch as faults", which keeps a failed latch read from looking like every fault bit to the SDZ group, and "ALSA: pcm_dmaengine: report an XRUN when the DMA channel failed"). The device tree nodes are in #396.
AOP and microphones (commits 1-7)
apple,t6041-aopin its own schema: the four register windows of Apple's device tree (the AOP SRAM and the ASC wrapper, which the driver uses, then the second ASC window and the configuration registers), one mailbox, the DART, and theaudio,alsandlasservice children (apple,t6041-aop-als/apple,t6041-aop-laswith the existingapple,aop-als/apple,aop-lasfallbacks).apple,t6041-aop-audiofor theaudiochild: the compatible (with theapple,t6030-aop-audiofallback the driver matches), the ADMAC channels by name (dmafor the microphones,spkrandsensefor the speakers, the optionalcout/cinfor the jack), the microphone card's chassis and machine names, and#sound-dai-cells(3 = the speaker back-end, 4 = the jack back-end). Modelled onapple,t8140-aop-audio.__ETEXT) at the EC0p IOVA and writes its log segment (__OS_LOG) at its physical address; without the latter it stops within a second on a storm of DART faults. The bootloader reserves both as/reserved-memory/aop-etext@…andaop-oslog@…, and the driver maps them before the start. Each node must be unique, page aligned and at most 1 MiB, each mapping is read back, and nothing starts if a check fails. The mappings go on unbind once the coprocessor is quiesced, and are kept, like the other buffers, when the shutdown can't be confirmed.apple/aop-als-cal.bin, extracted from macOS) is optional. Without it the sensor still reports, uncalibrated, with a warning.sync_stopdrains the completion callbacks. The card name fields can't overflow, andAudioSetDeviceProp<T>is onlyZeroablewhenTis.apple,t6041-aop-audiofollows macOS's IOPAudioLPMicDevice flow. On this firmware LPAI property 301 is the read-only stream description (writing it returns kIOReturnNotWritable). The channel masks go to property 300 instead (supported, enabled and history channels, 7 each, from the ADT's/product/audio), followed by the PDM and high-frequency decimator setup. A refused step is logged and doesn't stop the probe, so the next step's answer is still seen.The AOP's DAPF is not part of this PR: our loader programs it before Linux starts.
Speakers (commits 8-15)
Bindings:
ti,tas27xxdocumentsSDZ-supply(the driver already takes it) andapple,sdz-group-fail-closed.apple,macaudiogetsapple,j616-macaudio.tas2764: keep SDZ high across system sleep (every machine). Pulling SDZ low on suspend put every amp of a shared line through a power-on reset on resume, milliseconds before ASoC unmuted a running stream. The woofer amps' sense then reported negative power for seconds, and speakersafetyd panicked 2-3 times per resume. The Apple quirk writes on page 0xfd are volatile and were lost too. The amp now waits in software shutdown across sleep, as between streams. The cache-only window and the sync stay. This comes before the SDZ group, so the group doesn't touch the resume path this commit removes.
tas2764: SDZ fault group. All six amps share one shutdown line, modelled as a fixed regulator that every amp takes as its SDZ-supply. Amps that share the supply and carry
apple,sdz-group-fail-closedform a group. An over-temperature or over-current interrupt on any of them forces the shared supply off, which puts the whole group in hardware shutdown, and no amp of the group powers up again until the driver is reloaded. A plain regulator disable can't do this: one amp's disable leaves a shared line up for the others. The group check and the supply enable are one step under the group lock. A member without a fault interrupt, or whose fault IRQ can't be requested, fails its probe, since it could never take the group down, and a member with a shutdown GPIO of its own is refused, since that line would bypass the supply.aop_audio: the J616s speaker streams (new file
aop_spkr.rs). The AOP firmware owns the MCA and the audio power states here.spkrruns MCA cluster 0: a 250-bit frame with six 32-bit playback slots and twelve 16-bit V/I sense slots, on base-ns ADMAC channels 0 and 1. The speaker component has front-endsj616-pcm-0..2and thespkrback-end. Startup attaches the service and raisespw1, prepare raisespwrd, hw_free drops back topw1, and the last shutdown goesidle, each confirmed by reading the state back. Every front-end holds its own power reference around its channel. Neither slot stream has a frame marker, so every start is frame-aligned:pwrd, and the sense front-end copies whole frames out of that ring;Without this, the sense pairs one amp's V with another amp's I, and speakersafetyd reads negative power and panics.
macaudio: fail closed when a speaker volume lock does not take (every machine). The controls are limited one at a time. If any of them fails while locking, every running playback of the card stops and prepares are refused until the driver is reloaded.
macaudio: lock the speaker volumes before userspace freezes (every machine). The volumes are locked from
PM_SUSPEND_PREPAREtoPM_POST_SUSPEND, so a frozen sound server's last period doesn't play into unmuted amps (a short crackle at every sleep). The speakersafetyd lease ends at suspend-prepare and this lock isn't reported as a lost lease, so the daemon keeps its temperatures across s2idle. The PM notifier lives only while the card is bound. Both lock changes come before the J616s variant, so the J616s speakers are never enabled with the older lock semantics.macaudio: J616s speakers. The
apple,j616-macaudiovariant: front-ends from the AOP component, I2S with an inverted frame clock on a 250-SCLK frame (secondary bclk ratio 250), amp gain cap 15,safe_vol-20 and V/I sense, as on the J316. Unlocked, the speakers go to 0 dB like on every other machine, with speakersafetyd limiting them.fe_cpu_is_platformskips the front-end platform lookup by OF node, which would add every component on the AOP audio node to every runtime (the jack component sits on the same node).Headphone jack (commits 16-21)
apple,macaudiogetsapple,j616-macaudio-jack.coutdrives MCA cluster 2 (a 64-bit frame at 48 kHz, two 32-bit slots, 24 valid bits) on base-ns ADMAC channel 4, andcinbrings the headset microphone back on the same serializer on channel 5. Each is a service of its own, with the speaker's power sequence (pw1at startup,pwrdwith the output or input flag at prepare) and no sense ring. The jack is a second ASoC component,t6040-aop-jack, for a card of its own: front-endsj616-jack-pcm-0(2 channels out, 1 in) and-1(routes only), and thejackback-end,sound-daiargument 4, answered throughof_xlate_dai_namewhile every other argument is deferred to the speaker component. It registers before the speaker component and only when the node listscoutandcin, and a failure costs the jack only. System sleep takes both services down with the streams suspended and brings back what ran before ASoC unmutes the codec.snd_soc_jack_add_pins()links the pins through their own list heads, and macaudio gave every card the same static array. With the jack as a second card, the card that probes last re-links the pins into its own jack, and the other card's next jack report walks into garbage. On our daily kernel that oopsed the CS42L84 interrupt thread at boot (snd_soc_jack_report→dapm_find_widget→strncmp) and left jack detection dead. The pins are now copied into each card's data.apple,j616-macaudio-jackis a sound card of its own. A card binds only once all of its codecs have probed, so as a link of the speaker card, a jack codec that never answers would take the speakers down with it. On its own card it costs the jack only. Its front-ends are the AOP jack component'sj616-jack-pcm-0/-1, with DSP_A and an inverted bit clock (as on the J700) and a 64× bit clock (3.072 MHz). The card has no sense link, so the fixed routePCM2 RX <- Speaker Sense Captureis now only added when the card has sense. Without that, the card failed to register. Cards with MCA front-ends lose a route to an unused widget.jack_max_volmachine setting appliessnd_soc_limit_volume()to "Jack DAC Playback Volume" before the card registers, so userspace only ever sees the capped range. The card fails to bind if the limit doesn't apply. Above 0 dB the DAC adds only digital gain. The 6 dB below that are margin: this board's analog full scale is unmeasured, and macOS adapts it to the detected headphone impedance, which this driver doesn't.Hardware tested on: Apple MacBook Pro (16-inch, M4 Pro, 2024), J616s / T6040, macOS 26.6.2 firmware, booted by our own loader.
How tested: Our daily kernel 7.3.0-m4.9, on
integration/auroraac6b017bce, carries this code. Its only additions in these files are test-only: a bring-up speaker ceiling parameter (speaker_ceiling_db, left at 0) andM4_SPKR/M4_JACKlog lines. On it:apple/j616.conf) running its thermal model on the V/I sense and unlocking the volumes to 0 dB; all six speakers were checked one by one by ear on an earlier daily build;The sleep, alignment and lease fixes in the speaker part come from those runs, where speakersafetyd used to panic on misaligned or reset sense data.
Not tested / limits:
coutandcinshare it; that the two slots still line up then (no left/right swap) has not been checked. Headphone impedance detection and the analog output level are not handled (hence the cap). The cs42l84 DSP_A path was only run on the J616s; I2S machines keep their register values, but that path was not re-run.apple,t6030-aop-audiois only documented as the fallback.apple,no-beamforming, which aop_audio reads on the AOP node, is left out of the schema: no T604x node uses it and no existing AOP binding documents it.Also needed (userspace, to go out together): aurora-silicon/asahi-audio#8 (the J616s speaker graph with the pop fix, and the jack card's priorities) and aurora-silicon/alsa-ucm-conf-asahi#5 (the speaker and jack card UCM).
Credits: The AOP, ALS/LAS and AOP audio drivers, the macaudio volume interlock, the tas2764 driver and speakersafetyd are the Asahi Linux and aurora contributors' work. The AOP speaker component's structure (the component context, the DAI tables and the dmaengine front-end helpers) is derived from aurora's
t8140_aop_audio.rs, and the power sequence follows the aurora contributors' J616s control test. The T6040 microphone flow follows what macOS's IOPAudioLPMicDevice sends. The T6041 ADMAC support underneath is Niklas Sheth's port (#379).Review notes: the series folds our bring-up history (frame alignment, sense period fix, pause/resume, a bring-up -10 dB ceiling that was later removed, the PM notifier lifetime fix) into the commits it fixed.
Commits:
Base:
integration/aurora(ac6b017bce). Built at every commit with our kernel configuration (Rust on; APPLE_AOP, the AOP ALS sensor, SND_SOC_APPLE_AOP_AUDIO, SND_SOC_APPLE_MACAUDIO, SND_SOC_CS42L84 and SND_SOC_TAS2764 as modules), W=1, no warnings.dt_binding_check(dtschema 2026.9) is clean for apple,t6041-aop.yaml, apple,t6041-aop-audio.yaml, apple,macaudio.yaml and ti,tas27xx.yaml.checkpatch --strictshows only: the MAINTAINERS reminder for the new files (the existingsound/soc/apple/*entry coversaop_spkr.rs); CamelCase on the existingCS42L84_ASP_RX_CHx_*register macros; continuation alignment that follows the rest ofcs42l84_set_dai_fmt(); and one long commit-log line quoting the kernel's oops.🤖 Generated with Claude Code