Merge branch 'timers-fixes-for-linus' of git://git.kernel.org/pub/scm/linux/kernel...
[safe/jmp/linux-2.6] / sound / pci / hda / hda_codec.c
index 8cbe3bf..0b70813 100644 (file)
@@ -19,7 +19,6 @@
  *  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 "hda_local.h"
 #include <sound/hda_hwdep.h>
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-/* define this option here to hide as static */
-static int power_save = CONFIG_SND_HDA_POWER_SAVE_DEFAULT;
-module_param(power_save, int, 0644);
-MODULE_PARM_DESC(power_save, "Automatic power-saving timeout "
-                "(in second, 0 = disable).");
-#endif
-
 /*
  * vendor / preset table
  */
@@ -52,20 +43,46 @@ struct hda_vendor_id {
 
 /* codec vendor labels */
 static struct hda_vendor_id hda_vendor_ids[] = {
-       { 0x10ec, "Realtek" },
+       { 0x1002, "ATI" },
        { 0x1057, "Motorola" },
+       { 0x1095, "Silicon Image" },
+       { 0x10de, "Nvidia" },
+       { 0x10ec, "Realtek" },
        { 0x1106, "VIA" },
+       { 0x111d, "IDT" },
+       { 0x11c1, "LSI" },
        { 0x11d4, "Analog Devices" },
        { 0x13f6, "C-Media" },
        { 0x14f1, "Conexant" },
+       { 0x17e8, "Chrontel" },
+       { 0x1854, "LG" },
+       { 0x1aec, "Wolfson Microelectronics" },
        { 0x434d, "C-Media" },
+       { 0x8086, "Intel" },
        { 0x8384, "SigmaTel" },
        {} /* terminator */
 };
 
-/* codec presets */
-#include "hda_patch.h"
+static DEFINE_MUTEX(preset_mutex);
+static LIST_HEAD(hda_preset_tables);
+
+int snd_hda_add_codec_preset(struct hda_codec_preset_list *preset)
+{
+       mutex_lock(&preset_mutex);
+       list_add_tail(&preset->list, &hda_preset_tables);
+       mutex_unlock(&preset_mutex);
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_codec_preset);
 
+int snd_hda_delete_codec_preset(struct hda_codec_preset_list *preset)
+{
+       mutex_lock(&preset_mutex);
+       list_del(&preset->list);
+       mutex_unlock(&preset_mutex);
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_delete_codec_preset);
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 static void hda_power_work(struct work_struct *work);
@@ -74,6 +91,72 @@ static void hda_keep_power_on(struct hda_codec *codec);
 static inline void hda_keep_power_on(struct hda_codec *codec) {}
 #endif
 
+const char *snd_hda_get_jack_location(u32 cfg)
+{
+       static char *bases[7] = {
+               "N/A", "Rear", "Front", "Left", "Right", "Top", "Bottom",
+       };
+       static unsigned char specials_idx[] = {
+               0x07, 0x08,
+               0x17, 0x18, 0x19,
+               0x37, 0x38
+       };
+       static char *specials[] = {
+               "Rear Panel", "Drive Bar",
+               "Riser", "HDMI", "ATAPI",
+               "Mobile-In", "Mobile-Out"
+       };
+       int i;
+       cfg = (cfg & AC_DEFCFG_LOCATION) >> AC_DEFCFG_LOCATION_SHIFT;
+       if ((cfg & 0x0f) < 7)
+               return bases[cfg & 0x0f];
+       for (i = 0; i < ARRAY_SIZE(specials_idx); i++) {
+               if (cfg == specials_idx[i])
+                       return specials[i];
+       }
+       return "UNKNOWN";
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_location);
+
+const char *snd_hda_get_jack_connectivity(u32 cfg)
+{
+       static char *jack_locations[4] = { "Ext", "Int", "Sep", "Oth" };
+
+       return jack_locations[(cfg >> (AC_DEFCFG_LOCATION_SHIFT + 4)) & 3];
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_connectivity);
+
+const char *snd_hda_get_jack_type(u32 cfg)
+{
+       static char *jack_types[16] = {
+               "Line Out", "Speaker", "HP Out", "CD",
+               "SPDIF Out", "Digital Out", "Modem Line", "Modem Hand",
+               "Line In", "Aux", "Mic", "Telephony",
+               "SPDIF In", "Digitial In", "Reserved", "Other"
+       };
+
+       return jack_types[(cfg & AC_DEFCFG_DEVICE)
+                               >> AC_DEFCFG_DEVICE_SHIFT];
+}
+EXPORT_SYMBOL_HDA(snd_hda_get_jack_type);
+
+/*
+ * Compose a 32bit command word to be sent to the HD-audio controller
+ */
+static inline unsigned int
+make_codec_cmd(struct hda_codec *codec, hda_nid_t nid, int direct,
+              unsigned int verb, unsigned int parm)
+{
+       u32 val;
+
+       val = (u32)(codec->addr & 0x0f) << 28;
+       val |= (u32)direct << 27;
+       val |= (u32)nid << 20;
+       val |= verb << 8;
+       val |= parm;
+       return val;
+}
+
 /**
  * snd_hda_codec_read - send a command and get the response
  * @codec: the HDA codec
@@ -90,17 +173,21 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
                                int direct,
                                unsigned int verb, unsigned int parm)
 {
+       struct hda_bus *bus = codec->bus;
        unsigned int res;
+
+       res = make_codec_cmd(codec, nid, direct, verb, parm);
        snd_hda_power_up(codec);
-       mutex_lock(&codec->bus->cmd_mutex);
-       if (!codec->bus->ops.command(codec, nid, direct, verb, parm))
-               res = codec->bus->ops.get_response(codec);
+       mutex_lock(&bus->cmd_mutex);
+       if (!bus->ops.command(bus, res))
+               res = bus->ops.get_response(bus);
        else
                res = (unsigned int)-1;
-       mutex_unlock(&codec->bus->cmd_mutex);
+       mutex_unlock(&bus->cmd_mutex);
        snd_hda_power_down(codec);
        return res;
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_read);
 
 /**
  * snd_hda_codec_write - send a single command without waiting for response
@@ -117,14 +204,19 @@ unsigned int snd_hda_codec_read(struct hda_codec *codec, hda_nid_t nid,
 int snd_hda_codec_write(struct hda_codec *codec, hda_nid_t nid, int direct,
                         unsigned int verb, unsigned int parm)
 {
+       struct hda_bus *bus = codec->bus;
+       unsigned int res;
        int err;
+
+       res = make_codec_cmd(codec, nid, direct, verb, parm);
        snd_hda_power_up(codec);
-       mutex_lock(&codec->bus->cmd_mutex);
-       err = codec->bus->ops.command(codec, nid, direct, verb, parm);
-       mutex_unlock(&codec->bus->cmd_mutex);
+       mutex_lock(&bus->cmd_mutex);
+       err = bus->ops.command(bus, res);
+       mutex_unlock(&bus->cmd_mutex);
        snd_hda_power_down(codec);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_write);
 
 /**
  * snd_hda_sequence_write - sequence writes
@@ -139,6 +231,7 @@ void snd_hda_sequence_write(struct hda_codec *codec, const struct hda_verb *seq)
        for (; seq->nid; seq++)
                snd_hda_codec_write(codec, seq->nid, 0, seq->verb, seq->param);
 }
+EXPORT_SYMBOL_HDA(snd_hda_sequence_write);
 
 /**
  * snd_hda_get_sub_nodes - get the range of sub nodes
@@ -160,6 +253,7 @@ int snd_hda_get_sub_nodes(struct hda_codec *codec, hda_nid_t nid,
        *start_id = (parm >> 16) & 0x7fff;
        return (int)(parm & 0x7fff);
 }
+EXPORT_SYMBOL_HDA(snd_hda_get_sub_nodes);
 
 /**
  * snd_hda_get_connections - get connection list
@@ -181,7 +275,8 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
        unsigned int shift, num_elems, mask;
        hda_nid_t prev_nid;
 
-       snd_assert(conn_list && max_conns > 0, return -EINVAL);
+       if (snd_BUG_ON(!conn_list || max_conns <= 0))
+               return -EINVAL;
 
        parm = snd_hda_param_read(codec, nid, AC_PAR_CONNLIST_LEN);
        if (parm & AC_CLIST_LONG) {
@@ -247,6 +342,7 @@ int snd_hda_get_connections(struct hda_codec *codec, hda_nid_t nid,
        }
        return conns;
 }
+EXPORT_SYMBOL_HDA(snd_hda_get_connections);
 
 
 /**
@@ -277,13 +373,14 @@ int snd_hda_queue_unsol_event(struct hda_bus *bus, u32 res, u32 res_ex)
        unsol->queue[wp] = res;
        unsol->queue[wp + 1] = res_ex;
 
-       schedule_work(&unsol->work);
+       queue_work(bus->workq, &unsol->work);
 
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_queue_unsol_event);
 
 /*
- * process queueud unsolicited events
+ * process queued unsolicited events
  */
 static void process_unsol_events(struct work_struct *work)
 {
@@ -310,7 +407,7 @@ static void process_unsol_events(struct work_struct *work)
 /*
  * initialize unsolicited queue
  */
-static int __devinit init_unsol_queue(struct hda_bus *bus)
+static int init_unsol_queue(struct hda_bus *bus)
 {
        struct hda_bus_unsolicited *unsol;
 
@@ -340,15 +437,17 @@ static int snd_hda_bus_free(struct hda_bus *bus)
 
        if (!bus)
                return 0;
-       if (bus->unsol) {
-               flush_scheduled_work();
+       if (bus->workq)
+               flush_workqueue(bus->workq);
+       if (bus->unsol)
                kfree(bus->unsol);
-       }
        list_for_each_entry_safe(codec, n, &bus->codec_list, list) {
                snd_hda_codec_free(codec);
        }
        if (bus->ops.private_free)
                bus->ops.private_free(bus);
+       if (bus->workq)
+               destroy_workqueue(bus->workq);
        kfree(bus);
        return 0;
 }
@@ -356,9 +455,24 @@ static int snd_hda_bus_free(struct hda_bus *bus)
 static int snd_hda_bus_dev_free(struct snd_device *device)
 {
        struct hda_bus *bus = device->device_data;
+       bus->shutdown = 1;
        return snd_hda_bus_free(bus);
 }
 
+#ifdef CONFIG_SND_HDA_HWDEP
+static int snd_hda_bus_dev_register(struct snd_device *device)
+{
+       struct hda_bus *bus = device->device_data;
+       struct hda_codec *codec;
+       list_for_each_entry(codec, &bus->codec_list, list) {
+               snd_hda_hwdep_add_sysfs(codec);
+       }
+       return 0;
+}
+#else
+#define snd_hda_bus_dev_register       NULL
+#endif
+
 /**
  * snd_hda_bus_new - create a HDA bus
  * @card: the card entry
@@ -367,18 +481,21 @@ static int snd_hda_bus_dev_free(struct snd_device *device)
  *
  * Returns 0 if successful, or a negative error code.
  */
-int __devinit snd_hda_bus_new(struct snd_card *card,
+int /*__devinit*/ snd_hda_bus_new(struct snd_card *card,
                              const struct hda_bus_template *temp,
                              struct hda_bus **busp)
 {
        struct hda_bus *bus;
        int err;
        static struct snd_device_ops dev_ops = {
+               .dev_register = snd_hda_bus_dev_register,
                .dev_free = snd_hda_bus_dev_free,
        };
 
-       snd_assert(temp, return -EINVAL);
-       snd_assert(temp->ops.command && temp->ops.get_response, return -EINVAL);
+       if (snd_BUG_ON(!temp))
+               return -EINVAL;
+       if (snd_BUG_ON(!temp->ops.command || !temp->ops.get_response))
+               return -EINVAL;
 
        if (busp)
                *busp = NULL;
@@ -393,11 +510,22 @@ int __devinit snd_hda_bus_new(struct snd_card *card,
        bus->private_data = temp->private_data;
        bus->pci = temp->pci;
        bus->modelname = temp->modelname;
+       bus->power_save = temp->power_save;
        bus->ops = temp->ops;
 
        mutex_init(&bus->cmd_mutex);
        INIT_LIST_HEAD(&bus->codec_list);
 
+       snprintf(bus->workq_name, sizeof(bus->workq_name),
+                "hd-audio%d", card->number);
+       bus->workq = create_singlethread_workqueue(bus->workq_name);
+       if (!bus->workq) {
+               snd_printk(KERN_ERR "cannot create workqueue %s\n",
+                          bus->workq_name);
+               kfree(bus);
+               return -ENOMEM;
+       }
+
        err = snd_device_new(card, SNDRV_DEV_BUS, bus, &dev_ops);
        if (err < 0) {
                snd_hda_bus_free(bus);
@@ -407,49 +535,85 @@ int __devinit snd_hda_bus_new(struct snd_card *card,
                *busp = bus;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_bus_new);
 
 #ifdef CONFIG_SND_HDA_GENERIC
 #define is_generic_config(codec) \
-       (codec->bus->modelname && !strcmp(codec->bus->modelname, "generic"))
+       (codec->modelname && !strcmp(codec->modelname, "generic"))
 #else
 #define is_generic_config(codec)       0
 #endif
 
+#ifdef MODULE
+#define HDA_MODREQ_MAX_COUNT   2       /* two request_modules()'s */
+#else
+#define HDA_MODREQ_MAX_COUNT   0       /* all presets are statically linked */
+#endif
+
 /*
  * find a matching codec preset
  */
-static const struct hda_codec_preset __devinit *
+static const struct hda_codec_preset *
 find_codec_preset(struct hda_codec *codec)
 {
-       const struct hda_codec_preset **tbl, *preset;
+       struct hda_codec_preset_list *tbl;
+       const struct hda_codec_preset *preset;
+       int mod_requested = 0;
 
        if (is_generic_config(codec))
                return NULL; /* use the generic parser */
 
-       for (tbl = hda_preset_tables; *tbl; tbl++) {
-               for (preset = *tbl; preset->id; preset++) {
+ again:
+       mutex_lock(&preset_mutex);
+       list_for_each_entry(tbl, &hda_preset_tables, list) {
+               if (!try_module_get(tbl->owner)) {
+                       snd_printk(KERN_ERR "hda_codec: cannot module_get\n");
+                       continue;
+               }
+               for (preset = tbl->preset; preset->id; preset++) {
                        u32 mask = preset->mask;
+                       if (preset->afg && preset->afg != codec->afg)
+                               continue;
+                       if (preset->mfg && preset->mfg != codec->mfg)
+                               continue;
                        if (!mask)
                                mask = ~0;
                        if (preset->id == (codec->vendor_id & mask) &&
                            (!preset->rev ||
-                            preset->rev == codec->revision_id))
+                            preset->rev == codec->revision_id)) {
+                               mutex_unlock(&preset_mutex);
+                               codec->owner = tbl->owner;
                                return preset;
+                       }
                }
+               module_put(tbl->owner);
+       }
+       mutex_unlock(&preset_mutex);
+
+       if (mod_requested < HDA_MODREQ_MAX_COUNT) {
+               char name[32];
+               if (!mod_requested)
+                       snprintf(name, sizeof(name), "snd-hda-codec-id:%08x",
+                                codec->vendor_id);
+               else
+                       snprintf(name, sizeof(name), "snd-hda-codec-id:%04x*",
+                                (codec->vendor_id >> 16) & 0xffff);
+               request_module(name);
+               mod_requested++;
+               goto again;
        }
        return NULL;
 }
 
 /*
- * snd_hda_get_codec_name - store the codec name
+ * get_codec_name - store the codec name
  */
-void snd_hda_get_codec_name(struct hda_codec *codec,
-                           char *name, int namelen)
+static int get_codec_name(struct hda_codec *codec)
 {
        const struct hda_vendor_id *c;
        const char *vendor = NULL;
        u16 vendor_id = codec->vendor_id >> 16;
-       char tmp[16];
+       char tmp[16], name[32];
 
        for (c = hda_vendor_ids; c->id; c++) {
                if (c->id == vendor_id) {
@@ -462,16 +626,21 @@ void snd_hda_get_codec_name(struct hda_codec *codec,
                vendor = tmp;
        }
        if (codec->preset && codec->preset->name)
-               snprintf(name, namelen, "%s %s", vendor, codec->preset->name);
+               snprintf(name, sizeof(name), "%s %s", vendor,
+                        codec->preset->name);
        else
-               snprintf(name, namelen, "%s ID %x", vendor,
+               snprintf(name, sizeof(name), "%s ID %x", vendor,
                         codec->vendor_id & 0xffff);
+       codec->name = kstrdup(name, GFP_KERNEL);
+       if (!codec->name)
+               return -ENOMEM;
+       return 0;
 }
 
 /*
  * look for an AFG and MFG nodes
  */
-static void __devinit setup_fg_nodes(struct hda_codec *codec)
+static void /*__devinit*/ setup_fg_nodes(struct hda_codec *codec)
 {
        int i, total_nodes;
        hda_nid_t nid;
@@ -527,14 +696,18 @@ static void snd_hda_codec_free(struct hda_codec *codec)
                return;
 #ifdef CONFIG_SND_HDA_POWER_SAVE
        cancel_delayed_work(&codec->power_work);
-       flush_scheduled_work();
+       flush_workqueue(codec->bus->workq);
 #endif
        list_del(&codec->list);
+       snd_array_free(&codec->mixers);
        codec->bus->caddr_tbl[codec->addr] = NULL;
        if (codec->patch_ops.free)
                codec->patch_ops.free(codec);
+       module_put(codec->owner);
        free_hda_cache(&codec->amp_cache);
        free_hda_cache(&codec->cmd_cache);
+       kfree(codec->name);
+       kfree(codec->modelname);
        kfree(codec->wcaps);
        kfree(codec);
 }
@@ -547,15 +720,17 @@ static void snd_hda_codec_free(struct hda_codec *codec)
  *
  * Returns 0 if successful, or a negative error code.
  */
-int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
-                               struct hda_codec **codecp)
+int /*__devinit*/ snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
+                                   int do_init, struct hda_codec **codecp)
 {
        struct hda_codec *codec;
-       char component[13];
+       char component[31];
        int err;
 
-       snd_assert(bus, return -EINVAL);
-       snd_assert(codec_addr <= HDA_MAX_CODEC_ADDRESS, return -EINVAL);
+       if (snd_BUG_ON(!bus))
+               return -EINVAL;
+       if (snd_BUG_ON(codec_addr > HDA_MAX_CODEC_ADDRESS))
+               return -EINVAL;
 
        if (bus->caddr_tbl[codec_addr]) {
                snd_printk(KERN_ERR "hda_codec: "
@@ -572,8 +747,17 @@ int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
        codec->bus = bus;
        codec->addr = codec_addr;
        mutex_init(&codec->spdif_mutex);
+       mutex_init(&codec->control_mutex);
        init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
        init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
+       snd_array_init(&codec->mixers, sizeof(struct snd_kcontrol *), 32);
+       if (codec->bus->modelname) {
+               codec->modelname = kstrdup(codec->bus->modelname, GFP_KERNEL);
+               if (!codec->modelname) {
+                       snd_hda_codec_free(codec);
+                       return -ENODEV;
+               }
+       }
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
        INIT_DELAYED_WORK(&codec->power_work, hda_power_work);
@@ -619,12 +803,44 @@ int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
                        snd_hda_codec_read(codec, nid, 0,
                                           AC_VERB_GET_SUBSYSTEM_ID, 0);
        }
+       if (bus->modelname)
+               codec->modelname = kstrdup(bus->modelname, GFP_KERNEL);
+
+       if (do_init) {
+               err = snd_hda_codec_configure(codec);
+               if (err < 0) {
+                       snd_hda_codec_free(codec);
+                       return err;
+               }
+       }
+       snd_hda_codec_proc_new(codec);
+
+       snd_hda_create_hwdep(codec);
+
+       sprintf(component, "HDA:%08x,%08x,%08x", codec->vendor_id,
+               codec->subsystem_id, codec->revision_id);
+       snd_component_add(codec->bus->card, component);
+
+       if (codecp)
+               *codecp = codec;
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_new);
+
+int snd_hda_codec_configure(struct hda_codec *codec)
+{
+       int err;
 
        codec->preset = find_codec_preset(codec);
+       if (!codec->name) {
+               err = get_codec_name(codec);
+               if (err < 0)
+                       return err;
+       }
        /* audio codec should override the mixer name */
-       if (codec->afg || !*bus->card->mixername)
-               snd_hda_get_codec_name(codec, bus->card->mixername,
-                                      sizeof(bus->card->mixername));
+       if (codec->afg || !*codec->bus->card->mixername)
+               strlcpy(codec->bus->card->mixername, codec->name,
+                       sizeof(codec->bus->card->mixername));
 
        if (is_generic_config(codec)) {
                err = snd_hda_parse_generic_codec(codec);
@@ -641,25 +857,9 @@ int __devinit snd_hda_codec_new(struct hda_bus *bus, unsigned int codec_addr,
                printk(KERN_ERR "hda-codec: No codec parser is available\n");
 
  patched:
-       if (err < 0) {
-               snd_hda_codec_free(codec);
-               return err;
-       }
-
-       if (codec->patch_ops.unsol_event)
-               init_unsol_queue(bus);
-
-       snd_hda_codec_proc_new(codec);
-#ifdef CONFIG_SND_HDA_HWDEP
-       snd_hda_create_hwdep(codec);
-#endif
-
-       sprintf(component, "HDA:%08x", codec->vendor_id);
-       snd_component_add(codec->bus->card, component);
-
-       if (codecp)
-               *codecp = codec;
-       return 0;
+       if (!err && codec->patch_ops.unsol_event)
+               err = init_unsol_queue(codec->bus);
+       return err;
 }
 
 /**
@@ -685,6 +885,21 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
        msleep(1);
        snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, format);
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_setup_stream);
+
+void snd_hda_codec_cleanup_stream(struct hda_codec *codec, hda_nid_t nid)
+{
+       if (!nid)
+               return;
+
+       snd_printdd("hda_codec_cleanup_stream: NID=0x%x\n", nid);
+       snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CHANNEL_STREAMID, 0);
+#if 0 /* keep the format */
+       msleep(1);
+       snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_STREAM_FORMAT, 0);
+#endif
+}
+EXPORT_SYMBOL_HDA(snd_hda_codec_cleanup_stream);
 
 /*
  * amp access functions
@@ -696,17 +911,17 @@ void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
 #define INFO_AMP_VOL(ch)       (1 << (1 + (ch)))
 
 /* initialize the hash table */
-static void __devinit init_hda_cache(struct hda_cache_rec *cache,
+static void /*__devinit*/ init_hda_cache(struct hda_cache_rec *cache,
                                     unsigned int record_size)
 {
        memset(cache, 0, sizeof(*cache));
        memset(cache->hash, 0xff, sizeof(cache->hash));
-       cache->record_size = record_size;
+       snd_array_init(&cache->buf, record_size, 64);
 }
 
 static void free_hda_cache(struct hda_cache_rec *cache)
 {
-       kfree(cache->buffer);
+       snd_array_free(&cache->buf);
 }
 
 /* query the hash.  allocate an entry if not found. */
@@ -718,35 +933,17 @@ static struct hda_cache_head  *get_alloc_hash(struct hda_cache_rec *cache,
        struct hda_cache_head *info;
 
        while (cur != 0xffff) {
-               info = (struct hda_cache_head *)(cache->buffer +
-                                                cur * cache->record_size);
+               info = snd_array_elem(&cache->buf, cur);
                if (info->key == key)
                        return info;
                cur = info->next;
        }
 
        /* add a new hash entry */
-       if (cache->num_entries >= cache->size) {
-               /* reallocate the array */
-               unsigned int new_size = cache->size + 64;
-               void *new_buffer;
-               new_buffer = kcalloc(new_size, cache->record_size, GFP_KERNEL);
-               if (!new_buffer) {
-                       snd_printk(KERN_ERR "hda_codec: "
-                                  "can't malloc amp_info\n");
-                       return NULL;
-               }
-               if (cache->buffer) {
-                       memcpy(new_buffer, cache->buffer,
-                              cache->size * cache->record_size);
-                       kfree(cache->buffer);
-               }
-               cache->size = new_size;
-               cache->buffer = new_buffer;
-       }
-       cur = cache->num_entries++;
-       info = (struct hda_cache_head *)(cache->buffer +
-                                        cur * cache->record_size);
+       info = snd_array_new(&cache->buf);
+       if (!info)
+               return NULL;
+       cur = snd_array_index(&cache->buf, info);
        info->key = key;
        info->val = 0;
        info->next = cache->hash[idx];
@@ -765,7 +962,7 @@ get_alloc_amp_hash(struct hda_codec *codec, u32 key)
 /*
  * query AMP capabilities for the given widget and direction
  */
-static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
+u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
 {
        struct hda_amp_info *info;
 
@@ -784,6 +981,7 @@ static u32 query_amp_caps(struct hda_codec *codec, hda_nid_t nid, int direction)
        }
        return info->amp_caps;
 }
+EXPORT_SYMBOL_HDA(query_amp_caps);
 
 int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
                              unsigned int caps)
@@ -797,6 +995,7 @@ int snd_hda_override_amp_caps(struct hda_codec *codec, hda_nid_t nid, int dir,
        info->head.val |= INFO_AMP_CAPS;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_override_amp_caps);
 
 /*
  * read the current volume to info
@@ -850,6 +1049,7 @@ int snd_hda_codec_amp_read(struct hda_codec *codec, hda_nid_t nid, int ch,
                return 0;
        return get_vol_mute(codec, info, nid, ch, direction, index);
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_read);
 
 /*
  * update the AMP value, mask = bit mask to set, val = the value
@@ -869,6 +1069,7 @@ int snd_hda_codec_amp_update(struct hda_codec *codec, hda_nid_t nid, int ch,
        put_vol_mute(codec, info, nid, ch, direction, idx, val);
        return 1;
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_update);
 
 /*
  * update the AMP stereo with the same mask and value
@@ -882,15 +1083,16 @@ int snd_hda_codec_amp_stereo(struct hda_codec *codec, hda_nid_t nid,
                                                idx, mask, val);
        return ret;
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_amp_stereo);
 
 #ifdef SND_HDA_NEEDS_RESUME
 /* resume the all amp commands from the cache */
 void snd_hda_codec_resume_amp(struct hda_codec *codec)
 {
-       struct hda_amp_info *buffer = codec->amp_cache.buffer;
+       struct hda_amp_info *buffer = codec->amp_cache.buf.list;
        int i;
 
-       for (i = 0; i < codec->amp_cache.size; i++, buffer++) {
+       for (i = 0; i < codec->amp_cache.buf.used; i++, buffer++) {
                u32 key = buffer->head.key;
                hda_nid_t nid;
                unsigned int idx, dir, ch;
@@ -907,17 +1109,9 @@ void snd_hda_codec_resume_amp(struct hda_codec *codec)
                }
        }
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_resume_amp);
 #endif /* SND_HDA_NEEDS_RESUME */
 
-/*
- * AMP control callbacks
- */
-/* retrieve parameters from private_value */
-#define get_amp_nid(kc)                ((kc)->private_value & 0xffff)
-#define get_amp_channels(kc)   (((kc)->private_value >> 16) & 0x3)
-#define get_amp_direction(kc)  (((kc)->private_value >> 18) & 0x1)
-#define get_amp_index(kc)      (((kc)->private_value >> 19) & 0xf)
-
 /* volume */
 int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_info *uinfo)
@@ -933,7 +1127,8 @@ int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
        caps = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
        if (!caps) {
                printk(KERN_WARNING "hda_codec: "
-                      "num_steps = 0 for NID=0x%x\n", nid);
+                      "num_steps = 0 for NID=0x%x (ctl = %s)\n", nid,
+                      kcontrol->id.name);
                return -EINVAL;
        }
        uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
@@ -942,6 +1137,7 @@ int snd_hda_mixer_amp_volume_info(struct snd_kcontrol *kcontrol,
        uinfo->value.integer.max = caps;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_info);
 
 int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
@@ -961,6 +1157,7 @@ int snd_hda_mixer_amp_volume_get(struct snd_kcontrol *kcontrol,
                        & HDA_AMP_VOLMASK;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_get);
 
 int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
@@ -985,6 +1182,7 @@ int snd_hda_mixer_amp_volume_put(struct snd_kcontrol *kcontrol,
        snd_hda_power_down(codec);
        return change;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_volume_put);
 
 int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                          unsigned int size, unsigned int __user *_tlv)
@@ -1011,6 +1209,146 @@ int snd_hda_mixer_amp_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                return -EFAULT;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_tlv);
+
+/*
+ * set (static) TLV for virtual master volume; recalculated as max 0dB
+ */
+void snd_hda_set_vmaster_tlv(struct hda_codec *codec, hda_nid_t nid, int dir,
+                            unsigned int *tlv)
+{
+       u32 caps;
+       int nums, step;
+
+       caps = query_amp_caps(codec, nid, dir);
+       nums = (caps & AC_AMPCAP_NUM_STEPS) >> AC_AMPCAP_NUM_STEPS_SHIFT;
+       step = (caps & AC_AMPCAP_STEP_SIZE) >> AC_AMPCAP_STEP_SIZE_SHIFT;
+       step = (step + 1) * 25;
+       tlv[0] = SNDRV_CTL_TLVT_DB_SCALE;
+       tlv[1] = 2 * sizeof(unsigned int);
+       tlv[2] = -nums * step;
+       tlv[3] = step;
+}
+EXPORT_SYMBOL_HDA(snd_hda_set_vmaster_tlv);
+
+/* find a mixer control element with the given name */
+static struct snd_kcontrol *
+_snd_hda_find_mixer_ctl(struct hda_codec *codec,
+                       const char *name, int idx)
+{
+       struct snd_ctl_elem_id id;
+       memset(&id, 0, sizeof(id));
+       id.iface = SNDRV_CTL_ELEM_IFACE_MIXER;
+       id.index = idx;
+       strcpy(id.name, name);
+       return snd_ctl_find_id(codec->bus->card, &id);
+}
+
+struct snd_kcontrol *snd_hda_find_mixer_ctl(struct hda_codec *codec,
+                                           const char *name)
+{
+       return _snd_hda_find_mixer_ctl(codec, name, 0);
+}
+EXPORT_SYMBOL_HDA(snd_hda_find_mixer_ctl);
+
+/* Add a control element and assign to the codec */
+int snd_hda_ctl_add(struct hda_codec *codec, struct snd_kcontrol *kctl)
+{
+       int err;
+       struct snd_kcontrol **knewp;
+
+       err = snd_ctl_add(codec->bus->card, kctl);
+       if (err < 0)
+               return err;
+       knewp = snd_array_new(&codec->mixers);
+       if (!knewp)
+               return -ENOMEM;
+       *knewp = kctl;
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_ctl_add);
+
+#ifdef CONFIG_SND_HDA_RECONFIG
+/* Clear all controls assigned to the given codec */
+void snd_hda_ctls_clear(struct hda_codec *codec)
+{
+       int i;
+       struct snd_kcontrol **kctls = codec->mixers.list;
+       for (i = 0; i < codec->mixers.used; i++)
+               snd_ctl_remove(codec->bus->card, kctls[i]);
+       snd_array_free(&codec->mixers);
+}
+
+void snd_hda_codec_reset(struct hda_codec *codec)
+{
+       int i;
+
+#ifdef CONFIG_SND_HDA_POWER_SAVE
+       cancel_delayed_work(&codec->power_work);
+       flush_workqueue(codec->bus->workq);
+#endif
+       snd_hda_ctls_clear(codec);
+       /* relase PCMs */
+       for (i = 0; i < codec->num_pcms; i++) {
+               if (codec->pcm_info[i].pcm) {
+                       snd_device_free(codec->bus->card,
+                                       codec->pcm_info[i].pcm);
+                       clear_bit(codec->pcm_info[i].device,
+                                 codec->bus->pcm_dev_bits);
+               }
+       }
+       if (codec->patch_ops.free)
+               codec->patch_ops.free(codec);
+       codec->proc_widget_hook = NULL;
+       codec->spec = NULL;
+       free_hda_cache(&codec->amp_cache);
+       free_hda_cache(&codec->cmd_cache);
+       init_hda_cache(&codec->amp_cache, sizeof(struct hda_amp_info));
+       init_hda_cache(&codec->cmd_cache, sizeof(struct hda_cache_head));
+       codec->num_pcms = 0;
+       codec->pcm_info = NULL;
+       codec->preset = NULL;
+       module_put(codec->owner);
+       codec->owner = NULL;
+}
+#endif /* CONFIG_SND_HDA_RECONFIG */
+
+/* create a virtual master control and add slaves */
+int snd_hda_add_vmaster(struct hda_codec *codec, char *name,
+                       unsigned int *tlv, const char **slaves)
+{
+       struct snd_kcontrol *kctl;
+       const char **s;
+       int err;
+
+       for (s = slaves; *s && !snd_hda_find_mixer_ctl(codec, *s); s++)
+               ;
+       if (!*s) {
+               snd_printdd("No slave found for %s\n", name);
+               return 0;
+       }
+       kctl = snd_ctl_make_virtual_master(name, tlv);
+       if (!kctl)
+               return -ENOMEM;
+       err = snd_hda_ctl_add(codec, kctl);
+       if (err < 0)
+               return err;
+       
+       for (s = slaves; *s; s++) {
+               struct snd_kcontrol *sctl;
+
+               sctl = snd_hda_find_mixer_ctl(codec, *s);
+               if (!sctl) {
+                       snd_printdd("Cannot find slave %s, skipped\n", *s);
+                       continue;
+               }
+               err = snd_ctl_add_slave(kctl, sctl);
+               if (err < 0)
+                       return err;
+       }
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_vmaster);
 
 /* switch */
 int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
@@ -1024,6 +1362,7 @@ int snd_hda_mixer_amp_switch_info(struct snd_kcontrol *kcontrol,
        uinfo->value.integer.max = 1;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_info);
 
 int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
@@ -1043,6 +1382,7 @@ int snd_hda_mixer_amp_switch_get(struct snd_kcontrol *kcontrol,
                         HDA_AMP_MUTE) ? 0 : 1;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_get);
 
 int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
                                 struct snd_ctl_elem_value *ucontrol)
@@ -1073,6 +1413,7 @@ int snd_hda_mixer_amp_switch_put(struct snd_kcontrol *kcontrol,
        snd_hda_power_down(codec);
        return change;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_amp_switch_put);
 
 /*
  * bound volume controls
@@ -1090,14 +1431,15 @@ int snd_hda_mixer_bind_switch_get(struct snd_kcontrol *kcontrol,
        unsigned long pval;
        int err;
 
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
+       mutex_lock(&codec->control_mutex);
        pval = kcontrol->private_value;
        kcontrol->private_value = pval & ~AMP_VAL_IDX_MASK; /* index 0 */
        err = snd_hda_mixer_amp_switch_get(kcontrol, ucontrol);
        kcontrol->private_value = pval;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_switch_get);
 
 int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
                                  struct snd_ctl_elem_value *ucontrol)
@@ -1106,7 +1448,7 @@ int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
        unsigned long pval;
        int i, indices, err = 0, change = 0;
 
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
+       mutex_lock(&codec->control_mutex);
        pval = kcontrol->private_value;
        indices = (pval & AMP_VAL_IDX_MASK) >> AMP_VAL_IDX_SHIFT;
        for (i = 0; i < indices; i++) {
@@ -1118,9 +1460,10 @@ int snd_hda_mixer_bind_switch_put(struct snd_kcontrol *kcontrol,
                change |= err;
        }
        kcontrol->private_value = pval;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err < 0 ? err : change;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_switch_put);
 
 /*
  * generic bound volume/swtich controls
@@ -1132,14 +1475,15 @@ int snd_hda_mixer_bind_ctls_info(struct snd_kcontrol *kcontrol,
        struct hda_bind_ctls *c;
        int err;
 
+       mutex_lock(&codec->control_mutex);
        c = (struct hda_bind_ctls *)kcontrol->private_value;
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
        kcontrol->private_value = *c->values;
        err = c->ops->info(kcontrol, uinfo);
        kcontrol->private_value = (long)c;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_info);
 
 int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol,
                                struct snd_ctl_elem_value *ucontrol)
@@ -1148,14 +1492,15 @@ int snd_hda_mixer_bind_ctls_get(struct snd_kcontrol *kcontrol,
        struct hda_bind_ctls *c;
        int err;
 
+       mutex_lock(&codec->control_mutex);
        c = (struct hda_bind_ctls *)kcontrol->private_value;
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
        kcontrol->private_value = *c->values;
        err = c->ops->get(kcontrol, ucontrol);
        kcontrol->private_value = (long)c;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_get);
 
 int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
                                struct snd_ctl_elem_value *ucontrol)
@@ -1165,8 +1510,8 @@ int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
        unsigned long *vals;
        int err = 0, change = 0;
 
+       mutex_lock(&codec->control_mutex);
        c = (struct hda_bind_ctls *)kcontrol->private_value;
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
        for (vals = c->values; *vals; vals++) {
                kcontrol->private_value = *vals;
                err = c->ops->put(kcontrol, ucontrol);
@@ -1175,9 +1520,10 @@ int snd_hda_mixer_bind_ctls_put(struct snd_kcontrol *kcontrol,
                change |= err;
        }
        kcontrol->private_value = (long)c;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err < 0 ? err : change;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_ctls_put);
 
 int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag,
                           unsigned int size, unsigned int __user *tlv)
@@ -1186,14 +1532,15 @@ int snd_hda_mixer_bind_tlv(struct snd_kcontrol *kcontrol, int op_flag,
        struct hda_bind_ctls *c;
        int err;
 
+       mutex_lock(&codec->control_mutex);
        c = (struct hda_bind_ctls *)kcontrol->private_value;
-       mutex_lock(&codec->spdif_mutex); /* reuse spdif_mutex */
        kcontrol->private_value = *c->values;
        err = c->ops->tlv(kcontrol, op_flag, size, tlv);
        kcontrol->private_value = (long)c;
