Verify at the board: S/PDIF output (PH17 card detect: done) #42

Closed
opened 2026-08-28 06:15:57 +00:00 by tiagoagueda · 4 comments
Owner

Updated 2026-08-29: part 2 is done. PH17 card detect works — cd-gpios replaced the
polling in 97a6d25, and a live hot-insert was verified on 2026-08-29. Only the S/PDIF
question below remains
, and it is still blocked on physical access.

Two things that need someone physically at the board. Both are quick.

1. Does a bitstream actually leave PH18?

S/PDIF now registers a card and plays — DMA interrupts advance, pll-audio reparents to
122.88 MHz and the module clock lands on exactly 24.576 MHz. But nothing has confirmed a
signal on the pin
, and it is not even confirmed the Telos fits the connector: "HDMI, AV,
SPDIF" in 01-hardware.md is [REPORTED], from a WikiDevi teardown of the
Meta variant.

Needs an S/PDIF receiver on the optical/coax jack, or a scope on PH18:

speaker-test -D hw:0,0 -c 2 -r 48000 -F S16_LE -t sine -l 1

Note PH18 function 3 is "Reserved" in the public A80 manual — which documents no S/PDIF block
at all — so the pinmux claim rests on the board's own script.bin and the vendor kernel.

2. Does PH17 track card presence?

The card detect pin was PH18, taken from the sys_config.fex in the vendor rootfs — the
file that claims machine = "cubieboard4" and that 33-REAL-PINMAP.md
already flagged as wrong about USB, WiFi and the A15 rail. The board's own script.bin has
sdc_det = port:PH17.

This retroactively explains a note that sat in the board DTS: with a pull-up on PH18 the line
never went low on insertion, and the conclusion recorded was "the detect switch is not
reaching PH18". It is not — it is on PH17.

broken-cd is kept for now because polling is known to work. Insert and remove a card while
watching PH17; if it tracks, cd-gpios can replace the polling.

> **Updated 2026-08-29: part 2 is done.** PH17 card detect works — `cd-gpios` replaced the > polling in `97a6d25`, and a live hot-insert was verified on 2026-08-29. **Only the S/PDIF > question below remains**, and it is still blocked on physical access. Two things that need someone physically at the board. Both are quick. ### 1. Does a bitstream actually leave PH18? S/PDIF now registers a card and plays — DMA interrupts advance, `pll-audio` reparents to 122.88 MHz and the module clock lands on exactly 24.576 MHz. But **nothing has confirmed a signal on the pin**, and it is not even confirmed the Telos fits the connector: "HDMI, AV, SPDIF" in [01-hardware.md](01-hardware.md) is **[REPORTED]**, from a WikiDevi teardown of the *Meta* variant. Needs an S/PDIF receiver on the optical/coax jack, or a scope on PH18: ```sh speaker-test -D hw:0,0 -c 2 -r 48000 -F S16_LE -t sine -l 1 ``` Note PH18 function 3 is "Reserved" in the public A80 manual — which documents no S/PDIF block at all — so the pinmux claim rests on the board's own `script.bin` and the vendor kernel. ### 2. Does PH17 track card presence? The card detect pin was **PH18**, taken from the `sys_config.fex` in the vendor rootfs — the file that claims `machine = "cubieboard4"` and that [33-REAL-PINMAP.md](33-REAL-PINMAP.md) already flagged as wrong about USB, WiFi and the A15 rail. The board's own script.bin has `sdc_det = port:PH17`. This retroactively explains a note that sat in the board DTS: with a pull-up on PH18 the line never went low on insertion, and the conclusion recorded was "the detect switch is not reaching PH18". It is not — it is on PH17. `broken-cd` is kept for now because polling is known to work. Insert and remove a card while watching PH17; if it tracks, `cd-gpios` can replace the polling.
Author
Owner

Update 2026-08-28 - part 1 got worse before it got better, and is still open.

S/PDIF was running at the wrong rate the whole time. pll-audio's M divider was modelled with
a zero offset where the hardware divides by M + 1, so the module clock that this issue records
as landing "on exactly 24.576 MHz" was really 23.6 MHz. It read back as exact because the
framework reports what it asked for.

