[ALSA] choose multi-channel jacks automatically
Takashi Iwai [Fri, 8 Apr 2005 13:05:13 +0000 (15:05 +0200)]
HDA Codec driver
Patch by C.L. Tien <cltien@cmedia.com.tw>:

The patch makes cm9880 to choose multi-channel jacks automatically.

1. I found the current code has basic_init, which already includes
   necessary controls for 6-stack initialization, so I don't need
   another model.

2. I add a new model 'auto' to let the driver find a. if there are
   option real panel/front panel, b. the jacks to be used for multichannel.

Because the jack color are based on MS's channel sequence, so the 'auto'
model will pick the same jacks for multichannel MS uses. I did this to
hope to minimize users questions. These code can also be applied to other
codecs but I don't have any to test.

Signed-off-by: Takashi Iwai <tiwai@suse.de>

sound/pci/hda/patch_cmedia.c

index a44d64e..e64e29d 100644 (file)
@@ -29,6 +29,7 @@
 #include <sound/core.h>
 #include "hda_codec.h"
 #include "hda_local.h"
+#define NUM_PINS       11
 
 
 /* board config type */
@@ -38,6 +39,7 @@ enum {
        CMI_FULL,       /* back 6-jack + front-panel 2-jack */
        CMI_FULL_DIG,   /* back 6-jack + front-panel 2-jack + digital I/O */
        CMI_ALLOUT,     /* back 5-jack + front-panel 2-jack + digital out */
+       CMI_AUTO,       /* let driver guess it */
 };
 
 struct cmi_spec {
@@ -48,6 +50,7 @@ struct cmi_spec {
 
        /* playback */
        struct hda_multi_out multiout;
+       hda_nid_t dac_nids[4];          /* NID for each DAC */
 
        /* capture */
        hda_nid_t *adc_nids;
@@ -63,6 +66,15 @@ struct cmi_spec {
        const struct cmi_channel_mode *channel_modes;
 
        struct hda_pcm pcm_rec[2];      /* PCM information */
+
+       /* pin deafault configuration */
+       hda_nid_t pin_nid[NUM_PINS];
+       unsigned int def_conf[NUM_PINS];
+       unsigned int pin_def_confs;
+
+       /* multichannel pins */
+       hda_nid_t multich_pin[4];       /* max 8-channel */
+       struct hda_verb multi_init[9];  /* 2 verbs for each pin + terminator */
 };
 
 /*
@@ -353,6 +365,175 @@ static int cmi9880_build_controls(struct hda_codec *codec)
        return 0;
 }
 
+#define AC_DEFCFG_ASSOC_SHIFT          4
+#define get_defcfg_connect(cfg) ((cfg & AC_DEFCFG_PORT_CONN) >> AC_DEFCFG_PORT_CONN_SHIFT)
+#define get_defcfg_association(cfg) ((cfg & AC_DEFCFG_DEF_ASSOC) >> AC_DEFCFG_ASSOC_SHIFT)
+#define get_defcfg_sequence(cfg) (cfg & AC_DEFCFG_SEQUENCE)
+
+/* get all pin default configuration in def_conf */
+static int cmi9880_get_pin_def_config(struct hda_codec *codec)
+{
+       struct cmi_spec *spec = codec->spec;
+       hda_nid_t nid, nid_start;
+       int i = 0, nodes;
+
+       nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start);
+       for (nid = nid_start; nid < nodes + nid_start; nid++) {
+               unsigned int wid_caps = snd_hda_param_read(codec, nid,
+                                                  AC_PAR_AUDIO_WIDGET_CAP);
+               unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
+               /* read all default configuration for pin complex */
+               if (wid_type == AC_WID_PIN) {
+                       spec->pin_nid[i] = nid;
+                       spec->def_conf[i] = 
+                               snd_hda_codec_read(codec, nid, 0,
+                                       AC_VERB_GET_CONFIG_DEFAULT, 0);
+                       i++;
+               }
+       }
+       spec->pin_def_confs = i;
+       return 0;
+}
+
+/* get a pin default configuration of nid in def_conf */
+static unsigned int cmi9880_get_def_config(struct hda_codec *codec, hda_nid_t nid)
+{
+       struct cmi_spec *spec = codec->spec;
+       int i = 0;
+
+       while (spec->pin_nid[i] != nid && i < spec->pin_def_confs)
+               i++;
+       if (i == spec->pin_def_confs)
+               return (unsigned int) -1;
+       else
+               return spec->def_conf[i];
+}
+
+/* decide what pins to use for multichannel playback */
+static int cmi9880_get_multich_pins(struct hda_codec *codec)
+{
+       struct cmi_spec *spec = codec->spec;
+       int i, j, pins, seq[4];
+       int max_channel = 0;
+       unsigned int def_conf, sequence;
+       hda_nid_t nid;
+
+       memset(spec->multich_pin, 0, sizeof(spec->multich_pin));
+       for (pins = 0, i = 0; i < spec->pin_def_confs && pins < 4; i++) {
+               def_conf = spec->def_conf[i];
+               /* skip pin not connected */
+               if (get_defcfg_connect(def_conf) == AC_JACK_PORT_NONE)
+                       continue;
+               /* get the sequence if association == 1 */
+               /* the other pins have association = 0, incorrect in spec 1.0 */
+               if (get_defcfg_association(def_conf) == 1) {
+                       sequence = get_defcfg_sequence(def_conf);
+                       seq[pins] = sequence;
+                       spec->multich_pin[pins] = spec->pin_nid[i];
+                       pins++; // ready for next slot
+                       max_channel += 2;
+               }
+       }
+       /* sort by sequence, data collected here will be for Windows */ 
+       for (i = 0; i < pins; i++) {
+               for (j = i + 1; j < pins; j++) {
+                       if (seq[j] < seq[i]) {
+                               sequence = seq[j];
+                               nid = spec->multich_pin[j];
+                               seq[j] = seq[i];
+                               spec->multich_pin[j] = spec->multich_pin[i];
+                               seq[i] = sequence;
+                               spec->multich_pin[i] = nid;
+                       }
+               }
+       }
+       /* the pin assignment is for front, C/LFE, surround and back */
+       if (max_channel >= 6) {
+               hda_nid_t temp;
+               /* exchange pin of C/LFE and surround */
+               temp = spec->multich_pin[1];
+               spec->multich_pin[1] = spec->multich_pin[2];
+               spec->multich_pin[2] = temp;
+       }
+       return max_channel;
+}
+
+/* fill in the multi_dac_nids table, which will decide
+   which audio widget to use for each channel */
+static int cmi9880_fill_multi_dac_nids(struct hda_codec *codec)
+{
+       struct cmi_spec *spec = codec->spec;
+       hda_nid_t nid;
+       int assigned[4];
+       int i, j;
+
+       /* clear the table, only one c-media dac assumed here */
+       memset(spec->dac_nids, 0, sizeof(spec->dac_nids));
+       memset(assigned, 0, sizeof(assigned));
+       /* check the pins we found */
+       for (i = 0; i < spec->multiout.max_channels / 2; i++) {
+               nid = spec->multich_pin[i];
+               /* nid 0x0b~0x0e is hardwired to audio widget 0x3~0x6 */
+               if (nid <= 0x0e && nid >= 0x0b) {
+                       spec->dac_nids[i] = nid - 0x08;
+                       assigned[nid - 0x0b] = 1;
+               }
+       }
+       /* left pin can be connect to any audio widget */
+       for (i = 0; i < spec->multiout.max_channels / 2; i++) {
+               if (!assigned[i]) {
+                       /* search for an empty channel */
+                       /* I should also check the pin type */
+                       for (j = 0; j < ARRAY_SIZE(spec->dac_nids); j++)
+                               if (! spec->dac_nids[j]) {
+                                       spec->dac_nids[j] = i + 3;
+                                       assigned[i] = 1;
+                                       break;
+                               }
+               }
+       }
+       return 0;
+}
+
+/* create multi_init table, which is used for multichannel initialization */
+static int cmi9880_fill_multi_init(struct hda_codec *codec)
+{
+       struct cmi_spec *spec = codec->spec;
+       hda_nid_t nid;
+       int i, j, k, len;
+
+       /* clear the table, only one c-media dac assumed here */
+       memset(spec->multi_init, 0, sizeof(spec->multi_init));
+       for (j = 0, i = 0; i < spec->multiout.max_channels / 2; i++) {
+               hda_nid_t conn[4];
+               nid = spec->multich_pin[i];
+               /* set as output */
+               spec->multi_init[j].nid = nid;
+               spec->multi_init[j].verb = AC_VERB_SET_PIN_WIDGET_CONTROL;
+               spec->multi_init[j].param = 0xc0;
+               j++;
+               /* nid 0x0f,0x10,0x1f,0x20 are needed to set connection */
+               switch (nid) {
+               case 0x0f:
+               case 0x10:
+               case 0x1f:
+               case 0x20:
+                       /* set connection */
+                       spec->multi_init[j].nid = nid;
+                       spec->multi_init[j].verb = AC_VERB_SET_CONNECT_SEL;
+                       /* find the index in connect list */
+                       len = snd_hda_get_connections(codec, nid, conn, 4);
+                       for (k = 0; k < len; k++)
+                               if (conn[k] == spec->dac_nids[i])
+                                       break;
+                       spec->multi_init[j].param = k < len ? k : 0;
+                       j++;
+                       break;
+               }
+       }
+       return 0;
+}
+
 static int cmi9880_init(struct hda_codec *codec)
 {
        struct cmi_spec *spec = codec->spec;
@@ -360,6 +541,8 @@ static int cmi9880_init(struct hda_codec *codec)
                snd_hda_sequence_write(codec, cmi9880_allout_init);
        else
                snd_hda_sequence_write(codec, cmi9880_basic_init);
+       if (spec->board_config == CMI_AUTO)
+               snd_hda_sequence_write(codec, spec->multi_init);
        return 0;
 }
 
@@ -546,6 +729,7 @@ static struct hda_board_config cmi9880_cfg_tbl[] = {
        { .modelname = "full", .config = CMI_FULL },
        { .modelname = "full_dig", .config = CMI_FULL_DIG },
        { .modelname = "allout", .config = CMI_ALLOUT },
+       { .modelname = "auto", .config = CMI_AUTO },
        {} /* terminator */
 };
 
@@ -570,10 +754,13 @@ static int patch_cmi9880(struct hda_codec *codec)
        codec->spec = spec;
        spec->board_config = snd_hda_check_board_config(codec, cmi9880_cfg_tbl);
        if (spec->board_config < 0) {
-               snd_printd(KERN_INFO "hda_codec: Unknown model for CMI9880\n");
-               spec->board_config = CMI_FULL_DIG; /* try everything */
+               snd_printdd(KERN_INFO "hda_codec: Unknown model for CMI9880\n");
+               spec->board_config = CMI_AUTO; /* try everything */
        }
 
+       /* copy default DAC NIDs */
+       memcpy(spec->dac_nids, cmi9880_dac_nids, sizeof(spec->dac_nids));
+
        switch (spec->board_config) {
        case CMI_MINIMAL:
        case CMI_MIN_FP:
@@ -605,10 +792,50 @@ static int patch_cmi9880(struct hda_codec *codec)
                spec->input_mux = &cmi9880_no_line_mux;
                spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
                break;
+       case CMI_AUTO:
+               {
+               unsigned int port_e, port_f, port_g, port_h;
+               unsigned int port_spdifi, port_spdifo;
+               /* collect pin default configuration */
+               cmi9880_get_pin_def_config(codec);
+               port_e = cmi9880_get_def_config(codec, 0x0f);
+               port_f = cmi9880_get_def_config(codec, 0x10);
+               port_g = cmi9880_get_def_config(codec, 0x1f);
+               port_h = cmi9880_get_def_config(codec, 0x20);
+               port_spdifi = cmi9880_get_def_config(codec, 0x13);
+               port_spdifo = cmi9880_get_def_config(codec, 0x12);
+               spec->front_panel = 1;
+               if ((get_defcfg_connect(port_e) == AC_JACK_PORT_NONE)
+               || (get_defcfg_connect(port_f) == AC_JACK_PORT_NONE)) {
+                       spec->surr_switch = 1;
+                       /* no front panel */
+                       if ((get_defcfg_connect(port_g) == AC_JACK_PORT_NONE)
+                       || (get_defcfg_connect(port_h) == AC_JACK_PORT_NONE)) {
+                               /* no optional rear panel */
+                               spec->board_config = CMI_MINIMAL;
+                               spec->front_panel = 0;
+                               spec->num_ch_modes = 2;
+                       } else
+                               spec->board_config = CMI_MIN_FP;
+                               spec->num_ch_modes = 3;
+                       spec->channel_modes = cmi9880_channel_modes;
+                       spec->input_mux = &cmi9880_basic_mux;
+               } else {
+                       spec->input_mux = &cmi9880_basic_mux;
+                       if (get_defcfg_connect(port_spdifo) != 1)
+                               spec->multiout.dig_out_nid = CMI_DIG_OUT_NID;
+                       if (get_defcfg_connect(port_spdifi) != 1)
+                               spec->dig_in_nid = CMI_DIG_IN_NID;
+               }
+               spec->multiout.max_channels = cmi9880_get_multich_pins(codec);
+               cmi9880_fill_multi_dac_nids(codec);
+               cmi9880_fill_multi_init(codec);
+               }
+               break;
        }
 
        spec->multiout.num_dacs = 4;
-       spec->multiout.dac_nids = cmi9880_dac_nids;
+       spec->multiout.dac_nids = spec->dac_nids;
 
        spec->adc_nids = cmi9880_adc_nids;