-       mutex_unlock(&codec->spdif_mutex);
+       mutex_unlock(&codec->control_mutex);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_mixer_bind_tlv);
 
 struct hda_ctl_ops snd_hda_bind_vol = {
        .info = snd_hda_mixer_amp_volume_info,
@@ -1201,6 +1548,7 @@ struct hda_ctl_ops snd_hda_bind_vol = {
        .put = snd_hda_mixer_amp_volume_put,
        .tlv = snd_hda_mixer_amp_tlv
 };
+EXPORT_SYMBOL_HDA(snd_hda_bind_vol);
 
 struct hda_ctl_ops snd_hda_bind_sw = {
        .info = snd_hda_mixer_amp_switch_info,
@@ -1208,6 +1556,7 @@ struct hda_ctl_ops snd_hda_bind_sw = {
        .put = snd_hda_mixer_amp_switch_put,
        .tlv = snd_hda_mixer_amp_tlv
 };
+EXPORT_SYMBOL_HDA(snd_hda_bind_sw);
 
 /*
  * SPDIF out controls
@@ -1308,6 +1657,29 @@ static unsigned int convert_to_spdif_status(unsigned short val)
        return sbits;
 }
 
+/* set digital convert verbs both for the given NID and its slaves */
+static void set_dig_out(struct hda_codec *codec, hda_nid_t nid,
+                       int verb, int val)
+{
+       hda_nid_t *d;
+
+       snd_hda_codec_write_cache(codec, nid, 0, verb, val);
+       d = codec->slave_dig_outs;
+       if (!d)
+               return;
+       for (; *d; d++)
+               snd_hda_codec_write_cache(codec, *d, 0, verb, val);
+}
+
+static inline void set_dig_out_convert(struct hda_codec *codec, hda_nid_t nid,
+                                      int dig1, int dig2)
+{
+       if (dig1 != -1)
+               set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_1, dig1);
+       if (dig2 != -1)
+               set_dig_out(codec, nid, AC_VERB_SET_DIGI_CONVERT_2, dig2);
+}
+
 static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol,
                                     struct snd_ctl_elem_value *ucontrol)
 {
@@ -1326,14 +1698,8 @@ static int snd_hda_spdif_default_put(struct snd_kcontrol *kcontrol,
        change = codec->spdif_ctls != val;
        codec->spdif_ctls = val;
 
-       if (change) {
-               snd_hda_codec_write_cache(codec, nid, 0,
-                                         AC_VERB_SET_DIGI_CONVERT_1,
-                                         val & 0xff);
-               snd_hda_codec_write_cache(codec, nid, 0,
-                                         AC_VERB_SET_DIGI_CONVERT_2,
-                                         val >> 8);
-       }
+       if (change)
+               set_dig_out_convert(codec, nid, val & 0xff, (val >> 8) & 0xff);
 
        mutex_unlock(&codec->spdif_mutex);
        return change;
@@ -1365,9 +1731,7 @@ static int snd_hda_spdif_out_switch_put(struct snd_kcontrol *kcontrol,
        change = codec->spdif_ctls != val;
        if (change) {
                codec->spdif_ctls = val;
-               snd_hda_codec_write_cache(codec, nid, 0,
-                                         AC_VERB_SET_DIGI_CONVERT_1,
-                                         val & 0xff);
+               set_dig_out_convert(codec, nid, val & 0xff, -1);
                /* unmute amp switch (if any) */
                if ((get_wcaps(codec, nid) & AC_WCAP_OUT_AMP) &&
                    (val & AC_DIG1_ENABLE))
@@ -1410,6 +1774,8 @@ static struct snd_kcontrol_new dig_mixes[] = {
        { } /* end */
 };
 
+#define SPDIF_MAX_IDX  4       /* 4 instances should be enough to probe */
+
 /**
  * snd_hda_create_spdif_out_ctls - create Output SPDIF-related controls
  * @codec: the HDA codec
@@ -1425,19 +1791,72 @@ int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid)
        int err;
        struct snd_kcontrol *kctl;
        struct snd_kcontrol_new *dig_mix;
+       int idx;
 
+       for (idx = 0; idx < SPDIF_MAX_IDX; idx++) {
+               if (!_snd_hda_find_mixer_ctl(codec, "IEC958 Playback Switch",
+                                            idx))
+                       break;
+       }
+       if (idx >= SPDIF_MAX_IDX) {
+               printk(KERN_ERR "hda_codec: too many IEC958 outputs\n");
+               return -EBUSY;
+       }
        for (dig_mix = dig_mixes; dig_mix->name; dig_mix++) {
                kctl = snd_ctl_new1(dig_mix, codec);
+               if (!kctl)
+                       return -ENOMEM;
+               kctl->id.index = idx;
                kctl->private_value = nid;
-               err = snd_ctl_add(codec->bus->card, kctl);
+               err = snd_hda_ctl_add(codec, kctl);
                if (err < 0)
                        return err;
        }
        codec->spdif_ctls =
-               snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0);
+               snd_hda_codec_read(codec, nid, 0,
+                                  AC_VERB_GET_DIGI_CONVERT_1, 0);
        codec->spdif_status = convert_to_spdif_status(codec->spdif_ctls);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_out_ctls);
+
+/*
+ * SPDIF sharing with analog output
+ */
+static int spdif_share_sw_get(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+       ucontrol->value.integer.value[0] = mout->share_spdif;
+       return 0;
+}
+
+static int spdif_share_sw_put(struct snd_kcontrol *kcontrol,
+                             struct snd_ctl_elem_value *ucontrol)
+{
+       struct hda_multi_out *mout = snd_kcontrol_chip(kcontrol);
+       mout->share_spdif = !!ucontrol->value.integer.value[0];
+       return 0;
+}
+
+static struct snd_kcontrol_new spdif_share_sw = {
+       .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+       .name = "IEC958 Default PCM Playback Switch",
+       .info = snd_ctl_boolean_mono_info,
+       .get = spdif_share_sw_get,
+       .put = spdif_share_sw_put,
+};
+
+int snd_hda_create_spdif_share_sw(struct hda_codec *codec,
+                                 struct hda_multi_out *mout)
+{
+       if (!mout->dig_out_nid)
+               return 0;
+       /* ATTENTION: here mout is passed as private_data, instead of codec */
+       return snd_hda_ctl_add(codec,
+                          snd_ctl_new1(&spdif_share_sw, mout));
+}
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_share_sw);
 
 /*
  * SPDIF input
@@ -1481,7 +1900,7 @@ static int snd_hda_spdif_in_status_get(struct snd_kcontrol *kcontrol,
        unsigned short val;
        unsigned int sbits;
 
-       val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0);
+       val = snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT_1, 0);
        sbits = convert_to_spdif_status(val);
        ucontrol->value.iec958.status[0] = sbits;
        ucontrol->value.iec958.status[1] = sbits >> 8;
@@ -1523,19 +1942,31 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
        int err;
        struct snd_kcontrol *kctl;
        struct snd_kcontrol_new *dig_mix;
+       int idx;
 
+       for (idx = 0; idx < SPDIF_MAX_IDX; idx++) {
+               if (!_snd_hda_find_mixer_ctl(codec, "IEC958 Capture Switch",
+                                            idx))
+                       break;
+       }
+       if (idx >= SPDIF_MAX_IDX) {
+               printk(KERN_ERR "hda_codec: too many IEC958 inputs\n");
+               return -EBUSY;
+       }
        for (dig_mix = dig_in_ctls; dig_mix->name; dig_mix++) {
                kctl = snd_ctl_new1(dig_mix, codec);
                kctl->private_value = nid;
-               err = snd_ctl_add(codec->bus->card, kctl);
+               err = snd_hda_ctl_add(codec, kctl);
                if (err < 0)
                        return err;
        }
        codec->spdif_in_enable =
-               snd_hda_codec_read(codec, nid, 0, AC_VERB_GET_DIGI_CONVERT, 0) &
+               snd_hda_codec_read(codec, nid, 0,
+                                  AC_VERB_GET_DIGI_CONVERT_1, 0) &
                AC_DIG1_ENABLE;
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_create_spdif_in_ctls);
 
 #ifdef SND_HDA_NEEDS_RESUME
 /*
@@ -1562,10 +1993,14 @@ int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid)
 int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
                              int direct, unsigned int verb, unsigned int parm)
 {
+       struct hda_bus *bus = codec->bus;
+       unsigned int res;
        int err;
+
+       res = make_codec_cmd(codec, nid, direct, verb, parm);
        snd_hda_power_up(codec);
-       mutex_lock(&codec->bus->cmd_mutex);
-       err = codec->bus->ops.command(codec, nid, direct, verb, parm);
+       mutex_lock(&bus->cmd_mutex);
+       err = bus->ops.command(bus, res);
        if (!err) {
                struct hda_cache_head *c;
                u32 key = build_cmd_cache_key(nid, verb);
@@ -1573,18 +2008,19 @@ int snd_hda_codec_write_cache(struct hda_codec *codec, hda_nid_t nid,
                if (c)
                        c->val = parm;
        }
-       mutex_unlock(&codec->bus->cmd_mutex);
+       mutex_unlock(&bus->cmd_mutex);
        snd_hda_power_down(codec);
        return err;
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_write_cache);
 
 /* resume the all commands from the cache */
 void snd_hda_codec_resume_cache(struct hda_codec *codec)
 {
-       struct hda_cache_head *buffer = codec->cmd_cache.buffer;
+       struct hda_cache_head *buffer = codec->cmd_cache.buf.list;
        int i;
 
-       for (i = 0; i < codec->cmd_cache.size; i++, buffer++) {
+       for (i = 0; i < codec->cmd_cache.buf.used; i++, buffer++) {
                u32 key = buffer->key;
                if (!key)
                        continue;
@@ -1592,6 +2028,7 @@ void snd_hda_codec_resume_cache(struct hda_codec *codec)
                                    get_cmd_cache_cmd(key), buffer->val);
        }
 }
+EXPORT_SYMBOL_HDA(snd_hda_codec_resume_cache);
 
 /**
  * snd_hda_sequence_write_cache - sequence writes with caching
@@ -1609,6 +2046,7 @@ void snd_hda_sequence_write_cache(struct hda_codec *codec,
                snd_hda_codec_write_cache(codec, seq->nid, 0, seq->verb,
                                          seq->param);
 }
+EXPORT_SYMBOL_HDA(snd_hda_sequence_write_cache);
 #endif /* SND_HDA_NEEDS_RESUME */
 
 /*
@@ -1622,6 +2060,7 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
 
        snd_hda_codec_write(codec, fg, 0, AC_VERB_SET_POWER_STATE,
                            power_state);
+       msleep(10); /* partial workaround for "azx_get_response timeout" */
 
        nid = codec->start_nid;
        for (i = 0; i < codec->num_nodes; i++, nid++) {
@@ -1668,6 +2107,17 @@ static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
        }
 }
 
+#ifdef CONFIG_SND_HDA_HWDEP
+/* execute additional init verbs */
+static void hda_exec_init_verbs(struct hda_codec *codec)
+{
+       if (codec->init_verbs.list)
+               snd_hda_sequence_write(codec, codec->init_verbs.list);
+}
+#else
+static inline void hda_exec_init_verbs(struct hda_codec *codec) {}
+#endif
+
 #ifdef SND_HDA_NEEDS_RESUME
 /*
  * call suspend and power-down; used both from PM and power-save
@@ -1694,6 +2144,7 @@ static void hda_call_codec_resume(struct hda_codec *codec)
        hda_set_power_state(codec,
                            codec->afg ? codec->afg : codec->mfg,
                            AC_PWRST_D0);
+       hda_exec_init_verbs(codec);
        if (codec->patch_ops.resume)
                codec->patch_ops.resume(codec);
        else {
@@ -1714,28 +2165,37 @@ static void hda_call_codec_resume(struct hda_codec *codec)
  *
  * Returns 0 if successful, otherwise a negative error code.
  */
-int __devinit snd_hda_build_controls(struct hda_bus *bus)
+int /*__devinit*/ snd_hda_build_controls(struct hda_bus *bus)
 {
        struct hda_codec *codec;
 
        list_for_each_entry(codec, &bus->codec_list, list) {
-               int err = 0;
-               /* fake as if already powered-on */
-               hda_keep_power_on(codec);
-               /* then fire up */
-               hda_set_power_state(codec,
-                                   codec->afg ? codec->afg : codec->mfg,
-                                   AC_PWRST_D0);
-               /* continue to initialize... */
-               if (codec->patch_ops.init)
-                       err = codec->patch_ops.init(codec);
-               if (!err && codec->patch_ops.build_controls)
-                       err = codec->patch_ops.build_controls(codec);
-               snd_hda_power_down(codec);
+               int err = snd_hda_codec_build_controls(codec);
                if (err < 0)
                        return err;
        }
+       return 0;
+}
+EXPORT_SYMBOL_HDA(snd_hda_build_controls);
 
+int snd_hda_codec_build_controls(struct hda_codec *codec)
+{
+       int err = 0;
+       /* fake as if already powered-on */
+       hda_keep_power_on(codec);
+       /* then fire up */
+       hda_set_power_state(codec,
+                           codec->afg ? codec->afg : codec->mfg,
+                           AC_PWRST_D0);
+       hda_exec_init_verbs(codec);
+       /* continue to initialize... */
+       if (codec->patch_ops.init)
+               err = codec->patch_ops.init(codec);
+       if (!err && codec->patch_ops.build_controls)
+               err = codec->patch_ops.build_controls(codec);
+       snd_hda_power_down(codec);
+       if (err < 0)
+               return err;
        return 0;
 }
 
@@ -1828,6 +2288,7 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
 
        return val;
 }
+EXPORT_SYMBOL_HDA(snd_hda_calc_stream_format);
 
 /**
  * snd_hda_query_supported_pcm - query the supported PCM rates and formats
@@ -1842,7 +2303,7 @@ unsigned int snd_hda_calc_stream_format(unsigned int rate,
  *
  * Returns 0 if successful, otherwise a negative error code.
  */
-int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
+static int snd_hda_query_supported_pcm(struct hda_codec *codec, hda_nid_t nid,
                                u32 *ratesp, u64 *formatsp, unsigned int *bpsp)
 {
        int i;
@@ -2007,6 +2468,7 @@ int snd_hda_is_supported_format(struct hda_codec *codec, hda_nid_t nid,
 
        return 1;
 }
+EXPORT_SYMBOL_HDA(snd_hda_is_supported_format);
 
 /*
  * PCM stuff
@@ -2032,12 +2494,12 @@ static int hda_pcm_default_cleanup(struct hda_pcm_stream *hinfo,
                                   struct hda_codec *codec,
                                   struct snd_pcm_substream *substream)
 {
-       snd_hda_codec_setup_stream(codec, hinfo->nid, 0, 0, 0);
+       snd_hda_codec_cleanup_stream(codec, hinfo->nid);
        return 0;
 }
 
-static int __devinit set_pcm_default_values(struct hda_codec *codec,
-                                           struct hda_pcm_stream *info)
+static int set_pcm_default_values(struct hda_codec *codec,
+                                 struct hda_pcm_stream *info)
 {
        /* query support PCM information from the given NID */
        if (info->nid && (!info->rates || !info->formats)) {
@@ -2051,16 +2513,122 @@ static int __devinit set_pcm_default_values(struct hda_codec *codec,
        if (info->ops.close == NULL)
                info->ops.close = hda_pcm_default_open_close;
        if (info->ops.prepare == NULL) {
-               snd_assert(info->nid, return -EINVAL);
+               if (snd_BUG_ON(!info->nid))
+                       return -EINVAL;
                info->ops.prepare = hda_pcm_default_prepare;
        }
        if (info->ops.cleanup == NULL) {
-               snd_assert(info->nid, return -EINVAL);
+               if (snd_BUG_ON(!info->nid))
+                       return -EINVAL;
                info->ops.cleanup = hda_pcm_default_cleanup;
        }
        return 0;
 }
 
+/*
+ * get the empty PCM device number to assign
+ */
+static int get_empty_pcm_device(struct hda_bus *bus, int type)
+{
+       static const char *dev_name[HDA_PCM_NTYPES] = {
+               "Audio", "SPDIF", "HDMI", "Modem"
+       };
+       /* starting device index for each PCM type */
+       static int dev_idx[HDA_PCM_NTYPES] = {
+               [HDA_PCM_TYPE_AUDIO] = 0,
+               [HDA_PCM_TYPE_SPDIF] = 1,
+               [HDA_PCM_TYPE_HDMI] = 3,
+               [HDA_PCM_TYPE_MODEM] = 6
+       };
+       /* normal audio device indices; not linear to keep compatibility */
+       static int audio_idx[4] = { 0, 2, 4, 5 };
+       int i, dev;
+
+       switch (type) {
+       case HDA_PCM_TYPE_AUDIO:
+               for (i = 0; i < ARRAY_SIZE(audio_idx); i++) {
+                       dev = audio_idx[i];
+                       if (!test_bit(dev, bus->pcm_dev_bits))
+                               break;
+               }
+               if (i >= ARRAY_SIZE(audio_idx)) {
+                       snd_printk(KERN_WARNING "Too many audio devices\n");
+                       return -EAGAIN;
+               }
+               break;
+       case HDA_PCM_TYPE_SPDIF:
+       case HDA_PCM_TYPE_HDMI:
+       case HDA_PCM_TYPE_MODEM:
+               dev = dev_idx[type];
+               if (test_bit(dev, bus->pcm_dev_bits)) {
+                       snd_printk(KERN_WARNING "%s already defined\n",
+                                  dev_name[type]);
+                       return -EAGAIN;
+               }
+               break;
+       default:
+               snd_printk(KERN_WARNING "Invalid PCM type %d\n", type);
+               return -EINVAL;
+       }
+       set_bit(dev, bus->pcm_dev_bits);
+       return dev;
+}
+
+/*
+ * attach a new PCM stream
+ */
+static int snd_hda_attach_pcm(struct hda_codec *codec, struct hda_pcm *pcm)
+{
+       struct hda_bus *bus = codec->bus;
+       struct hda_pcm_stream *info;
+       int stream, err;
+
+       if (snd_BUG_ON(!pcm->name))
+               return -EINVAL;
+       for (stream = 0; stream < 2; stream++) {
+               info = &pcm->stream[stream];
+               if (info->substreams) {
+                       err = set_pcm_default_values(codec, info);
+                       if (err < 0)
+                               return err;
+               }
+       }
+       return bus->ops.attach_pcm(bus, codec, pcm);
+}
+
+/* assign all PCMs of the given codec */
+int snd_hda_codec_build_pcms(struct hda_codec *codec)
+{
+       unsigned int pcm;
+       int err;
+
+       if (!codec->num_pcms) {
+               if (!codec->patch_ops.build_pcms)
+                       return 0;
+               err = codec->patch_ops.build_pcms(codec);
+               if (err < 0)
+                       return err;
+       }
+       for (pcm = 0; pcm < codec->num_pcms; pcm++) {
+               struct hda_pcm *cpcm = &codec->pcm_info[pcm];
+               int dev;
+
+               if (!cpcm->stream[0].substreams && !cpcm->stream[1].substreams)
+                       return 0; /* no substreams assigned */
+
+               if (!cpcm->pcm) {
+                       dev = get_empty_pcm_device(codec->bus, cpcm->pcm_type);
+                       if (dev < 0)
+                               return 0;
+                       cpcm->device = dev;
+                       err = snd_hda_attach_pcm(codec, cpcm);
+                       if (err < 0)
+                               return err;
+               }
+       }
+       return 0;
+}
+
 /**
  * snd_hda_build_pcms - build PCM information
  * @bus: the BUS
@@ -2092,27 +2660,13 @@ int __devinit snd_hda_build_pcms(struct hda_bus *bus)
        struct hda_codec *codec;
 
        list_for_each_entry(codec, &bus->codec_list, list) {
-               unsigned int pcm, s;
-               int err;
-               if (!codec->patch_ops.build_pcms)
-                       continue;
-               err = codec->patch_ops.build_pcms(codec);
+               int err = snd_hda_codec_build_pcms(codec);
                if (err < 0)
                        return err;
-               for (pcm = 0; pcm < codec->num_pcms; pcm++) {
-                       for (s = 0; s < 2; s++) {
-                               struct hda_pcm_stream *info;
-                               info = &codec->pcm_info[pcm].stream[s];
-                               if (!info->substreams)
-                                       continue;
-                               err = set_pcm_default_values(codec, info);
-                               if (err < 0)
-                                       return err;
-                       }
-               }
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_build_pcms);
 
 /**
  * snd_hda_check_board_config - compare the current codec with the config table
@@ -2131,11 +2685,11 @@ int snd_hda_check_board_config(struct hda_codec *codec,
                               int num_configs, const char **models,
                               const struct snd_pci_quirk *tbl)
 {
-       if (codec->bus->modelname && models) {
+       if (codec->modelname && models) {
                int i;
                for (i = 0; i < num_configs; i++) {
                        if (models[i] &&
-                           !strcmp(codec->bus->modelname, models[i])) {
+                           !strcmp(codec->modelname, models[i])) {
                                snd_printd(KERN_INFO "hda_codec: model '%s' is "
                                           "selected\n", models[i]);
                                return i;
@@ -2150,6 +2704,67 @@ int snd_hda_check_board_config(struct hda_codec *codec,
        if (!tbl)
                return -1;
        if (tbl->value >= 0 && tbl->value < num_configs) {
+#ifdef CONFIG_SND_DEBUG_VERBOSE
+               char tmp[10];
+               const char *model = NULL;
+               if (models)
+                       model = models[tbl->value];
+               if (!model) {
+                       sprintf(tmp, "#%d", tbl->value);
+                       model = tmp;
+               }
+               snd_printdd(KERN_INFO "hda_codec: model '%s' is selected "
+                           "for config %x:%x (%s)\n",
+                           model, tbl->subvendor, tbl->subdevice,
+                           (tbl->name ? tbl->name : "Unknown device"));
+#endif
+               return tbl->value;
+       }
+       return -1;
+}
+EXPORT_SYMBOL_HDA(snd_hda_check_board_config);
+
+/**
+ * snd_hda_check_board_codec_sid_config - compare the current codec
+                                         subsystem ID with the
+                                         config table
+
+          This is important for Gateway notebooks with SB450 HDA Audio
+          where the vendor ID of the PCI device is:
+               ATI Technologies Inc SB450 HDA Audio [1002:437b]
+          and the vendor/subvendor are found only at the codec.
+
+ * @codec: the HDA codec
+ * @num_configs: number of config enums
+ * @models: array of model name strings
+ * @tbl: configuration table, terminated by null entries
+ *
+ * Compares the modelname or PCI subsystem id of the current codec with the
+ * given configuration table.  If a matching entry is found, returns its
+ * config value (supposed to be 0 or positive).
+ *
+ * If no entries are matching, the function returns a negative value.
+ */
+int snd_hda_check_board_codec_sid_config(struct hda_codec *codec,
+                              int num_configs, const char **models,
+                              const struct snd_pci_quirk *tbl)
+{
+       const struct snd_pci_quirk *q;
+
+       /* Search for codec ID */
+       for (q = tbl; q->subvendor; q++) {
+               unsigned long vendorid = (q->subdevice) | (q->subvendor << 16);
+
+               if (vendorid == codec->subsystem_id)
+                       break;
+       }
+
+       if (!q->subvendor)
+               return -1;
+
+       tbl = q;
+
+       if (tbl->value >= 0 && tbl->value < num_configs) {
 #ifdef CONFIG_SND_DEBUG_DETECT
                char tmp[10];
                const char *model = NULL;
@@ -2168,6 +2783,7 @@ int snd_hda_check_board_config(struct hda_codec *codec,
        }
        return -1;
 }
+EXPORT_SYMBOL_HDA(snd_hda_check_board_codec_sid_config);
 
 /**
  * snd_hda_add_new_ctls - create controls from the array
@@ -2188,7 +2804,7 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
                kctl = snd_ctl_new1(knew, codec);
                if (!kctl)
                        return -ENOMEM;
-               err = snd_ctl_add(codec->bus->card, kctl);
+               err = snd_hda_ctl_add(codec, kctl);
                if (err < 0) {
                        if (!codec->addr)
                                return err;
@@ -2196,13 +2812,14 @@ int snd_hda_add_new_ctls(struct hda_codec *codec, struct snd_kcontrol_new *knew)
                        if (!kctl)
                                return -ENOMEM;
                        kctl->id.device = codec->addr;
-                       err = snd_ctl_add(codec->bus->card, kctl);
+                       err = snd_hda_ctl_add(codec, kctl);
                        if (err < 0)
                                return err;
                }
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_add_new_ctls);
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 static void hda_set_power_state(struct hda_codec *codec, hda_nid_t fg,
@@ -2212,6 +2829,7 @@ static void hda_power_work(struct work_struct *work)
 {
        struct hda_codec *codec =
                container_of(work, struct hda_codec, power_work.work);
+       struct hda_bus *bus = codec->bus;
 
        if (!codec->power_on || codec->power_count) {
                codec->power_transition = 0;
@@ -2219,8 +2837,8 @@ static void hda_power_work(struct work_struct *work)
        }
 
        hda_call_codec_suspend(codec);
-       if (codec->bus->ops.pm_notify)
-               codec->bus->ops.pm_notify(codec);
+       if (bus->ops.pm_notify)
+               bus->ops.pm_notify(bus);
 }
 
 static void hda_keep_power_on(struct hda_codec *codec)
@@ -2231,29 +2849,39 @@ static void hda_keep_power_on(struct hda_codec *codec)
 
 void snd_hda_power_up(struct hda_codec *codec)
 {
+       struct hda_bus *bus = codec->bus;
+
        codec->power_count++;
        if (codec->power_on || codec->power_transition)
                return;
 
        codec->power_on = 1;
-       if (codec->bus->ops.pm_notify)
-               codec->bus->ops.pm_notify(codec);
+       if (bus->ops.pm_notify)
+               bus->ops.pm_notify(bus);
        hda_call_codec_resume(codec);
        cancel_delayed_work(&codec->power_work);
        codec->power_transition = 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_power_up);
+
+#define power_save(codec)      \
+       ((codec)->bus->power_save ? *(codec)->bus->power_save : 0)
+
+#define power_save(codec)      \
+       ((codec)->bus->power_save ? *(codec)->bus->power_save : 0)
 
 void snd_hda_power_down(struct hda_codec *codec)
 {
        --codec->power_count;
        if (!codec->power_on || codec->power_count || codec->power_transition)
                return;
-       if (power_save) {
+       if (power_save(codec)) {
                codec->power_transition = 1; /* avoid reentrance */
-               schedule_delayed_work(&codec->power_work,
-                                     msecs_to_jiffies(power_save * 1000));
+               queue_delayed_work(codec->bus->workq, &codec->power_work,
+                               msecs_to_jiffies(power_save(codec) * 1000));
        }
 }
+EXPORT_SYMBOL_HDA(snd_hda_power_down);
 
 int snd_hda_check_amp_list_power(struct hda_codec *codec,
                                 struct hda_loopback_check *check,
@@ -2290,6 +2918,7 @@ int snd_hda_check_amp_list_power(struct hda_codec *codec,
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_check_amp_list_power);
 #endif
 
 /*
@@ -2309,6 +2938,7 @@ int snd_hda_ch_mode_info(struct hda_codec *codec,
                chmode[uinfo->value.enumerated.item].channels);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_info);
 
 int snd_hda_ch_mode_get(struct hda_codec *codec,
                        struct snd_ctl_elem_value *ucontrol,
@@ -2326,6 +2956,7 @@ int snd_hda_ch_mode_get(struct hda_codec *codec,
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_get);
 
 int snd_hda_ch_mode_put(struct hda_codec *codec,
                        struct snd_ctl_elem_value *ucontrol,
@@ -2336,7 +2967,8 @@ int snd_hda_ch_mode_put(struct hda_codec *codec,
        unsigned int mode;
 
        mode = ucontrol->value.enumerated.item[0];
-       snd_assert(mode < num_chmodes, return -EINVAL);
+       if (mode >= num_chmodes)
+               return -EINVAL;
        if (*max_channelsp == chmode[mode].channels)
                return 0;
        /* change the current channel setting */
@@ -2345,6 +2977,7 @@ int snd_hda_ch_mode_put(struct hda_codec *codec,
                snd_hda_sequence_write_cache(codec, chmode[mode].sequence);
        return 1;
 }
+EXPORT_SYMBOL_HDA(snd_hda_ch_mode_put);
 
 /*
  * input MUX helper
@@ -2365,6 +2998,7 @@ int snd_hda_input_mux_info(const struct hda_input_mux *imux,
        strcpy(uinfo->value.enumerated.name, imux->items[index].label);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_input_mux_info);
 
 int snd_hda_input_mux_put(struct hda_codec *codec,
                          const struct hda_input_mux *imux,
@@ -2386,6 +3020,7 @@ int snd_hda_input_mux_put(struct hda_codec *codec,
        *cur_val = idx;
        return 1;
 }
+EXPORT_SYMBOL_HDA(snd_hda_input_mux_put);
 
 
 /*
@@ -2397,14 +3032,31 @@ static void setup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid,
                                 unsigned int stream_tag, unsigned int format)
 {
        /* turn off SPDIF once; otherwise the IEC958 bits won't be updated */
-       if (codec->spdif_ctls & AC_DIG1_ENABLE)
-               snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1,
-                                   codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff);
+       if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+               set_dig_out_convert(codec, nid, 
+                                   codec->spdif_ctls & ~AC_DIG1_ENABLE & 0xff,
+                                   -1);
        snd_hda_codec_setup_stream(codec, nid, stream_tag, 0, format);
+       if (codec->slave_dig_outs) {
+               hda_nid_t *d;
+               for (d = codec->slave_dig_outs; *d; d++)
+                       snd_hda_codec_setup_stream(codec, *d, stream_tag, 0,
+                                                  format);
+       }
        /* turn on again (if needed) */
-       if (codec->spdif_ctls & AC_DIG1_ENABLE)
-               snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_DIGI_CONVERT_1,
-                                   codec->spdif_ctls & 0xff);
+       if (codec->spdif_status_reset && (codec->spdif_ctls & AC_DIG1_ENABLE))
+               set_dig_out_convert(codec, nid,
+                                   codec->spdif_ctls & 0xff, -1);
+}
+
+static void cleanup_dig_out_stream(struct hda_codec *codec, hda_nid_t nid)
+{
+       snd_hda_codec_cleanup_stream(codec, nid);
+       if (codec->slave_dig_outs) {
+               hda_nid_t *d;
+               for (d = codec->slave_dig_outs; *d; d++)
+                       snd_hda_codec_cleanup_stream(codec, *d);
+       }
 }
 
 /*
@@ -2416,11 +3068,12 @@ int snd_hda_multi_out_dig_open(struct hda_codec *codec,
        mutex_lock(&codec->spdif_mutex);
        if (mout->dig_out_used == HDA_DIG_ANALOG_DUP)
                /* already opened as analog dup; reset it once */
