Dealing with the Heaphone Jack Sense NID Programmable Pin on Sigmatel’s audio cards.

The issue

Whilst playing audio on a Dell laptop using a Sigmatel  STAC9205 sound card, whenever a headphone was plugged into the HP lateral jack, the speaker died. Obviously, this kinda sort of behaviour is pretty standard. Unluckily, there was no commutable option available through the sound mixer so as to enable or disable this software switch. Thus, there was no possibility of listening to the audio using both channels (HP and Line Out). At the same time, I mean.

Analysing the sigmatel driver

This sound card is implemented with the aid of the AC97 Intel’s audio codec bus. The LKM is called snd_hda_intel.ko, and accordingly to the alsamixer utility, it was a Sigmatel STAC9205. So, I started peering at the GNU/Linux Kernel sources, right inside the sound/pci/hda/ directory. In there, I found the patch_sigmatel.c source file, in charge of altering the snd_hda_intel driver accordingly to the internal registers and specs of a Sigmatel sound card. So, I found this line of code, telling me where I had to start reading at:

41     { .id = 0x838476a0, .name = "STAC9205", .patch = patch_stac9205 },

The routines in charge of dealing with the particularities of this sound card were well-defined in a hda_codec_ops data structure, according to the patch_stac9205() routine:

codec->patch_ops = stac92xx_patch_ops;

Looking at this data structure, I found out there was a routine reading from a certain card’s register so as to determine whether this “HP Jack” had something plugged into or not. Right here:

3358 static struct hda_codec_ops stac92xx_patch_ops = {
3359     .build_controls = stac92xx_build_controls,
3360     .build_pcms = stac92xx_build_pcms,
3361     .init = stac92xx_init,
3362     .free = stac92xx_free,
3363     .unsol_event = stac92xx_unsol_event,
3365     .resume = stac92xx_resume,
3366 #endif
3367 };

Have a look at the .unsol_event. From there, it was easy as pie to ended up right in here:

3322 static void stac92xx_unsol_event(struct hda_codec *codec, unsigned int res)
3323 {
3324         struct sigmatel_spec *spec = codec->spec;
3325         int idx = res >> 26 & 0x0f;
3327         switch ((res >> 26) & 0x30) {
3328                 case STAC_HP_EVENT:
3329                         stac92xx_hp_detect(codec, res);
3330                         /* fallthru */
3331                 case STAC_PWR_EVENT:
3332                         if (spec->num_pwrs > 0)
3333                                 stac92xx_pin_sense(codec, idx);
3334         }
3335 }

Quite so; whenever an unkown event was triggered, this function would be called. In our particular discussion, I was truly interested in this one: STAC_HP_EVENT, indicating something happened to the HP Jack. Obviously, the routine responsible of the HP Jack behaviour was stac92xx_hp_detect().

Altering the STAC_HP_EVENT behaviour

Below, the source code detecting whether the HP Jack Sense pin was available or not:

3254     presence = 0;
3255     if (spec->gpio_mute)
3256         presence = !(snd_hda_codec_read(codec, codec->afg, 0,
3257             AC_VERB_GET_GPIO_DATA, 0) & spec->gpio_mute);
3259     for (i = 0; i < cfg->hp_outs; i++) {
3260         if (presence)
3261             break;
3262         if (spec->hp_switch && cfg->hp_pins[i] == nid)
3263             break;
3264         presence = get_hp_pin_presence(codec, cfg->hp_pins[i]);
3265     }

Thus, all I had to do was to add this line of code before calling the branch in order to mute all the lines:

3267     //TCG:
3268     presence = 0;

Finally, I recompiled this kernel module and replaced the original one. From now on, I can listen to the audio using both channels simultaneously, no matter if I’ve got headphones plugged or not.