* [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono
@ 2026-08-05 18:35 Robin Everaars
2026-08-06 8:47 ` Pierre-Louis Bossart
0 siblings, 1 reply; 5+ messages in thread
From: Robin Everaars @ 2026-08-05 18:35 UTC (permalink / raw)
To: Shenghao Ding, Kevin Lu, Baojun Xu, Niranjan H Y, Mark Brown,
Liam Girdwood, linux-sound
[-- Attachment #1.1: Type: text/plain, Size: 20183 bytes --]
Hi,
On an ASUS ProArt PX13 HN7306EAC (AMD Strix Halo, amd_sdw_manager, two
TAS2783 plus an rt721-sdca on one SoundWire link) both TAS2783
amplifiers reproduce the stream's LEFT channel. The right channel is
delivered on the bus, received by both amps and never rendered by any
speaker. The machine plays mono, at full volume, with no error anywhere
in the log.
The cause is a single unconditional write. tas2783_init_seq[0] writes
0x01 to SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0) on every amp
that probes (sound/soc/codecs/tas2783-sdw.c:306 in v7.1.5, :308 in
today's mainline). That register appears to be the per-amp output
channel selector, and nothing in the driver ever gives it a different
value per device.
We are reporting the behaviour and the evidence rather than sending a
patch, because the last step needs information only TI has: the
authoritative meaning and value encoding of that control, and where a
per-amp value is supposed to come from. The specific
questions are at
the end.
There is one thing we would ask you to read even if you skip the rest:
the obvious-looking fix, keying the FU21 mute channel per amp, is a
measured dead end that breaks audio after resume. That section is
below, with the kernel's own error code, so nobody repeats it.
Hardware and software
=====================
Machine: ASUS ProArt PX13 HN7306EAC
DMI: sys_vendor ASUS, product_name "ProArt PX13 HN7306EAC",
board_name HN7306EAC, product_version 1.0,
BIOS HN7306EAC.307 dated 01/27/2026
SoC: AMD Strix Halo, ACP 7.0
Kernel: 7.1.5 x86_64, no out of tree audio patches
linux-firmware: 20260622
ALSA card: card 1, id "amdsoundwire", driver "amd-soundwire",
longname
"ASUSTeKCOMPUTERINC.-ProArtPX13HN7306EAC-1.0-HN7306EAC"
PCI parent: 0000:c4:00.5, vendor 0x1022 device 0x15e2,
subsystem 0x1043:0x1714
Peripherals, all th
ree on sdw-master-0-1 (amd_sdw_manager.1, SDW1):
sdw:0:1:0102:0000:01:8 TAS2783, device_number 3, slave-tas2783
sdw:0:1:0102:0000:01:b TAS2783, device_number 2, slave-tas2783
sdw:0:1:025d:0721:01 RT721 SDCA, rt721-sdca
Mixer controls the two amps expose on this kernel:
numid=9,iface=MIXER,name='tas2783-1 Amp Volume'
numid=10,iface=MIXER,name='tas2783-1 Speaker Volume'
numid=11,iface=MIXER,name='tas2783-2 Amp Volume'
numid=12,iface=MIXER,name='tas2783-2 Speaker Volume'
There is no channel or output selection control of any kind.
The observable defect
=====================
Play a stereo stream. Sound comes out of both speakers, and it is the
left channel of the stream in both. Content that exists only in the
right channel is inaudible. Nothing is logged.
This is easy to miss, and it was missed here for weeks, because every
casual test asks "is there sound" rather than "is there sound in both
channels". On a 13 inch chassis the two sp
eakers are close enough that
listening does not settle it.
How it was measured
===================
Ears were not trusted. The test plays a 2 kHz tone in one channel at a
time through the speakers and measures the acoustic result with the
machine's own DMIC array, scoring each recorded window with a Goertzel
filter at 2 kHz. Figures are Goertzel magnitudes, so they are
comparable within the table and the baseline row is the noise floor.
window mic L mic R verdict
baseline 0.3 0.3 noise floor
left only 4569.0 5358.0 +84 dB over baseline
right only 0.3 0.2 +0.4 dB, indistinguishable from silence
both 4554.0 5359.0 identical to left alone
"both" matching "left only" to within 0.3 percent is the whole finding.
The right channel contributes nothing acoustically.
Both amps are alive and both have firmware. Muting each in turn through
its "tas2783-N Speaker Volume" control (value 0 is -100 dB on that
co
ntrol, so it is a real mute) gives:
configuration mic L mic R conclusion
only tas2783-1 3445.0 1034.0 hotter on mic L, so amp 1 = left
only tas2783-2 1737.0 4616.0 hotter on mic R, so amp 2 = right
both muted 0.4 0.5 floor
So the physical mapping is tas2783-1 to the left speaker and tas2783-2
to the right speaker, which is what asoc_sdw_ti_spk_rtd_init() in
sound/soc/sdw_utils/soc_sdw_ti_amp.c:57-64 already assumes when it maps
name_prefix tas2783-1 to "Left Spk" and tas2783-2 to "Right Spk". The
machine glue has the mapping. The codec driver never receives it.
This is not a dead amp and not a firmware download failure.
What the code does
==================
Line numbers are v7.1.5 unless stated. The same code is in mainline
today, checked against master on 2026-08-05.
1. Both amps receive both bus channels. snd_sdw_params_to_config() sets
port_config->ch_mask = GENMASK(stream_config->ch_count - 1,
0)
(include/sound/sdw.h:46) identically for every peripheral, so each
amp has both channels available and must select one internally.
2. On this platform there is no SDCA function description for the amps,
so the driver takes its static fallback. tas_sdw_probe() only calls
sdca_parse_function() when the peripheral reports an SDCA function
(tas2783-sdw.c:1292-1319). On this machine the SDCA parser rejects
both amps:
acpi device:24: SDCA function UAJ (type 6) at 0x1
acpi device:26: function type only supported as DisCo constant
acpi device:28: function type only supported as DisCo constant
device:24 is the RT721. device:26 and device:28 are the two amps.
That message is sound/soc/sdca/sdca_functions.c:138, emitted when
mipi-sdca-control-0x5-subproperties carries no
mipi-sdca-control-dc-value, so the function type cannot be read. The
result is num_functions == 0 for both amps, tas_dev->sa_func_data
stays NULL, and n
o Channel Cluster table is parsed
(mipi-sdca-cluster-id-list, sdca_functions.c:2012).
3. With sa_func_data NULL, tas_io_init() writes the static sequence
instead of the DisCo init table (tas2783-sdw.c:1174-1179):
if (tas_dev->sa_func_data)
ret = sdca_regmap_write_init(dev, tas_dev->regmap,
tas_dev->sa_func_data);
else
ret = regmap_multi_reg_write(tas_dev->regmap,
tas2783_init_seq,
ARRAY_SIZE(tas2783_init_seq));
4. The first entry of that sequence is the same value for every amp
(tas2783-sdw.c:306):
REG_SEQ0(SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0x00), 0x01)
TAS2783_SDCA_ENT_PPU21 is 0x10 and the control selector is 0x10
(tas2783.h:66 and tas2783.h:87). Every amp on the link therefore ends
up with the same value in that control, and by the measurement above
that value
corresponds to the left channel.
Adjacent inconsistency in the same register, for whatever it is worth:
tas2783_reg_default[] lists that identical register with a default of
0x0 (tas2783-sdw.c:276), while the init sequence writes 0x01. The
register is readable, non-volatile and cached under REGCACHE_MAPLE, so
the cached default and the value the driver actually programs disagree.
What we ruled out, so you do not have to
========================================
Keying the FU21 mute channel per amp does not work
--------------------------------------------------
tas_fu21_event() writes the FU21 mute with the channel index as a
literal 1 (tas2783-sdw.c:830-831), and tas_fu23_event() carries the same
literal (tas2783-sdw.c:852-853). Since the 4th argument of
SDW_SDCA_CTL is the channel number
(include/linux/soundwire/sdw_registers.h:335-342), and since
TAS2783_DEVICE_CHANNEL_RIGHT is declared at tas2783.h:90 and referenced
nowhere in the driver while TAS2783_DEVI
CE_CHANNEL_LEFT is used at
tas2783-sdw.c:352, this looks like an unfinished per-amp channel
selection. It is not.
We built a kernel that picks the FU21 mute channel per amp from
name_prefix, the same idiom soc_sdw_rt_amp.c uses for the Realtek amps.
It compiled and it landed where intended. It changed nothing
acoustically. The kernel says why:
amd_sdw amd_sdw: ASoC: POST_PMU: tas2783-2 FU21 event failed: -61
-61 is -ENODATA. FU21 on this device has exactly one channel, index 1.
Channel 2 does not exist, so TAS2783_DEVICE_CHANNEL_RIGHT cannot address
FU21 at all. Measurement agreed: with that kernel live, left-only
playback with only amp 2 unmuted was still audible at the same level as
before (1643/4555 against 1643/4616), so amp 2 was still reproducing the
left channel.
It is worse than a no-op. After an s2idle resume that kernel silenced
ALL audio, including the left channel that had been working:
ASoC: POST_PMU: tas2783-2 FU21 event failed: -61
ASoC:
PRE_PMD: tas2783-2 FU21 event failed: -61
tas2783_reg_default[] has the FU21 mute for channel 1 set to muted
(tas2783-sdw.c:187), the amps come back from resume with that applied,
and the mis-targeted unmute to channel 2 fails, so nothing ever unmutes.
FU21's mute is a working mute. It is simply not a channel selector. We
reverted that kernel and are not proposing it.
UDMPU23 ClusterIndex is not it either
-------------------------------------
tas2783_reg_default[] writes the UDMPU23 ClusterIndex as cluster 0
identically for every amp (tas2783-sdw.c:302):
{SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23,
TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0), 0x0}
That was our first suspect after FU21, on the reasoning that per-amp
channel choice ought to come from a cluster. It is per-link constant in
the same way, so it is worth a look, but it is not the register that
third parties have made work. PPU21 is.
This is already reported downstream, with the same root c
ause
============================================================
We searched before writing, and the defect is not new. It is diagnosed
in a distribution issue tracker but has never been reported on this
list, and no patch addressing it has ever been posted. The most useful
prior work:
https://github.com/CachyOS/linux-cachyos/issues/737
"[BUG] ASUS ProArt PX13 HN7306EA - No sound (AMD ACP70 no playback
device)", opened 2026-02-27, 111 comments, still open
Inside that thread:
- 2026-07-06, ftoleedo, comment 4895230923, reaches the same conclusion
independently: "The ASUS ACPI tables carry no usable SDCA/DisCo
function data (function type only supported as DisCo constant in
dmesg), so the driver falls back to a static init that writes DSP
cluster index 0x01 to both amps. Result: mono out of a single
speaker". Same message, same register, same value, different
reporter, different distribution. That comment also states the defect
is still pres
ent in v7.2-rc1.
- Neal Starkie's out of tree patch 0010-control-to-set-channel.patch
exposes exactly that register as an ALSA enum, and it is reported
working by several PX13 owners:
static const unsigned int tas2783_ch_values[] = {
0, /* Off */
1, /* Left */
4, /* Right */
};
static SOC_VALUE_ENUM_SINGLE_DECL(tas2783_ch_enum,
SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21,
SDCA_CTL_UDMPU_CLUSTERINDEX, 0),
0, 0x7, tas2783_ch_select, tas2783_ch_values);
Users then set 'tas2783-1 Channel Playback' to Left and
'tas2783-2 Channel Playback' to Right and get stereo. Reported
working by ftoleedo, bjuraszewski and bcs-23 in that thread, and
carried as a DKMS module at
https://github.com/ftoleedo/px13-audio-fix (module/tas2783-sdw.c).
To be explicit about provenance: we have not built or measured that
control on this machine. We are quoting it because it names t
he same
register our own source reading and measurements point at, and
because its "1 means left" half is exactly what our acoustic results
show for both amps.
- Neal Starkie has posted to this list once, and it was not this patch:
https://patchwork.kernel.org/project/alsa-devel/patch/20260410170149.238916-1-nealstarkie@gmail.com/
("ALSA: tas2783-sdw: add Playback to volume controls name",
2026-04-10, still in state new). The channel control was never sent
upstream.
What we could not find anywhere, having searched the alsa-devel
patchwork project for tas2783, UDMPU, cluster, mono and stereo, and
marc.info for linux-sound:
- no mailing list report of this defect;
- no patch, merged or posted, that gives the channel selector a
different value per amp;
- no SDCA cluster documentation that pins down the TAS2783 encoding.
The only tas2783 patches currently in flight are two register table
fixes posted on 2026-08-05, neither of which changes this beha
viour:
https://patchwork.kernel.org/project/alsa-devel/patch/20260805091327.23944-1-peter.ujfalusi@linux.intel.com/
"ASoC: tas2783-sdw: drop duplicate reg_default entry"
https://patchwork.kernel.org/project/alsa-devel/patch/20260805091327.23944-2-peter.ujfalusi@linux.intel.com/
"ASoC: tas2783-sdw: sort the register default table"
The second one is worth a note here even though it is unrelated to the
mono defect. It reports that tas2783_reg_default[] is sorted by SDCA
entity rather than by register address, so regcache_lookup_reg()'s
bsearch misses 120 of its 196 entries and those get rewritten on every
regcache_sync(). Both patches keep the PPU21 and UDMPU23 defaults
discussed above unchanged.
The last commit to touch sound/soc/codecs/tas2783-sdw.c in mainline is
e26bb459d0f3 ("ASoC: tas2783: Update loaded firmware names to
linux-firmware 20260519", 2026-06-23), which is the firmware naming fix
and does not touch channel handling.
What we are asking
==================
We are not asking you to take a patch from us. We are asking for the
three facts that decide what the correct patch is, because they are not
derivable from the driver source or from userspace.
1. Is SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0) the intended
per-amp output channel selector on the TAS2783, and what is the
value encoding? The out of tree control above uses 0 for off, 1 for
left and 4 for right over a 3 bit field. That looks like a bitmask
rather than an index. If it is a mask, what does 5 do? Is there a
defined value for "sum both channels" that a mono board should be
using?
2. What does that control actually mean, because the two headers in the
tree disagree? tas2783.h:87 names selector 0x10
TAS2783_SDCA_CTL_UDMPU_CLUSTER, which matches
SDCA_CTL_UDMPU_CLUSTERINDEX (include/sound/sdca_function.h:425). But
the entity written here is PPU21, and for a Post Processing Unit the
SDCA spec assigns selec
tor 0x10 to PostureNumber
(include/sound/sdca_function.h:528). Which of the two meanings does
TAS2783 PPU21 implement at 0x10? A driver that gets
this wrong will keep working by accident and will be impossible to
review.
3. Where is the per-amp value supposed to come from? We can see four
candidates and cannot choose between them from outside:
a. the SDCA Channel Cluster table in the amp's DisCo description.
This is what sdca_asoc.c:1560 set_cluster() already does for
generic SDCA devices: walk the ClusterIndex Range rows, look up
each cluster with sdca_id_find_cluster(), and pick the one whose
channel count and channel relationship match. sdca_channel
already carries GENERIC_LEFT, GENERIC_RIGHT, FRONT_LEFT and
FRONT_RIGHT relationships for exactly this purpose. The driver
already parses and holds a struct sdca_function_data when one is
available, and uses it only for sdca_regmap_write_init(). Should
the cluster lookup be the primary path?
b. the per-amp firmware blob. On this machine the two blobs are
distinct files (ti/audio/tas2783/1714-1-0x8.bin and 1714-1-0xB.bin
in linux-firmware 20260622, 7273 and 7272 bytes), so they could
in principle carry the channel. Do they, and if so does the DSP
apply it, or is the register write still required afterwards?
c. a DisCo or _DSD property naming the channel per peripheral, keyed
on unique_id or on the SoundWire address.
d. the machine driver, since sdw_utils already knows which amp is
which speaker (soc_sdw_ti_amp.c:57-64) and Realtek's equivalent
keys off the name_prefix suffix.
If the answer is (a), then a second question follows for this
platform specifically: the ASUS ACPI tables on this laptop supply no
mipi-sdca-control-dc-value for the function type, so the parse fails
for both amps and there is no cluster table to consult. Is that a
plat
form firmware bug that TI can pursue with ASUS, and until such
machines are fixed, what should the static fallback in
tas2783_init_seq do instead of programming every amp identically?
We can test patches on this machine and can produce per-channel
acoustic numbers rather than impressions for each one, so a fix that
only changes the init path is verifiable here in one boot.
Reproducer
==========
Anyone with a two-amp TAS2783 board can confirm the defect in about 20
seconds with the tone check below. It needs pipewire tools and an
internal mic array, and it needs no second person and no ears.
Quick version, if you have a mic and can listen:
speaker-test -c 2 -t wav -D <speaker device>
The spoken "Front Left" and "Front Right" prompts will both come out of
both speakers, and both will be the left channel of the stream.
Objective version, which is what we used:
1. Generate a stereo wav with four windows: 2 s silence, 3 s of a 2 kHz
tone in the
left channel only, 3 s of the same tone in the right
channel only, then 3 s in both. Put 2 s of silence between windows
and apply a 20 ms fade in and out on each tone so the mic does not
see a click.
2. Start recording from the internal DMIC array, then play the file
through the speaker sink:
pw-record --target @DEFAULT_AUDIO_SOURCE@ --rate 48000 \
--channels 2 --format s16 rec.wav &
pw-play --target @DEFAULT_AUDIO_SINK@ probe.wav
3. Score each window in rec.wav with a Goertzel filter at 2 kHz on both
mic channels, and compare each tone window against the silent
baseline window.
FAIL is the right-only window sitting at the baseline while the
left-only window is far above it, and the both-channels window matching
the left-only window. On this machine that is left-only at +84 dB,
right-only at +0.4 dB.
Our script that does all of the above and prints PASS or FAIL is
available on request. It deliberately reports "audio
works at all" and
"stereo" as two separate verdicts, so that a machine that is known-mono
does not mask a real playback regression.
Thanks,
Confirmed fix, measured on this hardware
========================================
Writing PPU21 selector 0x10 per amp does fix it. I derive the value from
component->name_prefix, so tas2783-1 and -3 get 0x01 (Left) and -2 and -4
get 0x04 (Right), matching the mapping asoc_sdw_ti_spk_rtd_init() already
applies in sound/soc/sdw_utils/soc_sdw_ti_amp.c.
Acoustic result on the same measurement rig as above, right channel only:
before +0.4 dB over the noise floor, i.e. silence
after +70.7 dB, and the left/right imbalance is 0.3 dB
It also survives an s2idle cycle, which matters here: a driver re-bind re-
runs tas2783_init_seq and would put 0x01 back, so the value has to be re-
asserted rather than written once. I do it from the POST_PMU path for that
reason.
One implementation note that cost me a working machine on
the first attempt.
A failed channel write should warn and continue. Returning the error from
the DAPM event is the trap: that aborts the widget power-up and costs ALL
audio, while losing the channel select only costs stereo. My first attempt
at this defect went after the FU21 mute channel, returned -ENODATA from the
event, and silenced the machine after every resume.
I am happy to post this as a patch if that shape is wanted. I have
deliberately not sent one yet, because the correct source of the per-amp
assignment is the question below and name_prefix may not be the answer you
want upstream.
[-- Attachment #1.2: publickey - robineveraars@pm.me - 0x8B6BA132.asc --]
[-- Type: application/pgp-keys, Size: 889 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 322 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono
2026-08-05 18:35 [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono Robin Everaars
@ 2026-08-06 8:47 ` Pierre-Louis Bossart
2026-08-06 18:43 ` Robin Everaars
0 siblings, 1 reply; 5+ messages in thread
From: Pierre-Louis Bossart @ 2026-08-06 8:47 UTC (permalink / raw)
To: Robin Everaars, Shenghao Ding, Kevin Lu, Baojun Xu, Niranjan H Y,
Mark Brown, Liam Girdwood, linux-sound
> Confirmed fix, measured on this hardware
> ========================================
>
> Writing PPU21 selector 0x10 per amp does fix it. I derive the value from
> component->name_prefix, so tas2783-1 and -3 get 0x01 (Left) and -2 and -4
> get 0x04 (Right), matching the mapping asoc_sdw_ti_spk_rtd_init() already
> applies in sound/soc/sdw_utils/soc_sdw_ti_amp.c.
you mentioned a two-amp board but the wording seems to describe 4 amps?
Also not sure what mapping you are referring to, I looked up the code
and couldn't connect the dots between the last sentence and the code.
> Acoustic result on the same measurement rig as above, right channel only:
>
> before +0.4 dB over the noise floor, i.e. silence
> after +70.7 dB, and the left/right imbalance is 0.3 dB
>
> It also survives an s2idle cycle, which matters here: a driver re-bind re-
> runs tas2783_init_seq and would put 0x01 back, so the value has to be re-
> asserted rather than written once. I do it from the POST_PMU path for that
> reason.
This may work but that isn't the intended role of the PPU (Posture
Processing Unit). The spec says
"
PostureNumber (PPU)
This Control is provided in the PPU for selecting the pre-defined
mapping between input Channels and output
Channels and applying any Posture-related processing.
"
In theory the *same* posture number should be given to both amps, and
they shall modify their input to output mapping accordingly.
For aggregated amps (which is the case here), the spec also says the
PostureNumber register needs to be dual-ranked for synchronous changes
but that part isn't managed by the SoundWire/SDCA core just yet.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono
2026-08-06 8:47 ` Pierre-Louis Bossart
@ 2026-08-06 18:43 ` Robin Everaars
2026-08-06 20:30 ` Pierre-Louis Bossart
0 siblings, 1 reply; 5+ messages in thread
From: Robin Everaars @ 2026-08-06 18:43 UTC (permalink / raw)
To: pierre-louis.bossart, shenghao-ding, kevin-lu, baojun.xu,
niranjan.hy, broonie, lgirdwood, linux-sound
Cc: ckeepax, mstrozek, yung-chuan.liao, vkoul, Vijendar.Mukunda,
peter.ujfalusi, sen, nealstarkie, bjuraszewski, andrey.golovko,
linux-kernel
[-- Attachment #1.1: Type: text/plain, Size: 6688 bytes --]
> you mentioned a two-amp board but the wording seems to describe 4 amps?
Two amps: an ASUS ProArt PX13 HN7306EAC with two TAS2783 plus an
rt721-sdca on one link. Every number in the report came from those two.
The four-amp wording leaked in from my patch, which keys off the
name_prefix suffix and also handles -3 and -4. I have no such board, so
that half was an untested guess and should not have been described as
fact.
> Also not sure what mapping you are referring to, I looked up the code
> and couldn't connect the dots between the last sentence and the code.
Because there are no dots to connect. I meant
asoc_sdw_ti_spk_rtd_init() (soc_sdw_ti_amp.c:52-79), which picks a
speaker name from the suffix, -1 "Left Spk", -2 "Right Spk", -3
"Left Spk2", -4 "Right Spk2", and adds a DAPM route per amp. That names
widgets and programs nothing in the amp, so "matching the mapping"
claimed far more than the code does. My description was also wrong in
detail: I wrote "Left Spk
" for -3 where the code says "Left Spk2".
The assignment rests on measurement instead. Muting each amp in turn and
scoring the acoustic result puts amp 1 at the left speaker and amp 2 at
the right on this board.
> This may work but that isn't the intended role of the PPU (Posture
> Processing Unit).
> In theory the same posture number should be given to both amps, and
> they shall modify their input to output mapping accordingly.
Taken. Two things follow that I would like your view on, because the
driver already writes that control.
tas2783_init_seq[0] sets PostureNumber unconditionally on every amp that
probes (tas2783-sdw.c:306 in v7.1.5, :308 in mainline):
REG_SEQ0(SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0x00), 0x01)
while tas2783.h:87 calls selector 0x10 TAS2783_SDCA_CTL_UDMPU_CLUSTER,
i.e. UDMPU ClusterIndex (sdca_function.h:425), against a PPU21 entity
where 0x10 is PostureNumber (sdca_function.h:528). reg_default lists the
same register as 0x0
(tas2783-sdw.c:276). So my patch made an existing
unconditional write per-amp, trusting the local define's name.
1. Should init_seq[0] write PostureNumber at all, and if so is 0x01 right
for a device that does not yet know its posture?
2. For one shared posture number to give stereo, each amp must know which
speaker it is. Where from, on a board like this? The ACPI carries no
parseable SDCA function description for either amp ("function type
only supported as DisCo constant"), so sa_func_data stays NULL, no
Channel Cluster table is parsed and tas_io_init() takes the
regmap_multi_reg_write() branch (tas2783-sdw.c:1174-1179). Nothing
distinguishes the two amps, so one posture number gives both the same
mapping, which is the mono I measured. Broken platform firmware with
nothing for the driver to do but say so, or a driver fallback keyed on
something else?
> For aggregated amps (which is the case here), the spec also says the
> PostureNu
mber register needs to be dual-ranked for synchronous changes
> but that part isn't managed by the SoundWire/SDCA core just yet.
That sent me to the bus, where the dual-ranked handling already exists:
the core writes p_rt->ch_mask to SDW_DPN_CHANNELEN_B0 or _B1 by
bus->params.next_bank (stream.c:329-339), and tas_port_prep() already
forwards the same mask to DPN_PrepareCtrl (tas2783-sdw.c:1239).
Today nothing uses it per amp. snd_sdw_params_to_config() hands every
peripheral GENMASK(ch_count - 1, 0) (include/sound/sdw.h:46) and
tas_sdw_hw_params() never overrides it (tas2783-sdw.c:940), so both amps
enable both channels. rt1308-sdw.c:559 and max98373-sdw.c:541 override it
from a per-device rx_mask via .set_tdm_slot, and
asoc_sdw_cs_spk_feedback_rtd_init() (soc_sdw_cs_amp.c:94-106) already
assigns per-amp masks inside sdw_utils. tas2783 has neither: no
.set_tdm_slot in tas_dai_ops (:978), and soc_sdw_ti_amp.c never calls it.
I built that and measured it, with the PPU
21 patch removed so the result
was attributable. One boot, control first, dB over the noise floor:
configuration left right imbalance
shipped, PPU21 per amp +70.5 +69.7 +0.9 dB
ch_mask per amp +71.3 +71.1 +0.2 dB
For reference the unpatched driver scores +0.4 dB on the right channel,
i.e. silence. PostureNumber stayed 0x01 on BOTH amps throughout, since a
fresh probe re-runs init_seq against a regmap whose cached default is
0x0. So the amp renders whatever arrives on its enabled DP1 channel,
PPU21 does not need a two-channel cluster at IT21, and nothing has to
write PostureNumber per amp.
One catch, and it is why I have not posted a patch. The mask VALUE is not
what selects the channel. sdw_compute_slave_ports() advances the payload
block offset by bps * hweight32(ch_mask) as a running sum over
slave_rt_list, resetting it only for a slave that took every channel,
which it calls mirror mode (generic_bandwidt
h_allocation.c:44-84). I
tested that with a module parameter that inverts the assignment:
amp 1 amp 2 LEFT only, mic L / mic R
0x1 0x2 4282 / 289
0x2 0x1 4615 / 799
The kernel log confirms the masks inverted; the audio did not change. So
what fixes mono is a ONE-channel mask per amp, which stops the mirror-mode
reset and puts the amps at consecutive offsets. Which channel each lands
on follows its position in slave_rt_list, i.e. codec order in the DAI
link.
That leaves a machine driver no way to say "this peripheral renders the
right channel". It can only give each amp one channel and rely on codec
order matching the speakers. Here it does, tas2783-1 is the left speaker
and is first, which is exactly why the mask looked like it was working.
Reverse the enumeration and the speakers swap with nothing in the mask
able to correct it.
Is that positional behaviour intended, and is the missing
channel-to-peripheral binding a core gap? It look
s like the same class of
thing as the dual-ranked PostureNumber. If it is intended, then
.set_tdm_slot in tas2783 plus the assignment in soc_sdw_ti_amp.c is a
complete fix for boards whose codec order matches their speakers and I
will post it. If not, the fix belongs deeper than this codec and I would
rather not paper over it.
Either way the reported behaviour stands: on a two-amp board with no
DisCo data every amp renders the same channel, and no control in the
driver can change it. The PPU21 patch stays a local workaround on my own
machine and I am not proposing it upstream.
Last question, since the PPU came up: this laptop is a convertible with
real postures. Should PostureNumber be driven by the fold state the
tablet-mode switch already reports, rather than written once at init?
[-- Attachment #1.2: publickey - robineveraars@pm.me - 0x8B6BA132.asc --]
[-- Type: application/pgp-keys, Size: 889 bytes --]
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 322 bytes --]
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono
2026-08-06 18:43 ` Robin Everaars
@ 2026-08-06 20:30 ` Pierre-Louis Bossart
2026-08-07 5:21 ` Andrey Golovko
0 siblings, 1 reply; 5+ messages in thread
From: Pierre-Louis Bossart @ 2026-08-06 20:30 UTC (permalink / raw)
To: Robin Everaars, shenghao-ding, kevin-lu, baojun.xu, niranjan.hy,
broonie, lgirdwood, linux-sound
Cc: ckeepax, mstrozek, yung-chuan.liao, vkoul, Vijendar.Mukunda,
peter.ujfalusi, sen, nealstarkie, bjuraszewski, andrey.golovko,
linux-kernel
On 8/6/26 20:43, Robin Everaars wrote:
>> you mentioned a two-amp board but the wording seems to describe 4 amps?
>
> Two amps: an ASUS ProArt PX13 HN7306EAC with two TAS2783 plus an
> rt721-sdca on one link. Every number in the report came from those two.
> The four-amp wording leaked in from my patch, which keys off the
> name_prefix suffix and also handles -3 and -4. I have no such board, so
> that half was an untested guess and should not have been described as
> fact.
>
>> Also not sure what mapping you are referring to, I looked up the code
>> and couldn't connect the dots between the last sentence and the code.
>
> Because there are no dots to connect. I meant
> asoc_sdw_ti_spk_rtd_init() (soc_sdw_ti_amp.c:52-79), which picks a
> speaker name from the suffix, -1 "Left Spk", -2 "Right Spk", -3
> "Left Spk2", -4 "Right Spk2", and adds a DAPM route per amp. That names
> widgets and programs nothing in the amp, so "matching the mapping"
> claimed far more than the code does. My description was also wrong in
> detail: I wrote "Left Spk
> " for -3 where the code says "Left Spk2".
ok.
> The assignment rests on measurement instead. Muting each amp in turn and
> scoring the acoustic result puts amp 1 at the left speaker and amp 2 at
> the right on this board.
>
>> This may work but that isn't the intended role of the PPU (Posture
>> Processing Unit).
>> In theory the same posture number should be given to both amps, and
>> they shall modify their input to output mapping accordingly.
>
> Taken. Two things follow that I would like your view on, because the
> driver already writes that control.
>
> tas2783_init_seq[0] sets PostureNumber unconditionally on every amp that
> probes (tas2783-sdw.c:306 in v7.1.5, :308 in mainline):
>
> REG_SEQ0(SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PPU21, 0x10, 0x00), 0x01)
>
> while tas2783.h:87 calls selector 0x10 TAS2783_SDCA_CTL_UDMPU_CLUSTER,
> i.e. UDMPU ClusterIndex (sdca_function.h:425), against a PPU21 entity
> where 0x10 is PostureNumber (sdca_function.h:528). reg_default lists the
> same register as 0x0
> (tas2783-sdw.c:276). So my patch made an existing
> unconditional write per-amp, trusting the local define's name.
>
> 1. Should init_seq[0] write PostureNumber at all, and if so is 0x01 right
> for a device that does not yet know its posture?
The PostureNumber is just an index in a Posture Table, if the posture
isn't known there's probably a default. The value for postures is not
defined in the spec, it depends on every OEM and BIOS vendor but would
have to be generated by the codec vendor.
> 2. For one shared posture number to give stereo, each amp must know which
> speaker it is. Where from, on a board like this? The ACPI carries no
> parseable SDCA function description for either amp ("function type
> only supported as DisCo constant"), so sa_func_data stays NULL, no
> Channel Cluster table is parsed and tas_io_init() takes the
> regmap_multi_reg_write() branch (tas2783-sdw.c:1174-1179). Nothing
> distinguishes the two amps, so one posture number gives both the same
> mapping, which is the mono I measured. Broken platform firmware with
> nothing for the driver to do but say so, or a driver fallback keyed on
> something else?
No. Posture is a generalized concept of orientation (landscape v.
portrait modes), this was introduced to deal with flexible shapes where
a laptop may be used as a tablet or in 'tent' mode.
The knowledge of where speakers are is a prerequisite for the Posture
selection. Note that the mapping from channels to speakers is not
necessarily a selection, there could be processing/mixing.
>> For aggregated amps (which is the case here), the spec also says the
>> PostureNu
> mber register needs to be dual-ranked for synchronous changes
>> but that part isn't managed by the SoundWire/SDCA core just yet.
>
> That sent me to the bus, where the dual-ranked handling already exists:
> the core writes p_rt->ch_mask to SDW_DPN_CHANNELEN_B0 or _B1 by
> bus->params.next_bank (stream.c:329-339), and tas_port_prep() already
> forwards the same mask to DPN_PrepareCtrl (tas2783-sdw.c:1239).
No, dual-ranked registers are not the same thing as banked registers
used for transport dynamic changes.
> Today nothing uses it per amp. snd_sdw_params_to_config() hands every
> peripheral GENMASK(ch_count - 1, 0) (include/sound/sdw.h:46) and
> tas_sdw_hw_params() never overrides it (tas2783-sdw.c:940), so both amps
> enable both channels. rt1308-sdw.c:559 and max98373-sdw.c:541 override it
> from a per-device rx_mask via .set_tdm_slot, and
> asoc_sdw_cs_spk_feedback_rtd_init() (soc_sdw_cs_amp.c:94-106) already
> assigns per-amp masks inside sdw_utils. tas2783 has neither: no
> .set_tdm_slot in tas_dai_ops (:978), and soc_sdw_ti_amp.c never calls it.
The assumption is indeed that all channels are provided to the amp. The
amp has to either select or process what it sends to the output, each
amp is supposed to do something different.
> I built that and measured it, with the PPU
> 21 patch removed so the result
> was attributable. One boot, control first, dB over the noise floor:
>
> configuration left right imbalance
> shipped, PPU21 per amp +70.5 +69.7 +0.9 dB
> ch_mask per amp +71.3 +71.1 +0.2 dB
>
> For reference the unpatched driver scores +0.4 dB on the right channel,
> i.e. silence. PostureNumber stayed 0x01 on BOTH amps throughout, since a
> fresh probe re-runs init_seq against a regmap whose cached default is
> 0x0. So the amp renders whatever arrives on its enabled DP1 channel,
> PPU21 does not need a two-channel cluster at IT21, and nothing has to
> write PostureNumber per amp.
>
> One catch, and it is why I have not posted a patch. The mask VALUE is not
> what selects the channel. sdw_compute_slave_ports() advances the payload
> block offset by bps * hweight32(ch_mask) as a running sum over
> slave_rt_list, resetting it only for a slave that took every channel,
> which it calls mirror mode (generic_bandwidt
> h_allocation.c:44-84). I
> tested that with a module parameter that inverts the assignment:
>
> amp 1 amp 2 LEFT only, mic L / mic R
> 0x1 0x2 4282 / 289
> 0x2 0x1 4615 / 799
>
> The kernel log confirms the masks inverted; the audio did not change. So
> what fixes mono is a ONE-channel mask per amp, which stops the mirror-mode
> reset and puts the amps at consecutive offsets. Which channel each lands
> on follows its position in slave_rt_list, i.e. codec order in the DAI
> link.
>
> That leaves a machine driver no way to say "this peripheral renders the
> right channel". It can only give each amp one channel and rely on codec
> order matching the speakers. Here it does, tas2783-1 is the left speaker
> and is first, which is exactly why the mask looked like it was working.
> Reverse the enumeration and the speakers swap with nothing in the mask
> able to correct it.
My understanding is that firmware is supposed to know which amp is
which, and provide different settings.
> Is that positional behaviour intended, and is the missing
> channel-to-peripheral binding a core gap? It look
> s like the same class of
> thing as the dual-ranked PostureNumber. If it is intended, then
> .set_tdm_slot in tas2783 plus the assignment in soc_sdw_ti_amp.c is a
> complete fix for boards whose codec order matches their speakers and I
> will post it. If not, the fix belongs deeper than this codec and I would
> rather not paper over it.
>
> Either way the reported behaviour stands: on a two-amp board with no
> DisCo data every amp renders the same channel, and no control in the
> driver can change it. The PPU21 patch stays a local workaround on my own
> machine and I am not proposing it upstream.
I am not sure how each amp can know how to behave is there's no DisCo
data at all...
Maybe the mapping is implicit and defined by the Unique Number
I am not sure though how that Unique Number maps to the -1 and -2 suffix
sdw:0:1:0102:0000:01:8 TAS2783, device_number 3, slave-tas2783
sdw:0:1:0102:0000:01:b TAS2783, device_number 2, slave-tas2783
which one is left and which one is right?
> Last question, since the PPU came up: this laptop is a convertible with
> real postures. Should PostureNumber be driven by the fold state the
> tablet-mode switch already reports, rather than written once at init?
See explanation above, the PostureNumber is supposed to be dynamic based
on orientation, etc. This is NOT static channel selection, the output
can be a selection or processing of the input channel.
That said, we are missing a link between the sensors subsystems and ALSA
to determine that postureNumber, so take my explanations with a grain of
salt, there's currently no way to automagically update the posture number.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono
2026-08-06 20:30 ` Pierre-Louis Bossart
@ 2026-08-07 5:21 ` Andrey Golovko
0 siblings, 0 replies; 5+ messages in thread
From: Andrey Golovko @ 2026-08-07 5:21 UTC (permalink / raw)
To: Pierre-Louis Bossart, Robin Everaars
Cc: shenghao-ding, kevin-lu, baojun.xu, niranjan.hy, broonie,
lgirdwood, linux-sound, ckeepax, mstrozek, yung-chuan.liao, vkoul,
Vijendar.Mukunda, peter.ujfalusi, sen, nealstarkie, bjuraszewski,
Antoine Monnet, linux-kernel
On 8/6/26 22:30, Pierre-Louis Bossart wrote:
> Maybe the mapping is implicit and defined by the Unique Number
>
> I am not sure though how that Unique Number maps to the -1 and -2 suffix
>
> sdw:0:1:0102:0000:01:8 TAS2783, device_number 3, slave-tas2783
> sdw:0:1:0102:0000:01:b TAS2783, device_number 2, slave-tas2783
>
> which one is left and which one is right?
Measured on a second HN7306EAC (I'm the andrey.golovko in Cc - same
machine, same two amps at unique ID 0x8/0xB): 0x8 is the left speaker,
0xB the right. Confirmed by muting each amp's volume control in turn,
and by speaker-test with a per-amp one-channel mask applied: channel 1
comes out of the physically left speaker only, channel 2 out of the
right only. That matches Robin's amp 1 = left, amp 2 = right.
The Unique Number to -1/-2 suffix mapping is not implicit - it is
hardcoded in the kernel's machine descriptor. amd-acp70-acpi-match.c
has, for exactly this platform (rt721_l1u0_tas2783x2_l1u8b_adr):
.adr = 0x0001380102000001 -> .name_prefix = "tas2783-1", spk_l_endpoint
.adr = 0x00013B0102000001 -> .name_prefix = "tas2783-2", spk_r_endpoint
where spk_l_endpoint/spk_r_endpoint are aggregated endpoints with
group_position 0 and 1. So a channel-to-peripheral binding does exist
in the tree, keyed off the _ADR unique ID and complete with a
left/right position - it is just not plumbed into playback port
allocation: nothing turns group_position into a per-peripheral mask or
offset. device_number (2/3 here) is attach order and indeed cannot be
relied on; the unique ID via the match table can.
For cross-reference: the same mono issue on this machine was reported
in July by Antoine Monnet (added to Cc), with an inline patch of the
same shape as Robin's - per-amp one-channel mask derived from the
name_prefix suffix:
https://lore.kernel.org/all/778f017a-e1c2-4ab6-9968-6e4c6285180b@montane.tech/
I have been running it since late July with correct physical L/R on my
unit (Tested-by in that thread). Robin's finding that the mask value
is ignored and only its weight matters applies to that patch equally:
it works because the DAI-link codec order follows the adr_d table
order above, which happens to match the speakers. Given that the
descriptor already carries group_position per unique ID, deriving the
assignment from that rather than from parsing the name_prefix suffix
looks like the natural fix, and it would keep working if enumeration
order ever changed.
Thanks,
Andrey
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-08-07 5:22 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-05 18:35 [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono Robin Everaars
2026-08-06 8:47 ` Pierre-Louis Bossart
2026-08-06 18:43 ` Robin Everaars
2026-08-06 20:30 ` Pierre-Louis Bossart
2026-08-07 5:21 ` Andrey Golovko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox