* [PATCH] ALSA: hda/ca0132: Restore AE-5 audio registers after LED strip send
@ 2026-11-02 22:57 Rafael Medina
0 siblings, 0 replies; only message in thread
From: Rafael Medina @ 2026-11-02 22:57 UTC (permalink / raw)
To: Takashi Iwai, Jaroslav Kysela
Cc: Devin Christensen, linux-sound, linux-kernel
The AE-5 LED strip transport added by commit c3e35f355fad ("ALSA:
hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support")
reprograms the CA0113 audio serializer block in BAR2 to clock out the
WS2812 data, and at the end clears only the stream routing register.
The format and channel enable register at 0x100 is left at 0x3000ff8f
instead of the 0x00000700 it reads after a normal codec init, and the
analog outputs stay silent until the codec is initialised again.
The strip control is marked volatile, but alsactl still stores it, and
the udev rule runs "alsactl restore" for every card probe. The saved
state of a card that never had a strip attached contains "AE-5 LED
Strip" and "AE-5 LED Strip Count" with access "read write volatile".
ae5_strip_ctl_put() sent a frame even when the colors were unchanged,
so every boot of an AE-5 or AE-5 Plus ended with a silent card.
Save the registers that the transmit touches before programming them
and write them back at the end of ae5_strip_send_frame(), and do not
transmit from ae5_strip_ctl_put() when the colors did not change.
Fixes: c3e35f355fad ("ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support")
Cc: Devin Christensen <dchristensen8@proton.me>
Signed-off-by: Rafael Medina <rafael@gexdigital.net>
---
Tested on a Sound BlasterX AE-5 Plus (1102:0012, subsystem 1102:0191)
with v7.4-rc1:
- Stock: after boot BAR2 0x100 reads 0x3000ff8f and there is no audio.
Re-probing the card with the saved alsa state file moved aside gives
0x00000700 and working audio.
- Patched: BAR2 0x100 stays at 0x00000700 after "alsactl restore" and
after writing a non-zero color to "AE-5 LED Strip", and audio works
at boot.
No external LED strip is connected to the card, so I could not check the
strip output itself. The control write completed without error and the
audio registers were intact afterwards.
I did not try to find which of the restored registers is the one that
silences the outputs, so all of them are put back.
sound/hda/codecs/ca0132.c | 43 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 41 insertions(+), 2 deletions(-)
diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c
index 896091c11..b3e768be8 100644
--- a/sound/hda/codecs/ca0132.c
+++ b/sound/hda/codecs/ca0132.c
@@ -8416,6 +8416,10 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors,
struct hdac_stream *hstr = NULL;
unsigned int format, stream_tag;
u32 enc_words[4];
+ struct {
+ u32 r100, r104, r400, r408, r40c, r410, rc04;
+ u32 ser_base[4], ser_clk[4], ser_out[4];
+ } saved;
__le32 *dst;
int word_idx = 0;
int frame_words;
@@ -8501,6 +8505,24 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors,
/* Ensure DMA buffer writes are committed before triggering the HDA stream */
wmb();
+ /*
+ * Save every BAR2 register the strip transport reprograms, so that the
+ * audio serializer/clock/routing setup done at codec init can be put
+ * back afterwards. Leaving them modified silences the analog outputs.
+ */
+ saved.r100 = readl(spec->mem_base + 0x100);
+ saved.r104 = readl(spec->mem_base + 0x104);
+ saved.r400 = readl(spec->mem_base + 0x400);
+ saved.r408 = readl(spec->mem_base + 0x408);
+ saved.r40c = readl(spec->mem_base + 0x40c);
+ saved.r410 = readl(spec->mem_base + 0x410);
+ saved.rc04 = readl(spec->mem_base + 0xc04);
+ for (i = 0; i < 4; i++) {
+ saved.ser_base[i] = readl(CA0113_SERIALIZER_BASE(spec, i));
+ saved.ser_clk[i] = readl(CA0113_SERIALIZER_CLK(spec, i));
+ saved.ser_out[i] = readl(CA0113_SERIALIZER_OUT(spec, i));
+ }
+
/* 2. Configure CA0113 BAR2 hardware registers */
/* Power up clocks (CtxHdb 0x141b7 / 0x14210) */
writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04);
@@ -8559,8 +8581,19 @@ static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors,
snd_hdac_dsp_trigger(hstr, false);
codec_dbg(codec, "AE5 strip: trigger complete\n");
- /* Teardown: clear 0x104 pin mux back to 0 */
- writel(0x00000000, spec->mem_base + 0x104);
+ /* Teardown: put back the registers exactly as the audio path left them */
+ writel(saved.r104, spec->mem_base + 0x104);
+ for (i = 0; i < 4; i++) {
+ writel(saved.ser_out[i], CA0113_SERIALIZER_OUT(spec, i));
+ writel(saved.ser_clk[i], CA0113_SERIALIZER_CLK(spec, i));
+ writel(saved.ser_base[i], CA0113_SERIALIZER_BASE(spec, i));
+ }
+ writel(saved.r100, spec->mem_base + 0x100);
+ writel(saved.r410, spec->mem_base + 0x410);
+ writel(saved.r40c, spec->mem_base + 0x40c);
+ writel(saved.r408, spec->mem_base + 0x408);
+ writel(saved.r400, spec->mem_base + 0x400);
+ writel(saved.rc04, spec->mem_base + 0xc04);
snd_hdac_dsp_cleanup(hstr, &dmab);
scoped_guard(spinlock_irq, &bus->reg_lock)
@@ -8613,6 +8646,12 @@ static int ae5_strip_ctl_put(struct snd_kcontrol *kcontrol,
}
change = memcmp(spec->ae5_strip_cur_colors, colors,
sizeof(spec->ae5_strip_cur_colors)) != 0;
+ /*
+ * alsactl restores this control at every card probe with the saved
+ * values; do not run the whole transmit routine when nothing changed.
+ */
+ if (!change)
+ return 0;
num_leds = spec->ae5_strip_cur_num_leds;
ret = ae5_strip_send_frame(codec, colors, num_leds);
if (ret < 0)
--
2.56.0
^ permalink raw reply [flat|nested] only message in thread
only message in thread, other threads:[~2026-11-02 22:57 UTC | newest]
Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 22:57 [PATCH] ALSA: hda/ca0132: Restore AE-5 audio registers after LED strip send Rafael Medina
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®