--- /dev/null
+/*
+ * Universal Interface for Intel High Definition Audio Codec
+ *
+ * HD audio interface patch for SigmaTel STAC92xx
+ *
+ * Copyright (c) 2005 Embedded Alley Solutions, Inc.
+ * <matt@embeddedalley.com>
+ *
+ * Based on patch_cmedia.c and patch_realtek.c
+ * Copyright (c) 2004 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+
+#undef STAC_TEST
+
+struct sigmatel_spec {
+       /* playback */
+       struct hda_multi_out multiout;
+       hda_nid_t playback_nid;
+
+       /* capture */
+       hda_nid_t *adc_nids;
+       hda_nid_t *mux_nids;
+       unsigned int num_adcs;
+       hda_nid_t capture_nid;
+
+       /* power management*/
+       hda_nid_t *pstate_nids;
+       unsigned int num_pstates;
+
+       /* pin widgets */
+       hda_nid_t *pin_nids;
+       unsigned int num_pins;
+#ifdef STAC_TEST
+       unsigned int *pin_configs;
+#endif
+
+       /* codec specific stuff */
+       struct hda_verb *init;
+       snd_kcontrol_new_t *mixer;
+
+       /* capture source */
+       const struct hda_input_mux *input_mux;
+       unsigned int cur_mux[2];
+
+       /* channel mode */
+       unsigned int num_ch_modes;
+       unsigned int cur_ch_mode;
+       const struct sigmatel_channel_mode *channel_modes;
+
+       struct hda_pcm pcm_rec[1];      /* PCM information */
+};
+
+static hda_nid_t stac9200_adc_nids[1] = {
+        0x03,
+};
+
+static hda_nid_t stac9200_mux_nids[1] = {
+        0x0c,
+};
+
+static hda_nid_t stac9200_dac_nids[1] = {
+        0x02,
+};
+
+static hda_nid_t stac9200_pstate_nids[3] = {
+       0x01, 0x02, 0x03,
+};
+
+static hda_nid_t stac9200_pin_nids[8] = {
+       0x08, 0x09, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12,
+};
+
+static hda_nid_t stac922x_adc_nids[2] = {
+        0x06, 0x07,
+};
+
+static hda_nid_t stac922x_mux_nids[2] = {
+        0x12, 0x13,
+};
+
+static hda_nid_t stac922x_dac_nids[4] = {
+        0x02, 0x03, 0x04, 0x05,
+};
+
+static hda_nid_t stac922x_pstate_nids[7] = {
+       0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+};
+
+static hda_nid_t stac922x_pin_nids[10] = {
+       0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
+       0x0f, 0x10, 0x11, 0x15, 0x1b,
+};
+
+static int stac92xx_mux_enum_info(snd_kcontrol_t *kcontrol, snd_ctl_elem_info_t *uinfo)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sigmatel_spec *spec = codec->spec;
+       return snd_hda_input_mux_info(spec->input_mux, uinfo);
+}
+
+static int stac92xx_mux_enum_get(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sigmatel_spec *spec = codec->spec;
+       unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+       ucontrol->value.enumerated.item[0] = spec->cur_mux[adc_idx];
+       return 0;
+}
+
+static int stac92xx_mux_enum_put(snd_kcontrol_t *kcontrol, snd_ctl_elem_value_t *ucontrol)
+{
+       struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+       struct sigmatel_spec *spec = codec->spec;
+       unsigned int adc_idx = snd_ctl_get_ioffidx(kcontrol, &ucontrol->id);
+
+       return snd_hda_input_mux_put(codec, spec->input_mux, ucontrol,
+                                    spec->mux_nids[adc_idx], &spec->cur_mux[adc_idx]);
+}
+
+static struct hda_verb stac9200_ch2_init[] = {
+       /* set dac0mux for dac converter */
+       { 0x07, 0x701, 0x00},
+       {}
+};
+
+static struct hda_verb stac922x_ch2_init[] = {
+       /* set master volume and direct control */      
+       { 0x16, 0x70f, 0xff},
+       {}
+};
+
+struct sigmatel_channel_mode {
+       unsigned int channels;
+       const struct hda_verb *sequence;
+};
+
+static snd_kcontrol_new_t stac9200_mixer[] = {
+       HDA_CODEC_VOLUME("Master Playback Volume", 0xb, 0, HDA_OUTPUT),
+       HDA_CODEC_MUTE("Master Playback Switch", 0xb, 0, HDA_OUTPUT),
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Input Source",
+               .count = 1,
+               .info = stac92xx_mux_enum_info,
+               .get = stac92xx_mux_enum_get,
+               .put = stac92xx_mux_enum_put,
+       },
+       HDA_CODEC_VOLUME("Capture Volume", 0x0a, 0, HDA_OUTPUT),
+       HDA_CODEC_MUTE("Capture Switch", 0x0a, 0, HDA_OUTPUT),
+       HDA_CODEC_VOLUME("Input Mux Volume", 0x0c, 0, HDA_OUTPUT),
+       { } /* end */
+};
+
+static snd_kcontrol_new_t stac922x_mixer[] = {
+       HDA_CODEC_VOLUME("PCM Playback Volume", 0x2, 0x0, HDA_OUTPUT),
+       HDA_CODEC_MUTE("PCM Playback Switch", 0x2, 0x0, HDA_OUTPUT),
+       {
+               .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+               .name = "Input Source",
+               .count = 1,
+               .info = stac92xx_mux_enum_info,
+               .get = stac92xx_mux_enum_get,
+               .put = stac92xx_mux_enum_put,
+       },
+       HDA_CODEC_VOLUME("Capture Volume", 0x17, 0x0, HDA_INPUT),
+       HDA_CODEC_MUTE("Capture Switch", 0x17, 0x0, HDA_INPUT),
+       HDA_CODEC_VOLUME("Mux Capture Volume", 0x12, 0x0, HDA_OUTPUT),
+       { } /* end */
+};
+
+static struct hda_input_mux stac9200_input_mux = {
+       .num_items = 5,
+       .items = {
+               { "Port B", 0x0 },
+               { "Port C", 0x1 },
+               { "Port D", 0x2 },
+               { "Port A", 0x3 },
+               { "CD", 0x4 },
+       }
+};
+
+static struct hda_input_mux stac922x_input_mux = {
+       .num_items = 7,
+       .items = {
+               { "Port E", 0x0 },
+               { "CD", 0x1 },
+               { "Port F", 0x2 },
+               { "Port B", 0x3 },
+               { "Port C", 0x4 },
+               { "Port D", 0x5 },
+               { "Port A", 0x6 },
+       }
+};
+
+static int stac92xx_build_controls(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       int err;
+
+       err = snd_hda_add_new_ctls(codec, spec->mixer);
+       if (err < 0)
+               return err;
+
+       return 0;
+}
+
+#ifdef STAC_TEST
+static unsigned int stac9200_pin_configs[8] = {
+       0x40000100, 0x40000100, 0x0221401f, 0x01114010,
+       0x02a19020, 0x01a19021, 0x90100140, 0x01813122,
+};
+
+static unsigned int stac922x_pin_configs[14] = {
+       0x40000100, 0x40000100, 0x40000100, 0x01114010,
+       0x01813122, 0x40000100, 0x40000100, 0x40000100,
+       0x40000100, 0x40000100,
+};
+
+static void stac92xx_set_config_regs(struct hda_codec *codec)
+{
+       int i;
+       struct sigmatel_spec *spec = codec->spec;
+       unsigned int pin_cfg;
+
+       for (i=0; i < spec->num_pins; i++) {
+               snd_hda_codec_write(codec, spec->pin_nids[i], 0,
+                                   AC_VERB_SET_CONFIG_DEFAULT_BYTES_0,
+                                   spec->pin_configs[i] & 0x000000ff);
+               snd_hda_codec_write(codec, spec->pin_nids[i], 0,
+                                   AC_VERB_SET_CONFIG_DEFAULT_BYTES_1,
+                                   (spec->pin_configs[i] & 0x0000ff00) >> 8);
+               snd_hda_codec_write(codec, spec->pin_nids[i], 0,
+                                   AC_VERB_SET_CONFIG_DEFAULT_BYTES_2,
+                                   (spec->pin_configs[i] & 0x00ff0000) >> 16);
+               snd_hda_codec_write(codec, spec->pin_nids[i], 0,
+                                   AC_VERB_SET_CONFIG_DEFAULT_BYTES_3,
+                                   spec->pin_configs[i] >> 24);
+               pin_cfg = snd_hda_codec_read(codec, spec->pin_nids[i], 0,
+                                            AC_VERB_GET_CONFIG_DEFAULT,
+                                            0x00);     
+               printk("pin nid %2.2x pin config %8.8x\n", spec->pin_nids[i], pin_cfg);
+       }
+}
+#endif
+
+static int stac92xx_set_pinctl(struct hda_codec *codec, hda_nid_t nid, unsigned int value)
+{
+       unsigned int pin_ctl;
+
+       pin_ctl = snd_hda_codec_read(codec, nid, 0,
+                                    AC_VERB_GET_PIN_WIDGET_CONTROL,
+                                    0x00);
+       snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+                           pin_ctl | value);
+
+       return 0;
+}
+
+static int stac92xx_set_vref(struct hda_codec *codec, hda_nid_t nid)
+{
+       unsigned int vref_caps = snd_hda_param_read(codec, nid, AC_PAR_PIN_CAP) >> AC_PINCAP_VREF_SHIFT;
+       unsigned int vref_ctl = AC_PINCTL_VREF_HIZ;
+
+       if (vref_caps & AC_PINCAP_VREF_100)
+               vref_ctl = AC_PINCTL_VREF_100;
+       else if (vref_caps & AC_PINCAP_VREF_80)
+               vref_ctl = AC_PINCTL_VREF_80;
+       else if (vref_caps & AC_PINCAP_VREF_50)
+               vref_ctl = AC_PINCTL_VREF_50;
+       else if (vref_caps & AC_PINCAP_VREF_GRD)
+               vref_ctl = AC_PINCTL_VREF_GRD;
+
+       stac92xx_set_pinctl(codec, nid, vref_ctl);
+       
+       return 0;
+}
+
+static int stac92xx_config_pin(struct hda_codec *codec, hda_nid_t nid, unsigned int pin_cfg)
+{
+       switch((pin_cfg & AC_DEFCFG_DEVICE) >> AC_DEFCFG_DEVICE_SHIFT) {
+               case AC_JACK_HP_OUT:
+                       /* Enable HP amp */
+                       stac92xx_set_pinctl(codec, nid, AC_PINCTL_HP_EN);
+                       /* Fall through */
+               case AC_JACK_LINE_OUT:
+               case AC_JACK_SPEAKER:
+                       /* Enable output */
+                       stac92xx_set_pinctl(codec, nid, AC_PINCTL_OUT_EN);
+                       break;
+               case AC_JACK_MIC_IN:
+                       /* Set vref */
+                       stac92xx_set_vref(codec, nid);
+               case AC_JACK_CD:
+               case AC_JACK_LINE_IN:
+               case AC_JACK_AUX:
+                       /* Enable input */
+                       stac92xx_set_pinctl(codec, nid, AC_PINCTL_IN_EN);
+                       break;
+       }
+
+       return 0;
+}
+
+static int stac92xx_config_pins(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       int i;
+       unsigned int pin_cfg;
+
+       for (i=0; i < spec->num_pins; i++) {
+               /* Default to disabled */
+               snd_hda_codec_write(codec, spec->pin_nids[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL,
+                                   0x00);
+
+               pin_cfg = snd_hda_codec_read(codec, spec->pin_nids[i], 0,
+                                            AC_VERB_GET_CONFIG_DEFAULT,
+                                            0x00);
+               if (((pin_cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT) == AC_JACK_PORT_NONE)
+                       continue;       /* Move on */
+
+               stac92xx_config_pin(codec, spec->pin_nids[i], pin_cfg);
+       }
+
+       return 0;
+}
+
+static int stac92xx_init(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       int i;
+
+       for (i=0; i < spec->num_pstates; i++)
+               snd_hda_codec_write(codec, spec->pstate_nids[i], 0,
+                                   AC_VERB_SET_POWER_STATE, 0x00);
+
+       mdelay(100);
+
+       snd_hda_sequence_write(codec, spec->init);
+
+#ifdef STAC_TEST
+       stac92xx_set_config_regs(codec);
+#endif
+
+       stac92xx_config_pins(codec);
+
+       return 0;
+}
+
+/*
+ * Analog playback callbacks
+ */
+static int stac92xx_playback_pcm_open(struct hda_pcm_stream *hinfo,
+                                     struct hda_codec *codec,
+                                     snd_pcm_substream_t *substream)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
+}
+
+static int stac92xx_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+                                        struct hda_codec *codec,
+                                        unsigned int stream_tag,
+                                        unsigned int format,
+                                        snd_pcm_substream_t *substream)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       return snd_hda_multi_out_analog_prepare(codec, &spec->multiout, stream_tag,
+                                               format, substream);
+}
+
+static int stac92xx_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+                                       struct hda_codec *codec,
+                                       snd_pcm_substream_t *substream)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+/*
+ * Analog capture callbacks
+ */
+static int stac92xx_capture_pcm_prepare(struct hda_pcm_stream *hinfo,
+                                       struct hda_codec *codec,
+                                       unsigned int stream_tag,
+                                       unsigned int format,
+                                       snd_pcm_substream_t *substream)
+{
+       struct sigmatel_spec *spec = codec->spec;
+
+       snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
+                                   stream_tag, 0, format);
+       return 0;
+}
+
+static int stac92xx_capture_pcm_cleanup(struct hda_pcm_stream *hinfo,
+                                       struct hda_codec *codec,
+                                       snd_pcm_substream_t *substream)
+{
+       struct sigmatel_spec *spec = codec->spec;
+
+       snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number], 0, 0, 0);
+       return 0;
+}
+
+static struct hda_pcm_stream stac92xx_pcm_analog_playback = {
+       .substreams = 1,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = 0x02, /* NID to query formats and rates */
+       .ops = {
+               .open = stac92xx_playback_pcm_open,
+               .prepare = stac92xx_playback_pcm_prepare,
+               .cleanup = stac92xx_playback_pcm_cleanup
+       },
+};
+
+static struct hda_pcm_stream stac92xx_pcm_analog_capture = {
+       .substreams = 2,
+       .channels_min = 2,
+       .channels_max = 2,
+       .nid = 0x06, /* NID to query formats and rates */
+       .ops = {
+               .prepare = stac92xx_capture_pcm_prepare,
+               .cleanup = stac92xx_capture_pcm_cleanup
+       },
+};
+
+static int stac92xx_build_pcms(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec = codec->spec;
+       struct hda_pcm *info = spec->pcm_rec;
+
+       codec->num_pcms = 1;
+       codec->pcm_info = info;
+
+       info->name = "STAC92xx";
+       info->stream[SNDRV_PCM_STREAM_PLAYBACK] = stac92xx_pcm_analog_playback;
+       info->stream[SNDRV_PCM_STREAM_PLAYBACK].nid = spec->playback_nid;
+       info->stream[SNDRV_PCM_STREAM_CAPTURE] = stac92xx_pcm_analog_capture;
+       info->stream[SNDRV_PCM_STREAM_CAPTURE].nid = spec->capture_nid;
+
+       return 0;
+}
+
+static void stac92xx_free(struct hda_codec *codec)
+{
+       kfree(codec->spec);
+}
+
+static struct hda_codec_ops stac92xx_patch_ops = {
+       .build_controls = stac92xx_build_controls,
+       .build_pcms = stac92xx_build_pcms,
+       .init = stac92xx_init,
+       .free = stac92xx_free,
+};
+
+static int patch_stac9200(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec;
+
+       spec  = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+
+       spec->multiout.max_channels = 2;
+       spec->multiout.num_dacs = 1;
+       spec->multiout.dac_nids = stac9200_dac_nids;
+       spec->adc_nids = stac9200_adc_nids;
+       spec->mux_nids = stac9200_mux_nids;
+       spec->input_mux = &stac9200_input_mux;
+       spec->pstate_nids = stac9200_pstate_nids;
+       spec->num_pstates = 3;
+       spec->pin_nids = stac9200_pin_nids;
+#ifdef STAC_TEST
+       spec->pin_configs = stac9200_pin_configs;
+#endif
+       spec->num_pins = 8;
+       spec->init = stac9200_ch2_init;
+       spec->mixer = stac9200_mixer;
+       spec->playback_nid = 0x02;
+       spec->capture_nid = 0x03;
+
+       codec->patch_ops = stac92xx_patch_ops;
+
+       return 0;
+}
+
+static int patch_stac922x(struct hda_codec *codec)
+{
+       struct sigmatel_spec *spec;
+
+       spec  = kcalloc(1, sizeof(*spec), GFP_KERNEL);
+       if (spec == NULL)
+               return -ENOMEM;
+
+       codec->spec = spec;
+
+       spec->multiout.max_channels = 2;
+       spec->multiout.num_dacs = 4;
+       spec->multiout.dac_nids = stac922x_dac_nids;
+       spec->adc_nids = stac922x_adc_nids;
+       spec->mux_nids = stac922x_mux_nids;
+       spec->input_mux = &stac922x_input_mux;
+       spec->pstate_nids = stac922x_pstate_nids;
+       spec->num_pstates = 7;
+       spec->pin_nids = stac922x_pin_nids;
+#ifdef STAC_TEST
+       spec->pin_configs = stac922x_pin_configs;
+#endif
+       spec->num_pins = 10;
+       spec->init = stac922x_ch2_init;
+       spec->mixer = stac922x_mixer;
+       spec->playback_nid = 0x02;
+       spec->capture_nid = 0x06;
+
+       codec->patch_ops = stac92xx_patch_ops;
+
+       return 0;
+}
+
+/*
+ * patch entries
+ */
+struct hda_codec_preset snd_hda_preset_sigmatel[] = {
+       { .id = 0x83847690, .name = "STAC9200", .patch = patch_stac9200 },
+       { .id = 0x83847882, .name = "STAC9220 A1", .patch = patch_stac922x },
+       { .id = 0x83847680, .name = "STAC9221 A1", .patch = patch_stac922x },
+       { .id = 0x83847880, .name = "STAC9220 A2", .patch = patch_stac922x },
+       { .id = 0x83847681, .name = "STAC9220D/9223D A2", .patch = patch_stac922x },
+       { .id = 0x83847682, .name = "STAC9221 A2", .patch = patch_stac922x },
+       { .id = 0x83847683, .name = "STAC9221D A2", .patch = patch_stac922x },
+       {} /* terminator */
+};