S/PDIF with the AD1988 HDA codec

June 18th, 2007 by papillon

At first, it was totally easy to get the sound output working. The kernel that Kubuntu 7.04 installs by default already includes the necessary ALSA drivers for Intel HDA and the AD1988 codec, so I did not have to do a thing.

AD1988

That is only fine for the analog outputs, though. The digital signal that the chips spits out is unusable, since it is totally distorted. It sounds like as if the volume has been set way to high, and even with the volume turned halfway down, the distortion is still slightly noticeable. Since I usually listen to music with headphones that are connected to an external DAC, and I am probably a borderline audiophile type of person, this was clearly not acceptable.

My first foray into the world of ALSA drivers ended with a few lost hairs, some empty bottles of basic bavarian nourishment (a.k.a. beer), and the empty feeling, that I do not have a clue about hardware level programming. After looking at the functional diagram of the AD1988 I had some ideas what might be wrong, but I couldn’t point my finger to the relevant code. I was suspecting that the driver sets the amp gain for the digital out too high, but eventually I gave up. The fact that the output was fine after a complete cold boot and then suddenly began to distort again, did not help my motivation either.

A few days later, I was about to order a PCI soundcard which looked promising (M-Audio Audiophile 2496), but somehow I wanted to take another stab at the ALSA code before hitting the order button. As it turned out, this was a good idea.

After studying the Intel HDA documentation and again looking at the code, I started to at least see the tip of the iceberg and found something interesting. The codec is initialised according to tables in patch_analog.c, and one of the tables takes care of the S/PDIF widget NID:02:

static struct hda_verb ad1988_spdif_init_verbs[] = {
        /* SPDIF out sel */
        {0x02, AC_VERB_SET_CONNECT_SEL, 0x0}, /* PCM */
        {0x0b, AC_VERB_SET_CONNECT_SEL, 0x0}, /* ADC1 */
        {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
        {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)},
        /* SPDIF out pin */
        {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x27}, /* 0dB */
        {0x1b, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x17}, /* 0dB */
        { }
};

The input to the widget NID:1D receives input from the PCM stream coming from the computer, and mixes it with input from one of the three ADCs that are connected to line in, microphone, and other input plugs (ADC1 in this case).

Now, according to the output of cat /proc/asound/card0/codec#0, those three ADCs are muted, and thus should not send any input to the 1D widget. Unfortunately, they do, anyway. I am not sure why, but muting everything expect the PCM stream, by changing the fourth entry above, makes the distortions go away:

        {0x1d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)},

Maybe it is a bug in the codec, maybe it is some other bug in the ALSA drivers. Especially how it mutes the ADCs NID:08, 09, and 0F looks fishy:

        /* ADCs; muted */
        {0x08, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
        {0x09, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},
        {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE},

According to the functional diagram, those have no amp and thus cannot be muted. The audio selectors that input to the ADCs have, but muting these did not solve the problem in my tests.

Note: This change means that you will not get any sound from the inputs back in to your S/PDIF stream any more. Personally, this is an entirely acceptable solution for me. Oh, btw, this has been done with ALSA 1.0.14.