[PATCH] ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED strip support
From: dchristensen8
Date: Mon Sep 28 2026 - 13:28:38 EST
Hi Takashi, Jaroslav,
Please find attached a patch adding support for the external addressable RGB LED strip header on the Creative Sound BlasterX AE-5 and AE-5 Plus.
The patch is attached as a .patch file.
The Sound BlasterX AE-5 and AE-5 Plus expose a 3-pin 5V addressable RGB header driven directly by the CA0113 PCIe-to-HD-Audio bridge. Rather than using the CA0132 DSP or MMIO GPIO, the CA0113 contains an autonomous HDA link sniffer that snoops a designated HDA DMA playback stream, extracts the 24-bit GRB color payload, and serializes it as 800 kHz single-wire NRZ pulses to the strip (WS2812B protocol).
Expose the strip through a sysfs interface on the codec device:
ae5_strip_leds: read current colors write a comma-deparated #RRGGBB or R,G,B list (a single color replicates across all active LEDs)
ae5_strip_num_leds: get or set the active LED count (1..100)
Writes allocate an idle HDA playback stream, format a 32 KB DMA buffer populated with repeating WS2812 frames (5-word zero preamble, 4 audio words per LED, and reset gap), configure the CA0113 BAR2 serializers and stream-tag pin mux, and pulse the stream long enough for the reset latch to fire. Access is serialized with a per-spec mutex, and the attributes are only registered on QUIRK_AE5 devices.
Document the new sysfs attributes in documentation/ABI/testing.
Signed-off-by: Devin Christensen <dchristensen8@xxxxxxxxx>
Testing & Subsystem Context:
- Verified on physical Sound BlasterX AE-5 hardware.
- Tested continuous 60 FPS streaming and dynamic strip resizing (10 -> 7 -> 10 LEDs)
via the OpenRGB effects engine (MR: https://gitlab.com/CalcProgrammer1/OpenRGB/-/merge_requests/3658).
- Verified concurrent audio playback and LED updates without dropped samples or xruns.
- Built clean against tiwai/sound for-next (commit 72107104d4f5).
- Passes scripts/checkpatch.pl with 0 errors and 0 checks.From b7ccf89f34211856c6406d944949a0140681c3c0 Mon Sep 17 00:00:00 2001
From: Devin Christensen <dchristensen8@xxxxxxxxx>
Date: Mon, 28 Sep 2026 12:04:40 -0400
Subject: [PATCH] ALSA: hda/ca0132: Add Sound BlasterX AE-5 external RGB LED
strip support
The Sound BlasterX AE-5 and AE-5 Plus expose a 3-pin 5V addressable RGB
header driven directly by the CA0113 PCIe-to-HD-Audio bridge. Rather
than using the CA0132 DSP or MMIO GPIO, the CA0113 contains an
autonomous HDA link sniffer that snoops a designated HDA DMA playback
stream, extracts the 24-bit GRB color payload, and serializes it as
800 kHz single-wire NRZ pulses to the strip (WS2812B protocol).
Expose the strip through a sysfs interface on the codec device:
ae5_strip_leds read current colors; write a comma-separated
#RRGGBB or R,G,B list (a single color replicates
across all active LEDs)
ae5_strip_num_leds get or set the active LED count (1..100)
Writes allocate an idle HDA playback stream, format a 32 KB DMA buffer
populated with repeating WS2812 frames (5-word zero preamble, 4 audio
words per LED, and reset gap), configure the CA0113 BAR2 serializers and
stream-tag pin mux, and pulse the stream long enough for the reset latch
to fire. Access is serialized with a per-spec mutex, and the attributes
are only registered on QUIRK_AE5 devices.
Document the new sysfs attributes in Documentation/ABI/testing.
Signed-off-by: Devin Christensen <dchristensen8@xxxxxxxxx>
---
Testing & Subsystem Context:
- Verified on physical Sound BlasterX AE-5 hardware.
- Tested continuous 60 FPS streaming and dynamic strip resizing (10 -> 7 -> 10 LEDs)
via the OpenRGB effects engine (MR: https://gitlab.com/CalcProgrammer1/OpenRGB/-/merge_requests/3658).
- Verified concurrent audio playback and LED updates without dropped samples or xruns.
- Built clean against tiwai/sound for-next (commit 72107104d4f5).
.../ABI/testing/sysfs-devices-sound-ae5 | 30 ++
sound/hda/codecs/ca0132.c | 401 +++++++++++++++++-
2 files changed, 430 insertions(+), 1 deletion(-)
create mode 100644 Documentation/ABI/testing/sysfs-devices-sound-ae5
diff --git a/Documentation/ABI/testing/sysfs-devices-sound-ae5 b/Documentation/ABI/testing/sysfs-devices-sound-ae5
new file mode 100644
index 000000000..69f523a35
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-devices-sound-ae5
@@ -0,0 +1,30 @@
+What: /sys/bus/hdaudio/devices/<device>/ae5_strip_leds
+Date: September 2026
+KernelVersion: 7.3
+Contact: Devin Christensen <dchristensen8@xxxxxxxxx>
+Description:
+ (RW) Sets or reads the RGB color configuration for the external
+ addressable RGB LED strip header on Creative Sound BlasterX
+ AE-5 and AE-5 Plus cards (QUIRK_AE5).
+
+ Writing accepts a comma-separated list of colors in either
+ hexadecimal "#RRGGBB" or decimal "R,G,B" format. If a single
+ color is provided, it is replicated across all currently active
+ LEDs. Supplying fewer colors than active LEDs updates the
+ provided range while preserving existing downstream colors.
+ Supplying more colors automatically expands the active strip
+ count up to a maximum of 100 LEDs.
+
+ Reading returns a comma-separated list of the current colors in
+ "#RRGGBB" format terminated by a newline.
+
+What: /sys/bus/hdaudio/devices/<device>/ae5_strip_num_leds
+Date: September 2026
+KernelVersion: 7.3
+Contact: Devin Christensen <dchristensen8@xxxxxxxxx>
+Description:
+ (RW) Gets or sets the number of active addressable LEDs on the
+ external strip header (integer range: 1 to 100).
+
+ Writing updates the strip length and transmits the current color
+ buffer for the new count. Reading returns the current LED count.
diff --git a/sound/hda/codecs/ca0132.c b/sound/hda/codecs/ca0132.c
index 0f8fb62f0..464b48874 100644
--- a/sound/hda/codecs/ca0132.c
+++ b/sound/hda/codecs/ca0132.c
@@ -35,6 +35,8 @@
#include <sound/tlv.h>
#endif
+#include <linux/dma-mapping.h>
+
#define FLOAT_ZERO 0x00000000
#define FLOAT_ONE 0x3f800000
#define FLOAT_TWO 0x40000000
@@ -1060,6 +1062,8 @@ enum dsp_download_state {
* CA0132 specific
*/
+#define AE5_STRIP_MAX_LEDS 100
+
struct ca0132_spec {
struct hda_gen_spec gen;
@@ -1162,6 +1166,11 @@ struct ca0132_spec {
* Renames PlayEnhancement and CrystalVoice too.
*/
bool use_alt_controls;
+
+ /* AE-5 WS2812 strip control */
+ struct mutex ae5_strip_mutex;
+ u32 ae5_strip_cur_colors[AE5_STRIP_MAX_LEDS];
+ int ae5_strip_cur_num_leds;
};
/*
@@ -8316,8 +8325,386 @@ static void sbz_setup_defaults(struct hda_codec *codec)
}
/*
- * Setup default parameters for the Sound BlasterX AE-5 DSP.
+ * Sound BlasterX AE-5 External WS2812B RGB LED Strip Transport
*/
+static void ae5_strip_encode_24(u32 u, u32 *words)
+{
+ /* Bit-for-bit exact encoding matching CtxHda.sys FUN_00042944:
+ * 24-bit pixel -> 4 x 32-bit audio words (6 bits per word).
+ * Hardware audio serializer transmits MSB first (bits 31 down to 0).
+ * For word i (i8 = 23, 17, 11, 5), bit_idx 0..5 (MSB to LSB within the word)
+ * is placed at n = 29 - bit_idx * 4 (bits 29, 25, 21, 17, 13, 9):
+ * out |= (bit << n) | ((bit | 2) << (n + 1))
+ * Bit 0 -> 0b010 (375ns high, 1042ns low)
+ * Bit 1 -> 0b111 (1083ns high, 333ns low)
+ */
+ int i8 = 23;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ u32 out = 0;
+ int bit_idx;
+
+ for (bit_idx = 0; bit_idx < 6; bit_idx++) {
+ int bit = (u >> (i8 - bit_idx)) & 1;
+ int n = 29 - bit_idx * 4;
+
+ out |= (u32)(bit << n) | ((u32)(bit | 2) << (n + 1));
+ }
+ words[i] = out;
+ i8 -= 6;
+ }
+}
+
+static struct hdac_stream *ae5_strip_find_stream(struct hda_codec *codec)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct hdac_bus *bus = &codec->bus->core;
+ struct hdac_stream *s, *dsp = NULL, *first = NULL;
+ unsigned long flags;
+
+ spin_lock_irqsave(&bus->reg_lock, flags);
+ list_for_each_entry(s, &bus->stream_list, list) {
+ if (s->direction != SNDRV_PCM_STREAM_PLAYBACK)
+ continue;
+ if (s->running || s->locked)
+ continue;
+ if (!first)
+ first = s;
+ /* The DSP DMA consumes the DSP-loader stream (tag in dsp_stream_id). */
+ if (spec->dsp_stream_id && s->stream_tag == spec->dsp_stream_id)
+ dsp = s;
+ }
+ spin_unlock_irqrestore(&bus->reg_lock, flags);
+
+ return dsp ? dsp : first;
+}
+
+/*
+ * Caller must hold spec->ae5_strip_mutex.
+ */
+static int ae5_strip_send_frame(struct hda_codec *codec, const u32 *grb_colors, int num_leds)
+{
+ struct ca0132_spec *spec = codec->spec;
+ struct snd_dma_buffer dmab;
+ struct hdac_stream *hstr = NULL;
+ unsigned int format, stream_tag;
+ u32 enc_words[4];
+ u32 *dst;
+ int total_words = 0x8000 / sizeof(u32); /* 8192 words (32KB) */
+ int word_idx = 0;
+ int frame_words;
+ int led, w;
+ int retries = 20; /* ~100ms max */
+ u8 orig_sd_ctl = 0;
+
+ if (!spec || !spec->mem_base)
+ return -ENODEV;
+ if (num_leds <= 0 || num_leds > AE5_STRIP_MAX_LEDS)
+ return -EINVAL;
+
+ snd_hda_power_up(codec);
+
+ /*
+ * 44.1kHz, 24-bit, 2-channel format (0x4031) matches Windows CtxHda
+ * HDAUDIO_STREAM_FORMAT.
+ */
+ format = snd_hdac_stream_format(2, 24, 44100);
+
+ for (;;) {
+ hstr = ae5_strip_find_stream(codec);
+ if (!hstr) {
+ if (--retries <= 0) {
+ codec_warn(codec, "AE5 strip: no free azx stream after retries\n");
+ snd_hda_power_down(codec);
+ return -EBUSY;
+ }
+ usleep_range(5000, 10000);
+ continue;
+ }
+
+ stream_tag = snd_hdac_dsp_prepare(hstr, format, 0x8000, &dmab);
+ if ((int)stream_tag > 0)
+ break;
+
+ /* Prepare failed (e.g. -EBUSY due to race with playback starting); retry */
+ if (--retries <= 0) {
+ codec_warn(codec, "AE5 strip: snd_hdac_dsp_prepare failed %d\n",
+ (int)stream_tag);
+ snd_hda_power_down(codec);
+ return (int)stream_tag;
+ }
+ usleep_range(5000, 10000);
+ }
+
+ /* Prevent interrupt storms during free-running LED stream, saving original control bits */
+ if (hstr->sd_addr) {
+ orig_sd_ctl = readb(hstr->sd_addr);
+ writeb(orig_sd_ctl & ~0x1c, hstr->sd_addr); /* Clear IOCE, FEIE, DEIE */
+ }
+
+ /* 1. Populate the 32KB DMA buffer with repeating WS2812 frames + reset gaps */
+ memset(dmab.area, 0, 0x8000);
+ dst = (u32 *)dmab.area;
+
+ /* Words per frame: 5 words zero preamble + num_leds * 4 words + 83 words reset gap */
+ frame_words = 5 + (num_leds * 4) + 83;
+
+ while (word_idx + frame_words <= total_words) {
+ /* 5-word zero preamble per Windows CtxHda FUN_00042944 */
+ for (w = 0; w < 5; w++) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ /* LEDs */
+ for (led = 0; led < num_leds; led++) {
+ ae5_strip_encode_24(grb_colors[led], enc_words);
+ for (w = 0; w < 4; w++) {
+ *dst++ = enc_words[w];
+ word_idx++;
+ }
+ }
+ /* 83 words (> 1.8 ms) zero reset gap (WS2812 requires > 50 us low) */
+ for (w = 0; w < 83; w++) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ }
+ while (word_idx < total_words) {
+ *dst++ = 0;
+ word_idx++;
+ }
+ /* Ensure DMA buffer writes are committed before triggering the HDA stream */
+ wmb();
+
+ /* 2. Configure CA0113 BAR2 hardware registers */
+ /* Power up clocks (CtxHdb 0x141b7 / 0x14210) */
+ writel(readl(spec->mem_base + 0xc04) | 0x7, spec->mem_base + 0xc04);
+
+ /* Base serializer initialization (CtxHdb 0x14ba4) */
+ writel(readl(spec->mem_base + 0x400) | 1, spec->mem_base + 0x400);
+ writel(readl(spec->mem_base + 0x42c) & ~1, spec->mem_base + 0x42c);
+ writel(readl(spec->mem_base + 0x46c) & ~1, spec->mem_base + 0x46c);
+ writel(readl(spec->mem_base + 0x4ac) & ~1, spec->mem_base + 0x4ac);
+ writel(readl(spec->mem_base + 0x4ec) & ~1, spec->mem_base + 0x4ec);
+ writel(0, spec->mem_base + 0x43c);
+ writel(0, spec->mem_base + 0x47c);
+ writel(0, spec->mem_base + 0x4bc);
+ writel(0, spec->mem_base + 0x4fc);
+ writel(readl(spec->mem_base + 0x408) | 1, spec->mem_base + 0x408);
+ writel(readl(spec->mem_base + 0x40c) | 1, spec->mem_base + 0x40c);
+ writel((readl(spec->mem_base + 0x410) & ~0xb) | 0x14, spec->mem_base + 0x410);
+
+ /* Configure serializers 0..3 clocking (CtxHdb 0x14a6c) */
+ writel(readl(spec->mem_base + 0x43c) | 0x33, spec->mem_base + 0x43c);
+ writel(readl(spec->mem_base + 0x47c) | 0x33, spec->mem_base + 0x47c);
+ writel(readl(spec->mem_base + 0x4bc) | 0x33, spec->mem_base + 0x4bc);
+ writel(readl(spec->mem_base + 0x4fc) | 0x33, spec->mem_base + 0x4fc);
+
+ /* BAR2 + 0x100 format & channel enables:
+ * - base 0x70f
+ * - bit 28 = 1 (24-bit audio format via CtxHdb 0x14f58)
+ * - bits 29..31 = 1 (44.1 kHz sample rate via CtxHdb 0x14fa4)
+ * - bits 4..7 = 0xf (stage 1 enables)
+ * - bits 12..15 = 0xf (stage 2 enables)
+ * => 0x3000ff8f
+ */
+ writel((readl(spec->mem_base + 0x100) & ~0xe000ffff) | 0x3000ff8f, spec->mem_base + 0x100);
+
+ /* Enable serializer output drivers for pins 0..3 (CtxHdb 0x14e94):
+ * Channel 0 is 0x454, Channel 1 is 0x494, Channel 2 is 0x4d4, Channel 3 is 0x514
+ */
+ writel(readl(spec->mem_base + 0x454) | 1, spec->mem_base + 0x454);
+ writel(readl(spec->mem_base + 0x494) | 1, spec->mem_base + 0x494);
+ writel(readl(spec->mem_base + 0x4d4) | 1, spec->mem_base + 0x4d4);
+ writel(readl(spec->mem_base + 0x514) | 1, spec->mem_base + 0x514);
+
+ /* Route all channels 0..3 to stream_tag (CtxHdb 0x1502c) */
+ writel((stream_tag << 28) | (stream_tag << 20) | (stream_tag << 12) | (stream_tag << 4),
+ spec->mem_base + 0x104);
+
+ /* Enable CA0113 MMIO GPIO 0 & 1 */
+ ca0113_mmio_gpio_set(codec, 0, true);
+ ca0113_mmio_gpio_set(codec, 1, true);
+
+ codec_dbg(codec, "AE5 strip: send_frame: num_leds=%d stream_tag=%u (0x%08x)\n",
+ num_leds, stream_tag, readl(spec->mem_base + 0x104));
+
+ /* 3. Trigger stream transmission on HDA link */
+ snd_hdac_dsp_trigger(hstr, true);
+ /*
+ * WS2812 latches on the >50us reset gap after one frame. The 32KB buffer holds ~64
+ * repeating frame+reset cycles; at ~1.4ms/frame a 15ms run transmits ~10 complete
+ * cycles - more than enough to latch reliably. Keep it short so rapid sysfs writes
+ * (OpenRGB effects/theme changes) are not serialized behind a 1s blocking sleep.
+ */
+ usleep_range(15000, 20000);
+ 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);
+
+ /* Restore descriptor control register */
+ if (hstr->sd_addr)
+ writeb(orig_sd_ctl, hstr->sd_addr);
+
+ snd_hdac_dsp_cleanup(hstr, &dmab);
+ snd_hda_power_down(codec);
+
+ return 0;
+}
+
+static ssize_t ae5_strip_leds_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hdac_device *hdev = container_of(dev, struct hdac_device, dev);
+ struct hda_codec *codec = container_of(hdev, struct hda_codec, core);
+ struct ca0132_spec *spec = codec->spec;
+ int i, len = 0;
+
+ mutex_lock(&spec->ae5_strip_mutex);
+ for (i = 0; i < spec->ae5_strip_cur_num_leds && len < PAGE_SIZE - 16; i++) {
+ u32 grb = spec->ae5_strip_cur_colors[i];
+ u8 g = (grb >> 16) & 0xff;
+ u8 r = (grb >> 8) & 0xff;
+ u8 b = grb & 0xff;
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "%s#%02x%02x%02x",
+ i == 0 ? "" : ",", r, g, b);
+ }
+ mutex_unlock(&spec->ae5_strip_mutex);
+
+ len += scnprintf(buf + len, PAGE_SIZE - len, "\n");
+ return len;
+}
+
+static ssize_t ae5_strip_leds_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hdac_device *hdev = container_of(dev, struct hdac_device, dev);
+ struct hda_codec *codec = container_of(hdev, struct hda_codec, core);
+ struct ca0132_spec *spec = codec->spec;
+ u32 colors[AE5_STRIP_MAX_LEDS];
+ int num_leds = 0;
+ const char *p = buf;
+ int err;
+
+ codec_dbg(codec, "AE5 strip: leds_store received %zu bytes: '%.*s'\n",
+ count, (int)min_t(size_t, count, 64), buf);
+
+ while (*p && *p != '\n' && num_leds < AE5_STRIP_MAX_LEDS) {
+ unsigned int r = 0, g = 0, b = 0;
+ int n = 0;
+
+ while (*p == ' ' || *p == ',' || *p == '\t')
+ p++;
+ if (!*p || *p == '\n')
+ break;
+ if (*p == '#')
+ p++;
+
+ if (sscanf(p, "%2x%2x%2x%n", &r, &g, &b, &n) == 3 && n == 6) {
+ colors[num_leds++] = ((g & 0xff) << 16) | ((r & 0xff) << 8) | (b & 0xff);
+ p += n;
+ } else if (sscanf(p, "%u,%u,%u%n", &r, &g, &b, &n) == 3) {
+ colors[num_leds++] = ((min(g, 255U) & 0xff) << 16) |
+ ((min(r, 255U) & 0xff) << 8) |
+ (min(b, 255U) & 0xff);
+ p += n;
+ } else {
+ break;
+ }
+ }
+
+ if (num_leds == 0)
+ return -EINVAL;
+
+ mutex_lock(&spec->ae5_strip_mutex);
+
+ /* If 1 color is supplied, replicate it across all configured LEDs */
+ if (num_leds == 1) {
+ int i;
+
+ for (i = 1; i < spec->ae5_strip_cur_num_leds; i++)
+ colors[i] = colors[0];
+ num_leds = spec->ae5_strip_cur_num_leds;
+ } else if (num_leds < spec->ae5_strip_cur_num_leds) {
+ /* Partial update: keep existing colors for downstream LEDs and send full frame */
+ int i;
+
+ for (i = num_leds; i < spec->ae5_strip_cur_num_leds; i++)
+ colors[i] = spec->ae5_strip_cur_colors[i];
+ num_leds = spec->ae5_strip_cur_num_leds;
+ } else {
+ /* More colors supplied: expand strip count */
+ spec->ae5_strip_cur_num_leds = num_leds;
+ }
+
+ memcpy(spec->ae5_strip_cur_colors, colors, sizeof(u32) * num_leds);
+
+ err = ae5_strip_send_frame(codec, colors, num_leds);
+
+ mutex_unlock(&spec->ae5_strip_mutex);
+
+ if (err < 0)
+ return err;
+
+ return count;
+}
+static DEVICE_ATTR_RW(ae5_strip_leds);
+
+static ssize_t ae5_strip_num_leds_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct hdac_device *hdev = container_of(dev, struct hdac_device, dev);
+ struct hda_codec *codec = container_of(hdev, struct hda_codec, core);
+ struct ca0132_spec *spec = codec->spec;
+ int val;
+
+ mutex_lock(&spec->ae5_strip_mutex);
+ val = spec->ae5_strip_cur_num_leds;
+ mutex_unlock(&spec->ae5_strip_mutex);
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t ae5_strip_num_leds_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct hdac_device *hdev = container_of(dev, struct hdac_device, dev);
+ struct hda_codec *codec = container_of(hdev, struct hda_codec, core);
+ struct ca0132_spec *spec = codec->spec;
+ unsigned int n;
+ int err;
+
+ if (kstrtouint(buf, 0, &n) || n == 0 || n > AE5_STRIP_MAX_LEDS)
+ return -EINVAL;
+
+ mutex_lock(&spec->ae5_strip_mutex);
+ spec->ae5_strip_cur_num_leds = n;
+ err = ae5_strip_send_frame(codec, spec->ae5_strip_cur_colors, n);
+ mutex_unlock(&spec->ae5_strip_mutex);
+
+ if (err < 0)
+ return err;
+
+ return count;
+}
+static DEVICE_ATTR_RW(ae5_strip_num_leds);
+
+static struct attribute *ae5_strip_attrs[] = {
+ &dev_attr_ae5_strip_leds.attr,
+ &dev_attr_ae5_strip_num_leds.attr,
+ NULL,
+};
+
+static const struct attribute_group ae5_strip_attr_group = {
+ .attrs = ae5_strip_attrs,
+};
+
static void ae5_setup_defaults(struct hda_codec *codec)
{
struct ca0132_spec *spec = codec->spec;
@@ -8741,6 +9128,8 @@ static void ca0132_init_chip(struct hda_codec *codec)
unsigned int on;
mutex_init(&spec->chipio_mutex);
+ mutex_init(&spec->ae5_strip_mutex);
+ spec->ae5_strip_cur_num_leds = 10;
/*
* The Windows driver always does this upon startup, which seems to
@@ -9627,6 +10016,7 @@ static void ca0132_free(struct hda_codec *codec)
if (spec->mem_base)
pci_iounmap(codec->bus->pci, spec->mem_base);
#endif
+ mutex_destroy(&spec->ae5_strip_mutex);
kfree(spec->spec_init_verbs);
kfree(codec->spec);
codec->spec = NULL;
@@ -9956,6 +10346,9 @@ static void ca0132_codec_remove(struct hda_codec *codec)
{
struct ca0132_spec *spec = codec->spec;
+ if (ca0132_quirk(spec) == QUIRK_AE5)
+ sysfs_remove_group(&codec->core.dev.kobj, &ae5_strip_attr_group);
+
switch (ca0132_quirk(spec)) {
case QUIRK_GENERIC:
snd_hda_gen_remove(codec);
@@ -10085,6 +10478,12 @@ static int ca0132_codec_probe(struct hda_codec *codec,
ca0132_setup_unsol(codec);
+ if (ca0132_quirk(spec) == QUIRK_AE5) {
+ err = sysfs_create_group(&codec->core.dev.kobj, &ae5_strip_attr_group);
+ if (err)
+ codec_warn(codec, "AE5 strip: failed to create sysfs group: %d\n", err);
+ }
+
return 0;
error:
--
2.55.0