sound/soc/codecs/tas2783-sdw.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+)
A Data Port cannot complete channel preparation while the SDCA Function
is powered down: the peripheral raises the channel's bit in
DPn_PrepareStatus and never clears it.
tas_sdw_hw_params() takes care of that for a stream that is being set
up, and the retry loop there says so - "ensure power on so that port
prepare succeeds". Port preparation, however, also happens on a stream
that is merely re-prepared, without hw_params() running again. That is
what userspace does after a suspend in which the peripheral lost power:
snd_pcm_prepare() reaches .prepare and sdw_prepare_stream(), the port is
prepared afresh, but PDE23 is still at the PS3 reset default because
nothing wrote it since the device came back.
The result is silence with no error anywhere. The codec sets
simple_ch_prep_sm, so sdw_prep_deprep_slave_port() skips the
NOT_PREPARED poll, and a port that never prepares is indistinguishable
from a healthy one.
Power the Function up in the PRE_PREP callback, immediately before the
PrepareCtrl write it already performs, so that preparation has what it
needs on every path that prepares a port.
Measured on an ASUS ProArt PX13 (AMD ACP7.0, two TAS2783): after s2idle
with ~100 s of S0i3 residency, DPn_PrepareStatus stays at the channel
mask and there is no audio; writing PDE23 PS0 and re-issuing the prepare
clears it within 1 ms and audio returns.
Signed-off-by: Andrey Golovko <andrey.golovko@gmail.com>
---
Measured on ASUS ProArt PX13 HN7306EAC (AMD ACP7.0, two TAS2783 at unique
0x8/0xB plus an rt721-sdca on link 1), on broonie/sound for-next.
Before, after an s2idle cycle in which the amplifiers really were powered
off, with no userspace workaround running:
PDE23 req=0x3 act=0x3 DPn_PrepareStatus 0x1 / 0x2 no audio
With this patch, same machine, an 8 min 51 s cycle with 526 s of S0i3
residency:
PDE23 req=0x0 act=0x0 DPn_PrepareStatus 0x0 / 0x0 audio works
Full analysis of the failure, including a reproduction that talks to the
peripheral directly and needs no ALSA at all, is in this thread:
https://lore.kernel.org/all/20260812192500.7714-1-andrey.golovko@gmail.com/
There I suggested the fix might instead belong in the AMD ACP driver,
which advertises SNDRV_PCM_INFO_RESUME on its SoundWire DMA PCMs. I have
since tested that and it does not help, for a reason worth recording:
after resume userspace calls snd_pcm_prepare(), which reaches .prepare
and sdw_prepare_stream() but never hw_params() again -- ALSA only
requires hw_params() after hw_free(). The port is therefore re-prepared
correctly, the bank flips and PrepareCtrl is written, while the Function
is still at PS3. No PCM flag can change that; the power-up has to sit on
a callback that runs whenever a port is prepared.
sound/soc/codecs/tas2783-sdw.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c
index c217da5fccdf..34689b9764c9 100644
--- a/sound/soc/codecs/tas2783-sdw.c
+++ b/sound/soc/codecs/tas2783-sdw.c
@@ -1259,6 +1259,7 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch
enum sdw_port_prep_ops pre_ops)
{
struct device *dev = &slave->dev;
+ struct tas2783_prv *tas_dev = dev_get_drvdata(dev);
struct sdw_dpn_prop *dpn_prop;
u32 addr;
int ret;
@@ -1270,6 +1271,25 @@ static int tas_port_prep(struct sdw_slave *slave, struct sdw_prepare_ch *prep_ch
addr = SDW_DPN_PREPARECTRL(prep_ch->num);
switch (pre_ops) {
case SDW_OPS_PORT_PRE_PREP:
+ /*
+ * The Function has to be powered before the port can complete
+ * channel preparation. hw_params() does that when a stream is
+ * set up, but a stream that is only re-prepared - as userspace
+ * does after the peripheral lost power in S0i3 - does not go
+ * through hw_params() again, and the peripheral is back at its
+ * PS3 reset default. Power it up here, where it is needed.
+ */
+ scoped_guard(mutex, &tas_dev->pde_lock)
+ ret = regmap_write(tas_dev->regmap,
+ SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23,
+ TAS2783_SDCA_CTL_REQ_POW_STATE, 0),
+ TAS2783_SDCA_POW_STATE_ON);
+ if (ret) {
+ dev_err(dev, "power up failed for port %d, err=%d\n",
+ prep_ch->num, ret);
+ return ret;
+ }
+
ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask);
if (ret)
dev_err(dev, "prep failed for port %d, err=%d\n",
base-commit: 6f6fb37f9f9a8ae88faa1b5b1978e57381488502
--
2.53.0
I tested the exact inline patch from this message against v7.1.7 on the ASUS ProArt PX13 HN7306EAC. The normalized patch SHA-256 was: 68e9ae1300b17a7ba129b38c89d650964a8049cb03797a5eaf762f2d3fa78b40 The bound snd_soc_tas2783_sdw module had SHA-256: ec9eef4eb83f81761127c39c5da13d4ddb741f111fe2dcb6b64a5c4e0db245c0 I disabled and masked my resume rebind service before the test. The separate boot rebind stayed enabled because this machine still hits the unrelated 32 KiB firmware-write timeout during cold boot; it recovered both amplifiers on its first attempt before the baseline measurement. Before suspend, a controlled 2 kHz both-channel tone measured +58.7 dB over baseline through the internal microphone: baseline combined 2.2 tone combined 1799.4 The machine then completed an s2idle cycle from 16:41:19 to 17:13:44, about 32 minutes 25 seconds. The resume rebind remained masked and had no journal entries. After resume, playback opened and ran without a TAS2783 error and both amps remained Attached and bound to slave-tas2783, but the speakers were silent. A valid repeated acoustic capture measured: baseline combined 0.6 tone combined 0.2 tone relative to baseline -10.8 dB The first post-resume microphone capture produced an empty WAV due to the separate first-open issue already reported in the ACP PDM thread. I discarded that measurement, confirmed the DMIC could capture directly, then repeated the speaker measurement above with 353280 captured frames. So the PRE_PREP power-up patch does not fix the post-S0i3 speaker silence on this board. I am withholding Tested-by. The result suggests that restoring PDE23 is necessary for port preparation but is not sufficient for this machine's full TAS2783 resume state. One unrelated event occurred during the same resume: the RT721 jack-detect worker hit a NULL dereference in snd_jack_report(). Its stack did not contain TAS2783 or So undWire port preparation, so I have not attributed the speaker result to that Oops. I can test a follow-up patch or collect specific TAS2783 registers around the failed playback if useful. Thanks, Robin
On Thu, Aug 13, 2026 at 12:28:10AM +0300, Andrey Golovko wrote:
> A Data Port cannot complete channel preparation while the SDCA Function
> is powered down: the peripheral raises the channel's bit in
> DPn_PrepareStatus and never clears it.
...
> Power the Function up in the PRE_PREP callback, immediately before the
> PrepareCtrl write it already performs, so that preparation has what it
> needs on every path that prepares a port.
> case SDW_OPS_PORT_PRE_PREP:
> + /*
> + * The Function has to be powered before the port can complete
> + * channel preparation. hw_params() does that when a stream is
> + * set up, but a stream that is only re-prepared - as userspace
> + * does after the peripheral lost power in S0i3 - does not go
> + * through hw_params() again, and the peripheral is back at its
> + * PS3 reset default. Power it up here, where it is needed.
> + */
> + scoped_guard(mutex, &tas_dev->pde_lock)
> + ret = regmap_write(tas_dev->regmap,
> + SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23,
> + TAS2783_SDCA_CTL_REQ_POW_STATE, 0),
> + TAS2783_SDCA_POW_STATE_ON);
> + if (ret) {
> + dev_err(dev, "power up failed for port %d, err=%d\n",
> + prep_ch->num, ret);
> + return ret;
> + }
Does this DTRT if userspace restartss the stream by directly calling
SNDRV_PCM_IOCTL_RESUME (AMD adverise SNDR_PCM_INFO_RESUME...)?
Similarly we can suspend while prepared. Either something needs to
force us to prepare again or this needs to be moved somewhere that's
always called.
> +
> ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask);
> if (ret)
> dev_err(dev, "prep failed for port %d, err=%d\n",
>
> base-commit: 6f6fb37f9f9a8ae88faa1b5b1978e57381488502
> --
> 2.53.0
>
On Thu, Aug 13, 2026 at 02:22:52PM +0100, Mark Brown wrote:
> Does this DTRT if userspace restartss the stream by directly calling
> SNDRV_PCM_IOCTL_RESUME (AMD adverise SNDR_PCM_INFO_RESUME...)?
No, it does not. I wrote a test that takes that path deliberately - it
plays a 440/660 Hz tone straight to the hw device and, when the write
returns -ESTRPIPE, calls snd_pcm_resume() and never snd_pcm_prepare() -
and on this machine, with v1 applied:
before suspend after snd_pcm_resume()
DP1 PrepareCtrl 0x1 / 0x2 0x0 / 0x0
PDE23 req / act 0x0 / 0x0 0x3 / 0x3
tone 440 / 660 Hz +65.5 / +75.2 dB -1.7 / +1.8 dB
(two amplifiers on the link, tone measured through the internal
microphone against the noise floor of the same run). snd_pcm_resume()
returned 0, the PCM stayed RUNNING and nothing logged an error; the
ports were simply never prepared again, so the callback v1 hooks was
never reached. asoc_sdw_trigger() only calls sdw_enable_stream() on
SNDRV_PCM_TRIGGER_RESUME, and the channels are then enabled on ports
that no longer exist as far as the peripheral is concerned.
v2 preparing the stream on that trigger fixes it:
before suspend after snd_pcm_resume()
DP1 PrepareCtrl 0x1 / 0x2 0x1 / 0x2
PDE23 req / act 0x0 / 0x0 0x0 / 0x0
tone 440 / 660 Hz +73.7 / +88.3 dB +73.7 / +88.5 dB
It is on the list as
[PATCH v2 0/2] ASoC: tas2783: prepare the port again on the resume path
Message-ID: <20260813194000.10412-1-andrey.golovko@gmail.com>
> Similarly we can suspend while prepared.
That one I could not close, and I do not think it can be closed from
either the machine driver or the codec. A stream suspended while merely
PREPARED gets no trigger at all: snd_pcm_do_suspend() returns early when
the stream is not running, and snd_pcm_do_resume() returns early unless
the suspended state was RUNNING or DRAINING. Userspace calls
snd_pcm_start(), the ports are enabled, and nothing in the path knows
the peripheral came back empty. sdw_prepare_stream() cannot help
either: the SoundWire stream is still SDW_STREAM_PREPARED, which it
treats as "nothing to do" by design.
What does know is the bus - the peripheral goes UNATTACHED and comes
back uninitialized. Invalidating the prepared state of the streams that
peripheral takes part in looks like the right place to me. That is a
core change; tell me if you want it as part of this series and I will
write it.
Vijendar, thanks - that matches what the measurement says, and v2 takes
the first of the two options you named.
Robin, your negative result for v1 earlier today is consistent with all
of this: playback opening cleanly, both amplifiers attached and silent
is exactly what this path looks like from userspace. Two things worth
checking on your side before you spend another cycle:
- whether your player recovers with snd_pcm_resume() rather than
snd_pcm_prepare(). If it does, v1 alone could never have worked for
you, and v2 is the patch to test;
- v7.1.7 predates b627da430357 ("ASoC: tas2783-sdw: drop stale
regcache on uninitialized re-attach"), which is in for-7.3. Without
it the register writes made on resume can be dropped by the cache,
which is a second reason a v7.1.7 test says less than a for-next
one.
I would take you up on the offer of registers around a failed playback,
but on a for-next kernel with v2 applied. The register readout above
comes from a small out-of-tree module that reads DPn_PrepareCtrl,
DPn_PrepareStatus, ChannelEn and PDE23 over the bus without touching the
regmap cache; I am happy to send it to you off-list if that helps.
Thanks,
Andrey
Andrey,
thanks. One clarification about my negative v1 result: it was not an
active-playback resume test. The controlled pre-suspend tone had completed
before system suspend. The PipeWire sink was then idle for much longer than
WirePlumber's default five-second node-suspend timeout. After wake I started a
new pw-play process.
I did not trace the ALSA ioctls in that run, so I cannot prove exactly how
PipeWire reactivated the PCM. The test was not designed to call
snd_pcm_resume() on a running stream and should not be used as evidence for
that path.
That makes the missing b627da430357 on v7.1.7 the more relevant difference for
my result, although I have not proved that attribution yet. I agree that the
next useful test is the exact v2 series on broonie/sound for-next rather than
another cycle on v7.1.7.
I will keep the cases separate:
- an active hw stream recovered only with snd_pcm_resume();
- an idle PipeWire sink followed by fresh playback after resume;
- a hw stream left PREPARED across suspend, then started without another
prepare, to confirm the remaining gap described in the cover letter.
I will mask the resume rebind during each attributable cycle and keep the same
pre/post acoustic measurement.
Please send the register-readout module off-list. I can capture DP1
PrepareCtrl, PrepareStatus, ChannelEn and PDE23 around each path, especially if
one still fails on for-next.
Thanks,
Robin
On Wed, Aug 13, 2026 at 09:18:58PM +0000, Robin Everaars wrote: > thanks. One clarification about my negative v1 result: it was not an > active-playback resume test. The controlled pre-suspend tone had completed > before system suspend. Thank you for going back and checking, that is a useful correction. I had been reading your result as evidence about the resume ioctl, and it is not. Your second case is the one I would expect to have worked on v1: a fresh pw-play after the sink was suspended opens the PCM again, so it goes through hw_params() and prepare(), and that is the path v1 already covered. It failing on v7.1.7 fits your own attribution to the missing b627da430357 - without it the DAPM writes that follow are dropped against a stale cache, so the Function never comes up no matter how well the port is prepared. So yes, for-next rather than another v7.1.7 cycle. For the third case I would expect it to still fail, and that is the point of running it: the PCM core does not trigger a stream that was left PREPARED, so nothing in the codec or machine driver gets a chance to act. sdw_prepare_stream() on a stream still marked PREPARED is a no-op too, so even a call from there would not help. It needs the stream state invalidated when the peripheral goes UNATTACHED and comes back. If your readout shows PrepareCtrl=0x0 while the stream is running, that is the same gap and not a second bug. The module is on its way off-list. It reads and prints DP1 PrepareCtrl, PrepareStatus, the ChannelEn of the current bank and the PDE23 requested and actual power states, straight over the bus with sdw_read_no_pm(), so the codec regmap cache is out of the picture. It only reads unless you ask it to write. For a shape to compare against, here is what the resume-ioctl case gave on this machine, unpatched kernel to the left, v2 to the right: DP1 PrepareCtrl before/after 0x1,0x2 -> 0x0,0x0 0x1,0x2 -> 0x1,0x2 PDE23 req/act after 0x3/0x3 0x0/0x0 440/660 Hz tone after -1.7/+1.8 dB +73.7/+88.5 dB The tone figures are levels in narrow bands around 440 and 660 Hz in a three-second capture from the built-in microphone, on a scale where a capture of silence reads about 0 dB in the same bands. The absolute numbers say nothing outside this machine; noise floor against signal does. v2 of the series is at https://lore.kernel.org/linux-sound/20260813194000.10412-1-andrey.golovko@gmail.com/ Andrey
On 13/08/26 18:52, Mark Brown wrote:
> On Thu, Aug 13, 2026 at 12:28:10AM +0300, Andrey Golovko wrote:
>
>> A Data Port cannot complete channel preparation while the SDCA Function
>> is powered down: the peripheral raises the channel's bit in
>> DPn_PrepareStatus and never clears it.
> ...
>
>> Power the Function up in the PRE_PREP callback, immediately before the
>> PrepareCtrl write it already performs, so that preparation has what it
>> needs on every path that prepares a port.
>> case SDW_OPS_PORT_PRE_PREP:
>> + /*
>> + * The Function has to be powered before the port can complete
>> + * channel preparation. hw_params() does that when a stream is
>> + * set up, but a stream that is only re-prepared - as userspace
>> + * does after the peripheral lost power in S0i3 - does not go
>> + * through hw_params() again, and the peripheral is back at its
>> + * PS3 reset default. Power it up here, where it is needed.
>> + */
>> + scoped_guard(mutex, &tas_dev->pde_lock)
>> + ret = regmap_write(tas_dev->regmap,
>> + SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_PDE23,
>> + TAS2783_SDCA_CTL_REQ_POW_STATE, 0),
>> + TAS2783_SDCA_POW_STATE_ON);
>> + if (ret) {
>> + dev_err(dev, "power up failed for port %d, err=%d\n",
>> + prep_ch->num, ret);
>> + return ret;
>> + }
> Does this DTRT if userspace restartss the stream by directly calling
> SNDRV_PCM_IOCTL_RESUME (AMD adverise SNDR_PCM_INFO_RESUME...)?
> Similarly we can suspend while prepared. Either something needs to
> force us to prepare again or this needs to be moved somewhere that's
> always called.
I agree with Mark's observation. For drivers advertising SNDRV_PCM_INFO_RESUME,
userspace may resume a stream via SNDRV_PCM_IOCTL_RESUME without going through
a new prepare cycle. Similarly, a stream may be suspended while already
prepared and later resumed without re-running PRE_PREP.
If those paths do not guarantee port preparation is executed again, we either
need to force a re-prepare after power loss or move the power-up sequence to a
callback that is always hit before data transfer resumes.
>
>> +
>> ret = sdw_write_no_pm(slave, addr, prep_ch->ch_mask);
>> if (ret)
>> dev_err(dev, "prep failed for port %d, err=%d\n",
>>
>> base-commit: 6f6fb37f9f9a8ae88faa1b5b1978e57381488502
>> --
>> 2.53.0
>>
© 2016 - 2026 Red Hat, Inc.