The result was ordinary desktop audio through the iMac’s internal speakers, including browser video playback. Getting there took a small hardware-specific extension to the existing Linux driver, followed by a PipeWire profile change.

Work carried out 26–28 July 2026. Case study prepared 22 October 2026.

The Mac and the symptom

Our machine was an iMac16,2, the Late 2015 21.5-inch Retina 4K iMac, running an Ubuntu-based Linux system. It uses a Cirrus Logic CS4208 codec with codec ID 1013:4208 and subsystem ID 106b:8100.

The Intel HD Audio controller, ALSA devices and PipeWire output were all present. Playback streams opened successfully, the output was unmuted, and the audio stack reported no obvious error. Yet the built-in speakers produced no sound.

Scope matters

This documents the fix we validated for that exact hardware. Other iMacs, MacBooks and audio codecs can need different fixes; a similar product name is not enough to identify the same speaker system.

First, we ruled out the usual causes

We checked the selected output, PipeWire and WirePlumber services, ALSA mixer levels, speaker pins, converter power states and active playback streams. We also tested stereo, 2.1 and 4.0 output profiles. With the original driver, all three were silent.

During four-channel playback, both hardware converters were active and received separate channel pairs. That gave us a useful distinction: Linux was delivering an audio stream, but something later in the speaker path still needed attention. Changing the profile alone had not fixed it.

Each experiment had a defined rollback. We restored mixer levels and hardware state after tests, and kept a way to return to the packaged Linux codec driver.

The missing clues were on the amplifier side

The Linux audio stack was doing its job. The gap sat after the codec, with the speaker amplifiers, which on this board hang off the codec’s private I²C bus. The generic Linux driver has no model-specific knowledge of that arrangement.

Four amplifier targets sit at 0x6c, 0x6d, 0x6e and 0x6f in seven-bit address notation. All four acknowledged controlled probes, and we verified a reversible stop write before testing any start sequence.

The working setup combined three things: codec configuration for the four-channel path, the GPIO lines that gate the amplifier hardware, and a defined start and stop operation for each amplifier. Getting the order of those operations right proved to be part of the fix, not an implementation detail.

Successful writes still did not guarantee audible sound. Copying the codec settings alone failed; configuring and starting the amplifiers also failed in the early tests. The order in which the hardware was prepared became the next question.

The breakthrough was the playback sequence

The first audible test configured both CS4208 converters before starting the amplifiers. These are TX1 and TX2, at nodes 0x0a and 0x0b. Each converter’s channel-count control was set to 1, which represents two channels.

The successful combination also asserted GPIO0, GPIO4 and GPIO5, applied the amplifier configuration, started all four amplifiers, and played a four-channel stream. Earlier tests with the GPIO and amplifier settings but without that converter-first ordering had remained silent.

This pointed to startup timing and framing as important. We validated the complete working sequence; we did not independently prove that every retained GPIO setting was essential.

We put the sequence into the Linux driver

We added an iMac16,2-specific fixup to snd_hda_codec_cs420x, matched to subsystem 106b:8100. The driver prepares the converters and amplifiers during HDA_GEN_PCM_ACT_PREPARE, after the normal audio code has established the stream format. It stops the amplifiers during cleanup, with close handling as a fallback.

The following C examples are excerpts from our working kernel 7.0 port. The complete patch also defines the fixup, initialises its state, implements the private-bus helpers and registers the playback hook.

C · Match the codec subsystem
SND_PCI_QUIRK(0x106b, 0x8100, "iMac 16,2", CS4208_IMAC162),

This row belongs in the codec subsystem match table. Its fixup ID selects the iMac-specific configuration; adding the row alone does not initialise the amplifiers.

Prepare both converters before amplifier start

Inside the playback hook, the preparation branch sets both channel-count controls, configures the GPIO mask and then calls the amplifier initialisation helper. The constants used here are CS4208_IMAC162_TX1_NID = 0x0a, CS4208_IMAC162_TX2_NID = 0x0b and CS4208_IMAC162_GPIO_MASK = 0x31.

C · Stream preparation excerpt
case HDA_GEN_PCM_ACT_PREPARE:
    if (spec->imac162_amps_started)
        break;

    snd_hda_codec_write(codec, CS4208_IMAC162_TX1_NID, 0,
                        AC_VERB_SET_CVT_CHAN_COUNT, 1);
    snd_hda_codec_write(codec, CS4208_IMAC162_TX2_NID, 0,
                        AC_VERB_SET_CVT_CHAN_COUNT, 1);
    snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_MASK,
                        CS4208_IMAC162_GPIO_MASK);
    snd_hda_codec_write(codec, 0x01, 0,
                        AC_VERB_SET_GPIO_DIRECTION,
                        CS4208_IMAC162_GPIO_MASK);
    snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_GPIO_DATA,
                        CS4208_IMAC162_GPIO_MASK);

    err = cs4208_imac162_amp_start(codec);
    if (err < 0) {
        cs4208_imac162_write_all(codec, 0x01, 0xff);
        break;
    }
    spec->imac162_amps_started = true;
    break;

The full hook checks that this is the internal speaker PCM and holds a mutex around its lifecycle operations. The start helper applies the complete amplifier configuration before issuing the start write. If it fails, this branch attempts to stop all four amplifiers and leaves the started flag clear.

Give hardware waits a defined end

We also made private codec transactions synchronous and serialised, added short delays between writes, and used bounded acknowledgement and completion checks. Doing the expensive amplifier work at stream preparation avoided repeating it during WirePlumber’s device-profile probing.

