ALSA: hda - Preliminary support for new Conexant audio codecs
authorTakashi Iwai <tiwai@suse.de>
Wed, 15 Sep 2010 08:07:08 +0000 (10:07 +0200)
committerTakashi Iwai <tiwai@suse.de>
Thu, 16 Sep 2010 05:34:55 +0000 (07:34 +0200)
This patch adds the preliminary support for new Conexant audio codecs with
14f1:5097, 14f1:5098, 14f1:50a1, 14f1:50a2, 14f1:50ab, 14f1:50ac,
14f1:50b8 and 14f1:50b9.

Unlike other Conexant parsers, this is designed to be mostly automatic,
parsing from BIOS pin configurations.

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/patch_conexant.c

index 972e7c4..7eee0ff 100644 (file)
@@ -57,6 +57,12 @@ struct conexant_jack {
 
 };
 
+struct pin_dac_pair {
+       hda_nid_t pin;
+       hda_nid_t dac;
+       int type;
+};
+
 struct conexant_spec {
 
        struct snd_kcontrol_new *mixers[5];
@@ -77,6 +83,7 @@ struct conexant_spec {
        unsigned int cur_eapd;
        unsigned int hp_present;
        unsigned int auto_mic;
+       int auto_mic_ext;               /* autocfg.inputs[] index for ext mic */
        unsigned int need_dac_fix;
 
        /* capture */
@@ -110,9 +117,12 @@ struct conexant_spec {
        struct auto_pin_cfg autocfg;
        struct hda_input_mux private_imux;
        hda_nid_t private_dac_nids[AUTO_CFG_MAX_OUTS];
+       struct pin_dac_pair dac_info[8];
+       int dac_info_filled;
 
-       unsigned int dell_automute;
        unsigned int port_d_mode;
+       unsigned int auto_mute:1;       /* used in auto-parser */
+       unsigned int dell_automute:1;
        unsigned int dell_vostro:1;
        unsigned int ideapad:1;
        unsigned int thinkpad:1;
@@ -3253,6 +3263,610 @@ static int patch_cxt5066(struct hda_codec *codec)
        return 0;
 }
 
+/*
+ * Automatic parser for CX20641 & co
+ */
+
+static hda_nid_t cx_auto_adc_nids[] = { 0x14 };
+
+/* get the connection index of @nid in the widget @mux */
+static int get_connection_index(struct hda_codec *codec, hda_nid_t mux,
+                               hda_nid_t nid)
+{
+       hda_nid_t conn[HDA_MAX_NUM_INPUTS];
+       int i, nums;
+
+       nums = snd_hda_get_connections(codec, mux, conn, ARRAY_SIZE(conn));
+       for (i = 0; i < nums; i++)
+               if (conn[i] == nid)
+                       return i;
+       return -1;
+}
+
+/* get an unassigned DAC from the given list.
+ * Return the nid if found and reduce the DAC list, or return zero if
+ * not found
+ */
+static hda_nid_t get_unassigned_dac(struct hda_codec *codec, hda_nid_t pin,
+                                   hda_nid_t *dacs, int *num_dacs)
+{
+       int i, nums = *num_dacs;
+       hda_nid_t ret = 0;
+
+       for (i = 0; i < nums; i++) {
+               if (get_connection_index(codec, pin, dacs[i]) >= 0) {
+                       ret = dacs[i];
+                       break;
+               }
+       }
+       if (!ret)
+               return 0;
+       if (--nums > 0)
+               memmove(dacs, dacs + 1, nums * sizeof(hda_nid_t));
+       *num_dacs = nums;
+       return ret;
+}
+
+#define MAX_AUTO_DACS  5
+
+/* fill analog DAC list from the widget tree */
+static int fill_cx_auto_dacs(struct hda_codec *codec, hda_nid_t *dacs)
+{
+       hda_nid_t nid, end_nid;
+       int nums = 0;
+
+       end_nid = codec->start_nid + codec->num_nodes;
+       for (nid = codec->start_nid; nid < end_nid; nid++) {
+               unsigned int wcaps = get_wcaps(codec, nid);
+               unsigned int type = get_wcaps_type(wcaps);
+               if (type == AC_WID_AUD_OUT && !(wcaps & AC_WCAP_DIGITAL)) {
+                       dacs[nums++] = nid;
+                       if (nums >= MAX_AUTO_DACS)
+                               break;
+               }
+       }
+       return nums;
+}
+
+/* fill pin_dac_pair list from the pin and dac list */
+static int fill_dacs_for_pins(struct hda_codec *codec, hda_nid_t *pins,
+                             int num_pins, hda_nid_t *dacs, int *rest,
+                             struct pin_dac_pair *filled, int type)
+{
+       int i, nums;
+
+       nums = 0;
+       for (i = 0; i < num_pins; i++) {
+               filled[nums].pin = pins[i];
+               filled[nums].type = type;
+               filled[nums].dac = get_unassigned_dac(codec, pins[i], dacs, rest);
+               nums++;
+       }
+       return nums;
+}
+
+/* parse analog output paths */
+static void cx_auto_parse_output(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       hda_nid_t dacs[MAX_AUTO_DACS];
+       int i, j, nums, rest;
+
+       rest = fill_cx_auto_dacs(codec, dacs);
+       /* parse all analog output pins */
+       nums = fill_dacs_for_pins(codec, cfg->line_out_pins, cfg->line_outs,
+                                 dacs, &rest, spec->dac_info,
+                                 AUTO_PIN_LINE_OUT);
+       nums += fill_dacs_for_pins(codec, cfg->hp_pins, cfg->hp_outs,
+                                 dacs, &rest, spec->dac_info + nums,
+                                 AUTO_PIN_HP_OUT);
+       nums += fill_dacs_for_pins(codec, cfg->speaker_pins, cfg->speaker_outs,
+                                 dacs, &rest, spec->dac_info + nums,
+                                 AUTO_PIN_SPEAKER_OUT);
+       spec->dac_info_filled = nums;
+       /* fill multiout struct */
+       for (i = 0; i < nums; i++) {
+               hda_nid_t dac = spec->dac_info[i].dac;
+               if (!dac)
+                       continue;
+               switch (spec->dac_info[i].type) {
+               case AUTO_PIN_LINE_OUT:
+                       spec->private_dac_nids[spec->multiout.num_dacs] = dac;
+                       spec->multiout.num_dacs++;
+                       break;
+               case AUTO_PIN_HP_OUT:
+               case AUTO_PIN_SPEAKER_OUT:
+                       if (!spec->multiout.hp_nid) {
+                               spec->multiout.hp_nid = dac;
+                               break;
+                       }
+                       for (j = 0; j < ARRAY_SIZE(spec->multiout.extra_out_nid); j++)
+                               if (!spec->multiout.extra_out_nid[j]) {
+                                       spec->multiout.extra_out_nid[j] = dac;
+                                       break;
+                               }
+                       break;
+               }
+       }
+       spec->multiout.dac_nids = spec->private_dac_nids;
+       spec->multiout.max_channels = nums * 2;
+
+       if (cfg->hp_outs > 0)
+               spec->auto_mute = 1;
+       spec->vmaster_nid = spec->private_dac_nids[0];
+}
+
+/* auto-mute/unmute speaker and line outs according to headphone jack */
+static void cx_auto_hp_automute(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       int i, present;
+
+       if (!spec->auto_mute)
+               return;
+       present = 0;
+       for (i = 0; i < cfg->hp_outs; i++) {
+               if (snd_hda_jack_detect(codec, cfg->hp_pins[i])) {
+                       present = 1;
+                       break;
+               }
+       }
+       for (i = 0; i < cfg->line_outs; i++) {
+               snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL,
+                                   present ? 0 : PIN_OUT);
+       }
+       for (i = 0; i < cfg->speaker_outs; i++) {
+               snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL,
+                                   present ? 0 : PIN_OUT);
+       }
+}
+
+/* automatic switch internal and external mic */
+static void cx_auto_automic(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       struct hda_input_mux *imux = &spec->private_imux;
+       int ext_idx = spec->auto_mic_ext;
+
+       if (!spec->auto_mic)
+               return;
+       if (snd_hda_jack_detect(codec, cfg->inputs[ext_idx].pin)) {
+               snd_hda_codec_write(codec, spec->adc_nids[0], 0,
+                                   AC_VERB_SET_CONNECT_SEL,
+                                   imux->items[ext_idx].index);
+       } else {
+               snd_hda_codec_write(codec, spec->adc_nids[0], 0,
+                                   AC_VERB_SET_CONNECT_SEL,
+                                   imux->items[!ext_idx].index);
+       }
+}
+
+static void cx_auto_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+       int nid = (res & AC_UNSOL_RES_SUBTAG) >> 20;
+       switch (res >> 26) {
+       case CONEXANT_HP_EVENT:
+               cx_auto_hp_automute(codec);
+               conexant_report_jack(codec, nid);
+               break;
+       case CONEXANT_MIC_EVENT:
+               cx_auto_automic(codec);
+               conexant_report_jack(codec, nid);
+               break;
+       }
+}
+
+static int is_int_mic_conn(unsigned int def_conf)
+{
+       unsigned int loc = get_defcfg_location(def_conf);
+       return get_defcfg_connect(def_conf) == AC_JACK_PORT_FIXED ||
+               (loc & 0x30) == AC_JACK_LOC_INTERNAL;
+}
+
+/* return true if it's an internal-mic pin */
+static int is_int_mic(struct hda_codec *codec, hda_nid_t pin)
+{
+       unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
+       return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
+               is_int_mic_conn(def_conf);
+}
+
+/* return true if it's an external-mic pin */
+static int is_ext_mic(struct hda_codec *codec, hda_nid_t pin)
+{
+       unsigned int def_conf = snd_hda_codec_get_pincfg(codec, pin);
+       return get_defcfg_device(def_conf) == AC_JACK_MIC_IN &&
+               !is_int_mic_conn(def_conf);
+}
+
+/* check whether the pin config is suitable for auto-mic switching;
+ * auto-mic is enabled only when one int-mic and one-ext mic exist
+ */
+static void cx_auto_check_auto_mic(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+
+       if (is_ext_mic(codec, cfg->inputs[0].pin) &&
+           is_int_mic(codec, cfg->inputs[1].pin)) {
+               spec->auto_mic = 1;
+               spec->auto_mic_ext = 1;
+               return;
+       }
+       if (is_int_mic(codec, cfg->inputs[1].pin) &&
+           is_ext_mic(codec, cfg->inputs[0].pin)) {
+               spec->auto_mic = 1;
+               spec->auto_mic_ext = 0;
+               return;
+       }
+}
+
+static void cx_auto_parse_input(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       struct hda_input_mux *imux;
+       int i;
+
+       imux = &spec->private_imux;
+       for (i = 0; i < cfg->num_inputs; i++) {
+               int idx = get_connection_index(codec, spec->adc_nids[0],
+                                              cfg->inputs[i].pin);
+               if (idx >= 0) {
+                       const char *label;
+                       label = hda_get_autocfg_input_label(codec, cfg, i);
+                       snd_hda_add_imux_item(imux, label, idx, NULL);
+               }
+       }
+       if (imux->num_items == 2 && cfg->num_inputs == 2)
+               cx_auto_check_auto_mic(codec);
+       if (imux->num_items > 1 && !spec->auto_mic)
+               spec->input_mux = imux;
+}
+
+/* get digital-input audio widget corresponding to the given pin */
+static hda_nid_t cx_auto_get_dig_in(struct hda_codec *codec, hda_nid_t pin)
+{
+       hda_nid_t nid, end_nid;
+
+       end_nid = codec->start_nid + codec->num_nodes;
+       for (nid = codec->start_nid; nid < end_nid; nid++) {
+               unsigned int wcaps = get_wcaps(codec, nid);
+               unsigned int type = get_wcaps_type(wcaps);
+               if (type == AC_WID_AUD_IN && (wcaps & AC_WCAP_DIGITAL)) {
+                       if (get_connection_index(codec, nid, pin) >= 0)
+                               return nid;
+               }
+       }
+       return 0;
+}
+
+static void cx_auto_parse_digital(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       hda_nid_t nid;
+
+       if (cfg->dig_outs &&
+           snd_hda_get_connections(codec, cfg->dig_out_pins[0], &nid, 1) == 1)
+               spec->multiout.dig_out_nid = nid;
+       if (cfg->dig_in_pin)
+               spec->dig_in_nid = cx_auto_get_dig_in(codec, cfg->dig_in_pin);
+}
+
+#ifdef CONFIG_SND_HDA_INPUT_BEEP
+static void cx_auto_parse_beep(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       hda_nid_t nid, end_nid;
+
+       end_nid = codec->start_nid + codec->num_nodes;
+       for (nid = codec->start_nid; nid < end_nid; nid++)
+               if (get_wcaps_type(get_wcaps(codec, nid)) == AC_WID_BEEP) {
+                       set_beep_amp(spec, nid, 0, HDA_OUTPUT);
+                       break;
+               }
+}
+#else
+#define cx_auto_parse_beep(codec)
+#endif
+
+static int cx_auto_parse_auto_config(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       int err;
+
+       err = snd_hda_parse_pin_def_config(codec, &spec->autocfg, NULL);
+       if (err < 0)
+               return err;
+
+       cx_auto_parse_output(codec);
+       cx_auto_parse_input(codec);
+       cx_auto_parse_digital(codec);
+       cx_auto_parse_beep(codec);
+       return 0;
+}
+
+static void cx_auto_turn_on_eapd(struct hda_codec *codec, int num_pins,
+                                hda_nid_t *pins)
+{
+       int i;
+       for (i = 0; i < num_pins; i++) {
+               if (snd_hda_query_pin_caps(codec, pins[i]) & AC_PINCAP_EAPD)
+                       snd_hda_codec_write(codec, pins[i], 0,
+                                           AC_VERB_SET_EAPD_BTLENABLE, 0x02);
+       }
+}
+
+static void select_connection(struct hda_codec *codec, hda_nid_t pin,
+                             hda_nid_t src)
+{
+       int idx = get_connection_index(codec, pin, src);
+       if (idx >= 0)
+               snd_hda_codec_write(codec, pin, 0,
+                                   AC_VERB_SET_CONNECT_SEL, idx);
+}
+
+static void cx_auto_init_output(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       hda_nid_t nid;
+       int i;
+
+       for (i = 0; i < spec->multiout.num_dacs; i++)
+               snd_hda_codec_write(codec, spec->multiout.dac_nids[i], 0,
+                                   AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE);
+
+       for (i = 0; i < cfg->hp_outs; i++)
+               snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP);
+       if (spec->auto_mute) {
+               for (i = 0; i < cfg->hp_outs; i++) {
+                       snd_hda_codec_write(codec, cfg->hp_pins[i], 0,
+                                   AC_VERB_SET_UNSOLICITED_ENABLE,
+                                   AC_USRSP_EN | CONEXANT_HP_EVENT);
+               }
+               cx_auto_hp_automute(codec);
+       } else {
+               for (i = 0; i < cfg->line_outs; i++)
+                       snd_hda_codec_write(codec, cfg->line_out_pins[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+               for (i = 0; i < cfg->speaker_outs; i++)
+                       snd_hda_codec_write(codec, cfg->speaker_pins[i], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+       }
+
+       for (i = 0; i < spec->dac_info_filled; i++) {
+               nid = spec->dac_info[i].dac;
+               if (!nid)
+                       nid = spec->multiout.dac_nids[0];
+               select_connection(codec, spec->dac_info[i].pin, nid);
+       }
+
+       /* turn on EAPD */
+       cx_auto_turn_on_eapd(codec, cfg->line_outs, cfg->line_out_pins);
+       cx_auto_turn_on_eapd(codec, cfg->hp_outs, cfg->hp_pins);
+       cx_auto_turn_on_eapd(codec, cfg->speaker_outs, cfg->speaker_pins);
+}
+
+static void cx_auto_init_input(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       int i;
+
+       for (i = 0; i < spec->num_adc_nids; i++)
+               snd_hda_codec_write(codec, spec->adc_nids[i], 0,
+                                   AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0));
+
+       for (i = 0; i < cfg->num_inputs; i++) {
+               unsigned int type;
+               if (cfg->inputs[i].type == AUTO_PIN_MIC)
+                       type = PIN_VREF80;
+               else
+                       type = PIN_IN;
+               snd_hda_codec_write(codec, cfg->inputs[i].pin, 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, type);
+       }
+
+       if (spec->auto_mic) {
+               int ext_idx = spec->auto_mic_ext;
+               snd_hda_codec_write(codec, cfg->inputs[ext_idx].pin, 0,
+                                   AC_VERB_SET_UNSOLICITED_ENABLE,
+                                   AC_USRSP_EN | CONEXANT_MIC_EVENT);
+               cx_auto_automic(codec);
+       } else {
+               for (i = 0; i < spec->num_adc_nids; i++) {
+                       snd_hda_codec_write(codec, spec->adc_nids[i], 0,
+                                           AC_VERB_SET_CONNECT_SEL,
+                                           spec->private_imux.items[0].index);
+               }
+       }
+}
+
+static void cx_auto_init_digital(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+
+       if (spec->multiout.dig_out_nid)
+               snd_hda_codec_write(codec, cfg->dig_out_pins[0], 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT);
+       if (spec->dig_in_nid)
+               snd_hda_codec_write(codec, cfg->dig_in_pin, 0,
+                                   AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN);
+}
+
+static int cx_auto_init(struct hda_codec *codec)
+{
+       /*snd_hda_sequence_write(codec, cx_auto_init_verbs);*/
+       cx_auto_init_output(codec);
+       cx_auto_init_input(codec);
+       cx_auto_init_digital(codec);
+       return 0;
+}
+
+static int cx_auto_add_volume(struct hda_codec *codec, const char *basename,
+                             const char *dir, int cidx,
+                             hda_nid_t nid, int hda_dir)
+{
+       static char name[32];
+       static struct snd_kcontrol_new knew[] = {
+               HDA_CODEC_VOLUME(name, 0, 0, 0),
+               HDA_CODEC_MUTE(name, 0, 0, 0),
+       };
+       static char *sfx[2] = { "Volume", "Switch" };
+       int i, err;
+
+       for (i = 0; i < 2; i++) {
+               struct snd_kcontrol *kctl;
+               knew[i].private_value = HDA_COMPOSE_AMP_VAL(nid, 3, 0, hda_dir);
+               knew[i].subdevice = HDA_SUBDEV_AMP_FLAG;
+               knew[i].index = cidx;
+               snprintf(name, sizeof(name), "%s%s %s", basename, dir, sfx[i]);
+               kctl = snd_ctl_new1(&knew[i], codec);
+               if (!kctl)
+                       return -ENOMEM;
+               err = snd_hda_ctl_add(codec, nid, kctl);
+               if (err < 0)
+                       return err;
+               if (!(query_amp_caps(codec, nid, hda_dir) & AC_AMPCAP_MUTE))
+                       break;
+       }
+       return 0;
+}
+
+#define cx_auto_add_pb_volume(codec, nid, str, idx)                    \
+       cx_auto_add_volume(codec, str, " Playback", idx, nid, HDA_OUTPUT)
+
+static int cx_auto_build_output_controls(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       int i, err;
+       int num_line = 0, num_hp = 0, num_spk = 0;
+       static const char *texts[3] = { "Front", "Surround", "CLFE" };
+
+       if (spec->dac_info_filled == 1)
+               return cx_auto_add_pb_volume(codec, spec->dac_info[0].dac,
+                                            "Master", 0);
+       for (i = 0; i < spec->dac_info_filled; i++) {
+               const char *label;
+               int idx, type;
+               if (!spec->dac_info[i].dac)
+                       continue;
+               type = spec->dac_info[i].type;
+               if (type == AUTO_PIN_LINE_OUT)
+                       type = spec->autocfg.line_out_type;
+               switch (type) {
+               case AUTO_PIN_LINE_OUT:
+               default:
+                       label = texts[num_line++];
+                       idx = 0;
+                       break;
+               case AUTO_PIN_HP_OUT:
+                       label = "Headphone";
+                       idx = num_hp++;
+                       break;
+               case AUTO_PIN_SPEAKER_OUT:
+                       label = "Speaker";
+                       idx = num_spk++;
+                       break;
+               }
+               err = cx_auto_add_pb_volume(codec, spec->dac_info[i].dac,
+                                           label, idx);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+static int cx_auto_build_input_controls(struct hda_codec *codec)
+{
+       struct conexant_spec *spec = codec->spec;
+       struct auto_pin_cfg *cfg = &spec->autocfg;
+       static const char *prev_label;
+       int i, err, cidx;
+
+       err = cx_auto_add_volume(codec, "Capture", "", 0, spec->adc_nids[0],
+                                HDA_INPUT);
+       if (err < 0)
+               return err;
+       prev_label = NULL;
+       cidx = 0;
+       for (i = 0; i < cfg->num_inputs; i++) {
+               hda_nid_t nid = cfg->inputs[i].pin;
+               const char *label;
+               if (!(get_wcaps(codec, nid) & AC_WCAP_IN_AMP))
+                       continue;
+               label = hda_get_autocfg_input_label(codec, cfg, i);
+               if (label == prev_label)
+                       cidx++;
+               else
+                       cidx = 0;
+               prev_label = label;
+               err = cx_auto_add_volume(codec, label, " Capture", cidx,
+                                        nid, HDA_INPUT);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+
+static int cx_auto_build_controls(struct hda_codec *codec)
+{
+       int err;
+
+       err = cx_auto_build_output_controls(codec);
+       if (err < 0)
+               return err;
+       err = cx_auto_build_input_controls(codec);
+       if (err < 0)
+               return err;
+       return conexant_build_controls(codec);
+}
+
+static struct hda_codec_ops cx_auto_patch_ops = {
+       .build_controls = cx_auto_build_controls,
+       .build_pcms = conexant_build_pcms,
+       .init = cx_auto_init,
+       .free = conexant_free,
+       .unsol_event = cx_auto_unsol_event,
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+       .suspend = conexant_suspend,
+#endif
+       .reboot_notify = snd_hda_shutup_pins,
+};
+
+static int patch_conexant_auto(struct hda_codec *codec)
+{
+       struct conexant_spec *spec;
+       int err;
+
+       spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+       if (!spec)
+               return -ENOMEM;
+       codec->spec = spec;
+       spec->adc_nids = cx_auto_adc_nids;
+       spec->num_adc_nids = ARRAY_SIZE(cx_auto_adc_nids);
+       spec->capsrc_nids = spec->adc_nids;
+       err = cx_auto_parse_auto_config(codec);
+       if (err < 0) {
+               kfree(codec->spec);
+               codec->spec = NULL;
+               return err;
+       }
+       codec->patch_ops = cx_auto_patch_ops;
+       if (spec->beep_amp)
+               snd_hda_attach_beep_device(codec, spec->beep_amp);
+       return 0;
+}
+
 /*
  */
 
@@ -3271,6 +3885,22 @@ static struct hda_codec_preset snd_hda_preset_conexant[] = {
          .patch = patch_cxt5066 },
        { .id = 0x14f15069, .name = "CX20585",
          .patch = patch_cxt5066 },
+       { .id = 0x14f15097, .name = "CX20631",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f15098, .name = "CX20632",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150a1, .name = "CX20641",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150a2, .name = "CX20642",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150ab, .name = "CX20651",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150ac, .name = "CX20652",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150b8, .name = "CX20664",
+         .patch = patch_conexant_auto },
+       { .id = 0x14f150b9, .name = "CX20665",
+         .patch = patch_conexant_auto },
        {} /* terminator */
 };
 
@@ -3281,6 +3911,14 @@ MODULE_ALIAS("snd-hda-codec-id:14f15066");
 MODULE_ALIAS("snd-hda-codec-id:14f15067");
 MODULE_ALIAS("snd-hda-codec-id:14f15068");
 MODULE_ALIAS("snd-hda-codec-id:14f15069");
+MODULE_ALIAS("snd-hda-codec-id:14f15097");
+MODULE_ALIAS("snd-hda-codec-id:14f15098");
+MODULE_ALIAS("snd-hda-codec-id:14f150a1");
+MODULE_ALIAS("snd-hda-codec-id:14f150a2");
+MODULE_ALIAS("snd-hda-codec-id:14f150ab");
+MODULE_ALIAS("snd-hda-codec-id:14f150ac");
+MODULE_ALIAS("snd-hda-codec-id:14f150b8");
+MODULE_ALIAS("snd-hda-codec-id:14f150b9");
 
 MODULE_LICENSE("GPL");
 MODULE_DESCRIPTION("Conexant HD-audio codec");