← Part I: How We Fixed the Left Speaker on the Chuwi CoreBook X in Linux

The Chuwi CoreBook X left speaker, part II: the day I got tired of guessing

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

If you haven't read part I, start there. The short version: five weeks drilling down through Linux's audio stack until I discovered that the left channel goes through an AWINIC AW88298 chip with no mainline driver, connected via I2S to an undocumented output on the Conexant SN6180 codec. I built a daemon that configured it and the speaker worked. Just not quite right.

The symptom that wouldn't go away

When the daemon was up and running, the left speaker finally made sound. Left and right came out of their respective sides, stereo balance worked, levels were reasonable. One detail was missing: the first 100–200 ms of every sound were choppy. Or just plain missing.

The home test was brutal. With continuous music — put on an album and let it play — the left was perfect. But open a video with dialogue, where there are silences and voices come back; trigger a short system notification; start that classic "now left channel, now right channel" test video everyone uses for stereo checks. In all those cases, the first fragment of sound on the left arrived late and wrong.

The human brain catches those artifacts immediately. A driver that drops the beginning of every word in a video is, in practice, a broken driver.

What I tried without measuring anything

My main hypothesis was: "the AW88298 goes into protection and takes time to recover." Based on that, I spent days trying:

Every one of those ideas had a manual-based justification. None of them fixed the symptom. Some made it worse. One of them — periodically reading SYSINT — turned out to be actively counterproductive, because that register is read-clear: reading it wipes the interrupt history. I had spent hours of debugging destroying the evidence I was trying to examine.

The daemon worked "well enough" for continuous music, so I left it at that. But deep down I knew the problem was still there.

"For a proper experiment, measure before you touch"

After hours of fighting with no progress, I forced myself to stop and restate the problem from scratch. The unknown was no longer the HDA routing — I had that figured out. What I didn't know was what the AW88298 does when it transitions from digital silence to a signal while clocks are live. And the aw882xx PID 1852 datasheet (courtesy of the headers published by dianjixz) explicitly documented the relevant state observables: BSTS (boost start-up finished), SWS/WDS (amplifier switching status), PLL, clock, over-current, over-voltage. All in SYSST, read-only, non-destructive.

I had spent weeks reading the datasheet looking for things to write and had never once stopped to think about what I could read. SYSST, VBAT, PVDD, TEMP, ISNDAT, VSNDAT were a live snapshot of the chip's actual state. I just needed to take that snapshot every few milliseconds and see which bits changed at the exact moment of the glitch.

I wrote a script in five minutes. A bash loop reading six registers over I2C, printing only lines where something changed so we wouldn't drown in text, millisecond-precision timestamps. Zero intervention: the chip had no idea it was being watched.

while true; do
    sysst=$(read_be 0x01)
    pvdd=$(read_be 0x14)
    vbat=$(read_be 0x12)
    temp=$(read_be 0x13)
    # ...
    if [[ "$sig" != "$prev" ]]; then
        printf "T+%s  SYSST=%s  PVDD=%dmV  ...\n" "$(ts)" "$sysst" "$pvdd_mv"
        prev="$sig"
    fi
done

I stopped the daemon, looped a L/R test video, launched the sampler, and the pattern showed up on the very first run:

T+00:00.700 SYSST=0x2811 [PLLS CLKS] ← digital silence on L T+00:00.737 SYSST=0x0311 [PLLS CLKS WDS BSTS] ← just as signal comes in T+00:00.842 SYSST=0x2811 [PLLS CLKS] ← back to silence T+00:01.104 SYSST=0x2295 [PLLS OTLS CLKS BSTS] ← transient T+00:01.141 SYSST=0x2811 [PLLS CLKS] T+00:01.515 SYSST=0x0311 [PLLS CLKS WDS BSTS]

The WDS (amplifier switching) and BSTS (boost start-up finished) bits were flickering in sync with the audio. When the audio was digital silence — not clocks stopped, not a closed stream, silence inside a live stream — the chip was shutting down its switching stage and its boost converter. When the signal came back, it had to re-engage them. Those 100–200 ms were the time the boost took to come back up to operating level.

It wasn't a bug. It wasn't a tripped protection. It wasn't PipeWire suspending anything. It was the chip doing to the audio exactly what its firmware told it to do.

BSTCTRL2 and Smart Boost 2

The relevant register was somewhere I had never looked: BSTCTRL2 (0x61). Hardware readout:

BSTCTRL2 = 0x6673 BE bits[14:12] = 110 → Smart Boost 2 bits[10:8] = 110 → BST_TDEG bits[5:0] = 0x33 → VOUT_VREFSET

The public documentation for the aw882xx (PID 1852, published by dianjixz/aw882xx) describes the boost modes like this:

BST_MODE Behavior
000 Transparent — boost in bypass
001 Force Boost — boost is always active
101 Smart Boost 1
others (incl. 110) Smart Boost 2 — boost shuts down when audio level drops below a threshold and reactivates when it rises

Read that again: "boost shuts down when audio level drops below a threshold and reactivates when it rises." That text, written years ago in a datasheet for a chip designed to maximize efficiency, described exactly what my sampler was seeing. The chip shipped with the most aggressive power-saving mode enabled, one that cut its power stage the moment audio went quiet.

It makes complete sense: the AW88298 is designed primarily for products where audio is continuous — music, sustained voice, doorbells. In those cases, shutting the boost during silences saves battery. On a laptop, where audio starts and stops a hundred times a day — an email notification, the blip of an incoming message, a single word from the video you're watching over lunch — that energy saving turns into a glitch at the start of every event.

The fix: one i2cset

Changing BST_MODE to 001 (Force Boost) without touching the other fields in the register is a read-modify-write:

# Current value: 0x6673 BE
# Clear bits [14:12]: 0x6673 & 0x8FFF = 0x0673
# Set bit 12:         0x0673 | 0x1000 = 0x1673
# Little-endian on the bus:             0x7316
sudo i2cset -y 0 0x34 0x61 0x7316 w

One command. One register. The glitches vanished the instant I pressed Enter. I ran the L/R test again, played a dialogue video again, triggered system notifications again. Clean in every case.

I ran the sampler again out of curiosity. BSTS stayed at 1 the whole time. WDS too. The chip was no longer shutting down.

What came next

With Smart Boost 2 out of the picture, a second thing changed: the left speaker's volume ceiling.

For weeks I had been convinced the left couldn't match the right at maximum volume. I had tried pushing the DAC amp up to 0x4a (same value as the right) and always hit distortion or glitches well before that, around 0x39. I assumed it was a physical limitation of the speaker or the boost converter.

Wrong. The ceiling was being imposed by Smart Boost 2 dropping out under load. With Force Boost active, we retuned:

  1. Remove INPLEV=-6dB. We were using it as headroom against boost dropout. Without that dropout, the headroom is unnecessary.
  2. Raise the left DAC amp until the perceived volume matched the right. The result was 0x40 — 10 steps below the right side's maximum, which compensates for the different analog gain chains between the two chips.
  3. Implement a volume mirror in the daemon: the system slider moves the right DAC amp; the daemon reads that value once a second and writes the left as max(0, right - 10). A single slider, two speakers, for the first time in this laptop's life under Linux.

What I took away from this

  1. Measure before you touch. I had lost days trying register combinations because "I had a hypothesis." A hypothesis with no output data is just a guess with adjectives. One afternoon spent instrumenting observables and writing a 50-line sampler made more progress than a week of tuning parameters.
  2. A chip's defaults are ideological. The engineers who designed the AW88298 made sensible decisions for their primary use case: continuous audio, battery savings, maximum efficiency. Those decisions were wrong for my use case (a laptop with discontinuous audio), and the chip itself exposes the bit that reverses them. But I was never going to find it by guessing — only by measuring and then reading the datasheet with a concrete question already in mind.
  3. Stopping and changing your approach is worth its weight in gold. I was locked inside my own hypotheses, running variations of the same experiment with slightly different parameters. Progress came when I stopped asking "which register should I write now?" and started asking "what is the chip telling me if I watch it live?" That reframe wasn't a deep revelation: it was literally getting up from my chair.
  4. SYSINT is read-clear. Don't poll it. End of PSA.
  5. Open source works. This fix exists because someone named dianjixz published aw882xx register headers with documentation of the boost modes, because nadimkobeissi had documented the HDA side-codec pattern for Legion/AW88399, because a handful of kernel maintainers responded patiently on issues that had been open for weeks. Every piece of public documentation I wrote in part I and in this very post is my small contribution to making sure the next person who searches for AW88298 Linux doesn't spend five weeks finding what they need.

Current state

The daemon is running on cortez (my Chuwi CoreBook X). With systemctl enable aw88298 it survives reboots; the left speaker comes up in the first second after login, sounds clean, matches the right at maximum volume, and responds to the system volume slider in a unified way.

The next step is moving all of this into a kernel patch — side-codec init sequence, Force Boost in the probe, volume mirror as an ALSA kcontrol — and submitting it to LKML. That will be the subject of part III.

If you have the same laptop, the daemon and diagnostic scripts are in my repo: github.com/pacomont/chuwi-corebook-x-left-speaker. Any feedback is welcome, especially if you have an internal AW88298 datasheet with more complete documentation on the boost modes and silence detection thresholds — there are things in bits 11 and 13 of SYSST that I still don't know the names of.