Measured by timing a buffer of known length on the S/PDIF card:

30.000 s of 48000 Hz S16_LE stereo   ->  31.362 s     before the fix
30.000 s of 48000 Hz S16_LE stereo   ->  30.168 s     after

So anything played at the pin before today was 4% flat, and nobody could have heard that
without a receiver
- which is exactly what this issue is for. Fixed now, but it means the
"clocks verified" claim behind the original S/PDIF result was worth less than it looked.

Also inherited: the 44.1 kHz family is still 9.5% slow on this same PLL, #52. Any bitstream
check at 44.1 kHz will be measuring that bug, so test at 48 kHz.

Neither half of this issue is resolved - still nothing has confirmed a signal on PH18, and the
card-detect test on PH17 is still untouched.

**Update 2026-08-28 - part 1 got worse before it got better, and is still open.** S/PDIF was running at the wrong rate the whole time. `pll-audio`'s M divider was modelled with a zero offset where the hardware divides by M + 1, so the module clock that this issue records as landing "on exactly 24.576 MHz" was really 23.6 MHz. It read back as exact because the framework reports what it asked for. Measured by timing a buffer of known length on the S/PDIF card: ``` 30.000 s of 48000 Hz S16_LE stereo -> 31.362 s before the fix 30.000 s of 48000 Hz S16_LE stereo -> 30.168 s after ``` So anything played at the pin before today was 4% flat, and **nobody could have heard that without a receiver** - which is exactly what this issue is for. Fixed now, but it means the "clocks verified" claim behind the original S/PDIF result was worth less than it looked. Also inherited: the 44.1 kHz family is still 9.5% slow on this same PLL, **#52**. Any bitstream check at 44.1 kHz will be measuring that bug, so **test at 48 kHz.** Neither half of this issue is resolved - still nothing has confirmed a signal on PH18, and the card-detect test on PH17 is still untouched.
Author
Owner

Part 2 (PH17 card detect) is done — only S/PDIF remains

cd-gpios replaced the broken-cd polling in commit 97a6d25, and today it was exercised properly rather than just configured:

  • the driver claims the pin at probe: sunxi-mmc 1c0f000.mmc: Got CD GPIO
  • a card was hot-inserted into a running board and appeared without a reboot:
    mmc0: new high speed SDHC card at address b368
    mmcblk0: mmc0:b368 USD00 29.5 GiB
     mmcblk0: p1
    
  • it was then unmounted and the board rebooted onto it, several times

So PH17 tracks presence, and sys_config.fex's PH18 claim is confirmed wrong for a third subsystem after USB, WiFi and the A15 rail.

Still open, and still blocked-physical: part 1, whether a bitstream actually leaves PH18. That needs an S/PDIF receiver on the jack or a scope on the pin, and it also needs someone to confirm the Telos even fits the connector — "HDMI, AV, SPDIF" in 01-hardware.md is still [REPORTED], from a teardown of the Meta variant.

## Part 2 (PH17 card detect) is done — only S/PDIF remains `cd-gpios` replaced the `broken-cd` polling in commit `97a6d25`, and today it was exercised properly rather than just configured: - the driver claims the pin at probe: `sunxi-mmc 1c0f000.mmc: Got CD GPIO` - a card was **hot-inserted** into a running board and appeared without a reboot: ``` mmc0: new high speed SDHC card at address b368 mmcblk0: mmc0:b368 USD00 29.5 GiB mmcblk0: p1 ``` - it was then unmounted and the board rebooted onto it, several times So PH17 tracks presence, and `sys_config.fex`'s PH18 claim is confirmed wrong for a third subsystem after USB, WiFi and the A15 rail. **Still open, and still `blocked-physical`:** part 1, whether a bitstream actually leaves PH18. That needs an S/PDIF receiver on the jack or a scope on the pin, and it also needs someone to confirm the Telos even fits the connector — "HDMI, AV, SPDIF" in `01-hardware.md` is still `[REPORTED]`, from a teardown of the *Meta* variant.
tiagoagueda changed title from Verify at the board: S/PDIF output, and SD card detect on PH17 to Verify at the board: S/PDIF output (PH17 card detect: done) 2026-08-29 12:35:07 +00:00
Author
Owner

