Repository navigation
ASoC: apple: J713 (M4 MacBook Air) microphones, headphone jack and speakers - #820
Conversation
|
Rebased onto the current integration/aurora, with three fixes (not pushed yet) #820 no longer merges: on integration/aurora 21f7ffc, I have the rebased series locally ( Changes
The other 17 commits are unchanged apart from the rebase; one has a new DT context line. Testing
Separately (not in this update)On the previous stack, which had the suspend-to-idle series, one system suspend logged 🤖 Written by Claude Opus 5.5 (in Claude Code) for Eryk; the rebase, fixes and tests above were done the same way. |
The T8132 (M4) AOP runs the same firmware transport as the T6041 one, and its bootloader hands it over the same way. It also programs the AOP DART's address filter (DAPF) and reserves the filter's windows, which the firmware reaches through its DART at their physical addresses. The windows come in memory-region with the firmware segments, up to 16 of them. Signed-off-by: Erik Bourget <erik@ebourget.net>
The T8132 (M4) AOP takes the T6040 boot arguments (EC0p 0x100_0000_0000, nCal 1, alig 64, AOPt 0): iBoot leaves the boot-argument table at its defaults in the AOP SRAM and the AOP halted, and the ADT gives __ETEXT the DART address 0x100_0000_0000. The bootloader programs the AOP DART's DAPF and reserves the firmware segments and the DAPF windows, so the T6040 configuration applies unchanged, including the firmware's own nCal of 1 that the T6040 writes since 28174f8 ("soc: apple: aop: Write the T6040 firmware's own nCal boot argument"). Tested on J713 (MacBook Air 13" M4) with m1n1 handing the AOP over (its memory-region holds __ETEXT, __OS_LOG and the 15 DAPF windows): the AOP starts, and the microphones capture (3 channels, 48 kHz) through aop_audio. Signed-off-by: Erik Bourget <erik@ebourget.net> [eryk: rebased onto integration/aurora 21f7ffc; the T8132 config now inherits nCal 1 from the T6040 one (28174f8), so the message says so] Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The T8132 AOP audio service is the T6041 one: the same LPAI microphone flow, and the same ADMAC channel map for the speakers and the jack. Allow "apple,t8132-aop-audio" in front of the T6041 compatible. A machine brought up in steps lists only the streams it has so far, so allow the microphones alone, and the microphones with the jack, besides the speaker layouts. Signed-off-by: Erik Bourget <erik@ebourget.net>
Describe the T8132 AOP with its mailbox and DART, and the audio fabric's two ADMACs (admac-leap-ns on DART stream 10, admac-base-ns on stream 8), all disabled. Addresses and interrupts are the ADT's; the ADMAC channel map is the T6041 one: the microphones on LEAP channel 1. Signed-off-by: Erik Bourget <erik@ebourget.net>
Enable the AOP, its DART and mailbox, and the ADMACs on the MacBook Air 13" M4, with the AOP audio service for the three microphones. /chosen already asks for the AOP's handoff: m1n1 programs the AOP DART's DAPF and adds the firmware segments and the DAPF windows to the AOP's memory-region, or disables the AOP. Signed-off-by: Erik Bourget <erik@ebourget.net>
The jack component was only registered along with the speaker one, so a machine whose audio node lists the jack's DMA channels but not yet the speakers' got no jack. Register the speaker component only where the node lists "spkr", and the jack's where it lists "cout" and "cin", each on its own. Creating the services does not reach the firmware; nothing changes for the J616s, which lists all of them. Signed-off-by: Erik Bourget <erik@ebourget.net>
The MacBook Air 13" M4 (J713) headphone jack is a card of its own on the AOP, like the J616s one. Signed-off-by: Erik Bourget <erik@ebourget.net>
The J713 jack runs on the AOP firmware's MCA cluster 2 with the same CS42L84 setup as the J616s jack. Use the J616s jack configuration, including its -6 dB cap on the headphone DAC: this board's analog full scale is unmeasured too. Signed-off-by: Erik Bourget <erik@ebourget.net>
Add the CS42L84 on i2c1 (reset on nub GPIO 17, interrupt on AP GPIO 98, from the ADT), the jack's ADMAC channels on the AOP audio node (base-ns TX2 and RX2, as on the J616s) and the jack's own card. The codec is the first consumer of the i2c1 power domain, so the J713 power-domain policy now enables it; it has no parent domains. Signed-off-by: Erik Bourget <erik@ebourget.net>
The MacBook Air 13" M4 (J713) speakers are a card of their own on the AOP's speaker stream, like the J616s ones. Signed-off-by: Erik Bourget <erik@ebourget.net>
The MacBook Air 13" M4 (J713) runs its four SN012776 amps, a woofer and a tweeter per side, on the AOP's speaker stream exactly like the J616s: the same 250-bit frame, I2S with an inverted frame clock, six playback and twelve sense slots, of which the four amps take the first four and eight. Amp gain 15 and the -20 dB safe volume as on the other SN012776 machines. The speaker front-end's channel count was one per amp; add speaker_fe_channels for a CPU stream with more slots than amps, and give the J713 six. Its last two channels are silent. As on the J616s, the first front-end only completes the fixed routes: the J713 jack is a card of its own, so mark it route-only and leave its link unbound. The AOP component numbers the front-end PCMs by link, so the speakers stay hw:AppleJ713,1 and their sense hw:AppleJ713,2. Signed-off-by: Erik Bourget <erik@ebourget.net> [eryk: rebased onto integration/aurora 21f7ffc, keeping the new J614s configuration beside this one; mark the first front-end route-only, as on the J616s] Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add the four SN012776 amps on i2c3 from the ADT (audio-speaker at 0x38 and its speaker1..3: left woofer 0x38, right woofer 0x3b, left tweeter 0x39, right tweeter 0x3c), each with its sense slots from speaker-config (imon at RX byte 4i, vmon at 4i + 2), the shared interrupt on AP GPIO 14 and the shared shutdown line on AP GPIO 15 as the fail-closed SDZ group's fixed regulator. The speaker card lists the amps in the ADT provider order, so each takes its native playback slot, with the amp-tx-zd-config idle masks. The AOP audio node gets the speaker and sense channels (base-ns TX0 and RX0): the J616s stream, of which the four amps take the first four playback and eight sense slots. The amps are the first consumer of the i2c3 power domain. Signed-off-by: Erik Bourget <erik@ebourget.net>
On an AOP-fed speaker stream the TDM bus keeps running for a while after the playback stream stops, and the amplifiers' current and voltage sense slots are still sampled in that time. An amplifier put in software shutdown then can leave garbage in its sense slots. Add a property for such amplifiers: they must not enter software shutdown until the bus has stopped. Signed-off-by: Erik Bourget <erik@ebourget.net>
On the J713 the AOP keeps the speaker TDM bus running for a while after a playback stream stops, and the speaker protection still reads the amplifiers' I/V sense words in that time. Each amp is muted and then shut down in turn as the stream ends, so the shutdowns land in that tail. The right tweeter's voltage sense slot then reads full scale for about 20 samples at every stream stop, even for a stream of digital silence played with the volume locked; the protection daemons charge that as power. With apple,deferred-shutdown, mute the amp at the stream's end as before, but put it in software shutdown only 150 ms later, after the bus has stopped (it ran on for up to 73 ms on the J713). A stream that starts in between cancels it. Suspend and removal shut the amp down at once. Such an amp sees the bus clock stop while it waits, and may see it latched again after its shutdown; that clock-only fault is expected then and no longer logged. Amps without the property keep the current behaviour. On a J713 the full-scale words in the stream tails go from about 90 per test (three silent streams and a chord) to none, with no fault logged, and every amp still ends in software shutdown. Signed-off-by: Erik Bourget <erik@ebourget.net>
The J713's AOP keeps the speaker bus running briefly after a stream stops, and the speaker protection reads the amps' sense words in that time. Have the four amps wait for the bus clock to stop before they go to software shutdown. Signed-off-by: Erik Bourget <erik@ebourget.net>
With apple,deferred-shutdown the muted amp stays powered after a stream until the bus clock stops, and keeps latching TDM clock faults while it waits; the fault handler ignores them while stream_stopped is set. The amps share one fault line and each handler reads six latches over I2C, so a latch can reach its handler several milliseconds late. When the next stream unmutes the amp in that window, stream_stopped is already clear and the stale latch is reported as a fault on the new stream. On the J713 this hit the first amp the link unmutes on 9 of 32 silent streams that started within 140 ms of the end of the last one, and on none of 32 that started later. At unmute, clear the latches before clearing stream_stopped when the first latch register holds only the clock fault (the clear takes every latch, so any other fault there is left for the handler), and hold a lock across the handler's read and judgement of the latches so that the unmute cannot fall between them. With this, none of 96 silent streams logged a fault, 48 of them started within 140 ms of the last. Signed-off-by: Erik Bourget <erik@ebourget.net>
…leep Since the speaker volumes are locked from suspend-prepare, the speakers stay silent after the first system sleep. Locking clips every speaker volume to the lock's ceiling (1, -100 dB on the SN012776), and unlocking only lifts the ceiling again. After a lost lease that is fine: speakersafetyd gets -ETIMEDOUT, restarts and sets the volumes itself. The system-sleep lock is deliberately not reported as a lost lease, so the daemon keeps its state and never writes the volumes again. They stay at -100 dB until the daemon restarts. alsactl then stores them that way at shutdown and restores them at the next boot, where they stay unless the daemon's cold start sets the volumes again after that restore. Keep the speaker volumes at suspend-prepare, before the lock clips them, and set them again at the first unlock after it. That is the daemon's first ping after the resume, which in practice comes when playback next starts. These are the values the daemon set and still assumes. The daemon may not ping between two sleeps. A suspend-prepare that finds the volumes locked already keeps what an earlier one saved, so a second sleep before any playback doesn't lose them. Hold the card's controls for reading from keeping the volumes until they are clipped, so a volume the daemon writes meanwhile can't be overwritten by an older, higher one at the unlock. Hold them as well in the work that can unlock after a back-end starts or stops, so setting the volumes again there doesn't race a volume write either. Control writes hold them for writing and take volume_lock_mutex inside, in the same order. A new owner of the lock, such as a restarted daemon, sets the volumes itself: drop what was kept then, and when the card is removed. Nothing is kept for a card that is shutting down. The card's remove waits for the unlock work, which takes the card's controls, while the snd_card still exists. Tested on a J713: after a suspend-to-idle the four speaker volumes are back at 0 dB and the speakers play, without restarting speakersafetyd. The KUnit cases go through the lock control and the PM notifier: one sleep, two sleeps before the daemon pings, and a new owner after a sleep. Fixes: 1cbce20 ("ASoC: apple: macaudio: lock the speaker volumes before userspace freezes") Signed-off-by: Erik Bourget <erik@ebourget.net>
The AOP firmware keeps a device attachment for as long as it runs, and the driver has no way to drop one at removal. When the module is loaded again, the probe's attach calls for the microphone devices get kIOReturnBusy back and the probe fails. The machine then has no audio until it reboots: with the T6040 flow the speaker and jack streams are registered by the same probe and are lost as well. The speaker and jack services already treat that return as attached. Do the same in the probe, and share the constant with aop_spkr. The probe's property writes that follow are accepted by the still attached devices. Signed-off-by: Erik Bourget <erik@ebourget.net>
The component probe resets the amp and reinitialises the register cache, which clears the SDOUT idle slot mask and the force-0 enable. The idle slot setup cached in the driver data survives the reset, though, so when the card binds again and set_tdm_idle() asks for the same mask and mode, it returns early and the force-0 enable is never written. set_bclk_ratio() writes the mask again but not the enable. On the J713 every rebind of the speaker card left the two amps that force their idle slots to zero without the enable, and the other two amps' voltage sense words then came back with the top bit set in about a quarter of the frames. The speaker protection daemon reads those as a voltage without current and holds the speakers at a safe level. Clear the cached setup after the reset, so that the next set_tdm_idle() writes it. Signed-off-by: Erik Bourget <erik@ebourget.net>
The component probe requests the fault IRQ as a devres of the I2C device, which stays bound when the sound card is torn down. Every rebind of the card probes the component again and adds another handler for the same amp on the line: after two rebinds the J713's four amps had twelve handlers on their shared IRQ, each fault interrupt ran every one of them, and each does six I2C reads. Request the IRQ without devres and free it in the component remove. A devres would not do: when the I2C device itself is unbound, its devres release frees the IRQ first and only then unregisters the component, so a devm_free_irq() in the remove would find nothing and warn. Request it as the probe's last step, so that a probe that fails later does not leave a handler behind either. Signed-off-by: Erik Bourget <erik@ebourget.net> [eryk: request the IRQ without devres: on an I2C unbind the devres release frees it before the component is removed, and the devm_free_irq() in the remove then warns] Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tas2764_codec_probe() enables the SDZ supply and raises the shutdown GPIO before it resets the amp and writes its setup. When one of those writes fails, or an amp in an SDZ group can't get its fault IRQ, the probe returns with the supply still enabled. ASoC doesn't call the component's remove after a failed probe, so nothing balances that enable, and every later probe attempt adds another. The supply then stays on after the card is removed and, for an amp outside an SDZ group, through system sleep as well. Undo the power-up on those failures: lower the shutdown GPIO and drop the supply enable, as the remove does. There is no enable to drop when the amp has no SDZ supply (its SDZ is a GPIO, or not described), or when its group failed and the supply was already forced off. Signed-off-by: Erik Bourget <erik@ebourget.net> [eryk: rebased onto integration/aurora 21f7ffc, where the SDZ supply is optional: skip regulator_disable() when the amp has none] Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the component suspend until its resume, the fault handler can neither read nor clear the interrupt latches: the regmap is cache-only, and the I2C controller suspends after the card. A fault latched in that window, such as the TDM clock fault when the bus clock stops, holds the shared level-low line asserted. Every amp's handler then fails to read its latches and returns IRQ_NONE, in a loop until the IRQs are suspended, and again from resume_device_irqs() until the deferred component resume. On a J713 one suspend logged "tas2764_irq_locked: 15208 callbacks suppressed" and "interrupt latch N unreadable". Enough of it can make the spurious-IRQ detector disable the line, and with it the SDZ group's fault shutdown. Disable the IRQ at the start of the component suspend and enable it again at the end of the resume, on every path. disable_irq() nests across the amps sharing the line, and ASoC resumes every component it suspended, even after a failed suspend. A clock fault latched in sleep is then handled after the resume, as before. Signed-off-by: Eryk Wieliczko <eryk@wieliczko.ninja> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5d94364 to
9abd087
Compare
|
Merged. This comment is from Claude, working for Eryk, not from Eryk himself. Rebased onto integration/aurora Changes
Testing
🤖 Written by Claude Opus 5.5 (in Claude Code) for Eryk. |
The J713 (MacBook Air 13" M4, T8132) audio on the AOP: the internal microphones, the headphone jack and the speakers, plus the ASoC fixes the J713 speakers need. It's one ASoC series, and it replaces #437, #439, #663, #580, #759, #760, #765 and #799, plus the audio half of #434. The review points raised on those PRs are answered below.
Depends on #819 (
soc: apple: aop: T8132). Its two commits come first here; this PR adds the other 19.Before this can ship
Loader: J713: hand the AOP over with its DAPF, firmware segments and windows m1n1#47 (with the window-union fix, 1b9b611ba3ef) and arm: apple: reference m1n1's J713 AOP reservations from the OS tree u-boot#13 are merged on those repos'
aurora-wip, but not yet onmain. With an older m1n1 the AOP gets nomemory-region, and the driver refuses to start it ("No firmware memory-region from the bootloader").Speakers (the ASoC: apple: J713 (M4 MacBook Air) speakers on the AOP (depends on #437) #439 hold): they must not ship without J713 protection and the crossover. The open pieces:
Without a protection daemon, the kernel keeps the speaker volumes at the locked maximum, so the speakers stay silent.
Shared code: I could only boot a J713. These commits change code that other machines run, and need your regression boots:
aop_audioregistering each component where its own channels exist (commit 6). The J616s lists all of them, so it takes the same path as before.apple,deferred-shutdown. Other amps only take the newfault_lockin the IRQ handler.Commits
Microphones (3–5)
dma-namesallows the microphones alone, the microphones with the jack, and the two speaker layouts.iop-audio-controller: speaker base-ns 0/1, jack 4/5, mics on LEAP.Headphone jack (6–9)
aop_audioregisters the jack where the node lists its own channels, without a speaker stream. Creating the unused services doesn't reach the firmware.macaudio_j616_jack_cfg, including its −6 dB headphone DAC cap. This board's analog full scale is unmeasured too.ps_i2c1enabled. Without that the codec never probes (i2c-apple−110).Speakers (10–12)
macaudio_j713_cfg:speaker_fe_channels(6). The AOP's speaker front-end carries six channels, the last two silent, and one channel per amp made the open fail with EINVAL.amp-tx-zd-configidle masks 0xf0f0 / 0x0f0f;ps_i2c3enabled.Clean sense at stream ends (13–16)
apple,deferred-shutdownonti,tas27xx, set on the four J713 amps. The amp is muted at the stream's end as before, and goes to software shutdown 150 ms later. The AOP kept the bus running for up to 3520 frames (73 ms) after a stream ended.Speaker volumes after a system sleep (17)
removecancels the lock update work while thesnd_cardstill exists. That work takescontrols_rwsem, and a back-end stop during teardown can queue it.Card rebinds and module reloads (18–21)
aop_audioaccepts a device that is still attached from a previous load (kIOReturnBusy). The speaker and jack services already did. Before, reloadingsnd-soc-aopleft the machine with no audio until a reboot.tas2764writes the idle slot setup again after the probe's reset. Before, a rebind left the idle-filling amps without the force-0 enable (SDOUT_HIZ_9bit 7). That corrupted the right amps' V-sense, and the protection daemon then held the speakers at the safe level.tas2764frees its fault IRQ in remove, and requests it last in the probe. Before, each rebind added four more handlers on the shared line.Testing
This series, on integration/aurora 41c4055
On the J713. The test kernel also carried #429, for the J713 display.
Boot: untainted. All three cards register: microphones, jack, and speakers with their sense PCM. The AOP starts (
RTKit: Initializing (protocol version 12)) with no DART faults.Speaker protection: speakerguardd takes the lock and unlocks the volumes. Playback through PipeWire's UCM speaker route runs.
Amps: 4 handlers on the shared fault line; force-0 is
0x80on both idle-filling amps; the SDZ supply holds one enable per amp.Microphones: they record room noise at about −50 dBFS.
Silent restarts: 0.3 s silent streams with gaps of 40 ms to 1 s before each start. 0 of 64 starts logged an amp fault, and nothing was logged outside a stream.
Rebind and reload: after a
snd-soc-macaudiorebind and then a fullsnd-soc-aopreload, all three cards come back. Force-0 is still0x80, there are still 4 IRQ handlers, the microphones record, the speakers play and the daemon governs again.AOP start failure and rebind (asked for in soc: apple: aop: T8132 (M4 MacBook Air) AOP and microphones #434): unbinding
apple_aopitself times out waiting for the endpoints to acknowledge their shutdown (5 s, mask 0xfff). On the rebind, RTKit endpoint discovery fails (−62). The failure path then behaves as designed:Audio comes back after a reboot. So the AOP core can't be rebound on J713, but its failure path is safe. Consumer reloads, as above, are fine.
On our J713 tree when each part was first posted
Each part ran on integration/aurora with the earlier parts of this series. The commits are unchanged since, except commit 17's card-remove cancel.
Microphones and jack
cs42l84 0-004b: Cirrus Logic CS42L84, Revision: 36; the jack card registers.Jack DAC Playback Volumetops out at step 244, i.e. −6 dB.Headphone Playback MuxthroughJack HPtoHeadphoneis on.Headphone JackandHeadset Mic Jackflip on unplug and replug, with one codec interrupt per event.Speakers
These ran with speakersafetyd 3.0.0 holding the lease, on the M3 Air's j613 profile with 12 channels. The ADT's per-unit DC resistances (4.00/4.10 Ω woofers, 3.55/3.68 Ω tweeters) match j613's nominals.
Speaker volumes unlocked. Stopping it givesSpeaker volumes locked: Lock timeout.Deferred shutdown and the stale clock fault (13–16)
Sense tails, from three silent streams and a chord with the volumes locked: about 90 full-scale words per run before, all on the right tweeter's V slot at each stop. None after. Every amp still ends in software shutdown (
PWR_CTRL0x2).Restarts: pairs of short streams 90, 120 and 150 ms apart. All four amps play, the sense stays valid, and there are no pops.
Faults at restarts:
Under speakerguardd: a recording of 13 streams, including those restarts, has no full-scale sense words, and its replay certifies.
Volumes after sleep (17)
Jonathan reproduced the bug on a J616s in #580: six volumes at 1 after one s2idle cycle, with the daemon's PID unchanged.
Reloads and rebinds (18–21)
Reload (18): before,
rmmod/insmod snd_soc_aopfailed the probe on thepdm0attach. After: five reload cycles, and each time the three cards are back, the speakers and jack play, the mics record at the same level, and the daemon governs.Idle slots (19):
SDOUT_HIZ_9on the idle-filling amps.snd-soc-aopreload0x800x000x000x800x800x80Without it, after a reload, the right woofer's and right tweeter's V channels each had about 11,700 of 48,000 words with the top bit set. With it, none.
IRQ (20): 12 handlers on the fault line after two rebinds without the fix; 4 with it, fresh, after two rebinds and after a reload. The IRQ still fires and is handled.
SDZ (21): I made the first setup write fail, then bound the card twice and then cleanly. Enables on
sn012776-sdz: without the fix, 1-0038 holds 2 after the failed binds and 3 after the good one. With it, none and then 1 per amp.Checks
checkpatch --strict: no errors. The one warning and one check are the MAINTAINERS note, which the existingsound/soc/apple/*entry covers, and thekunit_test_suite()blank line, which follows the admission test header.aop.o,aop_audio.o,macaudio.o,tas2764.oand the J713 DTB. The tree as a whole stops indrivers/nvme/host/apple.c,mt7932_bt_pcie.cand the asahi Rust core, which are untouched toolchain cases on clang 23 / Rust 1.99 (CI pins clang 18 / Rust 1.96). Not run: the Fedora-base recipe.apple-speaker-pm-volumes(3) andapple-speaker-admission(1) pass, with no reports.run-host-tests: 132/133, the same as the base.apple-dcpfails on bare integration/aurora as well.dt_binding_checkis clean forapple,t6041-aop-audio.yaml,apple,macaudio.yamlandti,tas27xx.yaml.CHECK_DTBSont8132-j713.dtbgives the same diagnostics as the base.Not tested: other machines (see Shared code above). Headphone impedance detection, which this driver doesn't do, as on the J616s.