hda-codec - user-space configuration interface

Added the user-space configuration interface via hwdep.
This works as an individual codec support code, so other built-in
kernel support codes won't be necessary if the user-space tool
supports the same codec as well.

diff -r e0bcabde9015 include/hda_hwdep.h
--- a/include/hda_hwdep.h	Thu Aug 02 15:52:18 2007 +0200
+++ b/include/hda_hwdep.h	Thu Aug 02 16:10:16 2007 +0200
@@ -22,6 +22,39 @@
 #define __SOUND_HDA_HWDEP_H
 
 #define HDA_HWDEP_VERSION	((1 << 16) | (0 << 8) | (0 << 0)) /* 1.0.0 */
+
+/* status */
+enum {
+	HDA_STATUS_UNINITIALIZED,
+	HDA_STATUS_RUNNING,
+};
+
+#define HDA_CODEC_NAME_LEN	64
+#define HDA_CODEC_SPEC_LEN	64
+
+/* codec information */
+struct hda_codec_info {
+	/* codec ids */
+	u32 vendor_id;
+	u32 subsystem_id;
+	u32 revision_id;
+	/* PCI ids */
+	u16 pci_vendor;
+	u16 pci_device;
+	u16 pci_subvendor;
+	u16 pci_subdevice;
+	/* codec slot and widgets */
+	u16 codec_addr;
+	u16 afg;
+	u16 mfg;
+	u16 num_nodes;
+	u16 start_nid;
+	u16 reserved;
+	/* set by hda-tool */
+	char name[HDA_CODEC_NAME_LEN];
+	/* misc info (freely used by hda-tool) */
+	u32 spec[HDA_CODEC_SPEC_LEN];
+};
 
 /* verb */
 #define HDA_REG_NID_SHIFT	24
