← Back to blog

How We Fixed the Left Speaker on the Chuwi CoreBook X in Linux

April 11, 2026 · Francisco Montañés García · 15 min read

A 5-week journey through ACPI, HDA, I2C, I2S, and an undocumented chip to get stereo sound working on a laptop where Linux only played through one speaker.

Update (April 12, 2026): the daemon described below had a subtle flaw I only caught after shipping — every sound on the left speaker was missing its first ~100–200 ms. The full story of finding and fixing it is now in Part II: The day I got tired of guessing.

The Problem

The Chuwi CoreBook X is an AMD Ryzen 5 7430U laptop with a peculiar audio defect on Linux: only the right speaker works. The left speaker is completely silent. It works on Windows — but only with Chuwi's OEM driver. A clean Windows install also fails.

This affects every Linux user of this laptop. Reports exist on Chuwi forums, kernel bugzilla, and the SOF (Sound Open Firmware) tracker.

The Initial Investigation (March 2026)

Two Speakers, Two Worlds

The first step was understanding the laptop's audio architecture. Dumping the HDA codec info:

$ cat /proc/asound/card1/codec#0
Codec: Conexant SN6180
Vendor Id: 0x14f120d1
Subsystem Id: 0x27821221

The Conexant SN6180 is the main HDA codec. It has an integrated Class-D amplifier that drives the right speaker through pin 0x17. This works perfectly on Linux.

But the left speaker isn't connected to the SN6180. So what is it connected to?

The Ghost Chip

Searching through the ACPI tables (the firmware description of the hardware), we found a mysterious device:

Scope (_SB.I2CB)
{
    Device (CHIP)
    {
        Name (_HID, "AWDZ8298")
        // I2C addresses: 0x34 and 0x35 at 400kHz
        // GPIO: pin 121 (reset)
    }
}

AWDZ8298 — an AWINIC device on AMD's I2C bus. After research, we identified it: an AWINIC AW88298, a Class-D Smart PA amplifier. It's a chip specialized in amplifying audio for small speakers, with thermal protection, gain control, and an integrated boost converter.

First Contact via I2C

The chip is alive and responds:

$ sudo i2cget -y 0 0x34 0x00 w
0x5218    # Byte-swap → 0x1852 = AW88298 PID ✓

The AW88298 uses 16-bit big-endian registers, but i2cget returns bytes in little-endian order (SMBus protocol). Every read needs mental byte-swapping: 0x5218 is really 0x1852.

Reading the status register:

$ sudo i2cget -y 0 0x34 0x01 w
0x2000    # → BE 0x0020 → bit5 NOCLKS = 1

NOCLKS — "No clock". The chip is powered on but receives no audio signal. It needs an I2S (Inter-IC Sound) clock from somewhere, and nobody is providing it.

The Wrong Turns

