From nobody Tue Sep 29 10:32:56 2026 Received: from mail-05.mail-europe.com (mail-05.mail-europe.com [85.9.206.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id DBAF63C7E0E; Sun, 9 Aug 2026 10:16:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.9.206.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270584; cv=none; b=tCr0X802q8Az8VG6ODjpOaGa+ES8sS7FDjezkHd/IrhKp/Vgb/A0C4ZTFRzjEzUdZr3051/TTdan0zz3852HV81JtNf3b8K8IkMKjJA+XszCr5wFP4c6yRsj1R001AuNsecqbHVN+BcJnLuJ4tHIja9qzV95iwXaIyLaehcpI6c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270584; c=relaxed/simple; bh=xAvKIMSarrMteQnXUSTrrN4SGnb0sKQGx0wtYG5MyTA=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=S6GZhi+DWiLhynR+ZousWnRJ/nyQgJL5SpFztKxD1edR01+HENnBEYlAR66E8XUJ/I76293+365bAzBlc9QYBgFOVI6+GDiFHplwfx5Hv/MTZwrlJUuE7AlOsYKScUub8dHIvy3VQLJy5dwU4dD1tmdqn9gNmHGSfsYmWbCVTxI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi; spf=pass smtp.mailfrom=saarinenkoti.fi; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b=iGIjdfS/; arc=none smtp.client-ip=85.9.206.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b="iGIjdfS/" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=saarinenkoti.fi; s=protonmail3; t=1786270566; x=1786529766; bh=bLGBl5iyOkFHomVE5ec7XpLAZu5Q7qxrFW/yQFBlTkY=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=iGIjdfS/S4OhzAQI0JOsvfWApCsC1O1sNKUqpKEnLeogSX/r3X75R6iUmni94NFfd ltOBOILFwZFdtJDLFO8AZh0+GCQZeYCWWBicDBv3JUIZoVN5838mcHkDxNf0CObBms LUPUBgH5ADm6+MD+Oe9WPWtNNywMVmLCaWh30ouoIM/R9vRHTyXU6DRxKQA/n/fF7w 7N40C/tLbutrE1Bh9dVv18Qrh3QcjBflOb6G8OHPGH+HXVJFVjfw03zvDOovjQZP9n moyIETJk2paZokmrGSgj1KE/624uRLpRUCOi8peMkV9AYdBc1BhIi2XNy9Z4vu+W2O pOnX6LD3NNQJQ== Date: Sun, 09 Aug 2026 10:16:01 +0000 To: Shenghao Ding , Kevin Lu , Baojun Xu , Sen Wang From: Ville Saarinen Cc: Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/3] ASoC: tas2783: let regmap-sdw-mbq poll for deferred transactions Message-ID: <20260809101541.4969-2-wiza@saarinenkoti.fi> In-Reply-To: <20260809101541.4969-1-wiza@saarinenkoti.fi> References: <20260809101541.4969-1-wiza@saarinenkoti.fi> Feedback-ID: 137674609:user:proton X-Pm-Message-ID: b57dfde39cfeb5c40499c7b9378a1af959f785e3 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" In SDCA, a Function answering COMMAND_IGNORED to a Control write has deferred the transaction rather than rejected it. regmap_sdw_mbq_write() handles that by calling regmap_sdw_mbq_poll_busy(), which waits for the Entity-0 Function Busy bit to clear and then retries once. Neither half of that works for this codec: - poll_busy only polls if ->readable_reg() accepts the Entity-0 Function Status address. tas2783_readable_register() answers out of tas2783_sdca_mbq_size(), which has no case for that address, so the poll is skipped and the core falls through to a bare fsleep(cfg.timeout_us). - tas2783_mbq_cfg sets only .mbq_size, so timeout_us and retry_us are both 0. The fallback wait is fsleep(0) and the retry is therefore instantaneous. Every deferred write consequently fails by construction, returning -ENODATA and logging "Defer on undeferrable control". Add the Function Status register to the mbq size table so the poll can run, and to the volatile table so a later regcache_sync() never writes back to a status register. Mark the UDMPU23 Cluster Index deferrable and give the mbq cfg a poll interval and a deadline. Note the two cfg fields reach read_poll_timeout() as (sleep_us, timeout_us), i.e. .timeout_us is the poll interval and .retry_us the overall deadline -- the reverse of the kerneldoc on struct regmap_sdw_mbq_cfg. The values here follow the code, which is what runs. Developed with AI assistance. The assistant traced the -ENODATA into regmap_sdw_mbq_poll_busy() and drafted the fix. All hardware measurements quoted above were run by the submitter on the affected machine. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ville Saarinen --- sound/soc/codecs/tas2783-sdw.c | 37 ++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 3d0b11654..5a7ac6224 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -423,6 +423,13 @@ static int tas2783_sdca_mbq_size(struct device *dev, u= 32 reg) case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x01, 0): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_FU23, 0x01, 1): case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_OT25, 0x04, 0): + /* + * Entity 0 Function Status. regmap_sdw_mbq_poll_busy() only polls the + * Function Busy bit if ->readable_reg() accepts this address; without + * it the core drops into a bare fsleep(cfg.timeout_us) and, with that + * left at 0, retries a deferred transaction instantly and fails. + */ + case SDW_SDCA_CTL(1, 0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0): return 1; =20 case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_IT26, 0x10, 0): @@ -505,6 +512,8 @@ static bool tas2783_volatile_register(struct device *de= v, u32 reg) case 0x400 ... 0x440: /* Data port 4. */ case 0x500 ... 0x540: /* Data port 5. */ case 0x800001: + /* Function Status is a live status word; never let the cache hold it. */ + case SDW_SDCA_CTL(1, 0, SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0): return true; =20 default: @@ -525,8 +534,36 @@ static const struct regmap_config tas_regmap =3D { .use_single_write =3D true, }; =20 +/* + * The amp answers COMMAND_IGNORED (-ENODATA) to a UDMPU23 Cluster Index w= rite, + * which in SDCA terms means the Function deferred the transaction. Declar= ing it + * deferrable stops regmap-sdw-mbq warning about it and documents the inte= nt; + * the poll-and-retry in regmap_sdw_mbq_write() runs either way. + */ +static bool tas2783_sdca_deferrable(struct device *dev, unsigned int reg) +{ + switch (reg) { + case SDW_SDCA_CTL(1, TAS2783_SDCA_ENT_UDMPU23, + TAS2783_SDCA_CTL_UDMPU_CLUSTER, 0): + return true; + + default: + return false; + } +} + static const struct regmap_sdw_mbq_cfg tas2783_mbq_cfg =3D { .mbq_size =3D tas2783_sdca_mbq_size, + .deferrable =3D tas2783_sdca_deferrable, + /* + * NB: regmap_sdw_mbq_poll_busy() passes these to read_poll_timeout() as + * (sleep_us, timeout_us) -- i.e. timeout_us is the poll interval and + * retry_us the overall deadline, the opposite of what the kerneldoc on + * struct regmap_sdw_mbq_cfg says. Values below follow the code, which + * is what actually runs (checked against mainline master too). + */ + .timeout_us =3D 1000, + .retry_us =3D 100000, }; =20 static s32 tas2783_digital_getvol(struct snd_kcontrol *kcontrol, --=20 2.55.0 From nobody Tue Sep 29 10:32:56 2026 Received: from mail-06.mail-europe.com (mail-06.mail-europe.com [85.9.210.45]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 4CEBD37F32C; Sun, 9 Aug 2026 10:16:21 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.9.210.45 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270583; cv=none; b=BOYW1bnQMVm2eFSXxt8KnSlNzQwK3wFP92a/1NCEJlP+yBkkSxp9CDyaW0ELZY81LXG8lW8wwU4jr5/fA5BhMIlO8D/Bvn5aWjIDIO0lu2XyzeP2aZ3D0Pumy4+SgEc0cWzN/oVOIKk45keBpSgGs3tDIub2uPRi0ltduhflKMQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270583; c=relaxed/simple; bh=jenqEVO80LcQFgofcgPccusuDcxGNNfIiAFXF9vwG+Y=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Scr7IMVvXokhJjoWrpKjY0tHDRMa38DTe2HKw8Zk5/lu3rvWXV8WrfOJFb6YPGeKghxOpI7zzrxLsL9y/XsRNa1NYU2QyM6avWGQybV8kdRYml0Mw4L7dAtiPKo6LmjtMMEda1261/c+QvNGuYAUbyPw/ctbfiYJH5H7gnzlD5k= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi; spf=pass smtp.mailfrom=saarinenkoti.fi; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b=eUPS32fD; arc=none smtp.client-ip=85.9.210.45 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b="eUPS32fD" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=saarinenkoti.fi; s=protonmail3; t=1786270569; x=1786529769; bh=h1cNAfatHwIozSaZ53W8Ip+iNnZf6TfIMCKV+UtORxM=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=eUPS32fDpY1SNXFaYTBp8VUbG2lHb+1xGcQu4bOnDZnzQOw4adn5qBL6VSYRl3XtI 3Eq3g65KnMuQMIAu2Wd7OL0JYjEE7Lovkbr564An5wf7QTkD49y9Lgg03BXIfzqZ+Q 5v7Dtj2GT7VnNUIMTg7+YtOpwcNrAh+kDBr9iiVCxTpCJerM89DWmSi7/yCJUIMi6Z G9FjNKCp1Xas+KaFM5700vuiWG2cyfmwv5spJKJ46VwQTXwoagmNUhHEAQ9mZMRY5m sBd00U7Rth+qcKB80WalnJeu/DV0OwvZ89Gb1fHyu6Ps1kIivPkhIdXnGIZf5ARQby aTthO/NWJopDQ== Date: Sun, 09 Aug 2026 10:16:06 +0000 To: Shenghao Ding , Kevin Lu , Baojun Xu , Sen Wang From: Ville Saarinen Cc: Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/3] ASoC: tas2783: add RX Single Channel Switch to split a two-amp stereo pair Message-ID: <20260809101541.4969-3-wiza@saarinenkoti.fi> In-Reply-To: <20260809101541.4969-1-wiza@saarinenkoti.fi> References: <20260809101541.4969-1-wiza@saarinenkoti.fi> Feedback-ID: 137674609:user:proton X-Pm-Message-ID: cbb048fb8e9c9855f22b924e3a071c1fa5c7e05a Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A pair of TAS2783 amplifiers aggregated on one link renders the same channel from both amplifiers, so a stereo stream is heard as the left channel from both speakers and right-channel content is inaudible. On the HP OmniBook X Flip 14-kc0xxx (board 8EA1) that is every stereo stream. The SDCA control that would select the rendered channel, the UDMPU23 Cluster Index, is written only from tas2783_reg_default[] as 0x0 and never per device -- but on this part it cannot be written at all. The amplifier answers COMMAND_IGNORED (-ENODATA) in every state tried: streaming, idle, and with the SDCA function confirmed actually powered on (PDE23 Actual Power State =3D=3D ON); as a 4-byte MBQ write, as a plain single-byte write, after clearing the latched Entity-0 status bits, and on the Next rank of a dual-ranked control. A genuine device read of the same Control (sdw_read_no_pm) also returns -ENODATA, which is the tell: it is not write-protected, it is not implemented. Do the split on the host side instead, where it costs no device access. sdw_compute_slave_ports() advances the payload block offset by one channel per slave port, except that a slave whose ch_mask covers every channel of the stream is treated as mirror mode and the offset is reset for the next slave. snd_sdw_params_to_config() hands both amplifiers ch_mask 0x3 on a 2-channel stream, so both were given the same block offset -- confirmed on the wire, DP1_CHANNELEN 0x03 and DP1_OFFSETCTRL1 0x41 read identically on the two amplifiers during playback. Claiming a single channel drops the pair out of mirror mode and the core assigns consecutive offsets in sdw_stream_add_slave() order, i.e. dai_link codec order. Expose that as a boolean "RX Single Channel Switch", left off by default so an unconfigured card behaves exactly as before and the split is opt-in from the machine's UCM profile. Note what the control deliberately does not do. It selects *whether* an amplifier takes one channel or mirrors the whole stream; it cannot select *which* channel. sdw_compute_slave_ports() advances the offset by "port_bo +=3D bps * hweight32(p_rt->ch_mask)" -- the popcount of the mask only, never which bit is set -- so BIT(0) and BIT(1) are indistinguishable to the allocator and the side an amplifier renders is fixed by its position in the slave iteration order, i.e. by the machine driver's codec order. An earlier version of this patch exposed an "RX Channel Select" enum with Left/Right values on the rt1316/rt1318 model; that was measured to be inert in exactly that respect and would have promised an ABI the bus cannot honour. Measured on the affected machine with a 1 kHz tone that is left-only for its first half and right-only for its second, isolating each amplifier by muting the other and capturing on the internal DMIC array, with each condition normalised to both amplifiers muted. Every run was verified from the kernel log to have re-run hw_params with the new setting. With the switch on for both amplifiers: left content +14.1 dB from amp 1, +0.1 dB from amp 2 right content -0.4 dB from amp 1, +17.5 dB from amp 2 With the switch off for both, i.e. the previous behaviour: left content +14.0 dB from amp 1, +17.0 dB from amp 2 right content 0.0 dB from amp 1, -0.2 dB from amp 2 So with the switch on, each amplifier carries one side and the other side sits within 0.4 dB of the muted floor; with it off, both amplifiers render the same left channel and right-channel content is inaudible on both. The level each amplifier produces is unchanged between the two settings (+14.1 vs +14.0 for amp 1, +17.5 vs +17.0 for amp 2), which is the expected signature of a change in which channel reaches an amplifier rather than in its gain. Developed with AI assistance. The assistant did the register-level analysis, identified the mirror-mode reset in sdw_compute_slave_ports() as the cause and drafted the patch. Several earlier hypotheses it produced were wrong and were discarded only because they were measured: most of the SDCA Cluster Index work summarised above, and the Left/Right enum of the earlier version, whose changelog claimed a per-side assignment that the bus allocator cannot implement. All hardware measurements quoted above were run by the submitter on the affected machine. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ville Saarinen --- sound/soc/codecs/tas2783-sdw.c | 100 +++++++++++++++++++++++++++++++++ 1 file changed, 100 insertions(+) diff --git a/sound/soc/codecs/tas2783-sdw.c b/sound/soc/codecs/tas2783-sdw.c index 5a7ac6224..d1addd8ae 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -96,6 +96,8 @@ struct tas2783_prv { u8 rca_binaryname[64]; u8 dev_name[32]; bool hw_init; + /* take one channel instead of mirroring; applied at hw_params */ + bool rx_single_ch; /* wq for firmware download */ wait_queue_head_t fw_wait; bool fw_dl_task_done; @@ -590,6 +592,84 @@ static s32 tas2783_amp_putvol(struct snd_kcontrol *kco= ntrol, return snd_soc_put_volsw(kcontrol, ucontrol); } =20 +/* + * UDMPU23 Cluster Index selects which channel of the incoming stream this= amp + * renders. The driver only ever sets it from tas2783_reg_default[] as 0x0= , and + * never per device, so in a two-amp aggregated setup both amps come up on= the + * same cluster and render the same channel -- on the HP OmniBook X Flip 14 + * (board 8EA1) that means both speakers play the left channel and right-c= hannel + * content is inaudible. The firmware blobs do encode a per-amp channel in= page-0 + * config registers (0x80000a differs 2a/1a between the two blobs), but th= e SDCA + * cluster index overrides it, so the split has to be set here. + * + * Unlike rt1316/rt1318, the selection CANNOT be pushed to the device. The= amp + * answers COMMAND_IGNORED (-ENODATA) to a UDMPU23 Cluster Index write in = every + * state tried -- streaming, idle, and with the SDCA function verified act= ually + * powered on (PDE23 Actual Power State =3D=3D ON), as a 4-byte MBQ write,= as a + * plain single-byte write, after clearing the latched Entity-0 status bit= s, and + * on the Next rank of a dual-ranked control. A genuine device read of the= same + * control (sdw_read_no_pm) also returns -ENODATA, which is the tell: the = control + * is not merely write-protected, it is not implemented on this part. Main= line + * master carries no channel assignment for tas2783 either, so there is no= thing + * upstream to backport. + * + * So the split is done on the host side instead, where it costs no device + * access at all. sdw_compute_slave_ports() walks the slaves of a stream a= nd + * advances the payload block offset by one channel per slave port -- but = only + * if the slave asked for fewer channels than the stream carries. A slave = whose + * ch_mask covers every channel of the stream is treated as "mirror mode" = and + * the offset is reset for the next slave, which is precisely what this dr= iver + * used to request: snd_sdw_params_to_config() hands both amps ch_mask 0x3= on a + * 2-channel stream, so both were given the same block offset and both ren= dered + * the same (left) samples. Confirmed on the wire: DP1_CHANNELEN 0x03 and + * DP1_OFFSETCTRL1 0x41 read identically on the two amps during playback. + * + * Asking for a single channel per amp drops the pair out of mirror mode, = and + * the core then assigns consecutive block offsets in the order the amps c= all + * sdw_stream_add_slave() -- i.e. dai_link codec order, which on this boar= d is + * amp 0x9 (spk_l_endpoint, group_position 0) then amp 0xC (spk_r, positio= n 1). + * + * Note carefully what this control can and cannot do. It selects *whether= * this + * amp takes a single channel or mirrors the whole stream; it does NOT and= cannot + * select *which* channel. sdw_compute_slave_ports() advances the payload = offset + * by "port_bo +=3D bps * hweight32(p_rt->ch_mask)" -- the popcount of the= mask + * only, never which bit is set -- so BIT(0) and BIT(1) are indistinguisha= ble to + * the allocator, and the side an amp ends up rendering is fixed by its po= sition + * in the slave iteration order. Setting this switch on both amps of a pai= r, with + * the same ch_mask on both, still produces a correct stereo split; the ma= chine + * driver's codec order is what assigns the sides. + * + * Off preserves the old mirror behaviour, so an unconfigured card behaves + * exactly as before and the split is opt-in from UCM. + */ +static int tas2783_rx_single_ch_get(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component =3D snd_kcontrol_chip(kcontrol); + struct tas2783_prv *tas_dev =3D + snd_soc_component_get_drvdata(component); + + ucontrol->value.integer.value[0] =3D tas_dev->rx_single_ch; + + return 0; +} + +static int tas2783_rx_single_ch_put(struct snd_kcontrol *kcontrol, + struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *component =3D snd_kcontrol_chip(kcontrol); + struct tas2783_prv *tas_dev =3D + snd_soc_component_get_drvdata(component); + bool val =3D !!ucontrol->value.integer.value[0]; + + if (tas_dev->rx_single_ch =3D=3D val) + return 0; + + tas_dev->rx_single_ch =3D val; + + return 1; +} + static const struct snd_kcontrol_new tas2783_snd_controls[] =3D { SOC_SINGLE_RANGE_EXT_TLV("Amp Volume", TAS2783_AMP_LEVEL, 1, 0, 20, 0, tas2783_amp_getvol, @@ -597,6 +677,9 @@ static const struct snd_kcontrol_new tas2783_snd_contro= ls[] =3D { SOC_SINGLE_RANGE_EXT_TLV("Speaker Volume", TAS2783_DVC_LVL, 0, 0, 200, 1, tas2783_digital_getvol, tas2783_digital_putvol, tas2781_dvc_tlv), + SOC_SINGLE_BOOL_EXT("RX Single Channel Switch", 0, + tas2783_rx_single_ch_get, + tas2783_rx_single_ch_put), }; =20 static s32 tas2783_validate_calibdata(struct tas2783_prv *tas_dev, @@ -992,6 +1075,23 @@ static s32 tas_sdw_hw_params(struct snd_pcm_substream= *substream, else port_config.num =3D 2; =20 + /* + * Claim a single channel of the stream so the bus drops this amp out of + * "mirror mode" and gives it its own payload block offset instead of the + * same one as its pair. Which channel that turns out to be is decided by + * slave iteration order, not by the mask -- see the comment above + * tas2783_rx_single_ch_get(). + */ + if (substream->stream =3D=3D SNDRV_PCM_STREAM_PLAYBACK && + stream_config.ch_count > 1 && tas_dev->rx_single_ch) { + stream_config.ch_count =3D 1; + port_config.ch_mask =3D BIT(0); + } + + dev_dbg(tas_dev->dev, "port %u: single_ch=3D%u ch_count=3D%d ch_mask=3D%#= x\n", + port_config.num, tas_dev->rx_single_ch, + stream_config.ch_count, port_config.ch_mask); + ret =3D sdw_stream_add_slave(sdw_peripheral, &stream_config, &port_config, 1, sdw_stream); if (ret) --=20 2.55.0 From nobody Tue Sep 29 10:32:56 2026 Received: from mail-05.mail-europe.com (mail-05.mail-europe.com [85.9.206.169]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B39A13C4577 for ; Sun, 9 Aug 2026 10:16:24 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=85.9.206.169 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270586; cv=none; b=ig4dMnQX9yxfop0kWDzF8+jZWfd5tvLhSo4Ry8e4cCDlZI2LCM1lnlnYZqSHfR/fDMNOYOapzac83LrasjpndXEbNsMzQijWxKqtbXKa4TV1SkFCyybcNVc81b3P/vG8dVc32DRjFNnvL8LOYuKv3Yipw2Sof9Tpv2NbQ9REnDU= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786270586; c=relaxed/simple; bh=TPFUcX3A7JoyNEA3ac/a7vTtA/ydvSA0ivyyMlAHRYI=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: MIME-Version:Content-Type; b=Qy1FRX7eC5DWimG1gPU94lKbhiFdVU9cUPzgIuTZSxo35DmagkAfa7fKwGdUl/bySRDWQhxEgaKrfZK4ocL60M0YqOfQH7+cc/yLWiK4geokNGxO4jYmFecO0ZHTfl/T9VsjlYzqbUt6nNrQEObI6FtEmInBjd2jm7Yj+F2Tyls= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi; spf=pass smtp.mailfrom=saarinenkoti.fi; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b=CHnEQxpf; arc=none smtp.client-ip=85.9.206.169 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=saarinenkoti.fi Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=saarinenkoti.fi header.i=@saarinenkoti.fi header.b="CHnEQxpf" DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=saarinenkoti.fi; s=protonmail3; t=1786270574; x=1786529774; bh=6NdnXekWMWGk/nB2bensin1fqZep1VzxSs+L/LrylAo=; h=Date:To:From:Cc:Subject:Message-ID:In-Reply-To:References: Feedback-ID:From:To:Cc:Date:Subject:Reply-To:Feedback-ID: Message-ID:BIMI-Selector; b=CHnEQxpflqwiXfM7UGZmo2eLngNrzEvF4BnWHXy3imdS367HF2rEI1+9KBoX6H87h s8TELUTZmqZu+Z9wW7UwZGWgnXMAf7qAWgZO12Am5OmHC/DIGAcToa9uIfLTen6vWj cssxR4TYLqLDLG8A3xkGgm1qlixCaQflnWqv/sBuYjxks3gpq06+yllAp+tWwlFWlD 3WbZEznb8zrM6I/9PKDdIAFPuX/eRSNcksXsWKdh+Yl+/108j9hSLkg8mO14PXefsO Sst55d4rJoYGgPyXIXtMHrbixIA+0fCVP+GXr4M0ZsjaDuaDc/L4mcx8C3fwYef0T5 lKD5W35gjAgeA== Date: Sun, 09 Aug 2026 10:16:10 +0000 To: Shenghao Ding , Kevin Lu , Baojun Xu , Sen Wang From: Ville Saarinen Cc: Liam Girdwood , Mark Brown , Jaroslav Kysela , Takashi Iwai , linux-sound@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 3/3] ASoC: tas2783: drop firmware-owned registers from the regmap cache Message-ID: <20260809101541.4969-4-wiza@saarinenkoti.fi> In-Reply-To: <20260809101541.4969-1-wiza@saarinenkoti.fi> References: <20260809101541.4969-1-wiza@saarinenkoti.fi> Feedback-ID: 137674609:user:proton X-Pm-Message-ID: ed479765ff40cd4976a2a049a6a8281db4b4337a Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" The firmware image is downloaded with sdw_nwrite_no_pm(), which writes straight to the peripheral and bypasses the regmap cache. The cache keeps holding the tas2783_reg_default[] entries for every register the firmware image owns, so cache and device disagree from the moment the download completes. tas2783_sdca_dev_resume() then does regcache_cache_only(false) followed by regcache_sync(), and regcache_sync() writes out every cached register. On a system resume the peripheral stays attached and keeps its device state, so hw_init is still set and the firmware is never re-downloaded - but the sync stamps the stale defaults back onto the device on top of the firmware tuning that is still live there. On an HP OmniBook X Flip 14, which carries two aggregated TAS2783 amps, this was measured with a cache-bypassing debugfs read taken after an s2idle cycle: all 12 registers where the firmware image differs from the defaults table had reverted to their default value on both amps, and the two amps had become byte-identical over 0x800001-0x800040. Among the lost values are the four page-0 bytes that give each amp of a stereo pair its own configuration. The speakers are silent after resume and stay silent until the machine is rebooted. Drop each downloaded file's destination range from the cache once it has been written, so the cache no longer claims to know registers the firmware owns and regcache_sync() has nothing stale to write over them. This is safe: a suspend deep enough for the peripheral to actually lose its state also takes it UNATTACHED, which clears hw_init and triggers a full firmware re-download on re-attach. Developed with AI assistance. The assistant diagnosed the interaction between the cache-bypassing firmware download and the resume-time regcache_sync(), and drafted the patch. All hardware measurements quoted above were run by the submitter on the affected machine. The submitter has reviewed the change, understands it and takes responsibility for it. Assisted-by: Claude:claude-opus-5 Signed-off-by: Ville Saarinen --- 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 d1addd8ae..f79e730b0 100644 --- a/sound/soc/codecs/tas2783-sdw.c +++ b/sound/soc/codecs/tas2783-sdw.c @@ -912,6 +912,26 @@ static void tas2783_fw_ready(const struct firmware *fm= w, void *context) "FW download failed: %d", ret); break; } + + /* + * The firmware image is written with sdw_nwrite_no_pm(), which + * bypasses the regmap cache. The cache therefore keeps holding + * the stale reg_defaults entries for every register the + * firmware owns, and the regcache_sync() done on resume writes + * those defaults back out over the firmware tuning. That wipes + * the per-amp configuration, including the channel assignment, + * and leaves the speakers silent until the next full re-init. + * + * Drop the firmware-owned registers from the cache so nothing + * stale can ever be synced over them. This is safe because a + * suspend deep enough to lose device state also takes the + * peripheral UNATTACHED, which clears hw_init and triggers a + * full firmware re-download on re-attach. + */ + if (file->length) + regcache_drop_region(tas_dev->regmap, file->dest_addr, + file->dest_addr + file->length - 1); + cur_file++; } mutex_unlock(&tas_dev->pde_lock); --=20 2.55.0