@@ -32,6 +65,126 @@ struct hda_verb_ioctl {
 struct hda_verb_ioctl {
 	u32 verb;	/* HDA_VERB() */
 	u32 res;	/* response */
+};
+
+/* unsolicited event */
+struct hda_unsol_ioctl {
+	u16 nid;	/* widget nid */
+	u8 tag;		/* identifier tag */
+	u8 shift;	/* shift bit (usually 26) */
+};
+
+#define HDA_REG_USR0		0
+#define HDA_MAX_USER_REGS	32
+
+enum {
+	HDA_CMD_TYPE_NOP,		/* no value */
+	HDA_CMD_TYPE_INT,		/* INT(val) */
+	HDA_CMD_TYPE_LABEL,		/* string */
+	HDA_CMD_TYPE_LIST,		/* list */
+	HDA_CMD_TYPE_CMDS,		/* list of commands */
+	HDA_CMD_TYPE_IF,		/* (CMD(cond) CMD(yes) CMD(no) */
+	HDA_CMD_TYPE_NOT,		/* CMD(A) */
+	HDA_CMD_TYPE_AND,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_OR,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_SHIFTL,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_SHIFTR,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_PLUS,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_MINUS,		/* (CMD(A) CMD(B)) */
+	HDA_CMD_TYPE_LNOT,		/* CMD(A) */
+	HDA_CMD_TYPE_LAND,		/* (CMD(A) CMD(B)...) */
+	HDA_CMD_TYPE_LOR,		/* (CMD(A) CMD(B)...) */
+	HDA_CMD_TYPE_GET_REG,		/* INT(id) */
+	HDA_CMD_TYPE_SET_REG,		/* (INT(id) INT(val)) */
+	HDA_CMD_TYPE_VERBS = 0x20,	/* write array of u32 verbs */
+	HDA_CMD_TYPE_AMP_READ,		/* INT(AMP) */
+	HDA_CMD_TYPE_VERB_UPDATE,	/* (VERB VAL) */
+	HDA_CMD_TYPE_AMP_UPDATE,	/* (AMP VAL) */
+	HDA_CMD_TYPE_CTL_NOTIFY,	/* INT(numid) */
+	HDA_CMD_TYPE_SET_CHANNELS,	/* INT(channels) */
+	HDA_CMD_TYPE_DELAY,		/* INT(msec) */
+	HDA_CMD_TYPE_LAST,
+};
+
+struct hda_tlv {
+	u16 type;
+	u16 len;
+	char val[0];
+};
+
+/* value for HDA_CMD_TYPE_AMP_UPDATE */
+#define HDA_AMP_NID_SHIFT	24
+#define HDA_AMP_CH_SHIFT	23
+#define HDA_AMP_DIR_SHIFT	22
+#define HDA_AMP_IDX_SHIFT	16
+#define HDA_AMP_MASK_SHIFT	0
+#define HDA_AMP_IDX(nid,ch,diridx,mask)					\
+	(((nid) << HDA_AMP_NID_SHIFT ) |				\
+	 ((ch) << HDA_AMP_CH_SHIFT) |					\
+	 ((diridx) << HDA_AMP_IDX_SHIFT) |				\
+	 ((mask) << HDA_AMP_MASK_SHIFT))
+
+/* mixer type */
+enum {
+	HDA_MIXER_TYPE_VOLUME,
+	HDA_MIXER_TYPE_SWITCH,
+	HDA_MIXER_TYPE_BIND_VOLUME,
+	HDA_MIXER_TYPE_BIND_SWITCH,
+	HDA_MIXER_TYPE_ENUM,
+	HDA_MIXER_TYPE_BOOLEAN
+};
+
+struct hda_mixer_head {
+	u32 numid;		/* num id: stored in return from driver */
+	u32 iface;		/* interface id */
+	u32 device;		/* device number */
+	u32 subdevice;		/* subdevice number */
+	char name[44];		/* ASCII name */
+	u32 index;		/* index of item */
+	u32 type;		/* HDA_MIXER_TYPE_XXX */
+};
+
+struct hda_volsw_mixer_head {
+	struct hda_mixer_head head;
+	u32 value[1];
+};
+
+struct hda_enum_mixer_head {
+	struct hda_mixer_head head;
+	u32 cur_val;
+	u32 reserved;
+	struct hda_tlv tlv[0];
+	
+};
+
+#define hda_bool_mixer_head	hda_enum_mixer_head
+
+/* standard PCM */
+enum {
+	HDA_PCM_TYPE_ANALOG_MULTI_OUT,
+	HDA_PCM_TYPE_ANALOG_IN,
+	HDA_PCM_TYPE_SPDIF_OUT,
+	HDA_PCM_TYPE_SPDIF_IN,
+	HDA_PCM_TYPE_EXT_OUT,
+	HDA_PCM_TYPE_EXT_IN,
+};
+
+struct hda_usr_pcm_info {
+	u8 type;		/* PCM type: HDA_PCM_TYPE_XXX */
+	u8 is_modem;		/* is modem? */
+	u8 substreams;		/* number of substreams */
+	u8 channels_min;	/* min number of channels */
+	u8 channels_max;	/* max number of channels */
+	u16 nid;		/* reference widget NID */
+	u32 rates;		/* supported rates (optional) */
+	u32 maxbps;		/* supported max bit per sample */
+	u64 formats;		/* supported formats */
+	/* assigned widgets */
+	u16 assoc_nids[8];
+	u16 extra_nids[8];
+	/* device attributes */
+	char name[32];		/* PCM name */
+	u32 device;		/* device number */
 };
 
 /*
@@ -40,5 +193,16 @@ struct hda_verb_ioctl {
 #define HDA_IOCTL_PVERSION		_IOR('H', 0x10, int)
 #define HDA_IOCTL_VERB_WRITE		_IOWR('H', 0x11, struct hda_verb_ioctl)
 #define HDA_IOCTL_GET_WCAP		_IOWR('H', 0x12, struct hda_verb_ioctl)
+#define HDA_IOCTL_GET_STATUS		_IOR('H', 0x13, int)
+#define HDA_IOCTL_SET_STATUS		_IOW('H', 0x14, int)
+#define HDA_IOCTL_GET_CODEC_INFO	_IOR('H', 0x15, struct hda_codec_info)
+#define HDA_IOCTL_SET_CODEC_INFO	_IOW('H', 0x16, struct hda_codec_info)
+#define HDA_IOCTL_CMD_EXEC		_IOWR('H', 0x17, int)
+#define HDA_IOCTL_ADD_MIXER		_IOWR('H', 0x20, struct hda_mixer_head)
+#define HDA_IOCTL_ADD_PCM		_IOWR('H', 0x30, struct hda_usr_pcm_info)
+#define HDA_IOCTL_ADD_INIT		_IOWR('H', 0x50, int)
+#define HDA_IOCTL_ADD_RESUME		_IOWR('H', 0x51, int)
+#define HDA_IOCTL_ADD_UNSOL		_IOWR('H', 0x52, struct hda_unsol_ioctl)
+#define HDA_IOCTL_RESET			_IO('H', 0x60)
 
 #endif
diff -r e0bcabde9015 pci/Kconfig
--- a/pci/Kconfig	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/Kconfig	Thu Aug 02 16:10:16 2007 +0200
@@ -507,8 +507,22 @@ config SND_HDA_HWDEP
 	help
 	  Say Y here to build a hwdep interface for HD-audio driver.
 	  This interface can be used for out-of-bound communication
-	  with codecs for debugging purposes.
-
+	  with codecs for debugging purposes, and also for user-space
+	  configuration tools.
+
+config SND_HDA_USER_CONFIG
+	bool "User-space configuration for HD-audio driver"
+	depends on SND_HDA_HWDEP && EXPERIMENTAL
+	help
+	  Say Y here to include the user-space configuration interface
+	  to HD-audio driver.  With the user-space configuration tool,
+	  the built-in codec support below can be omitted (if the
+	  corresponding stuff exists).
+
+	  As default, the kernel built-in code is preferred to the
+	  user-space configurator.  To choose the user-space
+	  configurator explicitly, pass "model=user" option.
+  
 config SND_HDA_CODEC_REALTEK
 	bool "Build Realtek HD-audio codec support"
 	depends on SND_HDA_INTEL
diff -r e0bcabde9015 pci/hda/Makefile
--- a/pci/hda/Makefile	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/Makefile	Thu Aug 02 16:10:16 2007 +0200
@@ -14,5 +14,6 @@ snd-hda-intel-$(CONFIG_SND_HDA_CODEC_ATI
 snd-hda-intel-$(CONFIG_SND_HDA_CODEC_ATIHDMI) += patch_atihdmi.o
 snd-hda-intel-$(CONFIG_SND_HDA_CODEC_CONEXANT) += patch_conexant.o
 snd-hda-intel-$(CONFIG_SND_HDA_CODEC_VIA) += patch_via.o
+snd-hda-intel-$(CONFIG_SND_HDA_USER_CONFIG) += hda_user_config.o
 
 obj-$(CONFIG_SND_HDA_INTEL) += snd-hda-intel.o
diff -r e0bcabde9015 pci/hda/hda_codec.c
--- a/pci/hda/hda_codec.c	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_codec.c	Thu Aug 02 16:10:16 2007 +0200
@@ -388,6 +388,13 @@ int __devinit snd_hda_bus_new(struct snd
 	return 0;
 }
 
+#ifdef CONFIG_SND_HDA_USER_CONFIG
+#define is_user_config(codec) \
+	(codec->bus->modelname && !strcmp(codec->bus->modelname, "user"))
+#else
+#define is_user_config(codec)	0
+#endif
+
 #ifdef CONFIG_SND_HDA_GENERIC
 #define is_generic_config(codec) \
 	(codec->bus->modelname && !strcmp(codec->bus->modelname, "generic"))
@@ -403,6 +410,8 @@ find_codec_preset(struct hda_codec *code
 {
 	const struct hda_codec_preset **tbl, *preset;
 
+	if (is_user_config(codec))
+		return NULL; /* use user configurator */
 	if (is_generic_config(codec))
 		return NULL; /* use the generic parser */
 
@@ -428,9 +437,15 @@ void snd_hda_get_codec_name(struct hda_c
 {
 	const struct hda_vendor_id *c;
 	const char *vendor = NULL;
-	u16 vendor_id = codec->vendor_id >> 16;
+	u16 vendor_id;
 	char tmp[16];
 
+	if (codec->name) {
+		strlcpy(name, codec->name, namelen);
+		return;
+	}
+
+	vendor_id = codec->vendor_id >> 16;
 	for (c = hda_vendor_ids; c->id; c++) {
 		if (c->id == vendor_id) {
 			vendor = c->name;
@@ -589,6 +604,12 @@ int __devinit snd_hda_codec_new(struct h
 		snd_hda_get_codec_name(codec, bus->card->mixername,
 				       sizeof(bus->card->mixername));
 
+#ifdef CONFIG_SND_HDA_USER_CONFIG
+	if (is_user_config(codec)) {
+		err = snd_hda_create_usr_codec(codec);
+		goto patched;
+	}
+#endif
 #ifdef CONFIG_SND_HDA_GENERIC
 	if (is_generic_config(codec)) {
 		err = snd_hda_parse_generic_codec(codec);
@@ -603,6 +624,8 @@ int __devinit snd_hda_codec_new(struct h
 	/* call the default parser */
 #ifdef CONFIG_SND_HDA_GENERIC
 	err = snd_hda_parse_generic_codec(codec);
+#elif defined(CONFIG_SND_HDA_USER_CONFIG)
+	err = snd_hda_create_usr_codec(codec);
 #else
 	printk(KERN_ERR "hda-codec: No codec parser is available\n");
 	err = -ENODEV;
@@ -1442,6 +1465,35 @@ int snd_hda_create_spdif_in_ctls(struct 
 	return 0;
 }
 
+
+#ifdef CONFIG_SND_HDA_USER_CONFIG
+/*
+ * remove controls assigned to the SPDIF-in
+ */
+static int remove_ctls(struct hda_codec *codec, struct snd_kcontrol_new *c)
+{
+	struct snd_ctl_elem_id id;
+
+	for (; c->name; c++) {
+		memset(&id, 0, sizeof(id));
+		id.iface = c->iface;
+		strlcpy(id.name, c->name, sizeof(id.name));
+		id.index = c->index;
+		snd_ctl_remove_id(codec->bus->card, &id);
+	}
+	return 0;
+}
+
+int snd_hda_remove_spdif_out_ctls(struct hda_codec *codec)
+{
+	return remove_ctls(codec, dig_mixes);
+}
+
+int snd_hda_remove_spdif_in_ctls(struct hda_codec *codec)
+{
+	return remove_ctls(codec, dig_in_ctls);
+}
+#endif
 
 /*
  * set power state of the codec
@@ -1830,7 +1882,7 @@ static int __devinit set_pcm_default_val
 /*
  * attach a new PCM stream
  */
-static int __devinit
+int
 snd_hda_attach_pcm(struct hda_codec *codec, struct hda_pcm *pcm, int stream)
 {
 	struct hda_pcm_stream *info;
diff -r e0bcabde9015 pci/hda/hda_codec.h
--- a/pci/hda/hda_codec.h	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_codec.h	Thu Aug 02 16:10:16 2007 +0200
@@ -528,6 +528,7 @@ struct hda_pcm {
 	struct hda_pcm_stream stream[2];
 	unsigned int is_modem;	/* modem codec? */
 	int device;	/* assigned device number */
+	struct snd_pcm *pcm_dev;	/* assigned pcm instance */
 };
 
 /* codec information */
@@ -562,6 +563,7 @@ struct hda_codec {
 
 	/* codec specific info */
 	void *spec;
+	int spec_type;
 
 	/* widget capabilities cache */
 	unsigned int num_nodes;
@@ -587,6 +589,10 @@ enum {
 	HDA_INPUT, HDA_OUTPUT
 };
 
+/* spec type */
+enum {
+	HDA_SPEC_PRESET, HDA_SPEC_GENERIC, HDA_SPEC_USER_CONFIG
+};
 
 /*
  * constructors
@@ -632,6 +638,8 @@ int snd_hda_build_controls(struct hda_bu
  * PCM
  */
 int snd_hda_build_pcms(struct hda_bus *bus);
+int snd_hda_attach_pcm(struct hda_codec *codec, struct hda_pcm *cpcm,
+		       int stream);
 void snd_hda_codec_setup_stream(struct hda_codec *codec, hda_nid_t nid,
 				u32 stream_tag,
 				int channel_id, int format);
diff -r e0bcabde9015 pci/hda/hda_generic.c
--- a/pci/hda/hda_generic.c	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_generic.c	Thu Aug 02 16:10:16 2007 +0200
@@ -1048,6 +1048,7 @@ int snd_hda_parse_generic_codec(struct h
 		return -ENOMEM;
 	}
 	codec->spec = spec;
+	codec->spec_type = HDA_SPEC_GENERIC;
 	INIT_LIST_HEAD(&spec->nid_list);
 
 	if ((err = build_afg_tree(codec)) < 0)
diff -r e0bcabde9015 pci/hda/hda_hwdep.c
--- a/pci/hda/hda_hwdep.c	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_hwdep.c	Thu Aug 02 16:10:16 2007 +0200
@@ -23,11 +23,15 @@
 #include <linux/slab.h>
 #include <linux/pci.h>
 #include <linux/compat.h>
-#include <linux/mutex.h>
 #include <sound/core.h>
 #include "hda_codec.h"
 #include "hda_local.h"
 #include <sound/hda_hwdep.h>
+
+
+/*
+ * common hwdep ioctls
+ */
 
 /*
  * write/read an out-of-bound verb
@@ -76,6 +80,11 @@ static int hda_hwdep_ioctl(struct snd_hw
 	case HDA_IOCTL_GET_WCAP:
 		return get_wcap_ioctl(codec, argp);
 	}
+	
+#ifdef CONFIG_SND_HDA_USER_CONFIG
+	if (codec->spec_type == HDA_SPEC_USER_CONFIG)
+		return hda_usr_codec_ioctl(codec, cmd, argp);
+#endif
 	return -ENOIOCTLCMD;
 }
 
diff -r e0bcabde9015 pci/hda/hda_intel.c
--- a/pci/hda/hda_intel.c	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_intel.c	Thu Aug 02 16:10:16 2007 +0200
@@ -1309,7 +1309,7 @@ static void azx_pcm_free(struct snd_pcm 
 	}
 }
 
-static int __devinit
+static int
 azx_attach_pcm_stream(struct hda_codec *codec, struct hda_pcm *cpcm, int stream)
 {
 	struct azx *chip = codec->bus->private_data; 
@@ -1329,6 +1329,7 @@ azx_attach_pcm_stream(struct hda_codec *
 		err = snd_pcm_new(chip->card, cpcm->name, pcm_dev, 0, 0, &pcm);
 		if (err < 0)
 			return err;
+		cpcm->pcm_dev = pcm;
 		strcpy(pcm->name, cpcm->name);
 		apcm = kzalloc(sizeof(*apcm), GFP_KERNEL);
 		if (apcm == NULL)
diff -r e0bcabde9015 pci/hda/hda_local.h
--- a/pci/hda/hda_local.h	Thu Aug 02 15:52:18 2007 +0200
+++ b/pci/hda/hda_local.h	Thu Aug 02 16:10:16 2007 +0200
@@ -152,6 +152,11 @@ int snd_hda_create_spdif_out_ctls(struct
 int snd_hda_create_spdif_out_ctls(struct hda_codec *codec, hda_nid_t nid);
 int snd_hda_create_spdif_in_ctls(struct hda_codec *codec, hda_nid_t nid);
 
+#ifdef CONFIG_SND_HDA_USER_CONFIG
+int snd_hda_remove_spdif_out_ctls(struct hda_codec *codec);
+int snd_hda_remove_spdif_in_ctls(struct hda_codec *codec);
+#endif
+
 /*
  * input MUX helper
  */
@@ -364,5 +369,8 @@ int snd_hda_override_amp_caps(struct hda
  * hwdep interface
  */
 int snd_hda_create_hwdep(struct hda_codec *codec);
+int snd_hda_create_usr_codec(struct hda_codec *codec);
+int hda_usr_codec_ioctl(struct hda_codec *codec, unsigned int cmd,
+			void __user *argp);
 
 #endif /* __SOUND_HDA_LOCAL_H */
diff -r e0bcabde9015 pci/hda/hda_user_config.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pci/hda/hda_user_config.c	Thu Aug 02 16:10:16 2007 +0200
@@ -0,0 +1,1428 @@
+/*
+ * HD-Audio user-configuration interface
+ *
+ * Copyright (c) 2007 Takashi Iwai <tiwai@suse.de>
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This driver is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ */
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/pci.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <sound/core.h>
+#include "hda_codec.h"
+#include "hda_local.h"
+#include <sound/hda_hwdep.h>
+
+struct hda_usr_spec {
+	struct hda_codec *codec;
+
+	/* status */
+	int status;
+
+	/* registered unsol events */
+	struct list_head init_list;
+	struct list_head resume_list;
+	struct list_head unsol_list;
+
+	/* registerd mixer controls */
+	unsigned int num_kctl_alloc, num_kctl_used;
+	struct snd_kcontrol **kctl_alloc;
+
+	/* codec info */
+	struct hda_codec_info codec_info;
+
+	/* PCM */
+	struct hda_pcm *pcm_rec[HDA_MAX_PCMS];
+	struct list_head pcm_info_list;
+	struct hda_multi_out multiout;
+	hda_nid_t dig_in_nid;
+
+	/* user registers */
+	int user_reg[HDA_MAX_USER_REGS];
+
+	/* locks */
+	struct mutex reg_mutex;
+};
+
+#define MAX_TLV_LEN	4096
+
+
+static int process_cmd(struct hda_codec *codec, const struct hda_tlv *cmd,
+		       int *valuep, int *maxlen);
+
+/*
+ * Check length of the current TLV whether it fits within the given max
+ * length.  Returns zero if OK, or a negative error if not fit.
+ * Reduce the max length for this item, too.
+ */
+static int check_tlv_len(const struct hda_tlv *tlv, int *maxlen)
+{
+	if (tlv->len > MAX_TLV_LEN)
+		return -EINVAL;
+	if (tlv->len % 4 != 0)
+		return -EINVAL;
+	if (tlv->len + 4 > *maxlen)
+		return -EINVAL;
+	*maxlen -= tlv->len + 4;
+	return 0;
+}
+
+/*
+ * Process the given number of TLV entries
+ */
+static int process_num_args(struct hda_codec *codec, const struct hda_tlv *cmd,
+			    int nums, int *args, int len)
+{
+	int i, err;
+	for (i = 0; i < nums; i++) {
+		err = process_cmd(codec, cmd, &args[i], &len);
+		if (err < 0)
+			return err;
+		cmd += cmd->len / 4 + 1;
+	}
+	return 0;
+}
+
+static int process_verbs(struct hda_codec *codec, u32 *verbs, int *valuep,
+			 int maxlen)
+{
+	u32 verb;
+
+	/* async write */
+	for (; maxlen > 4; maxlen -= 4) {
+		verb = *verbs++;
+		if (!verb)
+			continue;
+		snd_hda_codec_write(codec,
+				    verb >> HDA_REG_NID_SHIFT, 0,
+				    (verb >> HDA_REG_VERB_SHIFT) & 0xfff,
+				    verb & 0xff);
+	} 
+	/* sync write at the last verb */
+	verb = *verbs;
+	verb = snd_hda_codec_read(codec,
+				  verb >> HDA_REG_NID_SHIFT, 0,
+				  (verb >> HDA_REG_VERB_SHIFT) & 0xfff,
+				  verb & 0xff);
+	if (valuep)
+		*valuep = verb;
+	return 0;
+}
+
+static int process_amp_read(struct hda_codec *codec,
+			    const struct hda_tlv *cmd, int *valuep,
+			    int len)
+
+{
+	int err, verb;
+
+	if (!valuep)
+		return 0;
+	err = process_cmd(codec, cmd, &verb, &len);
+	if (err < 0)
+		return err;
+	*valuep = snd_hda_codec_amp_read(codec, verb >> HDA_AMP_NID_SHIFT,
+					 (verb >> HDA_AMP_CH_SHIFT) & 1,
+					 (verb >> HDA_AMP_DIR_SHIFT) & 1,
+					 (verb >> HDA_AMP_IDX_SHIFT) & 0xf)
+		& (verb >> HDA_AMP_MASK_SHIFT) & 0xff;
+	return 0;
+}
+
+static int process_verb_update(struct hda_codec *codec,
+			       const struct hda_tlv *cmd, int *valuep,
+			       int maxlen)
+{
+	int val[2], err;
+
+	err = process_num_args(codec, cmd, 2, val, maxlen);
+	if (err < 0)
+		return err;
+	if (val[0] & 0xff)
+		val[1] &= val[0] & 0xff; /* masked */
+	err = snd_hda_codec_read(codec,
+				 val[0] >> HDA_REG_NID_SHIFT, 0,
+				 (val[0] >> HDA_REG_VERB_SHIFT) & 0xfff,
+				 val[1]);
+	if (valuep)
+		*valuep = err;
+	return 0;
+}
+
+static int process_amp_update(struct hda_codec *codec,
+			      const struct hda_tlv *cmd, int maxlen)
+{
+	int val[2], err;
+
+	err = process_num_args(codec, cmd, 2, val, maxlen);
+	if (err < 0)
+		return err;
+	snd_hda_codec_amp_update(codec,
+				 val[0] >> HDA_AMP_NID_SHIFT,
+				 (val[0] >> HDA_AMP_CH_SHIFT) & 1,
+				 (val[0] >> HDA_AMP_DIR_SHIFT) & 1,
+				 (val[0] >> HDA_AMP_IDX_SHIFT) & 0xf,
+				 (val[0] >> HDA_AMP_MASK_SHIFT) & 0xff,
+				 val[1]);
+	return 0;
+}
+
+static int process_ctl_notify(struct hda_codec *codec, u32 val)
+{
+	struct snd_ctl_elem_id id;
+
+	memset(&id, 0, sizeof(id));
+	id.numid = val;
+	snd_ctl_notify(codec->bus->card, SNDRV_CTL_EVENT_MASK_VALUE, &id);
+	return 0;
+}
+
+static int process_if(struct hda_codec *codec,
+		      const struct hda_tlv *cmd, int *valuep, int maxlen)
+{
+	int cond, err;
+
+	err = process_cmd(codec, cmd, &cond, &maxlen);
+	if (err < 0)
+		return err;
+	cmd = cmd + (cmd->len / 4 + 1);
+	if (cond)
+		return process_cmd(codec, cmd, valuep, &maxlen);
+	if (check_tlv_len(cmd, &maxlen))
+		return -EINVAL;
+	if (!maxlen)
+		return 0;
+	cmd = cmd + (cmd->len / 4 + 1);
+	return process_cmd(codec, cmd, valuep, &maxlen);
+}
+
+static int process_one_op(struct hda_codec *codec, int type,
+			  const struct hda_tlv *cmd, int *valuep, int len)
+{
+	int err;
+
+	if (!valuep)
+		return 0;
+	err = process_cmd(codec, cmd, valuep, &len);
+	if (err < 0)
+		return err;
+	switch (type) {
+	case HDA_CMD_TYPE_NOT:	*valuep = ~*valuep; break;
+	case HDA_CMD_TYPE_LNOT:	*valuep = !*valuep; break;
+	}
+	return 0;
+}
+
+static int process_two_ops(struct hda_codec *codec, int type,
+			   const struct hda_tlv *cmd, int *valuep, int len)
+{
+	int val[2], err;
+
+	if (!valuep)
+		return 0;
+	err = process_num_args(codec, cmd, 2, val, len);
+	if (err < 0)
+		return err;
+	switch (type) {
+	case HDA_CMD_TYPE_AND:	*valuep = val[0] & val[1]; break;
+	case HDA_CMD_TYPE_OR:	*valuep = val[0] | val[1]; break;
+	case HDA_CMD_TYPE_SHIFTL: *valuep = val[0] << val[1]; break;
+	case HDA_CMD_TYPE_SHIFTR: *valuep = val[0] >> val[1]; break;
+	case HDA_CMD_TYPE_PLUS: *valuep = val[0] + val[1]; break;
+	case HDA_CMD_TYPE_MINUS: *valuep = val[0] - val[1]; break;
+	}
+	return 0;
+}
+
+static int process_landor(struct hda_codec *codec, int type,
+			   const struct hda_tlv *cmd, int *valuep, int len)
+{
+	int err, val;
+
+	while (len > 0) {
+		err = process_cmd(codec, cmd, &val, &len);
+		if (err < 0)
+			return err;
+		if (valuep)
+			*valuep = !!val;
+		if ((val && type == HDA_CMD_TYPE_LOR) ||
+		    (!val && type == HDA_CMD_TYPE_LAND))
+			return 0;
+		cmd = cmd + (cmd->len / 4 + 1);
+	}
+	return 0;
+}			  
+
+static int process_get_reg(struct hda_codec *codec, const struct hda_tlv *cmd,
+			   int *valuep, int len)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	int err, reg;
+
+	if (!valuep)
+		return 0;
+	err = process_cmd(codec, cmd, &reg, &len);
+	if (err < 0)
+		return err;
+	if (reg < 0 || reg >= HDA_MAX_USER_REGS)
+		return -EINVAL;
+	*valuep = spec->user_reg[reg];
+	return 0;
+}
+
+static int process_set_reg(struct hda_codec *codec, const struct hda_tlv *cmd,
+			   int *valuep, int len)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	int err, val[2];
+
+	err = process_num_args(codec, cmd, 2, val, len);
+	if (err < 0)
+		return err;
+	if (val[0] < 0 || val[0] >= HDA_MAX_USER_REGS)
+		return -EINVAL;
+	spec->user_reg[val[0]] = val[1];
+	if (valuep)
+		*valuep = val[1];
+	return 0;
+}
+
+static int process_set_channels(struct hda_codec *codec, int val)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	if (val < 0)
+		return val;
+	spec->multiout.max_channels = val;
+	spec->multiout.num_dacs = val / 2;
+	return 0;
+}
+
+static int process_cmd(struct hda_codec *codec, const struct hda_tlv *cmd,
+		       int *valuep, int *maxlen)
+{
+	int ret = 0;
+	int len, type;
+	u32 *val;
+
+	if (!cmd)
+		return 0;
+	if (check_tlv_len(cmd, maxlen))
+		return -EINVAL;
+	len = cmd->len;
+	type = cmd->type;
+	cmd++;
+	val = (u32 *)cmd;
+
+	if (type == HDA_CMD_TYPE_NOP)
+		return 0;
+	if (len < 4)
+		return -EINVAL;
+
+	switch (type) {
+	case HDA_CMD_TYPE_INT:
+		if (valuep)
+			*valuep = *val;
+		return 0;
+	case HDA_CMD_TYPE_CMDS: {
+		while (len > 0) {
+			ret = process_cmd(codec, cmd, valuep, &len);
+			if (ret < 0)
+				return ret;
+			cmd += cmd->len / 4 + 1;
+		}
+		return 0;
+	}
+	case HDA_CMD_TYPE_IF:
+		return process_if(codec, cmd, valuep, len);
+	case HDA_CMD_TYPE_NOT:
+	case HDA_CMD_TYPE_LNOT:
+		return process_one_op(codec, type, cmd, valuep, len);
+	case HDA_CMD_TYPE_AND:
+	case HDA_CMD_TYPE_OR:
+	case HDA_CMD_TYPE_SHIFTL:
+	case HDA_CMD_TYPE_SHIFTR:
+	case HDA_CMD_TYPE_PLUS:
+	case HDA_CMD_TYPE_MINUS:
+		return process_two_ops(codec, type, cmd, valuep, len);
+	case HDA_CMD_TYPE_LAND:
+	case HDA_CMD_TYPE_LOR:
+		return process_landor(codec, type, cmd, valuep, len);
+	case HDA_CMD_TYPE_GET_REG:
+		return process_get_reg(codec, cmd, valuep, len);
+	case HDA_CMD_TYPE_SET_REG:
+		return process_set_reg(codec, cmd, valuep, len);
+	case HDA_CMD_TYPE_VERBS:
+		return process_verbs(codec, val, valuep, len);
+	case HDA_CMD_TYPE_AMP_READ:
+		return process_amp_read(codec, cmd, valuep, len);
+	case HDA_CMD_TYPE_VERB_UPDATE:
+		return process_verb_update(codec, cmd, valuep, len);
+	case HDA_CMD_TYPE_AMP_UPDATE:
+		return process_amp_update(codec, cmd, len);
+	case HDA_CMD_TYPE_CTL_NOTIFY:
+		return process_ctl_notify(codec, *val);
+	case HDA_CMD_TYPE_SET_CHANNELS:
+		return process_set_channels(codec, *val);
+	case HDA_CMD_TYPE_DELAY:
+		if (*val > 0 && *val < 10 * 1000)
+			msleep(*val);
+		return 0;
+	}
+	return -EINVAL;
+}
+
+static int do_process_cmd(struct hda_codec *codec, const struct hda_tlv *cmd)
+{
+	int maxlen = cmd->len + 4;
+	return process_cmd(codec, cmd, NULL, &maxlen);
+}
+
+static void *alloc_tlv(const char __user *data, int head_size)
+{
+	u16 type, len;
+	char *rec;
+	
+	if (get_user(type, (u16 __user *)data) ||
+	    get_user(len, (u16 __user *)(data + 2)))
+		return NULL;
+	if (len > MAX_TLV_LEN)
+		return NULL;
+	rec = kmalloc(len + head_size, GFP_KERNEL);
+	if (!rec)
+		return NULL;
+	if (copy_from_user(rec + head_size, data, len + 4)) {
+		kfree(rec);
+		return NULL;
+	}
+	memset(rec, 0, head_size);
+	return rec;
+}
+
+/*
+ * mixer interface
+ */
+
+static int hda_enum_mixer_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo);
+static int hda_enum_mixer_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int hda_enum_mixer_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int hda_bool_mixer_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo);
+static int hda_bool_mixer_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+static int hda_bool_mixer_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol);
+
+
+static struct snd_kcontrol_new ctl_templates[] = {
+	[HDA_MIXER_TYPE_VOLUME] = HDA_CODEC_VOLUME(NULL, 0, 0, 0),
+	[HDA_MIXER_TYPE_SWITCH] = HDA_CODEC_MUTE(NULL, 0, 0, 0),
+	[HDA_MIXER_TYPE_BIND_VOLUME] = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_READ |
+			  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+		.info = snd_hda_mixer_bind_ctls_info,
+		.get =  snd_hda_mixer_bind_ctls_get,
+		.put = snd_hda_mixer_bind_ctls_put,
+		.tlv = { .c = snd_hda_mixer_bind_tlv },
+	},
+	[HDA_MIXER_TYPE_BIND_SWITCH] = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = snd_hda_mixer_bind_ctls_info,
+		.get =  snd_hda_mixer_bind_ctls_get,
+		.put = snd_hda_mixer_bind_ctls_put,
+	},
+	[HDA_MIXER_TYPE_ENUM] = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = hda_enum_mixer_info,
+		.get = hda_enum_mixer_get,
+		.put = hda_enum_mixer_put,
+	},
+	[HDA_MIXER_TYPE_BOOLEAN] = {
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.info = hda_bool_mixer_info,
+		.get = hda_bool_mixer_get,
+		.put = hda_bool_mixer_put,
+	},
+};
+
+/*
+ * Do a force resume of the given control
+ *
+ * This is called from resume callback and at the creation of the
+ * control to warm the cache
+ */
+static void resume_ctl(struct hda_codec *codec,
+		       struct snd_kcontrol *kctl,
+		       struct snd_ctl_elem_value *val)
+{
+	int c, count;
+
+	codec->in_resume = 1;
+	count = kctl->count ? kctl->count : 1;
+	for (c = 0; c < count; c++) {
+		memset(val, 0, sizeof(*val));
+		val->id = kctl->id;
+		val->id.index = kctl->id.index ? kctl->id.index : c;
+		if (kctl->get(kctl, val) >= 0)
+			kctl->put(kctl, val);
+	}
+	codec->in_resume = 0;
+}
+
+#define NUM_CONTROL_ALLOC	32
+
+static int add_new_ctl(struct hda_codec *codec, struct hda_mixer_head *id,
+		       unsigned long private_value,
+		       void (*private_free)(struct snd_kcontrol *))
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct snd_kcontrol_new knew;
+	struct snd_kcontrol *kctl;
+	struct snd_ctl_elem_value *val;
+	int err;
+
+	if (spec->num_kctl_used >= spec->num_kctl_alloc) {
+		int num = spec->num_kctl_alloc + NUM_CONTROL_ALLOC;
+		struct snd_kcontrol **new_array;
+		/* array + terminator */
+		new_array = kcalloc(num + 1, sizeof(*new_array), GFP_KERNEL);
+		if (!new_array)
+			return -ENOMEM;
+		if (spec->kctl_alloc) {
+			memcpy(new_array, spec->kctl_alloc,
+			       sizeof(*new_array) * spec->num_kctl_alloc);
+			kfree(spec->kctl_alloc);
+		}
+		spec->kctl_alloc = new_array;
+		spec->num_kctl_alloc = num;
+	}
+
+	knew = ctl_templates[id->type];
+	knew.name = id->name;
+	knew.iface = id->iface;
+	knew.device = id->device;
+	knew.subdevice = id->subdevice;
+	knew.index = id->index;
+	knew.private_value = private_value;
+
+	kctl = snd_ctl_new1(&knew, codec);
+	if (!kctl)
+		return -ENOMEM;
+	kctl->private_free = private_free;
+	err = snd_ctl_add(codec->bus->card, kctl);
+	if (err < 0)
+		return err;
+	id->numid = kctl->id.numid;
+	spec->kctl_alloc[spec->num_kctl_used++] = kctl;
+	val = kmalloc(sizeof(*val), GFP_KERNEL);
+	if (val) {
+		resume_ctl(codec, kctl, val);
+		kfree(val);
+	}
+	return 0;
+}
+
+/*
+ * generic enum controls
+ */
+
+#define MAX_ENUM_ITEMS	16
+
+struct hda_enum_mixer_rec {
+	char *label[MAX_ENUM_ITEMS];
+	struct hda_tlv *cmd[MAX_ENUM_ITEMS];
+	unsigned int nitems;
+	unsigned int cur_val;
+	struct hda_tlv data[0];
+};
+
+#define get_enum(ctl)	(struct hda_enum_mixer_rec *)((ctl)->private_value)
+
+static int hda_enum_mixer_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	struct hda_enum_mixer_rec *mux;
+
+	mux = get_enum(kcontrol);
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = mux->nitems;
+	if (uinfo->value.enumerated.item >= mux->nitems)
+		uinfo->value.enumerated.item = mux->nitems - 1;
+	strlcpy(uinfo->value.enumerated.name,
+		mux->label[uinfo->value.enumerated.item],
+		sizeof(uinfo->value.enumerated.name));
+	return 0;
+}
+
+static int hda_enum_mixer_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_enum_mixer_rec *mux = get_enum(kcontrol);
+	ucontrol->value.enumerated.item[0] = mux->cur_val;
+	return 0;
+}
+
+static int hda_enum_mixer_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_enum_mixer_rec *mux = get_enum(kcontrol);
+	unsigned int item;
+
+	item = ucontrol->value.enumerated.item[0];
+	if (item >= mux->nitems)
+		item = mux->nitems - 1;
+	if (item != mux->cur_val || codec->in_resume) {
+		mux->cur_val = item;
+		do_process_cmd(codec, mux->cmd[item]);
+		return 1;
+	}
+	return 0;
+}
+
+static void hda_kctl_priv_free(struct snd_kcontrol *kctl)
+{
+	kfree((void *)kctl->private_value);
+}
+
+static int hda_enum_mixer_new(struct hda_codec *codec,
+			      struct hda_mixer_head *id,
+			      const char __user *data)
+{
+	struct hda_enum_mixer_rec *rec;
+	struct hda_tlv *tlv;
+	int cur_val, maxlen;
+
+	if (get_user(cur_val, (const u32 __user *)data))
+		return -EFAULT;
+	data += 8;
+	rec = alloc_tlv(data, sizeof(*rec));
+	if (!rec)
+		return -ENOMEM;
+	tlv = rec->data;
+	if (tlv->type != HDA_CMD_TYPE_LIST)
+		goto error;
+	maxlen = tlv->len;
+	tlv++;
+	while (maxlen > 0) {
+		if (rec->nitems >= MAX_ENUM_ITEMS)
+			goto error;
+		if (check_tlv_len(tlv, &maxlen))
+			goto error;
+		if (tlv->type != HDA_CMD_TYPE_LABEL)
+			goto error;
+		rec->label[rec->nitems] = tlv->val;
+		tlv += tlv->len / 4 + 1;
+		if (check_tlv_len(tlv, &maxlen))
+			goto error;
+		rec->cmd[rec->nitems] = tlv;
+		tlv += tlv->len / 4 + 1;
+		rec->nitems++;
+	}
+	if (!rec->nitems)
+		goto error;
+	rec->cur_val = cur_val;
+
+	if (add_new_ctl(codec, id, (unsigned long)rec, hda_kctl_priv_free) < 0)
+		goto error;
+
+	return 0;
+
+ error:
+	kfree(rec);
+	return -EINVAL;
+}
+
+/*
+ * generic boolean controls
+ */
+struct hda_bool_mixer_rec {
+	char *label[MAX_ENUM_ITEMS];
+	struct hda_tlv *cmd[MAX_ENUM_ITEMS];
+	unsigned int nitems;
+	unsigned int cur_val;
+	struct hda_tlv data[0];
+};
+
+#define get_bool(ctl)	(struct hda_bool_mixer_rec *)((ctl)->private_value)
+
+static int hda_bool_mixer_info(struct snd_kcontrol *kcontrol,
+			       struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_BOOLEAN;
+	uinfo->count = 1;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 1;
+	return 0;
+}
+
+static int hda_bool_mixer_get(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_bool_mixer_rec *rec = get_bool(kcontrol);
+	ucontrol->value.integer.value[0] = rec->cur_val;
+	return 0;
+}
+
+static int hda_bool_mixer_put(struct snd_kcontrol *kcontrol,
+			      struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct hda_bool_mixer_rec *rec = get_bool(kcontrol);
+	unsigned int val;
+
+	val = !!ucontrol->value.integer.value[0];
+	if (val != rec->cur_val || codec->in_resume) {
+		rec->cur_val = val;
+		do_process_cmd(codec, rec->cmd[val]);
+		return 1;
+	}
+	return 0;
+}
+
+static int hda_bool_mixer_new(struct hda_codec *codec,
+			      struct hda_mixer_head *id,
+			      const char __user *data)
+{
+	struct hda_bool_mixer_rec *rec;
+	struct hda_tlv *tlv;
+	int i, cur_val, maxlen;
+
+	if (get_user(cur_val, (const u32 __user *)data))
+		return -EFAULT;
+	data += 8;
+	rec = alloc_tlv(data, sizeof(*rec));
+	if (!rec)
+		return -ENOMEM;
+	tlv = rec->data;
+	if (tlv->type != HDA_CMD_TYPE_LIST)
+		goto error;
+	maxlen = tlv->len;
+	tlv++;
+	for (i = 0; i < 2; i++) {
+		if (check_tlv_len(tlv, &maxlen))
+			goto error;
+		rec->cmd[i] = tlv;
+		tlv += tlv->len / 4 + 1;
+	}
+	rec->cur_val = cur_val;
+
+	if (add_new_ctl(codec, id, (unsigned long)rec, hda_kctl_priv_free) < 0)
+		goto error;
+
+	return 0;
+
+ error:
+	kfree(rec);
+	return -EINVAL;
+}
+
+/*
+ * bind multiple controls
+ */
+#define HDA_MAX_BIND_CTLS	8
+
+static int hda_bind_mixer_new(struct hda_codec *codec,
+			      struct hda_mixer_head *id,
+			      const u32 __user *data)
+{
+	struct hda_bind_ctls *c;
+	long values[HDA_MAX_BIND_CTLS];
+	int nums;
+
+	for (nums = 0; nums < HDA_MAX_BIND_CTLS - 1;) {
+		if (get_user(values[nums], data))
+			return -EFAULT;
+		if (!values[nums++])
+			break;
+		data++;
+	}
+	if (nums <= 1)
+		return -EINVAL;
+	c = kmalloc(sizeof(*c) + sizeof(long) * nums, GFP_KERNEL);
+	if (!c)
+		return -ENOMEM;
+	if (id->type == HDA_MIXER_TYPE_BIND_VOLUME)
+		c->ops = &snd_hda_bind_vol;
+	else
+		c->ops = &snd_hda_bind_sw;
+	memcpy(c->values, values, sizeof(long) * nums);
+	if (add_new_ctl(codec, id, (unsigned long)c, hda_kctl_priv_free) < 0)
+		goto error;
+
+	return 0;
+
+ error:
+	kfree(c);
+	return -EINVAL;
+}
+
+/*
+ * ADD_MIXER ioctl
+ */
+static int hda_mixer_new(struct hda_codec *codec, char __user *argp)
+{
+	struct hda_mixer_head header;
+	struct hda_mixer_head __user *headp =
+		(struct hda_mixer_head __user *)argp;
+	unsigned int value;
+	int err;
+
+	if (copy_from_user(&header, argp, sizeof(header)))
+		return -EFAULT;
+	argp += sizeof(header);
+	switch (header.type) {
+	case HDA_MIXER_TYPE_ENUM:
+		err = hda_enum_mixer_new(codec, &header, argp);
+		break;
+	case HDA_MIXER_TYPE_BOOLEAN:
+		err = hda_bool_mixer_new(codec, &header, argp);
+		break;
+	case HDA_MIXER_TYPE_VOLUME:
+	case HDA_MIXER_TYPE_SWITCH:
+		if (get_user(value, (const unsigned int __user *)argp))
+			return -EFAULT;
+		err = add_new_ctl(codec, &header, value, NULL);
+		break;
+	case HDA_MIXER_TYPE_BIND_VOLUME:
+	case HDA_MIXER_TYPE_BIND_SWITCH:
+		err = hda_bind_mixer_new(codec, &header,
+					 (const u32 __user *)argp);
+		break;
+	default:
+		err = -EINVAL;
+		break;
+	}
+	if (err < 0)
+		return err;
+	if (put_user(header.numid, &headp->numid))
+		printk(KERN_ERR "hda-hwdep: cannot write numid %d\n",
+		       header.numid);
+
+	return 0;
+}
+
+
+/*
+ * PCM interface
+ */
+static int pcm_type_directions[] = {
+	[HDA_PCM_TYPE_ANALOG_MULTI_OUT] = SNDRV_PCM_STREAM_PLAYBACK,
+	[HDA_PCM_TYPE_ANALOG_IN] = SNDRV_PCM_STREAM_CAPTURE,
+	[HDA_PCM_TYPE_SPDIF_OUT] = SNDRV_PCM_STREAM_PLAYBACK,
+	[HDA_PCM_TYPE_SPDIF_IN] = SNDRV_PCM_STREAM_CAPTURE,
+	[HDA_PCM_TYPE_EXT_OUT] = SNDRV_PCM_STREAM_PLAYBACK,
+	[HDA_PCM_TYPE_EXT_IN] = SNDRV_PCM_STREAM_CAPTURE,
+};
+
+static int analog_multi_playback_pcm_open(struct hda_pcm_stream *hinfo,
+					  struct hda_codec *codec,
+					  struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_open(codec, &spec->multiout, substream);
+}
+
+static int analog_multi_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+					     struct hda_codec *codec,
+					     unsigned int stream_tag,
+					     unsigned int format,
+					     struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_prepare(codec, &spec->multiout,
+						stream_tag, format, substream);
+}
+
+static int analog_multi_playback_pcm_cleanup(struct hda_pcm_stream *hinfo,
+					     struct hda_codec *codec,
+					     struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_analog_cleanup(codec, &spec->multiout);
+}
+
+static int spdif_playback_pcm_open(struct hda_pcm_stream *hinfo,
+				   struct hda_codec *codec,
+				   struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_open(codec, &spec->multiout);
+}
+
+static int spdif_playback_pcm_prepare(struct hda_pcm_stream *hinfo,
+				      struct hda_codec *codec,
+				      unsigned int stream_tag,
+				      unsigned int format,
+				      struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_prepare(codec, &spec->multiout,
+					     stream_tag, format, substream);
+}
+
+static int spdif_playback_pcm_close(struct hda_pcm_stream *hinfo,
+				    struct hda_codec *codec,
+				    struct snd_pcm_substream *substream)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return snd_hda_multi_out_dig_close(codec, &spec->multiout);
+}
+
+static struct hda_pcm_ops pcm_usr_ops[] = {
+	[HDA_PCM_TYPE_ANALOG_MULTI_OUT] = {
+		.open = analog_multi_playback_pcm_open,
+		.prepare = analog_multi_playback_pcm_prepare,
+		.cleanup = analog_multi_playback_pcm_cleanup
+	},
+	[HDA_PCM_TYPE_ANALOG_IN] = {},
+	[HDA_PCM_TYPE_SPDIF_OUT] = {
+		.open = spdif_playback_pcm_open,
+		.close = spdif_playback_pcm_close,
+		.prepare = spdif_playback_pcm_prepare
+	},
+	[HDA_PCM_TYPE_SPDIF_IN] = {},
+	[HDA_PCM_TYPE_EXT_OUT] = {},
+	[HDA_PCM_TYPE_EXT_IN] = {},
+};
+
+struct hda_usr_pcm_list {
+	struct hda_usr_pcm_info info;
+	struct list_head list;
+};
+
+static int hda_pcm_new(struct hda_codec *codec, void __user *argp)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_pcm *cpcm;
+	struct hda_usr_pcm_list *list;
+	struct hda_usr_pcm_info *rec;
+	int i, err, stream;
+
+	list = kmalloc(sizeof(*list), GFP_KERNEL);
+	if (!list)
+		return -ENOMEM;
+	rec = &list->info;
+	if (copy_from_user(rec, argp, sizeof(*rec))) {
+		err = -EFAULT;
+		goto error;
+	}
+
+	if (rec->device >= HDA_MAX_PCMS) {
+		err = -EINVAL;
+		goto error;
+	}
+	cpcm = spec->pcm_rec[rec->device];
+	if (!cpcm) {
+		cpcm = kzalloc(sizeof(*cpcm), GFP_KERNEL);
+		if (!cpcm) {
+			err = -ENOMEM;
+			goto error;
+		}
+		cpcm->device = rec->device;
+		cpcm->is_modem = rec->is_modem;
+		spec->pcm_rec[rec->device] = cpcm;
+	}
+	switch (rec->type) {
+	case HDA_PCM_TYPE_ANALOG_MULTI_OUT:
+		if (spec->multiout.dac_nids) {
+			err = -EBUSY;
+			goto error;
+		}
+		spec->multiout.dac_nids = rec->assoc_nids;
+		spec->multiout.hp_nid = rec->extra_nids[0];
+		for (i = 0; i < 3; i++)
+			spec->multiout.extra_out_nid[i] =
+				rec->extra_nids[i + 1];
+		spec->multiout.max_channels = rec->channels_max;
+		break;
+	case HDA_PCM_TYPE_SPDIF_OUT:
+		spec->multiout.dig_out_nid = rec->assoc_nids[0];
+		break;
+	case HDA_PCM_TYPE_SPDIF_IN:
+		spec->dig_in_nid = rec->assoc_nids[0];
+		break;
+	}
+	if (!cpcm->name)
+		cpcm->name = rec->name;
+	stream = pcm_type_directions[rec->type];
+	cpcm->stream[stream].substreams = rec->substreams;
+	cpcm->stream[stream].channels_min = rec->channels_min;
+	cpcm->stream[stream].channels_max = rec->channels_max;
+	cpcm->stream[stream].nid = rec->nid;
+	cpcm->stream[stream].rates = rec->rates;
+	cpcm->stream[stream].formats = rec->formats;
+	cpcm->stream[stream].maxbps = rec->maxbps;
+	cpcm->stream[stream].ops = pcm_usr_ops[rec->type];
+
+	err = snd_hda_attach_pcm(codec, cpcm, stream);
+	if (err < 0)
+		goto error;
+
+	/* create related controls */
+	switch (rec->type) {
+	case HDA_PCM_TYPE_SPDIF_OUT:
+		err = snd_hda_create_spdif_out_ctls(codec, rec->assoc_nids[0]);
+		if (err < 0)
+			goto error;
+		break;
+	case HDA_PCM_TYPE_SPDIF_IN:
+		err = snd_hda_create_spdif_in_ctls(codec, rec->assoc_nids[0]);
+		if (err < 0)
+			goto error;
+		break;
+	}
+
+	list_add_tail(&list->list, &spec->pcm_info_list);
+	return 0;
+
+ error:
+	kfree(list);
+	return err;
+}
+
+static void free_pcms(struct hda_codec *codec)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_usr_pcm_list *list;
+	int i;
+
+	while (!list_empty(&spec->pcm_info_list)) {
+		list = list_entry(spec->pcm_info_list.next,
+				  struct hda_usr_pcm_list, list);
+		list_del(&list->list);
+		kfree(list);
+	}
+	for (i = 0; i < HDA_MAX_PCMS; i++)
+		kfree(spec->pcm_rec[i]);
+}
+
+/*
+ * codec info ioctl
+ */
+
+static int get_codec_info_ioctl(struct hda_codec *codec,
+				struct hda_codec_info __user *argp)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	if (copy_to_user(argp, &spec->codec_info, sizeof(spec->codec_info)))
+		return -EFAULT;
+	return 0;
+}
+
+static int set_codec_info_ioctl(struct hda_codec *codec,
+				struct hda_codec_info __user *argp)
+{
+	struct hda_usr_spec *spec = codec->spec;
+
+	if (copy_from_user(&spec->codec_info, argp, sizeof(spec->codec_info)))
+		return -EFAULT;
+	/* update information */
+	if (*spec->codec_info.name) {
+		char *mixername = codec->bus->card->mixername;
+		codec->name = spec->codec_info.name;
+		if (codec->afg || !*mixername)
+			strlcpy(mixername, spec->codec_info.name,
+				sizeof(spec->codec_info.name));
+	}
+	return 0;
+}
+
+/*
+ * execute an out-of-bound command from TLV
+ */
+static int cmd_exec_ioctl(struct hda_codec *codec,
+			  const char __user *data)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_tlv *tlv;
+
+	tlv = alloc_tlv(data, 0);
+	if (!tlv)
+		return -ENOMEM;
+	mutex_lock(&spec->reg_mutex);
+	do_process_cmd(codec, tlv);
+	mutex_unlock(&spec->reg_mutex);
+	kfree(tlv);
+	return 0;
+}
+
+/*
+ * command sequences for unsol event, initializaton and resume
+ */
+
+struct hda_cmd_list {
+	struct list_head list;
+	struct hda_tlv tlv[0];
+};
+
+static struct hda_cmd_list *
+add_cmd_list(const char __user *data, struct list_head *list_head)
+{
+	struct hda_cmd_list *list;
+	list = alloc_tlv(data, sizeof(*list));
+	if (!list)
+		return NULL;
+	list_add_tail(&list->list, list_head);
+	return list;
+}
+
+static void free_cmd_lists(struct list_head *list)
+{
+	struct hda_cmd_list *cmd;
+	while (!list_empty(list)) {
+		cmd = list_entry(list->next, struct hda_cmd_list, list);
+		list_del(&cmd->list);
+		kfree(cmd);
+	}
+}
+
+/*
+ * add an initialization sequence
+ */
+static int add_init_ioctl(struct hda_codec *codec,
+			  const char __user *data)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_cmd_list *list;
+
+	mutex_lock(&spec->reg_mutex);
+	list = add_cmd_list(data, &spec->init_list);
+	if (!list) {
+		mutex_unlock(&spec->reg_mutex);
+		return -EINVAL;
+	}
+	/* execute */
+	do_process_cmd(codec, list->tlv);
+	mutex_unlock(&spec->reg_mutex);
+	return 0;
+}
+
+/*
+ * add an additional resume sequence
+ */
+static int add_resume_ioctl(struct hda_codec *codec,
+			    const char __user *data)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_cmd_list *list;
+
+	mutex_lock(&spec->reg_mutex);
+	list = add_cmd_list(data, &spec->resume_list);
+	if (!list) {
+		mutex_unlock(&spec->reg_mutex);
+		return -EINVAL;
+	}
+	mutex_unlock(&spec->reg_mutex);
+	return 0;
+}
+
+/*
+ * add an unsolidate event handler
+ */
+struct hda_unsol_list {
+	struct hda_unsol_ioctl val;
+	struct list_head list;
+	struct hda_tlv tlv[0];
+};
+
+static void free_unsol_lists(struct list_head *list)
+{
+	struct hda_unsol_list *unsol;
+	while (!list_empty(list)) {
+		unsol = list_entry(list->next, struct hda_unsol_list, list);
+		list_del(&unsol->list);
+		kfree(unsol);
+	}
+}
+
+static int add_unsol_ioctl(struct hda_codec *codec,
+			   const char __user *data)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_unsol_ioctl arg;
+	struct hda_unsol_list *unsol;
+
+	if (copy_from_user(&arg, data, sizeof(arg)))
+		return -EFAULT;
+	data += sizeof(arg);
+	mutex_lock(&spec->reg_mutex);
+	unsol = alloc_tlv(data, sizeof(*unsol));
+	if (!unsol) {
+		mutex_unlock(&spec->reg_mutex);
+		return -EINVAL;
+	}
+	list_add_tail(&unsol->list, &spec->unsol_list);
+	unsol->val = arg;
+	/* enable unsolicited event */
+	snd_hda_codec_write(codec, unsol->val.nid, 0, 
+			    AC_VERB_SET_UNSOLICITED_ENABLE,
+			    AC_USRSP_EN | unsol->val.tag);
+	mutex_unlock(&spec->reg_mutex);
+	return 0;
+}
+
+/*
+ * unsol_event callback for hda_codec patch
+ */
+static void hda_usr_unsol_event(struct hda_codec *codec, unsigned int res)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_unsol_list *unsol;
+
+	mutex_lock(&spec->reg_mutex);
+	if (spec->status != HDA_STATUS_RUNNING)
+		goto out;
+
+	list_for_each_entry(unsol, &spec->unsol_list, list) {
+		unsigned int tag = (res >> unsol->val.shift);
+		if (unsol->val.tag == tag) {
+			do_process_cmd(codec, unsol->tlv);
+			break;
+		}
+	}
+ out:
+	mutex_unlock(&spec->reg_mutex);
+}
+
+/*
+ * get/set current status
+ */
+
+static int get_status_ioctl(struct hda_codec *codec,
+			    int __user *argp)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	return put_user(spec->status, argp);
+}
+
+static int set_status_ioctl(struct hda_codec *codec,
+			    unsigned int __user *argp)
+{
+	struct hda_usr_spec *spec = codec->spec;
+
+	if (get_user(spec->status, argp))
+		return -EFAULT;
+	if (spec->status == HDA_STATUS_RUNNING)
+		snd_card_register(codec->bus->card);
+	return 0;
+}
+
+
+/*
+ * free callback for hda_codec patch
+ */
+static void hda_usr_free(struct hda_codec *codec)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	
+	if (!spec)
+		return;
+
+	free_cmd_lists(&spec->init_list);
+	free_cmd_lists(&spec->resume_list);
+	free_unsol_lists(&spec->unsol_list);
+
+	kfree(spec->kctl_alloc);
+	free_pcms(codec);
+
+	kfree(spec);
+}
+
+/*
+ * reset - release all resources
+ */
+static int hda_reset(struct hda_codec *codec)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct snd_card *card = codec->bus->card;
+	int i;
+	
+	mutex_lock(&spec->reg_mutex);
+	free_cmd_lists(&spec->init_list);
+	free_cmd_lists(&spec->resume_list);
+	free_unsol_lists(&spec->unsol_list);
+
+	/* release controls */
+	down_write(&card->controls_rwsem);
+	for (i = 0; i < spec->num_kctl_used; i++)
+		snd_ctl_remove(card, spec->kctl_alloc[i]);
+	up_write(&card->controls_rwsem);
+	kfree(spec->kctl_alloc);
+	spec->kctl_alloc = NULL;
+	spec->num_kctl_alloc = spec->num_kctl_used = 0;
+	/* SPDIF controls */
+	if (spec->multiout.dig_out_nid)
+		snd_hda_remove_spdif_out_ctls(codec);
+	if (spec->dig_in_nid)
+		snd_hda_remove_spdif_in_ctls(codec);
+
+	/* relase PCMs */
+	for (i = 0; i < HDA_MAX_PCMS; i++) {
+		if (spec->pcm_rec[i] && spec->pcm_rec[i]->pcm_dev)
+			snd_device_free(card, spec->pcm_rec[i]->pcm_dev);
+	}
+	free_pcms(codec);
+	memset(spec->pcm_rec, 0, sizeof(spec->pcm_rec));
+
+	/* misc */
+	memset(&spec->multiout, 0, sizeof(spec->multiout));
+	spec->dig_in_nid = 0;
+	       
+	spec->status = HDA_STATUS_UNINITIALIZED;
+	mutex_unlock(&spec->reg_mutex);
+
+	return 0;
+}
+
+#ifdef CONFIG_PM
+/*
+ * resume callback for hda_codec patch
+ */
+static int hda_usr_resume(struct hda_codec *codec)
+{
+	struct hda_usr_spec *spec = codec->spec;
+	struct hda_cmd_list *cmd;
+	struct hda_unsol_list *unsol;
+
+	mutex_lock(&spec->reg_mutex);
+	list_for_each_entry(cmd, &spec->init_list, list) {
+		do_process_cmd(codec, cmd->tlv);
+	}
+	list_for_each_entry(cmd, &spec->resume_list, list) {
+		do_process_cmd(codec, cmd->tlv);
+	}
+	list_for_each_entry(unsol, &spec->unsol_list, list) {
+		snd_hda_codec_write(codec, unsol->val.nid, 0, 
+				    AC_VERB_SET_UNSOLICITED_ENABLE,
+				    AC_USRSP_EN | unsol->val.tag);
+	}
+
+	if (spec->kctl_alloc) {
+		struct snd_ctl_elem_value *val;
+		int i;
+		val = kmalloc(sizeof(*val), GFP_KERNEL);
+		if (!val)
+			return -ENOMEM;
+		for (i = 0; i < spec->num_kctl_used; i++) {
+			struct snd_kcontrol *kctl = spec->kctl_alloc[i];
+			resume_ctl(codec, kctl, val);
+		}
+		kfree(val);
+	}
+	if (spec->multiout.dig_out_nid)
+		snd_hda_resume_spdif_out(codec);
+	if (spec->dig_in_nid)
+		snd_hda_resume_spdif_in(codec);
+	mutex_unlock(&spec->reg_mutex);
+	return 0;
+}
+#endif
+
+static struct hda_codec_ops hda_usr_patch_ops = {
+	.free = hda_usr_free,
+	.unsol_event = hda_usr_unsol_event,
+#ifdef CONFIG_PM
+	.resume = hda_usr_resume,
+#endif
+};
+
+/*
+ * create a codec instance with user-space configuration
+ */
+int __devinit snd_hda_create_usr_codec(struct hda_codec *codec)
+{
+	struct hda_usr_spec *spec;
+	struct pci_dev *pci;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (!spec)
+		return -ENOMEM;
+
+	spec->codec = codec;
+	INIT_LIST_HEAD(&spec->init_list);
+	INIT_LIST_HEAD(&spec->resume_list);
+	INIT_LIST_HEAD(&spec->unsol_list);
+	INIT_LIST_HEAD(&spec->pcm_info_list);
+	mutex_init(&spec->reg_mutex);
+
+	/* set up codec info */
+	spec->codec_info.vendor_id = codec->vendor_id;
+	spec->codec_info.subsystem_id = codec->subsystem_id;
+	spec->codec_info.revision_id = codec->revision_id;
+
+	pci = codec->bus->pci;
+	if (pci) {
+		spec->codec_info.pci_vendor = pci->vendor;
+		spec->codec_info.pci_device = pci->device;
+		spec->codec_info.pci_subvendor = pci->subsystem_vendor;
+		spec->codec_info.pci_subdevice = pci->subsystem_device;
+	}
+
+	spec->codec_info.codec_addr = codec->addr;
+	spec->codec_info.afg = codec->afg;
+	spec->codec_info.mfg = codec->mfg;
+	spec->codec_info.num_nodes = codec->num_nodes;
+	spec->codec_info.start_nid = codec->start_nid;
+
+	/*
+	 * add hooks
+	 */
+	codec->spec = spec;
+	codec->spec_type = HDA_SPEC_USER_CONFIG;
+	codec->patch_ops = hda_usr_patch_ops;
+
+	return 0;
+}
+
+
+int hda_usr_codec_ioctl(struct hda_codec *codec, unsigned int cmd,
+			void __user *argp)
+{
+	switch (cmd) {
+	case HDA_IOCTL_GET_STATUS:
+		return get_status_ioctl(codec, argp);
+	case HDA_IOCTL_SET_STATUS:
+		return set_status_ioctl(codec, argp);
+	case HDA_IOCTL_GET_CODEC_INFO:
+		return get_codec_info_ioctl(codec, argp);
+	case HDA_IOCTL_SET_CODEC_INFO:
+		return set_codec_info_ioctl(codec, argp);
+	case HDA_IOCTL_CMD_EXEC:
+		return cmd_exec_ioctl(codec, argp);
+	case HDA_IOCTL_ADD_INIT:
+		return add_init_ioctl(codec, argp);
+	case HDA_IOCTL_ADD_UNSOL:
+		return add_unsol_ioctl(codec, argp);
+	case HDA_IOCTL_ADD_RESUME:
+		return add_resume_ioctl(codec, argp);
+	case HDA_IOCTL_ADD_MIXER:
+		return hda_mixer_new(codec, argp);
+	case HDA_IOCTL_ADD_PCM:
+		return hda_pcm_new(codec, argp);
+	case HDA_IOCTL_RESET:
+		return hda_reset(codec);
+	}
+	return -ENOIOCTLCMD;
+}