"It's the AMD ACP" (Spoiler: It Wasn't)

Our first hypothesis was that the AMD Audio Co-Processor (ACP) should provide I2S to the AW88298. The ACP is an independent audio processor inside the AMD SoC that can handle I2S and PDM interfaces.

We filed issues on the SOF and kernel trackers. The response from AMD (Vijendar Mukunda, audio team engineer) was clear:

"There is no role of ACP IP here. It's purely HDA stack use case where I2S based amplifiers are connected to HDA codec."

AMD said the ACP wasn't involved and that this was a "pure HDA stack case", similar to how Cirrus Logic connects CS35L41 amplifiers to Realtek codecs.

"It's Like CS35L41 with Realtek" (Spoiler: Almost)

The second lead was the snd-hda-scodec-cs35l41 driver, which solves an analogous problem: external amplifiers connected to Realtek HDA codecs. The kernel architecture for this is elegant:

  1. The HDA codec (Realtek) registers as a "component master"
  2. The external amplifier (CS35L41) registers as a "component"
  3. When both are present, the component binding framework links them
  4. The codec calls the amplifier's playback hooks to coordinate power on/off

But there's a fundamental difference: Realtek codecs have documented dedicated I2S output pins. The Conexant SN6180 has nothing like that in its public documentation. This made us doubt AMD's answer.

"So It IS the ACP After All" (Spoiler: Also No)

We read the ACP_I2S_PIN_CONFIG register directly from the ACP's memory-mapped registers:

pin_config = read_mmio(0xFCD81400)
# Result: 0 → I2S disabled in ACP

PIN_CONFIG = 0 — I2S disabled in the ACP. The ACP was completely off. AMD was right: the ACP doesn't participate.

But then... where does the I2S come from?

The Breakthrough (April 10, 2026)

The Test Module

We wrote a kernel module (aw88298_test.ko) that does exactly three things:

  1. Read the chip ID via I2C (verify communication)
  2. Power on the amplifier (clear PWDN, clear AMPPD)
  3. Check for I2S clock (read SYSST)

We loaded it while playing audio through the right speaker:

$ speaker-test -D hw:1,0 -c 2 -l 0 &
$ sudo insmod aw88298_test.ko

And in dmesg:

aw88298-test: Chip ID verified: 0x1852 (AW88298)
aw88298-test: [INITIAL] PLL=unlocked CLKS=no NOCLKS=no
aw88298-test: PLL locked and I2S clock present!
aw88298-test: *** TEST RESULT: I2S clock PRESENT ***

The PLL locked. There IS an I2S clock. It comes from somewhere — and it's not the ACP.

The Format Test

To confirm it was a real clock and not electrical noise, we changed the AW88298's I2S configuration:

# Change from 32-bit/64fs to 16-bit/32fs
$ sudo i2cset -y 0 0x34 0x06 0x0814 w
# Result: PLL loses lock → SYSST = 0x0000

Changing the format broke the PLL. Restoring the original format (32-bit, 64fs, 48kHz) locked it again. The clock is real and has specific parameters.

But No Audio

Despite the PLL being locked, the amplifier's current sense registers read zero:

$ sudo i2cget -y 0 0x34 0x15 w  # ISNDAT (current)
0x0000
$ sudo i2cget -y 0 0x34 0x16 w  # VSNDAT (voltage)
0x0000

Clock present, but no data. The I2S bus has BCLK and WS (Word Select), but the SDATA line is silent.

The Codec Dump That Changed Everything

We went back to the HDA codec dump and looked at it with fresh eyes:

Node 0x10 [Audio Output] — DAC for headphones
Node 0x11 [Audio Output] — DAC for right speaker
Node 0x22 [Audio Output] — Extra DAC, stream=0, UNASSIGNED
Node 0x23 [Audio Output] — Extra DAC, pin 0x26 (not connected)

Node 0x1d [Pin Complex] — Configured as [N/A], DISABLED
  Connection: 1
     0x22    ← Connected to DAC 0x22!

Four DACs. The SN6180 has four digital-to-analog converters, not two. And pin 0x1d — which was disabled with [N/A] configuration — is connected to DAC 0x22.

This is not normal for a simple HDA codec. Two extra DACs and two extra pins suggest the chip was designed for exactly this scenario: an I2S output to an external amplifier.

The Moment of Truth

# 1. Assign the active audio stream to DAC 0x22
$ sudo hda-verb /dev/snd/hwC1D0 0x22 0x706 0x50

# 2. Set the stream format
$ sudo hda-verb /dev/snd/hwC1D0 0x22 0x200 0x11

# 3. Enable pin 0x1d as output
$ sudo hda-verb /dev/snd/hwC1D0 0x1d 0x707 0x40

# 4. Enable EAPD on pin 0x1d
$ sudo hda-verb /dev/snd/hwC1D0 0x1d 0x70c 0x02

"It's playing really loud!"

The left speaker came alive. After 5 weeks of investigation, 4 open issues, communication with AMD engineers, ACPI register analysis, HDA codec dumps, kernel driver reading, and hundreds of I2C commands... the left speaker was playing.

Tuning Stereo

Channel Separation

The first sound was mono — both speakers played both channels. The trick for real stereo was elegant:

In HDA, each DAC has a channel parameter within the stream. If a stereo stream has channel 0 (left) and channel 1 (right), we can tell each DAC to take only one:

# DAC 0x11 (right speaker): take channel 1 only
$ sudo hda-verb /dev/snd/hwC1D0 0x11 0x706 0x51  # stream 5, channel 1

# DAC 0x22 (left speaker): take channel 0 only
$ sudo hda-verb /dev/snd/hwC1D0 0x22 0x706 0x50  # stream 5, channel 0

The AW88298 also has its own channel selector (I2SCTRL register, CHSEL=01 = Left), so it only reproduces the left channel from I2S. Perfect stereo.

The Clipping Problem

With YouTube music, the left speaker was choppy on peaks. The dropouts were synchronized with the music — when there was a bass hit or drum kick, the sound would briefly cut out.

Reading the AW88298's interrupt register:

$ sudo i2cget -y 0 0x34 0x02 w
0x9543  # → BE 0x4395
# CLIPIS = 1  — Clipping detected!
# UVLIS = 1   — Under-voltage!

The amplifier was clipping on signal peaks and the boost converter couldn't maintain voltage. The root cause: HAGCE = 0 — the Hardware Automatic Gain Control was disabled.

HAGC is an automatic gain control system that dynamically reduces volume when the signal approaches the amplifier's limits. Without it, peaks simply get clipped.

# Enable HAGC and increase boost current limit
$ sudo i2cset -y 0 0x34 0x05 0x6B00 w
# HAGCE=1, BST_IPEAK=11 (4.25A), HMUTE=0

The choppy audio disappeared completely.

The Complete Architecture

┌──────────────────────────────────────────────┐ │ AMD Ryzen 5 7430U │ │ │ │ ┌──────────┐ HDA Bus ┌────────────┐ │ │ │ HD Audio │<────────────>│ Conexant │ │ │ │Controller│ │ SN6180 │ │ │ └──────────┘ │ │ │ │ │ DAC 0x11 ───┼──>Pin 0x17──>Class-D──>RIGHT SPK │ │ │ │ │ │ DAC 0x22 ───┼──>Pin 0x1d──>I2S──┐ │ └─────────────┘ │ │ │ ┌──────────┐ │ │ │ │ I2C Bus │<───────────── control ────────┼───┐ │ │ └──────────┘ │ ▼ ▼ │ │ ┌──────────────┐ │ │ │ AWINIC │ │ │ │ AW88298 │ │ │ │ Smart PA │ │ │ └──────┬───────┘ └──────────────────────────────────────────────┘ │ LEFT SPK

Two completely different audio paths:

The AW88298 is controlled via I2C (separate bus from audio). Audio travels via I2S from the HDA codec.

The Registers That Matter

Conexant SN6180 (HDA Verbs)

ActionVerbParamDescription
Stream → DAC 0x220x706<stream_id>0Assign stream, channel 0 (left)
Stream → DAC 0x110x706<stream_id>1Assign stream, channel 1 (right)
Format DAC 0x220x200<format>Copy format from active stream
Pin 0x1d output0x7070x40Enable pin as output
Pin 0x1d EAPD0x70c0x02Enable output amplifier on pin

AWINIC AW88298 (I2C Registers)

RegisterAddressValueDescription
SYSCTRL0x040x3040SPK_GAIN=AV14, I2SEN=1, PWDN=0, AMPPD=0
SYSCTRL20x050x006BHAGCE=1, BST_IPEAK=11 (4.25A), HMUTE=0
I2SCTRL0x060x14E8Philips I2S, 32-bit, 64fs, 48kHz, Left ch.

Note on endianness: the AW88298 uses big-endian. With i2cset/i2cget (SMBus, little-endian), bytes are reversed. The value 0x3040 is written as i2cset -y 0 0x34 0x04 0x4030 w.

The Kernel Patch

The definitive fix is ~460 lines of new kernel code:

1. conexant.c — Component Binding + Fixup

We add component binding support to the Conexant driver (which never had it — this is a first). The fixup for the Chuwi CoreBook X (subsystem ID 0x2782:0x1221):

2. aw88298_hda.c — Side-Codec Driver

An I2C driver that:

Lessons Learned

1. AMD was right — but their answer was so terse we almost dismissed it. "Pure HDA stack case" is technically correct, but explains nothing. It took us weeks to prove it.

2. Public documentation lies by omission — the SN6180 doesn't publicly document its I2S capability. But the hardware is there: 4 DACs, I2S pins, all wired on the PCB.

3. The test module was key — without it, we'd still be debating whether I2S comes from ACP or the codec. 30 lines of C code resolved the question in 3 seconds.

4. HAGC is mandatory — without hardware automatic gain control, the amplifier clips on signal peaks. This isn't obvious until you play real music (test tones don't have peaks).

5. The kernel's component binding is elegant — the framework already exists for exactly this case. We just needed to adapt it to Conexant (which had never needed it before).

Current Status

Follow-up

After the daemon shipped I hit a subtle problem: every sound on the left speaker was losing its first ~100–200 ms. Continuous music was fine, but dialogue, notifications, and short clips were all choppy at startup. It turned out the AW88298 was intentionally idling its boost converter during digital silence and taking a moment to re-engage when signal returned. The fix was a single read-modify-write on an undocumented boost-mode register.

That whole debugging story — including the passive register sampler that surfaced the root cause, and a few lessons I took away from it — is in Part II: The day I got tired of guessing.

Acknowledgments