* Re: [BUG] ASoC: tas2783-sdw: every amp on the link selects the same channel, so a two-amp board plays mono [not found] ` <02f2cd9c-519c-4154-be58-9065f34c6001@linux.dev> @ 2026-08-06 18:43 ` Robin Everaars 2026-08-06 20:30 ` Pierre-Louis Bossart 0 siblings, 1 reply; 3+ 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] 3+ 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 ` [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 20:30 ` Pierre-Louis Bossart 2026-08-07 5:21 ` Andrey Golovko 0 siblings, 1 reply; 3+ 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] 3+ 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; 3+ 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] 3+ messages in thread
end of thread, other threads:[~2026-08-07 5:22 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <20260805183517.8665-1-robineveraars@pm.me>
[not found] ` <02f2cd9c-519c-4154-be58-9065f34c6001@linux.dev>
2026-08-06 18:43 ` [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 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