Verified at the board, and it was not working

This issue was blocked-physical waiting for someone to listen to the connector. Doing that
found a driver bug that no register on the board reveals.

S/PDIF output now works. Kernel 7.2.0-14859-g613f14be7ca6, deployed and promoted to known-good.

What was wrong

The A80 TX FIFO consumes one 32-bit word per IEC 958 subframe, and takes the sample from the
top of that word
. sun4i-spdif advertises four formats and maps S24_LE and S32_LE to the
same FMT24BIT as though interchangeable. Only one of the four lines up:

format hardware behaviour heard as
S16_LE (16-bit DMA, as upstream) FIFO does not latch 16-bit accesses noise on a valid LPCM carrier
S16_LE (32-bit DMA) packed L/R share a word → DMA drained 2x double speed
S20_3LE, S24_LE sample LSB-aligned → hardware clocks out padding silence
S32_LE sample fills the word correct

Measured, which is what made the double-speed case provable:

S32 : ALSA drains 44101 frames/s,  SPDIF emits 88206 subframes/s   ratio 2.000  correct
S16 : ALSA drains 88200 frames/s,  SPDIF emits 88265 subframes/s   ratio 1.001  2x too fast

The transmitter is correct in both cases - 88200 subframes/s is exactly 44100 stereo frames.
It is the buffer that drains at the wrong rate.

Why it needed ears

⚠️ TXCFG, FCTL, FSTA and the channel status bytes read identically for the correct,
silent and double-speed cases. No error bit, no underrun, zero xruns. A register diff between a
working and a broken configuration returns nothing.

⚠️ A sine wave cannot detect this. speaker-test produced an audible tone, and that was
briefly taken as proof the path worked. It is not - a mangled sine still sounds like a tone at
roughly the right pitch. Only music exposes it, and the first real signal was hearing "pulsed
gibberish" instead of a clean track.

Evidence for closure

  • pinmux: pin 242 (PH18): 6001000.spdif (GPIO UNCLAIMED) function spdif group PH18
  • stream: state: RUNNING, rate: 44100 (44100/1), 0 xruns, TXCNT advancing at 88200
    subframes/s
  • isolation: HDMI card closed throughout, so audio left via PH18 and nowhere else
  • listening: confirmed correct at the receiver, with the failure modes above each reproduced
    and then eliminated

Fix

patches/linux-asoc-sun9i-spdif/0002-*.patch - a quirk describing the FIFO behaviour and a mask
removing the formats it cannot carry, so userspace converts to S32_LE. plughw:1,0 on an
ordinary 16-bit file plays correctly; hw:1,0 with S16 now fails honestly with "Sample format
non available"
rather than playing at double speed.

Worth noting for anyone touching this: the mask is applied to sun4i_spdif_dai, because that is
what is passed to devm_snd_soc_register_component(). host->cpu_dai_drv is memcpy'd and
renamed in probe and then never registered - so the obvious place to put this change has no
effect at all. Arguably a second, latent bug.

This is an upstream candidate: it affects any sun9i board, and the quirk is small. Full write-up
in 59-spdif-s32-only.md, including two wrong turns I made on the way, both of which looked
correct from the measurements.

Not covered by this issue

AC-3 / DTS passthrough remains untested. IEC 61937 frames the compressed stream as 16-bit
stereo - exactly what this FIFO cannot carry - so it needs the payload pre-expanded to 32-bit
words. It also needs the IEC 958 non-audio bit, and amixer cset on IEC958 Playback Default
does not stick (reads back AES0=0x02 regardless), so the driver never sets TXCFG bit 16.
Without that flag a failure is indistinguishable from noise. Worth its own issue if passthrough
is wanted.

## Verified at the board, and it was not working This issue was `blocked-physical` waiting for someone to listen to the connector. Doing that found a driver bug that **no register on the board reveals**. S/PDIF output now works. Kernel `7.2.0-14859-g613f14be7ca6`, deployed and promoted to known-good. ## What was wrong The A80 TX FIFO consumes **one 32-bit word per IEC 958 subframe, and takes the sample from the top of that word**. `sun4i-spdif` advertises four formats and maps `S24_LE` and `S32_LE` to the same `FMT24BIT` as though interchangeable. Only one of the four lines up: | format | hardware behaviour | heard as | |---|---|---| | `S16_LE` (16-bit DMA, as upstream) | FIFO does not latch 16-bit accesses | noise on a valid LPCM carrier | | `S16_LE` (32-bit DMA) | packed L/R share a word → DMA drained 2x | double speed | | `S20_3LE`, `S24_LE` | sample LSB-aligned → hardware clocks out padding | **silence** | | **`S32_LE`** | sample fills the word | **correct** | Measured, which is what made the double-speed case provable: ``` S32 : ALSA drains 44101 frames/s, SPDIF emits 88206 subframes/s ratio 2.000 correct S16 : ALSA drains 88200 frames/s, SPDIF emits 88265 subframes/s ratio 1.001 2x too fast ``` The transmitter is correct in **both** cases - 88200 subframes/s is exactly 44100 stereo frames. It is the buffer that drains at the wrong rate. ## Why it needed ears ⚠️ `TXCFG`, `FCTL`, `FSTA` and the channel status bytes read **identically** for the correct, silent and double-speed cases. No error bit, no underrun, zero xruns. A register diff between a working and a broken configuration returns nothing. ⚠️ **A sine wave cannot detect this.** `speaker-test` produced an audible tone, and that was briefly taken as proof the path worked. It is not - a mangled sine still sounds like *a tone* at roughly the right pitch. Only music exposes it, and the first real signal was hearing "pulsed gibberish" instead of a clean track. ## Evidence for closure - **pinmux**: `pin 242 (PH18): 6001000.spdif (GPIO UNCLAIMED) function spdif group PH18` - **stream**: `state: RUNNING`, `rate: 44100 (44100/1)`, 0 xruns, `TXCNT` advancing at 88200 subframes/s - **isolation**: HDMI card `closed` throughout, so audio left via PH18 and nowhere else - **listening**: confirmed correct at the receiver, with the failure modes above each reproduced and then eliminated ## Fix `patches/linux-asoc-sun9i-spdif/0002-*.patch` - a quirk describing the FIFO behaviour and a mask removing the formats it cannot carry, so userspace converts to `S32_LE`. `plughw:1,0` on an ordinary 16-bit file plays correctly; `hw:1,0` with S16 now fails honestly with *"Sample format non available"* rather than playing at double speed. Worth noting for anyone touching this: the mask is applied to `sun4i_spdif_dai`, because that is what is passed to `devm_snd_soc_register_component()`. `host->cpu_dai_drv` is `memcpy`'d and renamed in probe and then **never registered** - so the obvious place to put this change has no effect at all. Arguably a second, latent bug. This is an upstream candidate: it affects any sun9i board, and the quirk is small. Full write-up in `59-spdif-s32-only.md`, including two wrong turns I made on the way, both of which looked correct from the measurements. ## Not covered by this issue **AC-3 / DTS passthrough remains untested.** IEC 61937 frames the compressed stream as 16-bit stereo - exactly what this FIFO cannot carry - so it needs the payload pre-expanded to 32-bit words. It also needs the IEC 958 non-audio bit, and `amixer cset` on `IEC958 Playback Default` does not stick (reads back `AES0=0x02` regardless), so the driver never sets `TXCFG` bit 16. Without that flag a failure is indistinguishable from noise. Worth its own issue if passthrough is wanted.
Author
Owner

Correction: the root cause above is wrong

Leaving the previous comment in place rather than editing it, because the wrong turns are part of
the record - but do not act on it. It named the format alignment as the cause and the fix as a
format mask. Both were symptoms.

The actual cause is one bit. sun4i_spdif_configure() programs FCTL.TXIM once per device:

/* Valid data at the MSB of TXFIFO Register */
regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, SUN4I_SPDIF_FCTL_TXIM, 0);

That bit selects where the FIFO takes valid data from, and the correct setting depends on the
sample width, not the device. A 16-bit sample written by a 16-bit DMA access sits in the low
half of the word; a 32-bit one fills it. Programmed once it can only ever suit one width:

