[ALSA] hda-codec - Fix connection list parsing
authorTakashi Iwai <tiwai@suse.de>
Mon, 21 Nov 2005 15:33:22 +0000 (16:33 +0100)
committerJaroslav Kysela <perex@suse.cz>
Tue, 3 Jan 2006 11:29:27 +0000 (12:29 +0100)
Modules: HDA Codec driver,HDA generic driver

- Fix connection list parsing (with ranged flag).
- Increase the max number of connections
- Introduce widget capabilities cache
- Power up/down widgets at init, suspend and resume

Signed-off-by: Takashi Iwai <tiwai@suse.de>
sound/pci/hda/hda_codec.c
sound/pci/hda/hda_codec.h
sound/pci/hda/hda_local.h
sound/pci/hda/patch_realtek.c
sound/pci/hda/patch_sigmatel.c

index 7f4e199..402ce00 100644 (file)
@@ -155,8 +155,9 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
                            hda_nid_t *conn_list, int max_conns)
 {
        unsigned int parm;
-       int i, j, conn_len, num_tupples, conns;
+       int i, conn_len, conns;
        unsigned int shift, num_elems, mask;
+       hda_nid_t prev_nid;
 
        snd_assert(conn_list && max_conns > 0, return -EINVAL);
 
@@ -171,7 +172,6 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
                num_elems = 4;
        }
        conn_len = parm & AC_CLIST_LENGTH;
-       num_tupples = num_elems / 2;
        mask = (1 << (shift-1)) - 1;
 
        if (! conn_len)
@@ -186,40 +186,38 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
 
        /* multi connection */
        conns = 0;
-       for (i = 0; i < conn_len; i += num_elems) {
-               parm = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_CONNECT_LIST, i);
-               for (j = 0; j < num_tupples; j++) {
-                       int range_val;
-                       hda_nid_t val1, val2, n;
-                       range_val = parm & (1 << (shift-1)); /* ranges */
-                       val1 = parm & mask;
-                       parm >>= shift;
-                       val2 = parm & mask;
-                       parm >>= shift;
-                       if (range_val) {
-                               /* ranges between val1 and val2 */
-                               if (val1 > val2) {
-                                       snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", val1, val2);
-                                       continue;
-                               }
-                               for (n = val1; n <= val2; n++) {
-                                       if (conns >= max_conns)
-                                               return -EINVAL;
-                                       conn_list[conns++] = n;
-                               }
-                       } else {
-                               if (! val1)
-                                       break;
-                               if (conns >= max_conns)
-                                       return -EINVAL;
-                               conn_list[conns++] = val1;
-                               if (! val2)
-                                       break;
-                               if (conns >= max_conns)
+       prev_nid = 0;
+       for (i = 0; i < conn_len; i++) {
+               int range_val;
+               hda_nid_t val, n;
+
+               if (i % num_elems == 0)
+                       parm = snd_hda_codec_read(codec, nid, 0,
+                                                 AC_VERB_GET_CONNECT_LIST, i);
+               range_val = !! (parm & (1 << (shift-1))); /* ranges */
+               val = parm & mask;
+               parm >>= shift;
+               if (range_val) {
+                       /* ranges between the previous and this one */
+                       if (! prev_nid || prev_nid >= val) {
+                               snd_printk(KERN_WARNING "hda_codec: invalid dep_range_val %x:%x\n", prev_nid, val);
+                               continue;
+                       }
+                       for (n = prev_nid + 1; n <= val; n++) {
+                               if (conns >= max_conns) {
+                                       snd_printk(KERN_ERR "Too many connections\n");
                                        return -EINVAL;
-                               conn_list[conns++] = val2;
+                               }
+                               conn_list[conns++] = n;
                        }
+               } else {
+                       if (conns >= max_conns) {
+                               snd_printk(KERN_ERR "Too many connections\n");
+                               return -EINVAL;
+                       }
+                       conn_list[conns++] = val;
                }
+               prev_nid = val;
        }
        return conns;
 }
@@ -456,6 +454,27 @@ static void setup_fg_nodes(struct hda_codec *codec)
 }
 
 /*
+ * read widget caps for each widget and store in cache
+ */
+static int read_widget_caps(struct hda_codec *codec, hda_nid_t fg_node)
+{
+       int i;
+       hda_nid_t nid;
+
+       codec->num_nodes = snd_hda_get_sub_nodes(codec, fg_node,
+                                                &codec->start_nid);
+       codec->wcaps = kmalloc(codec->num_nodes * 4, GFP_KERNEL);
+       if (! codec->wcaps)
+               return -ENOMEM;
+       nid = codec->start_nid;
+       for (i = 0; i < codec->num_nodes; i++, nid++)
+               codec->wcaps[i] = snd_hda_param_read(codec, nid,
+                                                    AC_PAR_AUDIO_WIDGET_CAP);
+       return 0;
+}
+
+
+/*
  * codec destructor
  */
 static void snd_hda_codec_free(struct hda_codec *codec)
@@ -467,6 +486,7 @@ static void snd_hda_codec_free(struct hda_codec *codec)
        if (codec->patch_ops.free)
                codec->patch_ops.free(codec);
        kfree(codec->amp_info);
+       kfree(codec->wcaps);
        kfree(codec);
 }
 
@@ -520,6 +540,12 @@ int snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
                return -ENODEV;
        }
 
+       if (read_widget_caps(codec, codec->afg ? codec->afg : codec->mfg) < 0) {
+               snd_printk(KERN_ERR "hda_codec: cannot malloc\n");
+               snd_hda_codec_free(codec);
+               return -ENOMEM;
+       }
+
        if (! codec->subsystem_id) {
                hda_nid_t nid = codec->afg ? codec->afg : codec->mfg;
                codec->subsystem_id = snd_hda_codec_read(codec, nid, 0,
@@ -647,7 +673,7 @@ static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
        if (! info)
                return 0;
        if (! (info->status & INFO_AMP_CAPS)) {
-               if (!(snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_AMP_OVRD))
+               if (! (get_wcaps(codec, nid) & AC_WCAP_AMP_OVRD))
                        nid = codec->afg;
                info->amp_caps = snd_hda_param_read(codec, nid, direction == HDA_OUTPUT ?
                                                    AC_PAR_AMP_OUT_CAP : AC_PAR_AMP_IN_CAP);
@@ -1195,6 +1221,31 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
 }
 
 
+/*
+ * set power state of the codec
+ */
+static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
+                               unsigned int power_state)
+{
+       hda_nid_t nid, nid_start;
+       int nodes;
+
+       snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE,
+                           power_state);
+
+       nodes = snd_hda_get_sub_nodes(codec, fg, &nid_start);
+       for (nid = nid_start; nid < nodes + nid_start; nid++) {
+               if (get_wcaps(codec, nid) & AC_WCAP_POWER)
+                       snd_hda_codec_write(codec, nid, 0,
+                                           AC_VERB_SET_POWER_STATE,
+                                           power_state);
+       }
+
+       if (power_state == AC_PWRST_D0)
+               msleep(10);
+}
+
+
 /**
  * snd_hda_build_controls - build mixer controls
  * @bus: the BUS
@@ -1222,6 +1273,9 @@ int snd_hda_build_controls(struct hda_bus *bus)
        list_for_each(p, &bus->codec_list) {
                struct hda_codec *codec = list_entry(p, struct hda_codec, list);
                int err;
+               hda_set_power_state(codec,
+                                   codec->afg ? codec->afg : codec->mfg,
+                                   AC_PWRST_D0);
                if (! codec->patch_ops.init)
                        continue;
                err = codec->patch_ops.init(codec);
@@ -1340,7 +1394,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
 
        val = 0;
        if (nid != codec->afg &&
-           snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
+           (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) {
                val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
                if (val == -1)
                        return -EIO;
@@ -1362,7 +1416,7 @@ int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
                unsigned int bps;
                unsigned int wcaps;
 
-               wcaps = snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+               wcaps = get_wcaps(codec, nid);
                streams = snd_hda_param_read(codec, nid, AC_PAR_STREAM);
                if (streams == -1)
                        return -EIO;
@@ -1432,7 +1486,7 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
        unsigned int val = 0, rate, stream;
 
        if (nid != codec->afg &&
-           snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP) & AC_WCAP_FORMAT_OVRD) {
+           (get_wcaps(codec, nid) & AC_WCAP_FORMAT_OVRD)) {
                val = snd_hda_param_read(codec, nid, AC_PAR_PCM);
                if (val == -1)
                        return 0;
@@ -1658,9 +1712,21 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
        int err;
 
        for (; knew->name; knew++) {
-               err = snd_ctl_add(codec->bus->card, snd_ctl_new1(knew, codec));
-               if (err < 0)
-                       return err;
+               struct snd_kcontrol *kctl;
+               kctl = snd_ctl_new1(knew, codec);
+               if (! kctl)
+                       return -ENOMEM;
+               err = snd_ctl_add(codec->bus->card, kctl);
+               if (err < 0) {
+                       if (! codec->addr)
+                               return err;
+                       kctl = snd_ctl_new1(knew, codec);
+                       if (! kctl)
+                               return -ENOMEM;
+                       kctl->id.device = codec->addr;
+                       if ((err = snd_ctl_add(codec->bus->card, kctl)) < 0)
+                               return err;
+               }
        }
        return 0;
 }
@@ -1874,8 +1940,7 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c
 
        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_caps = get_wcaps(codec, nid);
                unsigned int wid_type = (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
                unsigned int def_conf;
                short assoc, loc;
@@ -1993,6 +2058,9 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
                struct hda_codec *codec = list_entry(p, struct hda_codec, list);
                if (codec->patch_ops.suspend)
                        codec->patch_ops.suspend(codec, state);
+               hda_set_power_state(codec,
+                                   codec->afg ? codec->afg : codec->mfg,
+                                   AC_PWRST_D3);
        }
        return 0;
 }
@@ -2010,6 +2078,9 @@ int snd_hda_resume(struct hda_bus *bus)
 
        list_for_each(p, &bus->codec_list) {
                struct hda_codec *codec = list_entry(p, struct hda_codec, list);
+               hda_set_power_state(codec,
+                                   codec->afg ? codec->afg : codec->mfg,
+                                   AC_PWRST_D0);
                if (codec->patch_ops.resume)
                        codec->patch_ops.resume(codec);
        }
index 0b5c367..63e26c7 100644 (file)
@@ -214,6 +214,12 @@ enum {
 #define AC_PWRST_D2SUP                 (1<<2)
 #define AC_PWRST_D3SUP                 (1<<3)
 
+/* Power state values */
+#define AC_PWRST_D0                    0x00
+#define AC_PWRST_D1                    0x01
+#define AC_PWRST_D2                    0x02
+#define AC_PWRST_D3                    0x03
+
 /* Processing capabilies */
 #define AC_PCAP_BENIGN                 (1<<0)
 #define AC_PCAP_NUM_COEF               (0xff<<8)
@@ -376,7 +382,7 @@ enum {
 };
 
 /* max. connections to a widget */
-#define HDA_MAX_CONNECTIONS    16
+#define HDA_MAX_CONNECTIONS    32
 
 /* max. codec address */
 #define HDA_MAX_CODEC_ADDRESS  0x0f
@@ -542,6 +548,11 @@ struct hda_codec {
        /* codec specific info */
        void *spec;
 
+       /* widget capabilities cache */
+       unsigned int num_nodes;
+       hda_nid_t start_nid;
+       u32 *wcaps;
+
        /* hash for amp access */
        u16 amp_hash[32];
        int num_amp_entries;
index 5022904..ded3235 100644 (file)
@@ -87,7 +87,7 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
 /*
  * input MUX helper
  */
-#define HDA_MAX_NUM_INPUTS     8
+#define HDA_MAX_NUM_INPUTS     16
 struct hda_input_mux_item {
        const char *label;
        unsigned int index;
@@ -243,4 +243,16 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec, struct auto_pin_cfg *c
 #define PIN_HP         0xc0
 #define PIN_HP_AMP     0x80
 
+/*
+ * get widget capabilities
+ */
+static inline u32 get_wcaps(struct hda_codec *codec, hda_nid_t nid)
+{
+       if (nid < codec->start_nid ||
+           nid >= codec->start_nid + codec->num_nodes)
+               return snd_hda_param_read(codec, nid, AC_PAR_AUDIO_WIDGET_CAP);
+       return codec->wcaps[nid - codec->start_nid];
+}
+
+
 #endif /* __SOUND_HDA_LOCAL_H */
index 62e6993..77c5f95 100644 (file)
@@ -2091,8 +2091,7 @@ static int patch_alc880(struct hda_codec *codec)
 
        if (! spec->adc_nids && spec->input_mux) {
                /* check whether NID 0x07 is valid */
-               unsigned int wcap = snd_hda_param_read(codec, alc880_adc_nids[0],
-                                                      AC_PAR_AUDIO_WIDGET_CAP);
+               unsigned int wcap = get_wcaps(codec, alc880_adc_nids[0]);
                wcap = (wcap & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT; /* get type */
                if (wcap != AC_WID_AUD_IN) {
                        spec->adc_nids = alc880_adc_nids_alt;
index d8d68f5..c8c539c 100644 (file)
@@ -624,7 +624,7 @@ static int stac92xx_auto_create_hp_ctls(struct hda_codec *codec, struct auto_pin
        if (! pin)
                return 0;
 
-       wid_caps = snd_hda_param_read(codec, pin, AC_PAR_AUDIO_WIDGET_CAP);
+       wid_caps = get_wcaps(codec, pin);
        if (wid_caps & AC_WCAP_UNSOL_CAP)
                /* Enable unsolicited responses on the HP widget */
                snd_hda_codec_write(codec, pin, 0,
@@ -786,33 +786,10 @@ static int stac9200_parse_auto_config(struct hda_codec *codec)
        return 1;
 }
 
-static int stac92xx_init_pstate(struct hda_codec *codec)
-{
-       hda_nid_t nid, nid_start;
-       int nodes;
-
-       snd_hda_codec_write(codec, 0x01, 0, AC_VERB_SET_POWER_STATE, 0x00);
-
-       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);
-               if (wid_caps & AC_WCAP_POWER)
-                       snd_hda_codec_write(codec, nid, 0,
-                                    AC_VERB_SET_POWER_STATE, 0x00);
-       }
-
-       mdelay(100);
-
-       return 0;
-}
-
 static int stac92xx_init(struct hda_codec *codec)
 {
        struct sigmatel_spec *spec = codec->spec;
 
-       stac92xx_init_pstate(codec);
-
        snd_hda_sequence_write(codec, spec->init);
 
        stac92xx_auto_init_multi_out(codec);