-               snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
+               cleanup_dig_out_stream(codec, mout->dig_out_nid);
        mout->dig_out_used = HDA_DIG_EXCLUSIVE;
        mutex_unlock(&codec->spdif_mutex);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_open);
 
 int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
                                  struct hda_multi_out *mout,
@@ -2433,6 +3086,7 @@ int snd_hda_multi_out_dig_prepare(struct hda_codec *codec,
        mutex_unlock(&codec->spdif_mutex);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_prepare);
 
 /*
  * release the digital out
@@ -2445,18 +3099,47 @@ int snd_hda_multi_out_dig_close(struct hda_codec *codec,
        mutex_unlock(&codec->spdif_mutex);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_dig_close);
 
 /*
  * set up more restrictions for analog out
  */
 int snd_hda_multi_out_analog_open(struct hda_codec *codec,
                                  struct hda_multi_out *mout,
-                                 struct snd_pcm_substream *substream)
-{
-       substream->runtime->hw.channels_max = mout->max_channels;
+                                 struct snd_pcm_substream *substream,
+                                 struct hda_pcm_stream *hinfo)
+{
+       struct snd_pcm_runtime *runtime = substream->runtime;
+       runtime->hw.channels_max = mout->max_channels;
+       if (mout->dig_out_nid) {
+               if (!mout->analog_rates) {
+                       mout->analog_rates = hinfo->rates;
+                       mout->analog_formats = hinfo->formats;
+                       mout->analog_maxbps = hinfo->maxbps;
+               } else {
+                       runtime->hw.rates = mout->analog_rates;
+                       runtime->hw.formats = mout->analog_formats;
+                       hinfo->maxbps = mout->analog_maxbps;
+               }
+               if (!mout->spdif_rates) {
+                       snd_hda_query_supported_pcm(codec, mout->dig_out_nid,
+                                                   &mout->spdif_rates,
+                                                   &mout->spdif_formats,
+                                                   &mout->spdif_maxbps);
+               }
+               mutex_lock(&codec->spdif_mutex);
+               if (mout->share_spdif) {
+                       runtime->hw.rates &= mout->spdif_rates;
+                       runtime->hw.formats &= mout->spdif_formats;
+                       if (mout->spdif_maxbps < hinfo->maxbps)
+                               hinfo->maxbps = mout->spdif_maxbps;
+               }
+               mutex_unlock(&codec->spdif_mutex);
+       }
        return snd_pcm_hw_constraint_step(substream->runtime, 0,
                                          SNDRV_PCM_HW_PARAM_CHANNELS, 2);
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_open);
 
 /*
  * set up the i/o for analog out
@@ -2473,7 +3156,8 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
        int i;
 
        mutex_lock(&codec->spdif_mutex);
-       if (mout->dig_out_nid && mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
+       if (mout->dig_out_nid && mout->share_spdif &&
+           mout->dig_out_used != HDA_DIG_EXCLUSIVE) {
                if (chs == 2 &&
                    snd_hda_is_supported_format(codec, mout->dig_out_nid,
                                                format) &&
@@ -2483,8 +3167,7 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
                                             stream_tag, format);
                } else {
                        mout->dig_out_used = 0;
-                       snd_hda_codec_setup_stream(codec, mout->dig_out_nid,
-                                                  0, 0, 0);
+                       cleanup_dig_out_stream(codec, mout->dig_out_nid);
                }
        }
        mutex_unlock(&codec->spdif_mutex);
@@ -2515,6 +3198,7 @@ int snd_hda_multi_out_analog_prepare(struct hda_codec *codec,
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_prepare);
 
 /*
  * clean up the setting for analog out
@@ -2526,25 +3210,25 @@ int snd_hda_multi_out_analog_cleanup(struct hda_codec *codec,
        int i;
 
        for (i = 0; i < mout->num_dacs; i++)
-               snd_hda_codec_setup_stream(codec, nids[i], 0, 0, 0);
+               snd_hda_codec_cleanup_stream(codec, nids[i]);
        if (mout->hp_nid)
-               snd_hda_codec_setup_stream(codec, mout->hp_nid, 0, 0, 0);
+               snd_hda_codec_cleanup_stream(codec, mout->hp_nid);
        for (i = 0; i < ARRAY_SIZE(mout->extra_out_nid); i++)
                if (mout->extra_out_nid[i])
-                       snd_hda_codec_setup_stream(codec,
-                                                  mout->extra_out_nid[i],
-                                                  0, 0, 0);
+                       snd_hda_codec_cleanup_stream(codec,
+                                                    mout->extra_out_nid[i]);
        mutex_lock(&codec->spdif_mutex);
        if (mout->dig_out_nid && mout->dig_out_used == HDA_DIG_ANALOG_DUP) {
-               snd_hda_codec_setup_stream(codec, mout->dig_out_nid, 0, 0, 0);
+               cleanup_dig_out_stream(codec, mout->dig_out_nid);
                mout->dig_out_used = 0;
        }
        mutex_unlock(&codec->spdif_mutex);
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_multi_out_analog_cleanup);
 
 /*
- * Helper for automatic ping configuration
+ * Helper for automatic pin configuration
  */
 
 static int is_in_nid_list(hda_nid_t nid, hda_nid_t *list)
@@ -2602,20 +3286,21 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
                                 struct auto_pin_cfg *cfg,
                                 hda_nid_t *ignore_nids)
 {
-       hda_nid_t nid, nid_start;
-       int nodes;
+       hda_nid_t nid, end_nid;
        short seq, assoc_line_out, assoc_speaker;
        short sequences_line_out[ARRAY_SIZE(cfg->line_out_pins)];
        short sequences_speaker[ARRAY_SIZE(cfg->speaker_pins)];
+       short sequences_hp[ARRAY_SIZE(cfg->hp_pins)];
 
        memset(cfg, 0, sizeof(*cfg));
 
        memset(sequences_line_out, 0, sizeof(sequences_line_out));
        memset(sequences_speaker, 0, sizeof(sequences_speaker));
+       memset(sequences_hp, 0, sizeof(sequences_hp));
        assoc_line_out = assoc_speaker = 0;
 
-       nodes = snd_hda_get_sub_nodes(codec, codec->afg, &nid_start);
-       for (nid = nid_start; nid < nodes + nid_start; nid++) {
+       end_nid = codec->start_nid + codec->num_nodes;
+       for (nid = codec->start_nid; nid < end_nid; nid++) {
                unsigned int wid_caps = get_wcaps(codec, nid);
                unsigned int wid_type =
                        (wid_caps & AC_WCAP_TYPE) >> AC_WCAP_TYPE_SHIFT;
@@ -2638,6 +3323,10 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
                case AC_JACK_LINE_OUT:
                        seq = get_defcfg_sequence(def_conf);
                        assoc = get_defcfg_association(def_conf);
+
+                       if (!(wid_caps & AC_WCAP_STEREO))
+                               if (!cfg->mono_out_pin)
+                                       cfg->mono_out_pin = nid;
                        if (!assoc)
                                continue;
                        if (!assoc_line_out)
@@ -2666,9 +3355,12 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
                        cfg->speaker_outs++;
                        break;
                case AC_JACK_HP_OUT:
+                       seq = get_defcfg_sequence(def_conf);
+                       assoc = get_defcfg_association(def_conf);
                        if (cfg->hp_outs >= ARRAY_SIZE(cfg->hp_pins))
                                continue;
                        cfg->hp_pins[cfg->hp_outs] = nid;
+                       sequences_hp[cfg->hp_outs] = (assoc << 4) | seq;
                        cfg->hp_outs++;
                        break;
                case AC_JACK_MIC_IN: {
@@ -2707,12 +3399,53 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
                }
        }
 
+       /* FIX-UP:
+        * If no line-out is defined but multiple HPs are found,
+        * some of them might be the real line-outs.
+        */
+       if (!cfg->line_outs && cfg->hp_outs > 1) {
+               int i = 0;
+               while (i < cfg->hp_outs) {
+                       /* The real HPs should have the sequence 0x0f */
+                       if ((sequences_hp[i] & 0x0f) == 0x0f) {
+                               i++;
+                               continue;
+                       }
+                       /* Move it to the line-out table */
+                       cfg->line_out_pins[cfg->line_outs] = cfg->hp_pins[i];
+                       sequences_line_out[cfg->line_outs] = sequences_hp[i];
+                       cfg->line_outs++;
+                       cfg->hp_outs--;
+                       memmove(cfg->hp_pins + i, cfg->hp_pins + i + 1,
+                               sizeof(cfg->hp_pins[0]) * (cfg->hp_outs - i));
+                       memmove(sequences_hp + i - 1, sequences_hp + i,
+                               sizeof(sequences_hp[0]) * (cfg->hp_outs - i));
+               }
+       }
+
        /* sort by sequence */
        sort_pins_by_sequence(cfg->line_out_pins, sequences_line_out,
                              cfg->line_outs);
        sort_pins_by_sequence(cfg->speaker_pins, sequences_speaker,
                              cfg->speaker_outs);
+       sort_pins_by_sequence(cfg->hp_pins, sequences_hp,
+                             cfg->hp_outs);
        
+       /* if we have only one mic, make it AUTO_PIN_MIC */
+       if (!cfg->input_pins[AUTO_PIN_MIC] &&
+           cfg->input_pins[AUTO_PIN_FRONT_MIC]) {
+               cfg->input_pins[AUTO_PIN_MIC] =
+                       cfg->input_pins[AUTO_PIN_FRONT_MIC];
+               cfg->input_pins[AUTO_PIN_FRONT_MIC] = 0;
+       }
+       /* ditto for line-in */
+       if (!cfg->input_pins[AUTO_PIN_LINE] &&
+           cfg->input_pins[AUTO_PIN_FRONT_LINE]) {
+               cfg->input_pins[AUTO_PIN_LINE] =
+                       cfg->input_pins[AUTO_PIN_FRONT_LINE];
+               cfg->input_pins[AUTO_PIN_FRONT_LINE] = 0;
+       }
+
        /*
         * FIX-UP: if no line-outs are detected, try to use speaker or HP pin
         * as a primary output
@@ -2766,6 +3499,7 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
                   cfg->hp_outs, cfg->hp_pins[0],
                   cfg->hp_pins[1], cfg->hp_pins[2],
                   cfg->hp_pins[3], cfg->hp_pins[4]);
+       snd_printd("   mono: mono_out=0x%x\n", cfg->mono_out_pin);
        snd_printd("   inputs: mic=0x%x, fmic=0x%x, line=0x%x, fline=0x%x,"
                   " cd=0x%x, aux=0x%x\n",
                   cfg->input_pins[AUTO_PIN_MIC],
@@ -2777,11 +3511,13 @@ int snd_hda_parse_pin_def_config(struct hda_codec *codec,
 
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_parse_pin_def_config);
 
 /* labels for input pins */
 const char *auto_pin_cfg_labels[AUTO_PIN_LAST] = {
        "Mic", "Front Mic", "Line", "Front Line", "CD", "Aux"
 };
+EXPORT_SYMBOL_HDA(auto_pin_cfg_labels);
 
 
 #ifdef CONFIG_PM
@@ -2809,11 +3545,11 @@ int snd_hda_suspend(struct hda_bus *bus, pm_message_t state)
        }
        return 0;
 }
+EXPORT_SYMBOL_HDA(snd_hda_suspend);
 
 /**
  * snd_hda_resume - resume the codecs
  * @bus: the HDA bus
- * @state: resume state
  *
  * Returns 0 if successful.
  *
@@ -2830,16 +3566,79 @@ int snd_hda_resume(struct hda_bus *bus)
        }
        return 0;
 }
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-int snd_hda_codecs_inuse(struct hda_bus *bus)
-{
-       struct hda_codec *codec;
+EXPORT_SYMBOL_HDA(snd_hda_resume);
+#endif /* CONFIG_PM */
 
-       list_for_each_entry(codec, &bus->codec_list, list) {
-               if (snd_hda_codec_needs_resume(codec))
-                       return 1;
+/*
+ * generic arrays
+ */
+
+/* get a new element from the given array
+ * if it exceeds the pre-allocated array size, re-allocate the array
+ */
+void *snd_array_new(struct snd_array *array)
+{
+       if (array->used >= array->alloced) {
+               int num = array->alloced + array->alloc_align;
+               void *nlist;
+               if (snd_BUG_ON(num >= 4096))
+                       return NULL;
+               nlist = kcalloc(num + 1, array->elem_size, GFP_KERNEL);
+               if (!nlist)
+                       return NULL;
+               if (array->list) {
+                       memcpy(nlist, array->list,
+                              array->elem_size * array->alloced);
+                       kfree(array->list);
+               }
+               array->list = nlist;
+               array->alloced = num;
        }
-       return 0;
+       return snd_array_elem(array, array->used++);
 }
-#endif
-#endif
+EXPORT_SYMBOL_HDA(snd_array_new);
+
+/* free the given array elements */
+void snd_array_free(struct snd_array *array)
+{
+       kfree(array->list);
+       array->used = 0;
+       array->alloced = 0;
+       array->list = NULL;
+}
+EXPORT_SYMBOL_HDA(snd_array_free);
+
+/*
+ * used by hda_proc.c and hda_eld.c
+ */
+void snd_print_pcm_rates(int pcm, char *buf, int buflen)
+{
+       static unsigned int rates[] = {
+               8000, 11025, 16000, 22050, 32000, 44100, 48000, 88200,
+               96000, 176400, 192000, 384000
+       };
+       int i, j;
+
+       for (i = 0, j = 0; i < ARRAY_SIZE(rates); i++)
+               if (pcm & (1 << i))
+                       j += snprintf(buf + j, buflen - j,  " %d", rates[i]);
+
+       buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_HDA(snd_print_pcm_rates);
+
+void snd_print_pcm_bits(int pcm, char *buf, int buflen)
+{
+       static unsigned int bits[] = { 8, 16, 20, 24, 32 };
+       int i, j;
+
+       for (i = 0, j = 0; i < ARRAY_SIZE(bits); i++)
+               if (pcm & (AC_SUPPCM_BITS_8 << i))
+                       j += snprintf(buf + j, buflen - j,  " %d", bits[i]);
+
+       buf[j] = '\0'; /* necessary when j == 0 */
+}
+EXPORT_SYMBOL_HDA(snd_print_pcm_bits);
+
+MODULE_DESCRIPTION("HDA codec core");
+MODULE_LICENSE("GPL");