TXIM clear (mainline) TXIM set (vendor)
S16_LE noise on a valid LPCM carrier correct
S24_LE silence -
S32_LE correct noise

Now set in hw_params alongside the format, under a quirk. All formats work. Verified in both
directions - each is reproducibly broken with TXIM pinned the wrong way.

Kernel 7.2.0-14859-gc545046f1f86, deployed and promoted to known-good. Patch rewritten as
patches/linux-asoc-sun9i-spdif/0002-*.patch; the earlier format-mask patch is gone, not kept
alongside. Write-up in 59-spdif-txim.md (renamed - the old title asserted "only S32 works", which
is no longer true).

This is a genuine mainline bug for sun9i, not a board quirk, which makes the upstream case
stronger than when this issue was closed.

How it was found, since it generalises

Four hypotheses died on the way - widening the DMA, dropping S16, dropping S24, and setting TXIM
globally. Every one was killed by the listener, not by an instrument, because every register on
this block reads identically for a stream that is music, noise or silence: no error bit, no
underrun, zero xruns. A register diff between working and broken returns nothing.

And a sine cannot find it. speaker-test produced an audible tone through a badly broken
configuration and that was briefly taken as proof the path worked - a mangled sine still sounds
like a tone at roughly the right pitch.

The answer came from the vendor sun9i driver in the CC-A80 tree archived under #56 while
hunting the PowerVR DDK, not from searching the web. That archive has now paid for itself twice in
one day.

Two traps recorded in the note for whoever is here next: amixer cset silently writes the wrong
bytes to an IEC958 control (use iecset), and reading these registers while the block is
clock-gated returns 0x2 for every one of them.

## Correction: the root cause above is wrong Leaving the previous comment in place rather than editing it, because the wrong turns are part of the record - but do not act on it. It named the format alignment as the cause and the fix as a format mask. Both were symptoms. **The actual cause is one bit.** `sun4i_spdif_configure()` programs `FCTL.TXIM` once per device: ```c /* Valid data at the MSB of TXFIFO Register */ regmap_update_bits(host->regmap, SUN4I_SPDIF_FCTL, SUN4I_SPDIF_FCTL_TXIM, 0); ``` That bit selects where the FIFO takes valid data from, and the correct setting depends on the **sample width**, not the device. A 16-bit sample written by a 16-bit DMA access sits in the low half of the word; a 32-bit one fills it. Programmed once it can only ever suit one width: | | `TXIM` clear (mainline) | `TXIM` set (vendor) | | --- | --- | --- | | `S16_LE` | noise on a valid LPCM carrier | correct | | `S24_LE` | silence | - | | `S32_LE` | correct | noise | Now set in `hw_params` alongside the format, under a quirk. **All formats work.** Verified in both directions - each is reproducibly broken with `TXIM` pinned the wrong way. Kernel `7.2.0-14859-gc545046f1f86`, deployed and promoted to known-good. Patch rewritten as `patches/linux-asoc-sun9i-spdif/0002-*.patch`; the earlier format-mask patch is gone, not kept alongside. Write-up in `59-spdif-txim.md` (renamed - the old title asserted "only S32 works", which is no longer true). This is a genuine **mainline bug for sun9i**, not a board quirk, which makes the upstream case stronger than when this issue was closed. ## How it was found, since it generalises Four hypotheses died on the way - widening the DMA, dropping S16, dropping S24, and setting `TXIM` globally. **Every one was killed by the listener, not by an instrument**, because every register on this block reads identically for a stream that is music, noise or silence: no error bit, no underrun, zero xruns. A register diff between working and broken returns nothing. And a sine cannot find it. `speaker-test` produced an audible tone through a badly broken configuration and that was briefly taken as proof the path worked - a mangled sine still sounds like a tone at roughly the right pitch. The answer came from the **vendor sun9i driver** in the CC-A80 tree archived under #56 while hunting the PowerVR DDK, not from searching the web. That archive has now paid for itself twice in one day. Two traps recorded in the note for whoever is here next: `amixer cset` silently writes the wrong bytes to an IEC958 control (use `iecset`), and reading these registers while the block is clock-gated returns `0x2` for every one of them.
Sign in to join this conversation.
No description provided.