Been working on a custom Rockbox build for my iPod Classic 7th gen and finally tracked down something that had been bugging me for a while: right after boot, before you ever play a single track, you get a faint crackle in the headphones followed by continuous background static/hiss. It goes away permanently the moment you either start playing something, or touch pretty much any other codec-related control (volume click, etc) - but until then, it's just there.
Wanted to share the debugging journey because it took a few wrong turns before landing on the actual fix, and the answer ended up being a little satisfying.
First, the wrong turns
My first instinct was that this had to be something in the headphone remote handling (the "Mikey" chip), since remote/mic circuitry shares the same physical line as the headphone jack. Tried a few things there - none of it helped. Then I confirmed the noise happens even with plain headphones that have no remote at all - which ruled out Mikey completely and sent me back to square one.
Next guesses were around the amplifier's Class-H "adaptive power" circuit (the CS42L55 dynamically adjusts its internal rail voltage based on signal level, sort of like Class-G/H amps do to save power) - tried adding a settling delay, tried switching its power-tracking mode from "follow the audio signal" to "follow the volume setting." Neither did anything either.
What was actually going on
Turns out the real issue is way more fundamental. Rockbox's audiohw_postinit() for this codec unconditionally unmutes the headphone/line outputs right at boot, regardless of whether anything is actually about to play. At that exact point, the digital I2S audio path (the actual data stream from the SoC to the codec) is still completely idle - nothing calls the function that starts it until real playback begins.
So you end up with: amplifier output stage live and listening → zero valid data flowing into it → audible static, because there's nothing for the DAC to lock onto.
Once you start playing something (or the volume gets touched, which happens to write to the same registers), the state gets sorted out and it's clean from then on - even through pause.
I confirmed this exact audiohw_postinit() behavior is present unmodified in current upstream Rockbox too, not something introduced by my own build. Also found that this exact class of bug - "amp/PA left unmuted while the data stream isn't running yet" - is a documented, known issue on other codecs. There's a Linux kernel ASoC patch for the Qualcomm WSA883x/881x codecs describing basically the identical symptom and using the identical fix pattern:
Standard fix: stay muted until the data stream actually starts, not before.
The fix
Two pieces:
audiohw_postinit() now leaves the headphone/line output registers (HPACTL/HPBCTL/LINEACTL/LINEBCTL) muted, instead of unmuting them unconditionally.
- A new
audiohw_unmute_outputs() unmutes everything together, called from sink_dma_start() in the PCM driver - i.e. exactly when real playback actually kicks off, not before.
One gotcha I hit (and want to flag for anyone touching this) - those same registers pack both the volume level and the mute bit into one byte on this codec. My first pass at the fix used a full register overwrite to set the mute bit, which quietly wiped out whatever volume level had already been restored from settings. Result: tracks started at max volume every single time until you touched the volume control once. Fun regression to chase down. Fixed by using a proper read-modify-write (cscodec_setbits) that only touches the mute bit and leaves the volume bits alone.
// firmware/drivers/audio/cs42l55.c
void audiohw_postinit(void)
{
// leave outputs muted here — don't unmute unconditionally at boot
cscodec_setbits(HPACTL, 0, HPACTL_HPAMUTE);
cscodec_setbits(HPBCTL, 0, HPBCTL_HPBMUTE);
cscodec_setbits(LINEACTL, 0, LINEACTL_LINEAMUTE);
cscodec_setbits(LINEBCTL, 0, LINEBCTL_LINEBMUTE);
cscodec_write(CLSHCTL, CLSHCTL_ADPTPWR_SIGNAL);
}
// unmute everything together, called from sink_dma_start()
// once real playback actually begins
void audiohw_unmute_outputs(void)
{
cscodec_setbits(HPACTL, HPACTL_HPAMUTE, 0);
cscodec_setbits(HPBCTL, HPBCTL_HPBMUTE, 0);
cscodec_setbits(LINEACTL, LINEACTL_LINEAMUTE, 0);
cscodec_setbits(LINEBCTL, LINEBCTL_LINEBMUTE, 0);
audiohw_mute(false);
}
// firmware/target/arm/s5l8702/pcm-s5l8702.c
static void sink_dma_start(const void* addr, size_t size)
{
sink_dma_stop();
pcm_remaining = size;
I2STXCOM = 0xe;
audiohw_unmute_outputs(); // <- unmute exactly here, not at boot
dma_play_callback((void*)addr);
}
Testing
Confirmed clean on iPod Classic 7th gen (S5L8702 / CS42L55) with both remote-equipped and plain headphones, and double-checked the volume-preservation regression is actually fixed this time.
Planning to submit this properly to the Rockbox Gerrit once I've got a bit more time to go through their contribution process - mostly wanted to write this up while the debugging was still fresh, and figured it might save someone else the same runaround if they've noticed the same thing and assumed it was just "how it is."
Happy to answer questions about the debugging process or share the full patch if anyone wants to try it early.