C · Bounded status polling
static int cs4208_i2c_wait_status(struct hda_codec *codec,
                                  unsigned int mask,
                                  unsigned int expected)
{
    int status, i;

    for (i = 0; i < 20; i++) {
        status = cs_vendor_coef_get_sync(codec,
                                        CS4208_I2C_STATUS_COEF);
        if ((status & mask) == expected)
            return status;
        usleep_range(1000, 1500);
    }
    return -ETIMEDOUT;
}

This helper makes at most 20 status checks, with a short sleep after each unsuccessful check. A matching status returns immediately; exhausting the checks returns -ETIMEDOUT to the caller. Elapsed time also includes codec transactions and scheduling.

The start and stop operations

The validated amplifier sequence ends with register 0x01 = 0xfd to start each amplifier. Cleanup writes 0x01 = 0xff. These are part of the matched driver’s lifecycle, rather than commands we run manually before each song.

Why stereo applications needed a 4.0 output

The driver fix was only part of the solution. A two-channel hardware stream still left the speakers silent. On this machine, both transmitters had to carry an active stream; forcing their channel-count registers alone did not activate the second one.

The working desktop profile was Analog Surround 4.0 Output + Analog Stereo Input. Its full profile name is:

output:analog-surround-40+input:analog-stereo

PipeWire maps ordinary stereo applications into that four-channel hardware output. We confirmed audible left/right test tones through the normal desktop audio path, then confirmed real playback from a YouTube video in Waterfox.

The working audio path
  1. Stereo appBrowser or music player
  2. PipeWire 4.0Four hardware channels
  3. TX1 + TX2Both converters active
  4. AmplifiersInitialised by the driver

Sound reaches the built-in speakers through the normal desktop output.

WirePlumber saved the selected profile. Choosing Analog Stereo output again recreated the silent two-channel hardware path. The 4.0 label describes the hardware stream here; it does not mean applications need surround-sound content.

Making it persistent, and surviving a kernel change

We packaged the replacement codec module with DKMS so it could be installed alongside the distribution’s kernel modules. The first working package, cs4208-imac162/0.1, was built for 6.17.0-35-generic.

The move to 7.0.0-28-generic exposed an important failure: recompiling the old driver source against the new headers succeeded, but loading it triggered kernel errors. A successful build had not established runtime compatibility.

We removed that incompatible build, obtained the exact Ubuntu 7.0 source, checked it against the source descriptor’s SHA-256 hashes, and ported only our iMac changes onto the native 7.0 driver. Version 0.2 then loaded successfully, exposed the normal audio devices and produced audible test tones through PipeWire without the earlier kernel errors.

Match the source to the kernel

The validated 0.2 package uses AUTOINSTALL=no. Future kernels need a compatibility check or a source-level port before this custom driver is installed. We kept the original module and a removal path available for rollback.

If you are investigating the same Mac

Start by recording the model, kernel and codec IDs. These read-only checks help distinguish this case from a different audio problem:

cat /sys/class/dmi/id/product_name
uname -r
grep -E 'Codec:|Vendor Id:|Subsystem Id:' /proc/asound/card*/codec#*
modinfo -n snd_hda_codec_cs420x
dkms status
pactl list cards

For the case described here, the identifying values were iMac16,2, 0x10134208 and 0x106b8100. The installed custom module resolved to an updates/dkms/ path.

With a compatible version of the custom driver installed, select the built-in audio card’s Analog Surround 4.0 Output + Analog Stereo Input profile in your audio configuration tool, then choose that output for playback. Selecting the profile alone did not repair the stock-driver setup in our tests.

Follow-up testing exposed a separate volume-control problem: moving the desktop slider did not change the speakers’ loudness. The speaker transmitters exposed no hardware volume amplifier, but the generic mixer was still being used to set their level. A low slider setting therefore did not guarantee quiet playback.

We enabled api.alsa.soft-mixer = true for this sound card in WirePlumber, so volume adjustment happens in software before audio reaches the speakers. This machine uses WirePlumber 0.4 and its Lua configuration format; newer versions use a different format. Silent checks confirmed that all four channels followed the volume setting. A subsequent listening check confirmed that the volume was now usable.

Our WirePlumber 0.4 rule lives in ~/.config/wireplumber/main.lua.d/51-imac162-software-volume.lua:

Lua · WirePlumber 0.4 software volume
table.insert(alsa_monitor.rules, {
  matches = {
    { { "device.name", "equals", "alsa_card.pci-0000_00_1b.0" } },
  },
  apply_properties = {
    ["api.alsa.soft-mixer"] = true,
  },
})

The device name matches this machine’s built-in sound card. The setting applies to that whole card, including input volume control. Keep the working 4.0 profile selected; software volume and hardware channel routing solve separate parts of the problem.

Keep the output muted while checking volume control and after profile changes: one of our earlier switches reset the level to 100%. Confirm that attenuation is actually applied before attempting a listening check.

What we verified

  • Audible playback through the internal speakers using the modified driver.
  • Ordinary stereo playback through PipeWire with the 4.0 hardware profile.
  • Real browser video audio, beyond a low-level test stream.
  • Usable speaker volume control after enabling software attenuation.
  • A working DKMS package on each of the two specifically tested kernels.

The stereo input remained available, but these tests established speaker playback. They did not establish headphone or microphone performance, suspend/resume behaviour, compatibility with every future kernel, or support for other Mac models.

The useful lesson was to follow the complete audio path. A detected device, moving level meter and clean playback log were all real observations. Audible output only arrived when the codec, external amplifiers and desktop profile were working together.