Merge branch 'fixes' into devel
diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt
index 8923597..3579e82 100644
--- a/Documentation/sound/alsa/ALSA-Configuration.txt
+++ b/Documentation/sound/alsa/ALSA-Configuration.txt
@@ -1923,7 +1923,7 @@
   -------------------
 
     Module for sound cards based on the Asus AV100/AV200 chips,
-    i.e., Xonar D1, DX, D2, D2X, HDAV1.3 (Deluxe), Essence ST
+    i.e., Xonar D1, DX, D2, D2X, DS, HDAV1.3 (Deluxe), Essence ST
     (Deluxe) and Essence STX.
 
     This module supports autoprobe and multiple cards.
diff --git a/include/sound/pcm.h b/include/sound/pcm.h
index c83a4a7..13bc83c 100644
--- a/include/sound/pcm.h
+++ b/include/sound/pcm.h
@@ -262,6 +262,8 @@
 	unsigned int mask;
 };
 
+struct snd_pcm_hwptr_log;
+
 struct snd_pcm_runtime {
 	/* -- Status -- */
 	struct snd_pcm_substream *trigger_master;
@@ -310,7 +312,9 @@
 	struct snd_pcm_mmap_control *control;
 
 	/* -- locking / scheduling -- */
-	wait_queue_head_t sleep;
+	unsigned int twake: 1;		/* do transfer (!poll) wakeup */
+	wait_queue_head_t sleep;	/* poll sleep */
+	wait_queue_head_t tsleep;	/* transfer sleep */
 	struct fasync_struct *fasync;
 
 	/* -- private section -- */
@@ -340,6 +344,10 @@
 	/* -- OSS things -- */
 	struct snd_pcm_oss_runtime oss;
 #endif
+
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+	struct snd_pcm_hwptr_log *hwptr_log;
+#endif
 };
 
 struct snd_pcm_group {		/* keep linked substreams */
@@ -834,6 +842,8 @@
 int snd_pcm_lib_interleave_len(struct snd_pcm_substream *substream);
 int snd_pcm_lib_ioctl(struct snd_pcm_substream *substream,
 		      unsigned int cmd, void *arg);                      
+int snd_pcm_update_state(struct snd_pcm_substream *substream,
+			 struct snd_pcm_runtime *runtime);
 int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream);
 int snd_pcm_playback_xrun_check(struct snd_pcm_substream *substream);
 int snd_pcm_capture_xrun_check(struct snd_pcm_substream *substream);
diff --git a/include/sound/pcm_oss.h b/include/sound/pcm_oss.h
index cc4e226..760c969 100644
--- a/include/sound/pcm_oss.h
+++ b/include/sound/pcm_oss.h
@@ -61,7 +61,7 @@
 	struct snd_pcm_plugin *plugin_first;
 	struct snd_pcm_plugin *plugin_last;
 #endif
-	unsigned int prev_hw_ptr_interrupt;
+	unsigned int prev_hw_ptr_period;
 };
 
 struct snd_pcm_oss_file {
diff --git a/include/sound/version.h b/include/sound/version.h
index 2293914..7fed234 100644
--- a/include/sound/version.h
+++ b/include/sound/version.h
@@ -1,3 +1,3 @@
 /* include/version.h */
-#define CONFIG_SND_VERSION "1.0.21"
+#define CONFIG_SND_VERSION "1.0.22.1"
 #define CONFIG_SND_DATE ""
diff --git a/sound/core/control.c b/sound/core/control.c
index 268ab747..439ce64 100644
--- a/sound/core/control.c
+++ b/sound/core/control.c
@@ -237,8 +237,9 @@
 	access = ncontrol->access == 0 ? SNDRV_CTL_ELEM_ACCESS_READWRITE :
 		 (ncontrol->access & (SNDRV_CTL_ELEM_ACCESS_READWRITE|
 				      SNDRV_CTL_ELEM_ACCESS_INACTIVE|
-		 		      SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|
-		 		      SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
+				      SNDRV_CTL_ELEM_ACCESS_TLV_READWRITE|
+				      SNDRV_CTL_ELEM_ACCESS_TLV_COMMAND|
+				      SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK));
 	kctl.info = ncontrol->info;
 	kctl.get = ncontrol->get;
 	kctl.put = ncontrol->put;
@@ -1099,7 +1100,7 @@
 
 	if (copy_from_user(&tlv, _tlv, sizeof(tlv)))
 		return -EFAULT;
-	if (tlv.length < sizeof(unsigned int) * 3)
+	if (tlv.length < sizeof(unsigned int) * 2)
 		return -EINVAL;
 	down_read(&card->controls_rwsem);
 	kctl = snd_ctl_find_numid(card, tlv.numid);
diff --git a/sound/core/oss/pcm_oss.c b/sound/core/oss/pcm_oss.c
index d9c9635..82d4e33 100644
--- a/sound/core/oss/pcm_oss.c
+++ b/sound/core/oss/pcm_oss.c
@@ -632,6 +632,12 @@
 	return bytes_to_frames(runtime, (buffer_size * bytes) / runtime->oss.buffer_bytes);
 }
 
+static inline
+snd_pcm_uframes_t get_hw_ptr_period(struct snd_pcm_runtime *runtime)
+{
+	return runtime->hw_ptr_interrupt;
+}
+
 /* define extended formats in the recent OSS versions (if any) */
 /* linear formats */
 #define AFMT_S32_LE      0x00001000
@@ -1102,7 +1108,7 @@
 		return err;
 	}
 	runtime->oss.prepare = 0;
-	runtime->oss.prev_hw_ptr_interrupt = 0;
+	runtime->oss.prev_hw_ptr_period = 0;
 	runtime->oss.period_ptr = 0;
 	runtime->oss.buffer_used = 0;
 
@@ -1950,7 +1956,8 @@
 	return result;
 }
 
-static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream, snd_pcm_uframes_t hw_ptr)
+static void snd_pcm_oss_simulate_fill(struct snd_pcm_substream *substream,
+				      snd_pcm_uframes_t hw_ptr)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	snd_pcm_uframes_t appl_ptr;
@@ -1986,7 +1993,8 @@
 			if (runtime->oss.trigger)
 				goto _skip1;
 			if (atomic_read(&psubstream->mmap_count))
-				snd_pcm_oss_simulate_fill(psubstream, runtime->hw_ptr_interrupt);
+				snd_pcm_oss_simulate_fill(psubstream,
+						get_hw_ptr_period(runtime));
 			runtime->oss.trigger = 1;
 			runtime->start_threshold = 1;
 			cmd = SNDRV_PCM_IOCTL_START;
@@ -2105,11 +2113,12 @@
 	info.ptr = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr % runtime->buffer_size);
 	if (atomic_read(&substream->mmap_count)) {
 		snd_pcm_sframes_t n;
-		n = (delay = runtime->hw_ptr_interrupt) - runtime->oss.prev_hw_ptr_interrupt;
+		delay = get_hw_ptr_period(runtime);
+		n = delay - runtime->oss.prev_hw_ptr_period;
 		if (n < 0)
 			n += runtime->boundary;
 		info.blocks = n / runtime->period_size;
-		runtime->oss.prev_hw_ptr_interrupt = delay;
+		runtime->oss.prev_hw_ptr_period = delay;
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
 			snd_pcm_oss_simulate_fill(substream, delay);
 		info.bytes = snd_pcm_oss_bytes(substream, runtime->status->hw_ptr) & INT_MAX;
@@ -2673,18 +2682,22 @@
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	if (atomic_read(&substream->mmap_count))
-		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+		return runtime->oss.prev_hw_ptr_period !=
+						get_hw_ptr_period(runtime);
 	else
-		return snd_pcm_playback_avail(runtime) >= runtime->oss.period_frames;
+		return snd_pcm_playback_avail(runtime) >=
+						runtime->oss.period_frames;
 }
 
 static int snd_pcm_oss_capture_ready(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	if (atomic_read(&substream->mmap_count))
-		return runtime->oss.prev_hw_ptr_interrupt != runtime->hw_ptr_interrupt;
+		return runtime->oss.prev_hw_ptr_period !=
+						get_hw_ptr_period(runtime);
 	else
-		return snd_pcm_capture_avail(runtime) >= runtime->oss.period_frames;
+		return snd_pcm_capture_avail(runtime) >=
+						runtime->oss.period_frames;
 }
 
 static unsigned int snd_pcm_oss_poll(struct file *file, poll_table * wait)
diff --git a/sound/core/pcm.c b/sound/core/pcm.c
index 6884ae0..0d428d0 100644
--- a/sound/core/pcm.c
+++ b/sound/core/pcm.c
@@ -894,6 +894,7 @@
 	memset((void*)runtime->control, 0, size);
 
 	init_waitqueue_head(&runtime->sleep);
+	init_waitqueue_head(&runtime->tsleep);
 
 	runtime->status->state = SNDRV_PCM_STATE_OPEN;
 
@@ -921,6 +922,10 @@
 	snd_free_pages((void*)runtime->control,
 		       PAGE_ALIGN(sizeof(struct snd_pcm_mmap_control)));
 	kfree(runtime->hw_constraints.rules);
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+	if (runtime->hwptr_log)
+		kfree(runtime->hwptr_log);
+#endif
 	kfree(runtime);
 	substream->runtime = NULL;
 	put_pid(substream->pid);
diff --git a/sound/core/pcm_lib.c b/sound/core/pcm_lib.c
index a27545b2..aa54195 100644
--- a/sound/core/pcm_lib.c
+++ b/sound/core/pcm_lib.c
@@ -126,17 +126,6 @@
 	}
 }
 
-#ifdef CONFIG_SND_PCM_XRUN_DEBUG
-#define xrun_debug(substream, mask)	((substream)->pstr->xrun_debug & (mask))
-#else
-#define xrun_debug(substream, mask)	0
-#endif
-
-#define dump_stack_on_xrun(substream) do {		\
-		if (xrun_debug(substream, 2))		\
-			dump_stack();			\
-	} while (0)
-
 static void pcm_debug_name(struct snd_pcm_substream *substream,
 			   char *name, size_t len)
 {
@@ -147,6 +136,24 @@
 		 substream->number);
 }
 
+#define XRUN_DEBUG_BASIC	(1<<0)
+#define XRUN_DEBUG_STACK	(1<<1)	/* dump also stack */
+#define XRUN_DEBUG_JIFFIESCHECK	(1<<2)	/* do jiffies check */
+#define XRUN_DEBUG_PERIODUPDATE	(1<<3)	/* full period update info */
+#define XRUN_DEBUG_HWPTRUPDATE	(1<<4)	/* full hwptr update info */
+#define XRUN_DEBUG_LOG		(1<<5)	/* show last 10 positions on err */
+#define XRUN_DEBUG_LOGONCE	(1<<6)	/* do above only once */
+
+#ifdef CONFIG_SND_PCM_XRUN_DEBUG
+
+#define xrun_debug(substream, mask) \
+			((substream)->pstr->xrun_debug & (mask))
+
+#define dump_stack_on_xrun(substream) do {			\
+		if (xrun_debug(substream, XRUN_DEBUG_STACK))	\
+			dump_stack();				\
+	} while (0)
+
 static void xrun(struct snd_pcm_substream *substream)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
@@ -154,7 +161,7 @@
 	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
 		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
 	snd_pcm_stop(substream, SNDRV_PCM_STATE_XRUN);
-	if (xrun_debug(substream, 1)) {
+	if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {
 		char name[16];
 		pcm_debug_name(substream, name, sizeof(name));
 		snd_printd(KERN_DEBUG "XRUN: %s\n", name);
@@ -162,32 +169,102 @@
 	}
 }
 
-static snd_pcm_uframes_t
-snd_pcm_update_hw_ptr_pos(struct snd_pcm_substream *substream,
-			  struct snd_pcm_runtime *runtime)
-{
-	snd_pcm_uframes_t pos;
+#define hw_ptr_error(substream, fmt, args...)				\
+	do {								\
+		if (xrun_debug(substream, XRUN_DEBUG_BASIC)) {		\
+			xrun_log_show(substream);			\
+			if (printk_ratelimit()) {			\
+				snd_printd("PCM: " fmt, ##args);	\
+			}						\
+			dump_stack_on_xrun(substream);			\
+		}							\
+	} while (0)
 
-	pos = substream->ops->pointer(substream);
-	if (pos == SNDRV_PCM_POS_XRUN)
-		return pos; /* XRUN */
-	if (pos >= runtime->buffer_size) {
-		if (printk_ratelimit()) {
-			char name[16];
-			pcm_debug_name(substream, name, sizeof(name));
-			snd_printd(KERN_ERR  "BUG: %s, pos = 0x%lx, "
-				   "buffer size = 0x%lx, period size = 0x%lx\n",
-				   name, pos, runtime->buffer_size,
-				   runtime->period_size);
-		}
-		pos = 0;
+#define XRUN_LOG_CNT	10
+
+struct hwptr_log_entry {
+	unsigned long jiffies;
+	snd_pcm_uframes_t pos;
+	snd_pcm_uframes_t period_size;
+	snd_pcm_uframes_t buffer_size;
+	snd_pcm_uframes_t old_hw_ptr;
+	snd_pcm_uframes_t hw_ptr_base;
+};
+
+struct snd_pcm_hwptr_log {
+	unsigned int idx;
+	unsigned int hit: 1;
+	struct hwptr_log_entry entries[XRUN_LOG_CNT];
+};
+
+static void xrun_log(struct snd_pcm_substream *substream,
+		     snd_pcm_uframes_t pos)
+{
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct snd_pcm_hwptr_log *log = runtime->hwptr_log;
+	struct hwptr_log_entry *entry;
+
+	if (log == NULL) {
+		log = kzalloc(sizeof(*log), GFP_ATOMIC);
+		if (log == NULL)
+			return;
+		runtime->hwptr_log = log;
+	} else {
+		if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
+			return;
 	}
-	pos -= pos % runtime->min_align;
-	return pos;
+	entry = &log->entries[log->idx];
+	entry->jiffies = jiffies;
+	entry->pos = pos;
+	entry->period_size = runtime->period_size;
+	entry->buffer_size = runtime->buffer_size;;
+	entry->old_hw_ptr = runtime->status->hw_ptr;
+	entry->hw_ptr_base = runtime->hw_ptr_base;
+	log->idx = (log->idx + 1) % XRUN_LOG_CNT;
 }
 
-static int snd_pcm_update_hw_ptr_post(struct snd_pcm_substream *substream,
-				      struct snd_pcm_runtime *runtime)
+static void xrun_log_show(struct snd_pcm_substream *substream)
+{
+	struct snd_pcm_hwptr_log *log = substream->runtime->hwptr_log;
+	struct hwptr_log_entry *entry;
+	char name[16];
+	unsigned int idx;
+	int cnt;
+
+	if (log == NULL)
+		return;
+	if (xrun_debug(substream, XRUN_DEBUG_LOGONCE) && log->hit)
+		return;
+	pcm_debug_name(substream, name, sizeof(name));
+	for (cnt = 0, idx = log->idx; cnt < XRUN_LOG_CNT; cnt++) {
+		entry = &log->entries[idx];
+		if (entry->period_size == 0)
+			break;
+		snd_printd("hwptr log: %s: j=%lu, pos=%ld/%ld/%ld, "
+			   "hwptr=%ld/%ld\n",
+			   name, entry->jiffies, (unsigned long)entry->pos,
+			   (unsigned long)entry->period_size,
+			   (unsigned long)entry->buffer_size,
+			   (unsigned long)entry->old_hw_ptr,
+			   (unsigned long)entry->hw_ptr_base);
+		idx++;
+		idx %= XRUN_LOG_CNT;
+	}
+	log->hit = 1;
+}
+
+#else /* ! CONFIG_SND_PCM_XRUN_DEBUG */
+
+#define xrun_debug(substream, mask)	0
+#define xrun(substream)			do { } while (0)
+#define hw_ptr_error(substream, fmt, args...) do { } while (0)
+#define xrun_log(substream, pos)	do { } while (0)
+#define xrun_log_show(substream)	do { } while (0)
+
+#endif
+
+int snd_pcm_update_state(struct snd_pcm_substream *substream,
+			 struct snd_pcm_runtime *runtime)
 {
 	snd_pcm_uframes_t avail;
 
@@ -209,88 +286,94 @@
 		}
 	}
 	if (avail >= runtime->control->avail_min)
-		wake_up(&runtime->sleep);
+		wake_up(runtime->twake ? &runtime->tsleep : &runtime->sleep);
 	return 0;
 }
 
-#define hw_ptr_error(substream, fmt, args...)				\
-	do {								\
-		if (xrun_debug(substream, 1)) {				\
-			if (printk_ratelimit()) {			\
-				snd_printd("PCM: " fmt, ##args);	\
-			}						\
-			dump_stack_on_xrun(substream);			\
-		}							\
-	} while (0)
-
-static int snd_pcm_update_hw_ptr_interrupt(struct snd_pcm_substream *substream)
+static int snd_pcm_update_hw_ptr0(struct snd_pcm_substream *substream,
+				  unsigned int in_interrupt)
 {
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	snd_pcm_uframes_t pos;
-	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_ptr_interrupt, hw_base;
+	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
 	snd_pcm_sframes_t hdelta, delta;
 	unsigned long jdelta;
 
 	old_hw_ptr = runtime->status->hw_ptr;
-	pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
+	pos = substream->ops->pointer(substream);
 	if (pos == SNDRV_PCM_POS_XRUN) {
 		xrun(substream);
 		return -EPIPE;
 	}
-	if (xrun_debug(substream, 8)) {
-		char name[16];
-		pcm_debug_name(substream, name, sizeof(name));
-		snd_printd("period_update: %s: pos=0x%x/0x%x/0x%x, "
-			   "hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n",
-			   name, (unsigned int)pos,
-			   (unsigned int)runtime->period_size,
-			   (unsigned int)runtime->buffer_size,
-			   (unsigned long)old_hw_ptr,
-			   (unsigned long)runtime->hw_ptr_base,
-			   (unsigned long)runtime->hw_ptr_interrupt);
+	if (pos >= runtime->buffer_size) {
+		if (printk_ratelimit()) {
+			char name[16];
+			pcm_debug_name(substream, name, sizeof(name));
+			xrun_log_show(substream);
+			snd_printd(KERN_ERR  "BUG: %s, pos = %ld, "
+				   "buffer size = %ld, period size = %ld\n",
+				   name, pos, runtime->buffer_size,
+				   runtime->period_size);
+		}
+		pos = 0;
 	}
+	pos -= pos % runtime->min_align;
+	if (xrun_debug(substream, XRUN_DEBUG_LOG))
+		xrun_log(substream, pos);
 	hw_base = runtime->hw_ptr_base;
 	new_hw_ptr = hw_base + pos;
-	hw_ptr_interrupt = runtime->hw_ptr_interrupt + runtime->period_size;
-	delta = new_hw_ptr - hw_ptr_interrupt;
-	if (hw_ptr_interrupt >= runtime->boundary) {
-		hw_ptr_interrupt -= runtime->boundary;
-		if (hw_base < runtime->boundary / 2)
-			/* hw_base was already lapped; recalc delta */
-			delta = new_hw_ptr - hw_ptr_interrupt;
-	}
-	if (delta < 0) {
-		if (runtime->periods == 1 || new_hw_ptr < old_hw_ptr)
-			delta += runtime->buffer_size;
-		if (delta < 0) {
-			hw_ptr_error(substream, 
-				     "Unexpected hw_pointer value "
-				     "(stream=%i, pos=%ld, intr_ptr=%ld)\n",
-				     substream->stream, (long)pos,
-				     (long)hw_ptr_interrupt);
-#if 1
-			/* simply skipping the hwptr update seems more
-			 * robust in some cases, e.g. on VMware with
-			 * inaccurate timer source
-			 */
-			return 0; /* skip this update */
-#else
-			/* rebase to interrupt position */
-			hw_base = new_hw_ptr = hw_ptr_interrupt;
-			/* align hw_base to buffer_size */
-			hw_base -= hw_base % runtime->buffer_size;
-			delta = 0;
-#endif
-		} else {
+	if (in_interrupt) {
+		/* we know that one period was processed */
+		/* delta = "expected next hw_ptr" for in_interrupt != 0 */
+		delta = runtime->hw_ptr_interrupt + runtime->period_size;
+		if (delta > new_hw_ptr) {
 			hw_base += runtime->buffer_size;
 			if (hw_base >= runtime->boundary)
 				hw_base = 0;
 			new_hw_ptr = hw_base + pos;
+			goto __delta;
 		}
 	}
+	/* new_hw_ptr might be lower than old_hw_ptr in case when */
+	/* pointer crosses the end of the ring buffer */
+	if (new_hw_ptr < old_hw_ptr) {
+		hw_base += runtime->buffer_size;
+		if (hw_base >= runtime->boundary)
+			hw_base = 0;
+		new_hw_ptr = hw_base + pos;
+	}
+      __delta:
+	delta = (new_hw_ptr - old_hw_ptr) % runtime->boundary;
+	if (xrun_debug(substream, in_interrupt ?
+			XRUN_DEBUG_PERIODUPDATE : XRUN_DEBUG_HWPTRUPDATE)) {
+		char name[16];
+		pcm_debug_name(substream, name, sizeof(name));
+		snd_printd("%s_update: %s: pos=%u/%u/%u, "
+			   "hwptr=%ld/%ld/%ld/%ld\n",
+			   in_interrupt ? "period" : "hwptr",
+			   name,
+			   (unsigned int)pos,
+			   (unsigned int)runtime->period_size,
+			   (unsigned int)runtime->buffer_size,
+			   (unsigned long)delta,
+			   (unsigned long)old_hw_ptr,
+			   (unsigned long)new_hw_ptr,
+			   (unsigned long)runtime->hw_ptr_base);
+	}
+	/* something must be really wrong */
+	if (delta >= runtime->buffer_size + runtime->period_size) {
+		hw_ptr_error(substream,
+			       "Unexpected hw_pointer value %s"
+			       "(stream=%i, pos=%ld, new_hw_ptr=%ld, "
+			       "old_hw_ptr=%ld)\n",
+				     in_interrupt ? "[Q] " : "[P]",
+				     substream->stream, (long)pos,
+				     (long)new_hw_ptr, (long)old_hw_ptr);
+		return 0;
+	}
 
 	/* Do jiffies check only in xrun_debug mode */
-	if (!xrun_debug(substream, 4))
+	if (!xrun_debug(substream, XRUN_DEBUG_JIFFIESCHECK))
 		goto no_jiffies_check;
 
 	/* Skip the jiffies check for hardwares with BATCH flag.
@@ -299,7 +382,7 @@
 	 */
 	if (runtime->hw.info & SNDRV_PCM_INFO_BATCH)
 		goto no_jiffies_check;
-	hdelta = new_hw_ptr - old_hw_ptr;
+	hdelta = delta;
 	if (hdelta < runtime->delay)
 		goto no_jiffies_check;
 	hdelta -= runtime->delay;
@@ -308,130 +391,68 @@
 		delta = jdelta /
 			(((runtime->period_size * HZ) / runtime->rate)
 								+ HZ/100);
+		/* move new_hw_ptr according jiffies not pos variable */
+		new_hw_ptr = old_hw_ptr;
+		hw_base = delta;
+		/* use loop to avoid checks for delta overflows */
+		/* the delta value is small or zero in most cases */
+		while (delta > 0) {
+			new_hw_ptr += runtime->period_size;
+			if (new_hw_ptr >= runtime->boundary)
+				new_hw_ptr -= runtime->boundary;
+			delta--;
+		}
+		/* align hw_base to buffer_size */
 		hw_ptr_error(substream,
-			     "hw_ptr skipping! [Q] "
+			     "hw_ptr skipping! %s"
 			     "(pos=%ld, delta=%ld, period=%ld, "
-			     "jdelta=%lu/%lu/%lu)\n",
+			     "jdelta=%lu/%lu/%lu, hw_ptr=%ld/%ld)\n",
+			     in_interrupt ? "[Q] " : "",
 			     (long)pos, (long)hdelta,
 			     (long)runtime->period_size, jdelta,
-			     ((hdelta * HZ) / runtime->rate), delta);
-		hw_ptr_interrupt = runtime->hw_ptr_interrupt +
-				   runtime->period_size * delta;
-		if (hw_ptr_interrupt >= runtime->boundary)
-			hw_ptr_interrupt -= runtime->boundary;
-		/* rebase to interrupt position */
-		hw_base = new_hw_ptr = hw_ptr_interrupt;
-		/* align hw_base to buffer_size */
-		hw_base -= hw_base % runtime->buffer_size;
+			     ((hdelta * HZ) / runtime->rate), hw_base,
+			     (unsigned long)old_hw_ptr,
+			     (unsigned long)new_hw_ptr);
+		/* reset values to proper state */
 		delta = 0;
+		hw_base = new_hw_ptr - (new_hw_ptr % runtime->buffer_size);
 	}
  no_jiffies_check:
 	if (delta > runtime->period_size + runtime->period_size / 2) {
 		hw_ptr_error(substream,
-			     "Lost interrupts? "
-			     "(stream=%i, delta=%ld, intr_ptr=%ld)\n",
+			     "Lost interrupts? %s"
+			     "(stream=%i, delta=%ld, new_hw_ptr=%ld, "
+			     "old_hw_ptr=%ld)\n",
+			     in_interrupt ? "[Q] " : "",
 			     substream->stream, (long)delta,
-			     (long)hw_ptr_interrupt);
-		/* rebase hw_ptr_interrupt */
-		hw_ptr_interrupt =
-			new_hw_ptr - new_hw_ptr % runtime->period_size;
+			     (long)new_hw_ptr,
+			     (long)old_hw_ptr);
 	}
-	runtime->hw_ptr_interrupt = hw_ptr_interrupt;
+
+	if (runtime->status->hw_ptr == new_hw_ptr)
+		return 0;
 
 	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
 	    runtime->silence_size > 0)
 		snd_pcm_playback_silence(substream, new_hw_ptr);
 
-	if (runtime->status->hw_ptr == new_hw_ptr)
-		return 0;
-
+	if (in_interrupt) {
+		runtime->hw_ptr_interrupt = new_hw_ptr -
+				(new_hw_ptr % runtime->period_size);
+	}
 	runtime->hw_ptr_base = hw_base;
 	runtime->status->hw_ptr = new_hw_ptr;
 	runtime->hw_ptr_jiffies = jiffies;
 	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
 		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
 
-	return snd_pcm_update_hw_ptr_post(substream, runtime);
+	return snd_pcm_update_state(substream, runtime);
 }
 
 /* CAUTION: call it with irq disabled */
 int snd_pcm_update_hw_ptr(struct snd_pcm_substream *substream)
 {
-	struct snd_pcm_runtime *runtime = substream->runtime;
-	snd_pcm_uframes_t pos;
-	snd_pcm_uframes_t old_hw_ptr, new_hw_ptr, hw_base;
-	snd_pcm_sframes_t delta;
-	unsigned long jdelta;
-
-	old_hw_ptr = runtime->status->hw_ptr;
-	pos = snd_pcm_update_hw_ptr_pos(substream, runtime);
-	if (pos == SNDRV_PCM_POS_XRUN) {
-		xrun(substream);
-		return -EPIPE;
-	}
-	if (xrun_debug(substream, 16)) {
-		char name[16];
-		pcm_debug_name(substream, name, sizeof(name));
-		snd_printd("hw_update: %s: pos=0x%x/0x%x/0x%x, "
-			   "hwptr=0x%lx, hw_base=0x%lx, hw_intr=0x%lx\n",
-			   name, (unsigned int)pos,
-			   (unsigned int)runtime->period_size,
-			   (unsigned int)runtime->buffer_size,
-			   (unsigned long)old_hw_ptr,
-			   (unsigned long)runtime->hw_ptr_base,
-			   (unsigned long)runtime->hw_ptr_interrupt);
-	}
-
-	hw_base = runtime->hw_ptr_base;
-	new_hw_ptr = hw_base + pos;
-
-	delta = new_hw_ptr - old_hw_ptr;
-	jdelta = jiffies - runtime->hw_ptr_jiffies;
-	if (delta < 0) {
-		delta += runtime->buffer_size;
-		if (delta < 0) {
-			hw_ptr_error(substream, 
-				     "Unexpected hw_pointer value [2] "
-				     "(stream=%i, pos=%ld, old_ptr=%ld, jdelta=%li)\n",
-				     substream->stream, (long)pos,
-				     (long)old_hw_ptr, jdelta);
-			return 0;
-		}
-		hw_base += runtime->buffer_size;
-		if (hw_base >= runtime->boundary)
-			hw_base = 0;
-		new_hw_ptr = hw_base + pos;
-	}
-	/* Do jiffies check only in xrun_debug mode */
-	if (!xrun_debug(substream, 4))
-		goto no_jiffies_check;
-	if (delta < runtime->delay)
-		goto no_jiffies_check;
-	delta -= runtime->delay;
-	if (((delta * HZ) / runtime->rate) > jdelta + HZ/100) {
-		hw_ptr_error(substream,
-			     "hw_ptr skipping! "
-			     "(pos=%ld, delta=%ld, period=%ld, jdelta=%lu/%lu)\n",
-			     (long)pos, (long)delta,
-			     (long)runtime->period_size, jdelta,
-			     ((delta * HZ) / runtime->rate));
-		return 0;
-	}
- no_jiffies_check:
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
-	    runtime->silence_size > 0)
-		snd_pcm_playback_silence(substream, new_hw_ptr);
-
-	if (runtime->status->hw_ptr == new_hw_ptr)
-		return 0;
-
-	runtime->hw_ptr_base = hw_base;
-	runtime->status->hw_ptr = new_hw_ptr;
-	runtime->hw_ptr_jiffies = jiffies;
-	if (runtime->tstamp_mode == SNDRV_PCM_TSTAMP_ENABLE)
-		snd_pcm_gettime(runtime, (struct timespec *)&runtime->status->tstamp);
-
-	return snd_pcm_update_hw_ptr_post(substream, runtime);
+	return snd_pcm_update_hw_ptr0(substream, 0);
 }
 
 /**
@@ -1643,7 +1664,7 @@
 
 	snd_pcm_stream_lock_irqsave(substream, flags);
 	if (!snd_pcm_running(substream) ||
-	    snd_pcm_update_hw_ptr_interrupt(substream) < 0)
+	    snd_pcm_update_hw_ptr0(substream, 1) < 0)
 		goto _end;
 
 	if (substream->timer_running)
@@ -1674,7 +1695,7 @@
 	long tout;
 
 	init_waitqueue_entry(&wait, current);
-	add_wait_queue(&runtime->sleep, &wait);
+	add_wait_queue(&runtime->tsleep, &wait);
 	for (;;) {
 		if (signal_pending(current)) {
 			err = -ERESTARTSYS;
@@ -1717,7 +1738,7 @@
 			break;
 	}
  _endloop:
-	remove_wait_queue(&runtime->sleep, &wait);
+	remove_wait_queue(&runtime->tsleep, &wait);
 	*availp = avail;
 	return err;
 }
@@ -1776,6 +1797,7 @@
 		goto _end_unlock;
 	}
 
+	runtime->twake = 1;
 	while (size > 0) {
 		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
 		snd_pcm_uframes_t avail;
@@ -1797,15 +1819,17 @@
 		if (frames > cont)
 			frames = cont;
 		if (snd_BUG_ON(!frames)) {
+			runtime->twake = 0;
 			snd_pcm_stream_unlock_irq(substream);
 			return -EINVAL;
 		}
 		appl_ptr = runtime->control->appl_ptr;
 		appl_ofs = appl_ptr % runtime->buffer_size;
 		snd_pcm_stream_unlock_irq(substream);
-		if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
-			goto _end;
+		err = transfer(substream, appl_ofs, data, offset, frames);
 		snd_pcm_stream_lock_irq(substream);
+		if (err < 0)
+			goto _end_unlock;
 		switch (runtime->status->state) {
 		case SNDRV_PCM_STATE_XRUN:
 			err = -EPIPE;
@@ -1834,8 +1858,10 @@
 		}
 	}
  _end_unlock:
+	runtime->twake = 0;
+	if (xfer > 0 && err >= 0)
+		snd_pcm_update_state(substream, runtime);
 	snd_pcm_stream_unlock_irq(substream);
- _end:
 	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
 }
 
@@ -1993,6 +2019,7 @@
 		goto _end_unlock;
 	}
 
+	runtime->twake = 1;
 	while (size > 0) {
 		snd_pcm_uframes_t frames, appl_ptr, appl_ofs;
 		snd_pcm_uframes_t avail;
@@ -2021,15 +2048,17 @@
 		if (frames > cont)
 			frames = cont;
 		if (snd_BUG_ON(!frames)) {
+			runtime->twake = 0;
 			snd_pcm_stream_unlock_irq(substream);
 			return -EINVAL;
 		}
 		appl_ptr = runtime->control->appl_ptr;
 		appl_ofs = appl_ptr % runtime->buffer_size;
 		snd_pcm_stream_unlock_irq(substream);
-		if ((err = transfer(substream, appl_ofs, data, offset, frames)) < 0)
-			goto _end;
+		err = transfer(substream, appl_ofs, data, offset, frames);
 		snd_pcm_stream_lock_irq(substream);
+		if (err < 0)
+			goto _end_unlock;
 		switch (runtime->status->state) {
 		case SNDRV_PCM_STATE_XRUN:
 			err = -EPIPE;
@@ -2052,8 +2081,10 @@
 		xfer += frames;
 	}
  _end_unlock:
+	runtime->twake = 0;
+	if (xfer > 0 && err >= 0)
+		snd_pcm_update_state(substream, runtime);
 	snd_pcm_stream_unlock_irq(substream);
- _end:
 	return xfer > 0 ? (snd_pcm_sframes_t)xfer : err;
 }
 
diff --git a/sound/core/pcm_native.c b/sound/core/pcm_native.c
index 25b0641..053c6d6 100644
--- a/sound/core/pcm_native.c
+++ b/sound/core/pcm_native.c
@@ -27,6 +27,7 @@
 #include <linux/pm_qos_params.h>
 #include <linux/uio.h>
 #include <linux/dma-mapping.h>
+#include <linux/math64.h>
 #include <sound/core.h>
 #include <sound/control.h>
 #include <sound/info.h>
@@ -366,6 +367,38 @@
 	return usecs;
 }
 
+static int calc_boundary(struct snd_pcm_runtime *runtime)
+{
+	u_int64_t boundary;
+
+	boundary = (u_int64_t)runtime->buffer_size *
+		   (u_int64_t)runtime->period_size;
+#if BITS_PER_LONG < 64
+	/* try to find lowest common multiple for buffer and period */
+	if (boundary > LONG_MAX - runtime->buffer_size) {
+		u_int32_t remainder = -1;
+		u_int32_t divident = runtime->buffer_size;
+		u_int32_t divisor = runtime->period_size;
+		while (remainder) {
+			remainder = divident % divisor;
+			if (remainder) {
+				divident = divisor;
+				divisor = remainder;
+			}
+		}
+		boundary = div_u64(boundary, divisor);
+		if (boundary > LONG_MAX - runtime->buffer_size)
+			return -ERANGE;
+	}
+#endif
+	if (boundary == 0)
+		return -ERANGE;
+	runtime->boundary = boundary;
+	while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
+		runtime->boundary *= 2;
+	return 0;
+}
+
 static int snd_pcm_hw_params(struct snd_pcm_substream *substream,
 			     struct snd_pcm_hw_params *params)
 {
@@ -441,9 +474,9 @@
 	runtime->stop_threshold = runtime->buffer_size;
 	runtime->silence_threshold = 0;
 	runtime->silence_size = 0;
-	runtime->boundary = runtime->buffer_size;
-	while (runtime->boundary * 2 <= LONG_MAX - runtime->buffer_size)
-		runtime->boundary *= 2;
+	err = calc_boundary(runtime);
+	if (err < 0)
+		goto _error;
 
 	snd_pcm_timer_resolution_change(substream);
 	runtime->status->state = SNDRV_PCM_STATE_SETUP;
@@ -516,6 +549,7 @@
 			     struct snd_pcm_sw_params *params)
 {
 	struct snd_pcm_runtime *runtime;
+	int err;
 
 	if (PCM_RUNTIME_CHECK(substream))
 		return -ENXIO;
@@ -540,6 +574,7 @@
 		if (params->silence_threshold > runtime->buffer_size)
 			return -EINVAL;
 	}
+	err = 0;
 	snd_pcm_stream_lock_irq(substream);
 	runtime->tstamp_mode = params->tstamp_mode;
 	runtime->period_step = params->period_step;
@@ -553,10 +588,10 @@
 		if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK &&
 		    runtime->silence_size > 0)
 			snd_pcm_playback_silence(substream, ULONG_MAX);
-		wake_up(&runtime->sleep);
+		err = snd_pcm_update_state(substream, runtime);
 	}
 	snd_pcm_stream_unlock_irq(substream);
-	return 0;
+	return err;
 }
 
 static int snd_pcm_sw_params_user(struct snd_pcm_substream *substream,
@@ -917,6 +952,7 @@
 		runtime->status->state = state;
 	}
 	wake_up(&runtime->sleep);
+	wake_up(&runtime->tsleep);
 }
 
 static struct action_ops snd_pcm_action_stop = {
@@ -1002,6 +1038,7 @@
 					 SNDRV_TIMER_EVENT_MPAUSE,
 					 &runtime->trigger_tstamp);
 		wake_up(&runtime->sleep);
+		wake_up(&runtime->tsleep);
 	} else {
 		runtime->status->state = SNDRV_PCM_STATE_RUNNING;
 		if (substream->timer)
@@ -1059,6 +1096,7 @@
 	runtime->status->suspended_state = runtime->status->state;
 	runtime->status->state = SNDRV_PCM_STATE_SUSPENDED;
 	wake_up(&runtime->sleep);
+	wake_up(&runtime->tsleep);
 }
 
 static struct action_ops snd_pcm_action_suspend = {
diff --git a/sound/core/seq/seq_timer.c b/sound/core/seq/seq_timer.c
index f745c31..160b1bd 100644
--- a/sound/core/seq/seq_timer.c
+++ b/sound/core/seq/seq_timer.c
@@ -33,22 +33,21 @@
 
 #define SKEW_BASE	0x10000	/* 16bit shift */
 
-static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer_tick *tick,
-					      int tempo, int ppq)
+static void snd_seq_timer_set_tick_resolution(struct snd_seq_timer *tmr)
 {
-	if (tempo < 1000000)
-		tick->resolution = (tempo * 1000) / ppq;
+	if (tmr->tempo < 1000000)
+		tmr->tick.resolution = (tmr->tempo * 1000) / tmr->ppq;
 	else {
 		/* might overflow.. */
 		unsigned int s;
-		s = tempo % ppq;
-		s = (s * 1000) / ppq;
-		tick->resolution = (tempo / ppq) * 1000;
-		tick->resolution += s;
+		s = tmr->tempo % tmr->ppq;
+		s = (s * 1000) / tmr->ppq;
+		tmr->tick.resolution = (tmr->tempo / tmr->ppq) * 1000;
+		tmr->tick.resolution += s;
 	}
-	if (tick->resolution <= 0)
-		tick->resolution = 1;
-	snd_seq_timer_update_tick(tick, 0);
+	if (tmr->tick.resolution <= 0)
+		tmr->tick.resolution = 1;
+	snd_seq_timer_update_tick(&tmr->tick, 0);
 }
 
 /* create new timer (constructor) */
@@ -96,7 +95,7 @@
 	/* setup defaults */
 	tmr->ppq = 96;		/* 96 PPQ */
 	tmr->tempo = 500000;	/* 120 BPM */
-	snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq);
+	snd_seq_timer_set_tick_resolution(tmr);
 	tmr->running = 0;
 
 	tmr->type = SNDRV_SEQ_TIMER_ALSA;
@@ -180,7 +179,7 @@
 	spin_lock_irqsave(&tmr->lock, flags);
 	if ((unsigned int)tempo != tmr->tempo) {
 		tmr->tempo = tempo;
-		snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq);
+		snd_seq_timer_set_tick_resolution(tmr);
 	}
 	spin_unlock_irqrestore(&tmr->lock, flags);
 	return 0;
@@ -205,7 +204,7 @@
 	}
 
 	tmr->ppq = ppq;
-	snd_seq_timer_set_tick_resolution(&tmr->tick, tmr->tempo, tmr->ppq);
+	snd_seq_timer_set_tick_resolution(tmr);
 	spin_unlock_irqrestore(&tmr->lock, flags);
 	return 0;
 }
diff --git a/sound/drivers/dummy.c b/sound/drivers/dummy.c
index 252e04c..7f41990 100644
--- a/sound/drivers/dummy.c
+++ b/sound/drivers/dummy.c
@@ -45,109 +45,23 @@
 #define MAX_PCM_SUBSTREAMS	128
 #define MAX_MIDI_DEVICES	2
 
-#if 0 /* emu10k1 emulation */
-#define MAX_BUFFER_SIZE		(128 * 1024)
-static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime)
-{
-	int err;
-	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
-	if (err < 0)
-		return err;
-	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX);
-	if (err < 0)
-		return err;
-	return 0;
-}
-#define add_playback_constraints emu10k1_playback_constraints
-#endif
-
-#if 0 /* RME9652 emulation */
-#define MAX_BUFFER_SIZE		(26 * 64 * 1024)
-#define USE_FORMATS		SNDRV_PCM_FMTBIT_S32_LE
-#define USE_CHANNELS_MIN	26
-#define USE_CHANNELS_MAX	26
-#define USE_PERIODS_MIN		2
-#define USE_PERIODS_MAX		2
-#endif
-
-#if 0 /* ICE1712 emulation */
-#define MAX_BUFFER_SIZE		(256 * 1024)
-#define USE_FORMATS		SNDRV_PCM_FMTBIT_S32_LE
-#define USE_CHANNELS_MIN	10
-#define USE_CHANNELS_MAX	10
-#define USE_PERIODS_MIN		1
-#define USE_PERIODS_MAX		1024
-#endif
-
-#if 0 /* UDA1341 emulation */
-#define MAX_BUFFER_SIZE		(16380)
-#define USE_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
-#define USE_CHANNELS_MIN	2
-#define USE_CHANNELS_MAX	2
-#define USE_PERIODS_MIN		2
-#define USE_PERIODS_MAX		255
-#endif
-
-#if 0 /* simple AC97 bridge (intel8x0) with 48kHz AC97 only codec */
-#define USE_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
-#define USE_CHANNELS_MIN	2
-#define USE_CHANNELS_MAX	2
-#define USE_RATE		SNDRV_PCM_RATE_48000
-#define USE_RATE_MIN		48000
-#define USE_RATE_MAX		48000
-#endif
-
-#if 0 /* CA0106 */
-#define USE_FORMATS		SNDRV_PCM_FMTBIT_S16_LE
-#define USE_CHANNELS_MIN	2
-#define USE_CHANNELS_MAX	2
-#define USE_RATE		(SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000) 
-#define USE_RATE_MIN		48000 
-#define USE_RATE_MAX		192000
-#define MAX_BUFFER_SIZE		((65536-64)*8)
-#define MAX_PERIOD_SIZE		(65536-64)
-#define USE_PERIODS_MIN		2
-#define USE_PERIODS_MAX		8
-#endif
-
-
 /* defaults */
-#ifndef MAX_BUFFER_SIZE
 #define MAX_BUFFER_SIZE		(64*1024)
-#endif
-#ifndef MAX_PERIOD_SIZE
+#define MIN_PERIOD_SIZE		64
 #define MAX_PERIOD_SIZE		MAX_BUFFER_SIZE
-#endif
-#ifndef USE_FORMATS
 #define USE_FORMATS 		(SNDRV_PCM_FMTBIT_U8 | SNDRV_PCM_FMTBIT_S16_LE)
-#endif
-#ifndef USE_RATE
 #define USE_RATE		SNDRV_PCM_RATE_CONTINUOUS | SNDRV_PCM_RATE_8000_48000
 #define USE_RATE_MIN		5500
 #define USE_RATE_MAX		48000
-#endif
-#ifndef USE_CHANNELS_MIN
 #define USE_CHANNELS_MIN 	1
-#endif
-#ifndef USE_CHANNELS_MAX
 #define USE_CHANNELS_MAX 	2
-#endif
-#ifndef USE_PERIODS_MIN
 #define USE_PERIODS_MIN 	1
-#endif
-#ifndef USE_PERIODS_MAX
 #define USE_PERIODS_MAX 	1024
-#endif
-#ifndef add_playback_constraints
-#define add_playback_constraints(x) 0
-#endif
-#ifndef add_capture_constraints
-#define add_capture_constraints(x) 0
-#endif
 
 static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX;	/* Index 0-MAX */
 static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR;	/* ID for this card */
 static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 0};
+static char *model[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = NULL};
 static int pcm_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 1};
 static int pcm_substreams[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 8};
 //static int midi_devs[SNDRV_CARDS] = {[0 ... (SNDRV_CARDS - 1)] = 2};
@@ -162,6 +76,8 @@
 MODULE_PARM_DESC(id, "ID string for dummy soundcard.");
 module_param_array(enable, bool, NULL, 0444);
 MODULE_PARM_DESC(enable, "Enable this dummy soundcard.");
+module_param_array(model, charp, NULL, 0444);
+MODULE_PARM_DESC(model, "Soundcard model.");
 module_param_array(pcm_devs, int, NULL, 0444);
 MODULE_PARM_DESC(pcm_devs, "PCM devices # (0-4) for dummy driver.");
 module_param_array(pcm_substreams, int, NULL, 0444);
@@ -193,9 +109,28 @@
 	snd_pcm_uframes_t (*pointer)(struct snd_pcm_substream *);
 };
 
+struct dummy_model {
+	const char *name;
+	int (*playback_constraints)(struct snd_pcm_runtime *runtime);
+	int (*capture_constraints)(struct snd_pcm_runtime *runtime);
+	u64 formats;
+	size_t buffer_bytes_max;
+	size_t period_bytes_min;
+	size_t period_bytes_max;
+	unsigned int periods_min;
+	unsigned int periods_max;
+	unsigned int rates;
+	unsigned int rate_min;
+	unsigned int rate_max;
+	unsigned int channels_min;
+	unsigned int channels_max;
+};
+
 struct snd_dummy {
 	struct snd_card *card;
+	struct dummy_model *model;
 	struct snd_pcm *pcm;
+	struct snd_pcm_hardware pcm_hw;
 	spinlock_t mixer_lock;
 	int mixer_volume[MIXER_ADDR_LAST+1][2];
 	int capture_source[MIXER_ADDR_LAST+1][2];
@@ -203,6 +138,92 @@
 };
 
 /*
+ * card models
+ */
+
+static int emu10k1_playback_constraints(struct snd_pcm_runtime *runtime)
+{
+	int err;
+	err = snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (err < 0)
+		return err;
+	err = snd_pcm_hw_constraint_minmax(runtime, SNDRV_PCM_HW_PARAM_BUFFER_BYTES, 256, UINT_MAX);
+	if (err < 0)
+		return err;
+	return 0;
+}
+
+struct dummy_model model_emu10k1 = {
+	.name = "emu10k1",
+	.playback_constraints = emu10k1_playback_constraints,
+	.buffer_bytes_max = 128 * 1024,
+};
+
+struct dummy_model model_rme9652 = {
+	.name = "rme9652",
+	.buffer_bytes_max = 26 * 64 * 1024,
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 26,
+	.channels_max = 26,
+	.periods_min = 2,
+	.periods_max = 2,
+};
+
+struct dummy_model model_ice1712 = {
+	.name = "ice1712",
+	.buffer_bytes_max = 256 * 1024,
+	.formats = SNDRV_PCM_FMTBIT_S32_LE,
+	.channels_min = 10,
+	.channels_max = 10,
+	.periods_min = 1,
+	.periods_max = 1024,
+};
+
+struct dummy_model model_uda1341 = {
+	.name = "uda1341",
+	.buffer_bytes_max = 16380,
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.periods_min = 2,
+	.periods_max = 255,
+};
+
+struct dummy_model model_ac97 = {
+	.name = "ac97",
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_48000,
+	.rate_min = 48000,
+	.rate_max = 48000,
+};
+
+struct dummy_model model_ca0106 = {
+	.name = "ca0106",
+	.formats = SNDRV_PCM_FMTBIT_S16_LE,
+	.buffer_bytes_max = ((65536-64)*8),
+	.period_bytes_max = (65536-64),
+	.periods_min = 2,
+	.periods_max = 8,
+	.channels_min = 2,
+	.channels_max = 2,
+	.rates = SNDRV_PCM_RATE_48000|SNDRV_PCM_RATE_96000|SNDRV_PCM_RATE_192000,
+	.rate_min = 48000,
+	.rate_max = 192000,
+};
+
+struct dummy_model *dummy_models[] = {
+	&model_emu10k1,
+	&model_rme9652,
+	&model_ice1712,
+	&model_uda1341,
+	&model_ac97,
+	&model_ca0106,
+	NULL
+};
+
+/*
  * system timer interface
  */
 
@@ -509,7 +530,7 @@
 	.channels_min =		USE_CHANNELS_MIN,
 	.channels_max =		USE_CHANNELS_MAX,
 	.buffer_bytes_max =	MAX_BUFFER_SIZE,
-	.period_bytes_min =	64,
+	.period_bytes_min =	MIN_PERIOD_SIZE,
 	.period_bytes_max =	MAX_PERIOD_SIZE,
 	.periods_min =		USE_PERIODS_MIN,
 	.periods_max =		USE_PERIODS_MAX,
@@ -538,6 +559,7 @@
 static int dummy_pcm_open(struct snd_pcm_substream *substream)
 {
 	struct snd_dummy *dummy = snd_pcm_substream_chip(substream);
+	struct dummy_model *model = dummy->model;
 	struct snd_pcm_runtime *runtime = substream->runtime;
 	int err;
 
@@ -551,7 +573,7 @@
 	if (err < 0)
 		return err;
 
-	runtime->hw = dummy_pcm_hardware;
+	runtime->hw = dummy->pcm_hw;
 	if (substream->pcm->device & 1) {
 		runtime->hw.info &= ~SNDRV_PCM_INFO_INTERLEAVED;
 		runtime->hw.info |= SNDRV_PCM_INFO_NONINTERLEAVED;
@@ -560,10 +582,16 @@
 		runtime->hw.info &= ~(SNDRV_PCM_INFO_MMAP |
 				      SNDRV_PCM_INFO_MMAP_VALID);
 
-	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK)
-		err = add_playback_constraints(substream->runtime);
-	else
-		err = add_capture_constraints(substream->runtime);
+	if (model == NULL)
+		return 0;
+
+	if (substream->stream == SNDRV_PCM_STREAM_PLAYBACK) {
+		if (model->playback_constraints)
+			err = model->playback_constraints(substream->runtime);
+	} else {
+		if (model->capture_constraints)
+			err = model->capture_constraints(substream->runtime);
+	}
 	if (err < 0) {
 		dummy->timer_ops->free(substream);
 		return err;
@@ -823,17 +851,19 @@
 /*
  * proc interface
  */
-static void print_formats(struct snd_info_buffer *buffer)
+static void print_formats(struct snd_dummy *dummy,
+			  struct snd_info_buffer *buffer)
 {
 	int i;
 
 	for (i = 0; i < SNDRV_PCM_FORMAT_LAST; i++) {
-		if (dummy_pcm_hardware.formats & (1ULL << i))
+		if (dummy->pcm_hw.formats & (1ULL << i))
 			snd_iprintf(buffer, " %s", snd_pcm_format_name(i));
 	}
 }
 
-static void print_rates(struct snd_info_buffer *buffer)
+static void print_rates(struct snd_dummy *dummy,
+			struct snd_info_buffer *buffer)
 {
 	static int rates[] = {
 		5512, 8000, 11025, 16000, 22050, 32000, 44100, 48000,
@@ -841,19 +871,19 @@
 	};
 	int i;
 
-	if (dummy_pcm_hardware.rates & SNDRV_PCM_RATE_CONTINUOUS)
+	if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_CONTINUOUS)
 		snd_iprintf(buffer, " continuous");
-	if (dummy_pcm_hardware.rates & SNDRV_PCM_RATE_KNOT)
+	if (dummy->pcm_hw.rates & SNDRV_PCM_RATE_KNOT)
 		snd_iprintf(buffer, " knot");
 	for (i = 0; i < ARRAY_SIZE(rates); i++)
-		if (dummy_pcm_hardware.rates & (1 << i))
+		if (dummy->pcm_hw.rates & (1 << i))
 			snd_iprintf(buffer, " %d", rates[i]);
 }
 
-#define get_dummy_int_ptr(ofs) \
-	(unsigned int *)((char *)&dummy_pcm_hardware + (ofs))
-#define get_dummy_ll_ptr(ofs) \
-	(unsigned long long *)((char *)&dummy_pcm_hardware + (ofs))
+#define get_dummy_int_ptr(dummy, ofs) \
+	(unsigned int *)((char *)&((dummy)->pcm_hw) + (ofs))
+#define get_dummy_ll_ptr(dummy, ofs) \
+	(unsigned long long *)((char *)&((dummy)->pcm_hw) + (ofs))
 
 struct dummy_hw_field {
 	const char *name;
@@ -884,20 +914,21 @@
 static void dummy_proc_read(struct snd_info_entry *entry,
 			    struct snd_info_buffer *buffer)
 {
+	struct snd_dummy *dummy = entry->private_data;
 	int i;
 
 	for (i = 0; i < ARRAY_SIZE(fields); i++) {
 		snd_iprintf(buffer, "%s ", fields[i].name);
 		if (fields[i].size == sizeof(int))
 			snd_iprintf(buffer, fields[i].format,
-				    *get_dummy_int_ptr(fields[i].offset));
+				*get_dummy_int_ptr(dummy, fields[i].offset));
 		else
 			snd_iprintf(buffer, fields[i].format,
-				    *get_dummy_ll_ptr(fields[i].offset));
+				*get_dummy_ll_ptr(dummy, fields[i].offset));
 		if (!strcmp(fields[i].name, "formats"))
-			print_formats(buffer);
+			print_formats(dummy, buffer);
 		else if (!strcmp(fields[i].name, "rates"))
-			print_rates(buffer);
+			print_rates(dummy, buffer);
 		snd_iprintf(buffer, "\n");
 	}
 }
@@ -905,6 +936,7 @@
 static void dummy_proc_write(struct snd_info_entry *entry,
 			     struct snd_info_buffer *buffer)
 {
+	struct snd_dummy *dummy = entry->private_data;
 	char line[64];
 
 	while (!snd_info_get_line(buffer, line, sizeof(line))) {
@@ -924,9 +956,9 @@
 		if (strict_strtoull(item, 0, &val))
 			continue;
 		if (fields[i].size == sizeof(int))
-			*get_dummy_int_ptr(fields[i].offset) = val;
+			*get_dummy_int_ptr(dummy, fields[i].offset) = val;
 		else
-			*get_dummy_ll_ptr(fields[i].offset) = val;
+			*get_dummy_ll_ptr(dummy, fields[i].offset) = val;
 	}
 }
 
@@ -938,6 +970,7 @@
 		snd_info_set_text_ops(entry, chip, dummy_proc_read);
 		entry->c.text.write = dummy_proc_write;
 		entry->mode |= S_IWUSR;
+		entry->private_data = chip;
 	}
 }
 #else
@@ -948,6 +981,7 @@
 {
 	struct snd_card *card;
 	struct snd_dummy *dummy;
+	struct dummy_model *m = NULL, **mdl;
 	int idx, err;
 	int dev = devptr->id;
 
@@ -957,6 +991,15 @@
 		return err;
 	dummy = card->private_data;
 	dummy->card = card;
+	for (mdl = dummy_models; *mdl && model[dev]; mdl++) {
+		if (strcmp(model[dev], (*mdl)->name) == 0) {
+			printk(KERN_INFO
+				"snd-dummy: Using model '%s' for card %i\n",
+				(*mdl)->name, card->number);
+			m = dummy->model = *mdl;
+			break;
+		}
+	}
 	for (idx = 0; idx < MAX_PCM_DEVICES && idx < pcm_devs[dev]; idx++) {
 		if (pcm_substreams[dev] < 1)
 			pcm_substreams[dev] = 1;
@@ -966,6 +1009,33 @@
 		if (err < 0)
 			goto __nodev;
 	}
+
+	dummy->pcm_hw = dummy_pcm_hardware;
+	if (m) {
+		if (m->formats)
+			dummy->pcm_hw.formats = m->formats;
+		if (m->buffer_bytes_max)
+			dummy->pcm_hw.buffer_bytes_max = m->buffer_bytes_max;
+		if (m->period_bytes_min)
+			dummy->pcm_hw.period_bytes_min = m->period_bytes_min;
+		if (m->period_bytes_max)
+			dummy->pcm_hw.period_bytes_max = m->period_bytes_max;
+		if (m->periods_min)
+			dummy->pcm_hw.periods_min = m->periods_min;
+		if (m->periods_max)
+			dummy->pcm_hw.periods_max = m->periods_max;
+		if (m->rates)
+			dummy->pcm_hw.rates = m->rates;
+		if (m->rate_min)
+			dummy->pcm_hw.rate_min = m->rate_min;
+		if (m->rate_max)
+			dummy->pcm_hw.rate_max = m->rate_max;
+		if (m->channels_min)
+			dummy->pcm_hw.channels_min = m->channels_min;
+		if (m->channels_max)
+			dummy->pcm_hw.channels_max = m->channels_max;
+	}
+
 	err = snd_card_dummy_new_mixer(dummy);
 	if (err < 0)
 		goto __nodev;
diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig
index 351654c..1298c68 100644
--- a/sound/pci/Kconfig
+++ b/sound/pci/Kconfig
@@ -789,6 +789,7 @@
 	  Say Y here to include support for sound cards based on the
 	  Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X,
 	  Essence ST (Deluxe), and Essence STX.
+	  Support for the DS is experimental.
 	  Support for the HDAV1.3 (Deluxe) is very experimental.
 
 	  To compile this driver as a module, choose M here: the module
diff --git a/sound/pci/ac97/ac97_patch.c b/sound/pci/ac97/ac97_patch.c
index d9266ba..1caf5e3 100644
--- a/sound/pci/ac97/ac97_patch.c
+++ b/sound/pci/ac97/ac97_patch.c
@@ -544,25 +544,10 @@
 	return 0;
 }
 
-static int patch_wolfson_wm9705_specific(struct snd_ac97 * ac97)
-{
-	int err, i;
-	for (i = 0; i < ARRAY_SIZE(wm97xx_snd_ac97_controls); i++) {
-		if ((err = snd_ctl_add(ac97->bus->card, snd_ac97_cnew(&wm97xx_snd_ac97_controls[i], ac97))) < 0)
-			return err;
-	}
-	snd_ac97_write_cache(ac97,  0x72, 0x0808);
-	return 0;
-}
-
-static struct snd_ac97_build_ops patch_wolfson_wm9705_ops = {
-	.build_specific = patch_wolfson_wm9705_specific,
-};
-
 static int patch_wolfson05(struct snd_ac97 * ac97)
 {
 	/* WM9705, WM9710 */
-	ac97->build_ops = &patch_wolfson_wm9705_ops;
+	ac97->build_ops = &patch_wolfson_wm9703_ops;
 #ifdef CONFIG_TOUCHSCREEN_WM9705
 	/* WM9705 touchscreen uses AUX and VIDEO for touch */
 	ac97->flags |= AC97_HAS_NO_VIDEO | AC97_HAS_NO_AUX;
diff --git a/sound/pci/hda/hda_codec.c b/sound/pci/hda/hda_codec.c
index f98b47c..26ceace 100644
--- a/sound/pci/hda/hda_codec.c
+++ b/sound/pci/hda/hda_codec.c
@@ -824,6 +824,9 @@
 	struct hda_pincfg *pin;
 	unsigned int oldcfg;
 
+	if (get_wcaps_type(get_wcaps(codec, nid)) != AC_WID_PIN)
+		return -EINVAL;
+
 	oldcfg = snd_hda_codec_get_pincfg(codec, nid);
 	pin = look_up_pincfg(codec, list, nid);
 	if (!pin) {
@@ -899,6 +902,25 @@
 	}
 }
 
+/**
+ * snd_hda_shutup_pins - Shut up all pins
+ * @codec: the HDA codec
+ *
+ * Clear all pin controls to shup up before suspend for avoiding click noise.
+ * The controls aren't cached so that they can be resumed properly.
+ */
+void snd_hda_shutup_pins(struct hda_codec *codec)
+{
+	int i;
+	for (i = 0; i < codec->init_pins.used; i++) {
+		struct hda_pincfg *pin = snd_array_elem(&codec->init_pins, i);
+		/* use read here for syncing after issuing each verb */
+		snd_hda_codec_read(codec, pin->nid, 0,
+				   AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+	}
+}
+EXPORT_SYMBOL_HDA(snd_hda_shutup_pins);
+
 static void init_hda_cache(struct hda_cache_rec *cache,
 			   unsigned int record_size);
 static void free_hda_cache(struct hda_cache_rec *cache);
@@ -931,6 +953,7 @@
 #endif
 	list_del(&codec->list);
 	snd_array_free(&codec->mixers);
+	snd_array_free(&codec->nids);
 	codec->bus->caddr_tbl[codec->addr] = NULL;
 	if (codec->patch_ops.free)
 		codec->patch_ops.free(codec);
@@ -985,7 +1008,8 @@
 	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 hda_nid_item), 60);
+	snd_array_init(&codec->mixers, sizeof(struct hda_nid_item), 32);
+	snd_array_init(&codec->nids, sizeof(struct hda_nid_item), 32);
 	snd_array_init(&codec->init_pins, sizeof(struct hda_pincfg), 16);
 	snd_array_init(&codec->driver_pins, sizeof(struct hda_pincfg), 16);
 	if (codec->bus->modelname) {
@@ -1708,7 +1732,7 @@
 EXPORT_SYMBOL_HDA(snd_hda_find_mixer_ctl);
 
 /**
- * snd_hda_ctl-add - Add a control element and assign to the codec
+ * snd_hda_ctl_add - Add a control element and assign to the codec
  * @codec: HD-audio codec
  * @nid: corresponding NID (optional)
  * @kctl: the control element to assign
@@ -1723,19 +1747,25 @@
  *
  * snd_hda_ctl_add() checks the control subdev id field whether
  * #HDA_SUBDEV_NID_FLAG bit is set.  If set (and @nid is zero), the lower
- * bits value is taken as the NID to assign.
+ * bits value is taken as the NID to assign. The #HDA_NID_ITEM_AMP bit
+ * specifies if kctl->private_value is a HDA amplifier value.
  */
 int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
 		    struct snd_kcontrol *kctl)
 {
 	int err;
+	unsigned short flags = 0;
 	struct hda_nid_item *item;
 
-	if (kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) {
+	if (kctl->id.subdevice & HDA_SUBDEV_AMP_FLAG) {
+		flags |= HDA_NID_ITEM_AMP;
 		if (nid == 0)
-			nid = kctl->id.subdevice & 0xffff;
-		kctl->id.subdevice = 0;
+			nid = get_amp_nid_(kctl->private_value);
 	}
+	if ((kctl->id.subdevice & HDA_SUBDEV_NID_FLAG) != 0 && nid == 0)
+		nid = kctl->id.subdevice & 0xffff;
+	if (kctl->id.subdevice & (HDA_SUBDEV_NID_FLAG|HDA_SUBDEV_AMP_FLAG))
+		kctl->id.subdevice = 0;
 	err = snd_ctl_add(codec->bus->card, kctl);
 	if (err < 0)
 		return err;
@@ -1744,11 +1774,41 @@
 		return -ENOMEM;
 	item->kctl = kctl;
 	item->nid = nid;
+	item->flags = flags;
 	return 0;
 }
 EXPORT_SYMBOL_HDA(snd_hda_ctl_add);
 
 /**
+ * snd_hda_add_nid - Assign a NID to a control element
+ * @codec: HD-audio codec
+ * @nid: corresponding NID (optional)
+ * @kctl: the control element to assign
+ * @index: index to kctl
+ *
+ * Add the given control element to an array inside the codec instance.
+ * This function is used when #snd_hda_ctl_add cannot be used for 1:1
+ * NID:KCTL mapping - for example "Capture Source" selector.
+ */
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+		    unsigned int index, hda_nid_t nid)
+{
+	struct hda_nid_item *item;
+
+	if (nid > 0) {
+		item = snd_array_new(&codec->nids);
+		if (!item)
+			return -ENOMEM;
+		item->kctl = kctl;
+		item->index = index;
+		item->nid = nid;
+		return 0;
+	}
+	return -EINVAL;
+}
+EXPORT_SYMBOL_HDA(snd_hda_add_nid);
+
+/**
  * snd_hda_ctls_clear - Clear all controls assigned to the given codec
  * @codec: HD-audio codec
  */
@@ -1759,6 +1819,7 @@
 	for (i = 0; i < codec->mixers.used; i++)
 		snd_ctl_remove(codec->bus->card, items[i].kctl);
 	snd_array_free(&codec->mixers);
+	snd_array_free(&codec->nids);
 }
 
 /* pseudo device locking
@@ -3478,6 +3539,8 @@
 
 	for (; knew->name; knew++) {
 		struct snd_kcontrol *kctl;
+		if (knew->iface == -1)	/* skip this codec private value */
+			continue;
 		kctl = snd_ctl_new1(knew, codec);
 		if (!kctl)
 			return -ENOMEM;
diff --git a/sound/pci/hda/hda_codec.h b/sound/pci/hda/hda_codec.h
index 0a770a2..0c8f05c 100644
--- a/sound/pci/hda/hda_codec.h
+++ b/sound/pci/hda/hda_codec.h
@@ -789,6 +789,7 @@
 	u32 *wcaps;
 
 	struct snd_array mixers;	/* list of assigned mixer elements */
+	struct snd_array nids;		/* list of mapped mixer elements */
 
 	struct hda_cache_rec amp_cache;	/* cache for amp access */
 	struct hda_cache_rec cmd_cache;	/* cache for other commands */
@@ -898,6 +899,7 @@
 			     unsigned int cfg);
 int snd_hda_add_pincfg(struct hda_codec *codec, struct snd_array *list,
 		       hda_nid_t nid, unsigned int cfg); /* for hwdep */
+void snd_hda_shutup_pins(struct hda_codec *codec);
 
 /*
  * Mixer
diff --git a/sound/pci/hda/hda_generic.c b/sound/pci/hda/hda_generic.c
index 092c6a7..5ea2128 100644
--- a/sound/pci/hda/hda_generic.c
+++ b/sound/pci/hda/hda_generic.c
@@ -861,7 +861,8 @@
 	}
 
 	/* create input MUX if multiple sources are available */
-	err = snd_hda_ctl_add(codec, 0, snd_ctl_new1(&cap_sel, codec));
+	err = snd_hda_ctl_add(codec, spec->adc_node->nid,
+			      snd_ctl_new1(&cap_sel, codec));
 	if (err < 0)
 		return err;
 
diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c
index 40ccb41..b36919c 100644
--- a/sound/pci/hda/hda_hwdep.c
+++ b/sound/pci/hda/hda_hwdep.c
@@ -293,8 +293,11 @@
 {								\
 	struct snd_hwdep *hwdep = dev_get_drvdata(dev);		\
 	struct hda_codec *codec = hwdep->private_data;		\
-	char *after;						\
-	codec->type = simple_strtoul(buf, &after, 0);		\
+	unsigned long val;					\
+	int err = strict_strtoul(buf, 0, &val);			\
+	if (err < 0)						\
+		return err;					\
+	codec->type = val;					\
 	return count;						\
 }
 
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c
index ec9c348..1f516e6 100644
--- a/sound/pci/hda/hda_intel.c
+++ b/sound/pci/hda/hda_intel.c
@@ -2704,32 +2704,10 @@
 	/* ULI M5461 */
 	{ PCI_DEVICE(0x10b9, 0x5461), .driver_data = AZX_DRIVER_ULI },
 	/* NVIDIA MCP */
-	{ PCI_DEVICE(0x10de, 0x026c), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0371), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x03e4), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x03f0), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x044a), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x044b), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x055c), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x055d), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0590), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0774), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0775), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0776), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0777), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x07fc), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x07fd), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0ac0), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0ac1), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0ac2), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0ac3), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0be2), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0be3), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0be4), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0d94), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0d95), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0d96), .driver_data = AZX_DRIVER_NVIDIA },
-	{ PCI_DEVICE(0x10de, 0x0d97), .driver_data = AZX_DRIVER_NVIDIA },
+	{ PCI_DEVICE(PCI_VENDOR_ID_NVIDIA, PCI_ANY_ID),
+	  .class = PCI_CLASS_MULTIMEDIA_HD_AUDIO << 8,
+	  .class_mask = 0xffffff,
+	  .driver_data = AZX_DRIVER_NVIDIA },
 	/* Teradici */
 	{ PCI_DEVICE(0x6549, 0x1200), .driver_data = AZX_DRIVER_TERA },
 	/* Creative X-Fi (CA0110-IBG) */
diff --git a/sound/pci/hda/hda_local.h b/sound/pci/hda/hda_local.h
index 5778ae8..7cee364 100644
--- a/sound/pci/hda/hda_local.h
+++ b/sound/pci/hda/hda_local.h
@@ -31,6 +31,7 @@
  * in snd_hda_ctl_add(), so that this value won't appear in the outside.
  */
 #define HDA_SUBDEV_NID_FLAG	(1U << 31)
+#define HDA_SUBDEV_AMP_FLAG	(1U << 30)
 
 /*
  * for mixer controls
@@ -42,7 +43,7 @@
 /* mono volume with index (index=0,1,...) (channel=1,2) */
 #define HDA_CODEC_VOLUME_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx,  \
-	  .subdevice = HDA_SUBDEV_NID_FLAG | (nid), \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
 	  .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
 	  	    SNDRV_CTL_ELEM_ACCESS_TLV_READ | \
 	  	    SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK, \
@@ -63,7 +64,7 @@
 /* mono mute switch with index (index=0,1,...) (channel=1,2) */
 #define HDA_CODEC_MUTE_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
-	  .subdevice = HDA_SUBDEV_NID_FLAG | (nid), \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
 	  .info = snd_hda_mixer_amp_switch_info, \
 	  .get = snd_hda_mixer_amp_switch_get, \
 	  .put = snd_hda_mixer_amp_switch_put, \
@@ -81,7 +82,7 @@
 /* special beep mono mute switch with index (index=0,1,...) (channel=1,2) */
 #define HDA_CODEC_MUTE_BEEP_MONO_IDX(xname, xcidx, nid, channel, xindex, direction) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = xcidx, \
-	  .subdevice = HDA_SUBDEV_NID_FLAG | (nid), \
+	  .subdevice = HDA_SUBDEV_AMP_FLAG, \
 	  .info = snd_hda_mixer_amp_switch_info, \
 	  .get = snd_hda_mixer_amp_switch_get, \
 	  .put = snd_hda_mixer_amp_switch_put_beep, \
@@ -464,13 +465,20 @@
 u32 snd_hda_pin_sense(struct hda_codec *codec, hda_nid_t nid);
 int snd_hda_jack_detect(struct hda_codec *codec, hda_nid_t nid);
 
+/* flags for hda_nid_item */
+#define HDA_NID_ITEM_AMP	(1<<0)
+
 struct hda_nid_item {
 	struct snd_kcontrol *kctl;
+	unsigned int index;
 	hda_nid_t nid;
+	unsigned short flags;
 };
 
 int snd_hda_ctl_add(struct hda_codec *codec, hda_nid_t nid,
 		    struct snd_kcontrol *kctl);
+int snd_hda_add_nid(struct hda_codec *codec, struct snd_kcontrol *kctl,
+		    unsigned int index, hda_nid_t nid);
 void snd_hda_ctls_clear(struct hda_codec *codec);
 
 /*
diff --git a/sound/pci/hda/hda_proc.c b/sound/pci/hda/hda_proc.c
index c9afc04..f97d35d 100644
--- a/sound/pci/hda/hda_proc.c
+++ b/sound/pci/hda/hda_proc.c
@@ -61,18 +61,29 @@
 		return "UNKNOWN Widget";
 }
 
-static void print_nid_mixers(struct snd_info_buffer *buffer,
-			     struct hda_codec *codec, hda_nid_t nid)
+static void print_nid_array(struct snd_info_buffer *buffer,
+			    struct hda_codec *codec, hda_nid_t nid,
+			    struct snd_array *array)
 {
 	int i;
-	struct hda_nid_item *items = codec->mixers.list;
+	struct hda_nid_item *items = array->list, *item;
 	struct snd_kcontrol *kctl;
-	for (i = 0; i < codec->mixers.used; i++) {
-		if (items[i].nid == nid) {
-			kctl = items[i].kctl;
+	for (i = 0; i < array->used; i++) {
+		item = &items[i];
+		if (item->nid == nid) {
+			kctl = item->kctl;
 			snd_iprintf(buffer,
 			  "  Control: name=\"%s\", index=%i, device=%i\n",
-			  kctl->id.name, kctl->id.index, kctl->id.device);
+			  kctl->id.name, kctl->id.index + item->index,
+			  kctl->id.device);
+			if (item->flags & HDA_NID_ITEM_AMP)
+				snd_iprintf(buffer,
+				  "    ControlAmp: chs=%lu, dir=%s, "
+				  "idx=%lu, ofs=%lu\n",
+				  get_amp_channels(kctl),
+				  get_amp_direction(kctl) ? "Out" : "In",
+				  get_amp_index(kctl),
+				  get_amp_offset(kctl));
 		}
 	}
 }
@@ -528,7 +539,8 @@
 			    (data & (1<<i)) ? 1 : 0,
 			    (unsol & (1<<i)) ? 1 : 0);
 	/* FIXME: add GPO and GPI pin information */
-	print_nid_mixers(buffer, codec, nid);
+	print_nid_array(buffer, codec, nid, &codec->mixers);
+	print_nid_array(buffer, codec, nid, &codec->nids);
 }
 
 static void print_codec_info(struct snd_info_entry *entry,
@@ -608,7 +620,8 @@
 			snd_iprintf(buffer, " CP");
 		snd_iprintf(buffer, "\n");
 
-		print_nid_mixers(buffer, codec, nid);
+		print_nid_array(buffer, codec, nid, &codec->mixers);
+		print_nid_array(buffer, codec, nid, &codec->nids);
 		print_nid_pcms(buffer, codec, nid);
 
 		/* volume knob is a special widget that always have connection
diff --git a/sound/pci/hda/patch_analog.c b/sound/pci/hda/patch_analog.c
index 69a941c..214301d5 100644
--- a/sound/pci/hda/patch_analog.c
+++ b/sound/pci/hda/patch_analog.c
@@ -174,6 +174,7 @@
 static int ad198x_build_controls(struct hda_codec *codec)
 {
 	struct ad198x_spec *spec = codec->spec;
+	struct snd_kcontrol *kctl;
 	unsigned int i;
 	int err;
 
@@ -208,9 +209,7 @@
 			if (!kctl)
 				return -ENOMEM;
 			kctl->private_value = spec->beep_amp;
-			err = snd_hda_ctl_add(codec,
-						get_amp_nid_(spec->beep_amp),
-						kctl);
+			err = snd_hda_ctl_add(codec, 0, kctl);
 			if (err < 0)
 				return err;
 		}
@@ -239,6 +238,27 @@
 	}
 
 	ad198x_free_kctls(codec); /* no longer needed */
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+	if (!kctl)
+		kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->capsrc_nids[i]);
+		if (err < 0)
+			return err;
+	}
+
+	/* assign IEC958 enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec,
+			SNDRV_CTL_NAME_IEC958("",PLAYBACK,NONE) "Source");
+	if (kctl) {
+		err = snd_hda_add_nid(codec, kctl, 0,
+				      spec->multiout.dig_out_nid);
+		if (err < 0)
+			return err;
+	}
+
 	return 0;
 }
 
@@ -421,6 +441,11 @@
 	return 0;
 }
 
+static inline void ad198x_shutup(struct hda_codec *codec)
+{
+	snd_hda_shutup_pins(codec);
+}
+
 static void ad198x_free_kctls(struct hda_codec *codec)
 {
 	struct ad198x_spec *spec = codec->spec;
@@ -434,6 +459,46 @@
 	snd_array_free(&spec->kctls);
 }
 
+static void ad198x_power_eapd_write(struct hda_codec *codec, hda_nid_t front,
+				hda_nid_t hp)
+{
+	struct ad198x_spec *spec = codec->spec;
+	snd_hda_codec_write(codec, front, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    !spec->inv_eapd ? 0x00 : 0x02);
+	snd_hda_codec_write(codec, hp, 0, AC_VERB_SET_EAPD_BTLENABLE,
+			    !spec->inv_eapd ? 0x00 : 0x02);
+}
+
+static void ad198x_power_eapd(struct hda_codec *codec)
+{
+	/* We currently only handle front, HP */
+	switch (codec->vendor_id) {
+	case 0x11d41882:
+	case 0x11d4882a:
+	case 0x11d41884:
+	case 0x11d41984:
+	case 0x11d41883:
+	case 0x11d4184a:
+	case 0x11d4194a:
+	case 0x11d4194b:
+		ad198x_power_eapd_write(codec, 0x12, 0x11);
+		break;
+	case 0x11d41981:
+	case 0x11d41983:
+		ad198x_power_eapd_write(codec, 0x05, 0x06);
+		break;
+	case 0x11d41986:
+		ad198x_power_eapd_write(codec, 0x1b, 0x1a);
+		break;
+	case 0x11d41988:
+	case 0x11d4198b:
+	case 0x11d4989a:
+	case 0x11d4989b:
+		ad198x_power_eapd_write(codec, 0x29, 0x22);
+		break;
+	}
+}
+
 static void ad198x_free(struct hda_codec *codec)
 {
 	struct ad198x_spec *spec = codec->spec;
@@ -441,11 +506,29 @@
 	if (!spec)
 		return;
 
+	ad198x_shutup(codec);
 	ad198x_free_kctls(codec);
 	kfree(spec);
 	snd_hda_detach_beep_device(codec);
 }
 
+#ifdef SND_HDA_NEEDS_RESUME
+static int ad198x_suspend(struct hda_codec *codec, pm_message_t state)
+{
+	ad198x_shutup(codec);
+	ad198x_power_eapd(codec);
+	return 0;
+}
+
+static int ad198x_resume(struct hda_codec *codec)
+{
+	ad198x_init(codec);
+	snd_hda_codec_resume_amp(codec);
+	snd_hda_codec_resume_cache(codec);
+	return 0;
+}
+#endif
+
 static struct hda_codec_ops ad198x_patch_ops = {
 	.build_controls = ad198x_build_controls,
 	.build_pcms = ad198x_build_pcms,
@@ -454,6 +537,11 @@
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	.check_power_status = ad198x_check_power_status,
 #endif
+#ifdef SND_HDA_NEEDS_RESUME
+	.suspend = ad198x_suspend,
+	.resume = ad198x_resume,
+#endif
+	.reboot_notify = ad198x_shutup,
 };
 
 
@@ -701,6 +789,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "External Amplifier",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x1b,
 		.info = ad198x_eapd_info,
 		.get = ad198x_eapd_get,
 		.put = ad198x_eapd_put,
@@ -808,6 +897,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = ad1986a_hp_master_sw_put,
@@ -1612,6 +1702,7 @@
 	HDA_BIND_VOL("Master Playback Volume", &ad1981_hp_bind_master_vol),
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x05,
 		.name = "Master Playback Switch",
 		.info = ad198x_eapd_info,
 		.get = ad198x_eapd_get,
@@ -2136,6 +2227,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "External Amplifier",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x12,
 		.info = ad198x_eapd_info,
 		.get = ad198x_eapd_get,
 		.put = ad198x_eapd_put,
@@ -2257,6 +2349,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "IEC958 Playback Source",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x1b,
 		.info = ad1988_spdif_playback_source_info,
 		.get = ad1988_spdif_playback_source_get,
 		.put = ad1988_spdif_playback_source_put,
@@ -2372,6 +2465,12 @@
 	{ }
 };
 
+static struct hda_verb ad1988_spdif_in_init_verbs[] = {
+	/* unmute SPDIF input pin */
+	{0x1c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0)},
+	{ }
+};
+
 /* AD1989 has no ADC -> SPDIF route */
 static struct hda_verb ad1989_spdif_init_verbs[] = {
 	/* SPDIF-1 out pin */
@@ -2589,7 +2688,7 @@
 	if (! knew->name)
 		return -ENOMEM;
 	if (get_amp_nid_(val))
-		knew->subdevice = HDA_SUBDEV_NID_FLAG | get_amp_nid_(val);
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
 	knew->private_value = val;
 	return 0;
 }
@@ -3107,8 +3206,11 @@
 				ad1988_spdif_init_verbs;
 		}
 	}
-	if (spec->dig_in_nid && codec->vendor_id < 0x11d4989a)
+	if (spec->dig_in_nid && codec->vendor_id < 0x11d4989a) {
 		spec->mixers[spec->num_mixers++] = ad1988_spdif_in_mixers;
+		spec->init_verbs[spec->num_init_verbs++] =
+			ad1988_spdif_in_init_verbs;
+	}
 
 	codec->patch_ops = ad198x_patch_ops;
 	switch (board_config) {
@@ -3747,6 +3849,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = ad1884a_mobile_master_sw_put,
@@ -3775,6 +3878,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = ad1884a_mobile_master_sw_put,
@@ -4116,6 +4220,7 @@
 /*	HDA_CODEC_MUTE("Master Playback Switch", 0x21, 0x0, HDA_OUTPUT),*/
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.name = "Master Playback Switch",
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
diff --git a/sound/pci/hda/patch_cirrus.c b/sound/pci/hda/patch_cirrus.c
index fe0423c..7de782a 100644
--- a/sound/pci/hda/patch_cirrus.c
+++ b/sound/pci/hda/patch_cirrus.c
@@ -501,7 +501,8 @@
 	knew.private_value = pval;
 	snprintf(tmp, sizeof(tmp), "%s %s Switch", name, dir_sfx[dir]);
 	*kctlp = snd_ctl_new1(&knew, codec);
-	return snd_hda_ctl_add(codec, get_amp_nid_(pval), *kctlp);
+	(*kctlp)->id.subdevice = HDA_SUBDEV_AMP_FLAG;
+	return snd_hda_ctl_add(codec, 0, *kctlp);
 }
 
 static int add_volume(struct hda_codec *codec, const char *name,
@@ -514,7 +515,8 @@
 	knew.private_value = pval;
 	snprintf(tmp, sizeof(tmp), "%s %s Volume", name, dir_sfx[dir]);
 	*kctlp = snd_ctl_new1(&knew, codec);
-	return snd_hda_ctl_add(codec, get_amp_nid_(pval), *kctlp);
+	(*kctlp)->id.subdevice = HDA_SUBDEV_AMP_FLAG;
+	return snd_hda_ctl_add(codec, 0, *kctlp);
 }
 
 static void fix_volume_caps(struct hda_codec *codec, hda_nid_t dac)
@@ -751,6 +753,7 @@
 	spec->capture_bind[1] = make_bind_capture(codec, &snd_hda_bind_vol);
 	for (i = 0; i < 2; i++) {
 		struct snd_kcontrol *kctl;
+		int n;
 		if (!spec->capture_bind[i])
 			return -ENOMEM;
 		kctl = snd_ctl_new1(&cs_capture_ctls[i], codec);
@@ -760,6 +763,13 @@
 		err = snd_hda_ctl_add(codec, 0, kctl);
 		if (err < 0)
 			return err;
+		for (n = 0; n < AUTO_PIN_LAST; n++) {
+			if (!spec->adc_nid[n])
+				continue;
+			err = snd_hda_add_nid(codec, kctl, 0, spec->adc_nid[i]);
+			if (err < 0)
+				return err;
+		}
 	}
 	
 	if (spec->num_inputs > 1 && !spec->mic_detect) {
diff --git a/sound/pci/hda/patch_cmedia.c b/sound/pci/hda/patch_cmedia.c
index a45c116..ff60908 100644
--- a/sound/pci/hda/patch_cmedia.c
+++ b/sound/pci/hda/patch_cmedia.c
@@ -315,7 +315,8 @@
 static int cmi9880_build_controls(struct hda_codec *codec)
 {
 	struct cmi_spec *spec = codec->spec;
-	int err;
+	struct snd_kcontrol *kctl;
+	int i, err;
 
 	err = snd_hda_add_new_ctls(codec, cmi9880_basic_mixer);
 	if (err < 0)
@@ -340,6 +341,14 @@
 		if (err < 0)
 			return err;
 	}
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->adc_nids[i]);
+		if (err < 0)
+			return err;
+	}
 	return 0;
 }
 
diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c
index c578c28..685015a 100644
--- a/sound/pci/hda/patch_conexant.c
+++ b/sound/pci/hda/patch_conexant.c
@@ -111,8 +111,22 @@
 
 	unsigned int dell_automute;
 	unsigned int port_d_mode;
-	unsigned char ext_mic_bias;
 	unsigned int dell_vostro;
+
+	unsigned int ext_mic_present;
+	unsigned int recording;
+	void (*capture_prepare)(struct hda_codec *codec);
+	void (*capture_cleanup)(struct hda_codec *codec);
+
+	/* OLPC XO-1.5 supports DC input mode (e.g. for use with analog sensors)
+	 * through the microphone jack.
+	 * When the user enables this through a mixer switch, both internal and
+	 * external microphones are disabled. Gain is fixed at 0dB. In this mode,
+	 * we also allow the bias to be configured through a separate mixer
+	 * control. */
+	unsigned int dc_enable;
+	unsigned int dc_input_bias; /* offset into cxt5066_olpc_dc_bias */
+	unsigned int mic_boost; /* offset into cxt5066_analog_mic_boost */
 };
 
 static int conexant_playback_pcm_open(struct hda_pcm_stream *hinfo,
@@ -185,6 +199,8 @@
 				      struct snd_pcm_substream *substream)
 {
 	struct conexant_spec *spec = codec->spec;
+	if (spec->capture_prepare)
+		spec->capture_prepare(codec);
 	snd_hda_codec_setup_stream(codec, spec->adc_nids[substream->number],
 				   stream_tag, 0, format);
 	return 0;
@@ -196,6 +212,8 @@
 {
 	struct conexant_spec *spec = codec->spec;
 	snd_hda_codec_cleanup_stream(codec, spec->adc_nids[substream->number]);
+	if (spec->capture_cleanup)
+		spec->capture_cleanup(codec);
 	return 0;
 }
 
@@ -1723,6 +1741,22 @@
 	{}
 };
 
+static struct snd_kcontrol_new cxt5051_f700_mixers[] = {
+	HDA_CODEC_VOLUME("Mic Volume", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_MUTE("Mic Switch", 0x14, 0x01, HDA_INPUT),
+	HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Master Playback Switch",
+		.info = cxt_eapd_info,
+		.get = cxt_eapd_get,
+		.put = cxt5051_hp_master_sw_put,
+		.private_value = 0x1a,
+	},
+
+	{}
+};
+
 static struct hda_verb cxt5051_init_verbs[] = {
 	/* Line in, Mic */
 	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03},
@@ -1813,6 +1847,32 @@
 	{ } /* end */
 };
 
+static struct hda_verb cxt5051_f700_init_verbs[] = {
+	/* Line in, Mic */
+	{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x03},
+	{0x17, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0x0},
+	/* SPK  */
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT},
+	{0x1a, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* HP, Amp  */
+	{0x16, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP},
+	{0x16, AC_VERB_SET_CONNECT_SEL, 0x00},
+	/* DAC1 */
+	{0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+	/* Record selector: Int mic */
+	{0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1) | 0x44},
+	{0x14, AC_VERB_SET_CONNECT_SEL, 0x1},
+	/* SPDIF route: PCM */
+	{0x1c, AC_VERB_SET_CONNECT_SEL, 0x0},
+	/* EAPD */
+	{0x1a, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
+	{0x16, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CONEXANT_HP_EVENT},
+	{0x17, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN|CXT5051_PORTB_EVENT},
+	{ } /* end */
+};
+
 /* initialize jack-sensing, too */
 static int cxt5051_init(struct hda_codec *codec)
 {
@@ -1832,6 +1892,7 @@
 	CXT5051_HP,	/* no docking */
 	CXT5051_HP_DV6736,	/* HP without mic switch */
 	CXT5051_LENOVO_X200,	/* Lenovo X200 laptop */
+	CXT5051_F700,       /* HP Compaq Presario F700 */
 	CXT5051_MODELS
 };
 
@@ -1840,6 +1901,7 @@
 	[CXT5051_HP]		= "hp",
 	[CXT5051_HP_DV6736]	= "hp-dv6736",
 	[CXT5051_LENOVO_X200]	= "lenovo-x200",
+	[CXT5051_F700]          = "hp 700"
 };
 
 static struct snd_pci_quirk cxt5051_cfg_tbl[] = {
@@ -1849,6 +1911,7 @@
 		      CXT5051_LAPTOP),
 	SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP),
 	SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT5051_LENOVO_X200),
+	SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700),
 	{}
 };
 
@@ -1899,6 +1962,11 @@
 	case CXT5051_LENOVO_X200:
 		spec->init_verbs[0] = cxt5051_lenovo_x200_init_verbs;
 		break;
+	case CXT5051_F700:
+		spec->init_verbs[0] = cxt5051_f700_init_verbs;
+		spec->mixers[0] = cxt5051_f700_mixers;
+		spec->no_auto_mic = 1;
+		break;
 	}
 
 	return 0;
@@ -1966,53 +2034,97 @@
 	return 1;
 }
 
-/* toggle input of built-in and mic jack appropriately */
-static void cxt5066_automic(struct hda_codec *codec)
+static const struct hda_input_mux cxt5066_olpc_dc_bias = {
+	.num_items = 3,
+	.items = {
+		{ "Off", PIN_IN },
+		{ "50%", PIN_VREF50 },
+		{ "80%", PIN_VREF80 },
+	},
+};
+
+static int cxt5066_set_olpc_dc_bias(struct hda_codec *codec)
 {
 	struct conexant_spec *spec = codec->spec;
-	struct hda_verb ext_mic_present[] = {
-		/* enable external mic, port B */
-		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias},
+	/* Even though port F is the DC input, the bias is controlled on port B.
+	 * we also leave that port as an active input (but unselected) in DC mode
+	 * just in case that is necessary to make the bias setting take effect. */
+	return snd_hda_codec_write_cache(codec, 0x1a, 0,
+		AC_VERB_SET_PIN_WIDGET_CONTROL,
+		cxt5066_olpc_dc_bias.items[spec->dc_input_bias].index);
+}
 
-		/* switch to external mic input */
-		{0x17, AC_VERB_SET_CONNECT_SEL, 0},
+/* OLPC defers mic widget control until when capture is started because the
+ * microphone LED comes on as soon as these settings are put in place. if we
+ * did this before recording, it would give the false indication that recording
+ * is happening when it is not. */
+static void cxt5066_olpc_select_mic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	if (!spec->recording)
+		return;
 
-		/* disable internal mic, port C */
-		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
-		{}
-	};
-	static struct hda_verb ext_mic_absent[] = {
-		/* enable internal mic, port C */
-		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	if (spec->dc_enable) {
+		/* in DC mode we ignore presence detection and just use the jack
+		 * through our special DC port */
+		const struct hda_verb enable_dc_mode[] = {
+			/* disble internal mic, port C */
+			{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
-		/* switch to internal mic input */
-		{0x17, AC_VERB_SET_CONNECT_SEL, 1},
+			/* enable DC capture, port F */
+			{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN},
+			{},
+		};
 
-		/* disable external mic, port B */
-		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
-		{}
-	};
+		snd_hda_sequence_write(codec, enable_dc_mode);
+		/* port B input disabled (and bias set) through the following call */
+		cxt5066_set_olpc_dc_bias(codec);
+		return;
+	}
+
+	/* disable DC (port F) */
+	snd_hda_codec_write(codec, 0x1e, 0, AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
+
+	/* external mic, port B */
+	snd_hda_codec_write(codec, 0x1a, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+		spec->ext_mic_present ? CXT5066_OLPC_EXT_MIC_BIAS : 0);
+
+	/* internal mic, port C */
+	snd_hda_codec_write(codec, 0x1b, 0, AC_VERB_SET_PIN_WIDGET_CONTROL,
+		spec->ext_mic_present ? 0 : PIN_VREF80);
+}
+
+/* toggle input of built-in and mic jack appropriately */
+static void cxt5066_olpc_automic(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
 	unsigned int present;
 
-	present = snd_hda_jack_detect(codec, 0x1a);
-	if (present) {
+	if (spec->dc_enable) /* don't do presence detection in DC mode */
+		return;
+
+	present = snd_hda_codec_read(codec, 0x1a, 0,
+				     AC_VERB_GET_PIN_SENSE, 0) & 0x80000000;
+	if (present)
 		snd_printdd("CXT5066: external microphone detected\n");
-		snd_hda_sequence_write(codec, ext_mic_present);
-	} else {
+	else
 		snd_printdd("CXT5066: external microphone absent\n");
-		snd_hda_sequence_write(codec, ext_mic_absent);
-	}
+
+	snd_hda_codec_write(codec, 0x17, 0, AC_VERB_SET_CONNECT_SEL,
+		present ? 0 : 1);
+	spec->ext_mic_present = !!present;
+
+	cxt5066_olpc_select_mic(codec);
 }
 
 /* toggle input of built-in digital mic and mic jack appropriately */
 static void cxt5066_vostro_automic(struct hda_codec *codec)
 {
-	struct conexant_spec *spec = codec->spec;
 	unsigned int present;
 
 	struct hda_verb ext_mic_present[] = {
 		/* enable external mic, port B */
-		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, spec->ext_mic_bias},
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
 
 		/* switch to external mic input */
 		{0x17, AC_VERB_SET_CONNECT_SEL, 0},
@@ -2063,15 +2175,18 @@
 }
 
 /* unsolicited event for jack sensing */
-static void cxt5066_unsol_event(struct hda_codec *codec, unsigned int res)
+static void cxt5066_olpc_unsol_event(struct hda_codec *codec, unsigned int res)
 {
+	struct conexant_spec *spec = codec->spec;
 	snd_printdd("CXT5066: unsol event %x (%x)\n", res, res >> 26);
 	switch (res >> 26) {
 	case CONEXANT_HP_EVENT:
 		cxt5066_hp_automute(codec);
 		break;
 	case CONEXANT_MIC_EVENT:
-		cxt5066_automic(codec);
+		/* ignore mic events in DC mode; we're always using the jack */
+		if (!spec->dc_enable)
+			cxt5066_olpc_automic(codec);
 		break;
 	}
 }
@@ -2101,6 +2216,15 @@
 	},
 };
 
+static int cxt5066_set_mic_boost(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	return snd_hda_codec_write_cache(codec, 0x17, 0,
+		AC_VERB_SET_AMP_GAIN_MUTE,
+		AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT |
+			cxt5066_analog_mic_boost.items[spec->mic_boost].index);
+}
+
 static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol,
 					   struct snd_ctl_elem_info *uinfo)
 {
@@ -2111,15 +2235,8 @@
 					  struct snd_ctl_elem_value *ucontrol)
 {
 	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
-	int val;
-	hda_nid_t nid = kcontrol->private_value & 0xff;
-	int inout = (kcontrol->private_value & 0x100) ?
-		AC_AMP_GET_INPUT : AC_AMP_GET_OUTPUT;
-
-	val = snd_hda_codec_read(codec, nid, 0,
-		AC_VERB_GET_AMP_GAIN_MUTE, inout);
-
-	ucontrol->value.enumerated.item[0] = val & AC_AMP_GAIN;
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->mic_boost;
 	return 0;
 }
 
@@ -2127,26 +2244,132 @@
 					  struct snd_ctl_elem_value *ucontrol)
 {
 	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
 	const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
 	unsigned int idx;
-	hda_nid_t nid = kcontrol->private_value & 0xff;
-	int inout = (kcontrol->private_value & 0x100) ?
-		AC_AMP_SET_INPUT : AC_AMP_SET_OUTPUT;
-
-	if (!imux->num_items)
-		return 0;
 	idx = ucontrol->value.enumerated.item[0];
 	if (idx >= imux->num_items)
 		idx = imux->num_items - 1;
 
-	snd_hda_codec_write_cache(codec, nid, 0,
-		AC_VERB_SET_AMP_GAIN_MUTE,
-		AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | inout |
-			imux->items[idx].index);
+	spec->mic_boost = idx;
+	if (!spec->dc_enable)
+		cxt5066_set_mic_boost(codec);
+	return 1;
+}
+
+static void cxt5066_enable_dc(struct hda_codec *codec)
+{
+	const struct hda_verb enable_dc_mode[] = {
+		/* disable gain */
+		{0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE},
+
+		/* switch to DC input */
+		{0x17, AC_VERB_SET_CONNECT_SEL, 3},
+		{}
+	};
+
+	/* configure as input source */
+	snd_hda_sequence_write(codec, enable_dc_mode);
+	cxt5066_olpc_select_mic(codec); /* also sets configured bias */
+}
+
+static void cxt5066_disable_dc(struct hda_codec *codec)
+{
+	/* reconfigure input source */
+	cxt5066_set_mic_boost(codec);
+	/* automic also selects the right mic if we're recording */
+	cxt5066_olpc_automic(codec);
+}
+
+static int cxt5066_olpc_dc_get(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.integer.value[0] = spec->dc_enable;
+	return 0;
+}
+
+static int cxt5066_olpc_dc_put(struct snd_kcontrol *kcontrol,
+			     struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	int dc_enable = !!ucontrol->value.integer.value[0];
+
+	if (dc_enable == spec->dc_enable)
+		return 0;
+
+	spec->dc_enable = dc_enable;
+	if (dc_enable)
+		cxt5066_enable_dc(codec);
+	else
+		cxt5066_disable_dc(codec);
 
 	return 1;
 }
 
+static int cxt5066_olpc_dc_bias_enum_info(struct snd_kcontrol *kcontrol,
+					   struct snd_ctl_elem_info *uinfo)
+{
+	return snd_hda_input_mux_info(&cxt5066_olpc_dc_bias, uinfo);
+}
+
+static int cxt5066_olpc_dc_bias_enum_get(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	ucontrol->value.enumerated.item[0] = spec->dc_input_bias;
+	return 0;
+}
+
+static int cxt5066_olpc_dc_bias_enum_put(struct snd_kcontrol *kcontrol,
+					  struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct conexant_spec *spec = codec->spec;
+	const struct hda_input_mux *imux = &cxt5066_analog_mic_boost;
+	unsigned int idx;
+
+	idx = ucontrol->value.enumerated.item[0];
+	if (idx >= imux->num_items)
+		idx = imux->num_items - 1;
+
+	spec->dc_input_bias = idx;
+	if (spec->dc_enable)
+		cxt5066_set_olpc_dc_bias(codec);
+	return 1;
+}
+
+static void cxt5066_olpc_capture_prepare(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	/* mark as recording and configure the microphone widget so that the
+	 * recording LED comes on. */
+	spec->recording = 1;
+	cxt5066_olpc_select_mic(codec);
+}
+
+static void cxt5066_olpc_capture_cleanup(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	const struct hda_verb disable_mics[] = {
+		/* disable external mic, port B */
+		{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+		/* disble internal mic, port C */
+		{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+
+		/* disable DC capture, port F */
+		{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
+		{},
+	};
+
+	snd_hda_sequence_write(codec, disable_mics);
+	spec->recording = 0;
+}
+
 static struct hda_input_mux cxt5066_capture_source = {
 	.num_items = 4,
 	.items = {
@@ -2187,6 +2410,7 @@
 		.access = SNDRV_CTL_ELEM_ACCESS_READWRITE |
 				  SNDRV_CTL_ELEM_ACCESS_TLV_READ |
 				  SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK,
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_volume_info,
 		.get = snd_hda_mixer_amp_volume_get,
 		.put = snd_hda_mixer_amp_volume_put,
@@ -2198,6 +2422,24 @@
 	{}
 };
 
+static struct snd_kcontrol_new cxt5066_mixer_olpc_dc[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC Mode Enable Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = cxt5066_olpc_dc_get,
+		.put = cxt5066_olpc_dc_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "DC Input Bias Enum",
+		.info = cxt5066_olpc_dc_bias_enum_info,
+		.get = cxt5066_olpc_dc_bias_enum_get,
+		.put = cxt5066_olpc_dc_bias_enum_put,
+	},
+	{}
+};
+
 static struct snd_kcontrol_new cxt5066_mixers[] = {
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
@@ -2210,11 +2452,10 @@
 
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
-		.name = "Ext Mic Boost Capture Enum",
+		.name = "Analog Mic Boost Capture Enum",
 		.info = cxt5066_mic_boost_mux_enum_info,
 		.get = cxt5066_mic_boost_mux_enum_get,
 		.put = cxt5066_mic_boost_mux_enum_put,
-		.private_value = 0x17,
 	},
 
 	HDA_BIND_VOL("Capture Volume", &cxt5066_bind_capture_vol_others),
@@ -2296,10 +2537,10 @@
 	{0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */
 
 	/* Port B: external microphone */
-	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, CXT5066_OLPC_EXT_MIC_BIAS},
+	{0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
 	/* Port C: internal microphone */
-	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80},
+	{0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
 	/* Port D: unused */
 	{0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
@@ -2308,7 +2549,7 @@
 	{0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 	{0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */
 
-	/* Port F: unused */
+	/* Port F: external DC input through microphone port */
 	{0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, 0},
 
 	/* Port G: internal speakers */
@@ -2428,8 +2669,22 @@
 		cxt5066_hp_automute(codec);
 		if (spec->dell_vostro)
 			cxt5066_vostro_automic(codec);
-		else
-			cxt5066_automic(codec);
+	}
+	cxt5066_set_mic_boost(codec);
+	return 0;
+}
+
+static int cxt5066_olpc_init(struct hda_codec *codec)
+{
+	struct conexant_spec *spec = codec->spec;
+	snd_printdd("CXT5066: init\n");
+	conexant_init(codec);
+	cxt5066_hp_automute(codec);
+	if (!spec->dc_enable) {
+		cxt5066_set_mic_boost(codec);
+		cxt5066_olpc_automic(codec);
+	} else {
+		cxt5066_enable_dc(codec);
 	}
 	return 0;
 }
@@ -2470,7 +2725,7 @@
 	codec->spec = spec;
 
 	codec->patch_ops = conexant_patch_ops;
-	codec->patch_ops.init = cxt5066_init;
+	codec->patch_ops.init = conexant_init;
 
 	spec->dell_automute = 0;
 	spec->multiout.max_channels = 2;
@@ -2483,7 +2738,6 @@
 	spec->input_mux = &cxt5066_capture_source;
 
 	spec->port_d_mode = PIN_HP;
-	spec->ext_mic_bias = PIN_VREF80;
 
 	spec->num_init_verbs = 1;
 	spec->init_verbs[0] = cxt5066_init_verbs;
@@ -2510,20 +2764,28 @@
 		spec->dell_automute = 1;
 		break;
 	case CXT5066_OLPC_XO_1_5:
-		codec->patch_ops.unsol_event = cxt5066_unsol_event;
+		codec->patch_ops.init = cxt5066_olpc_init;
+		codec->patch_ops.unsol_event = cxt5066_olpc_unsol_event;
 		spec->init_verbs[0] = cxt5066_init_verbs_olpc;
 		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
+		spec->mixers[spec->num_mixers++] = cxt5066_mixer_olpc_dc;
 		spec->mixers[spec->num_mixers++] = cxt5066_mixers;
 		spec->port_d_mode = 0;
-		spec->ext_mic_bias = CXT5066_OLPC_EXT_MIC_BIAS;
+		spec->mic_boost = 3; /* default 30dB gain */
 
 		/* no S/PDIF out */
 		spec->multiout.dig_out_nid = 0;
 
 		/* input source automatically selected */
 		spec->input_mux = NULL;
+
+		/* our capture hooks which allow us to turn on the microphone LED
+		 * at the right time */
+		spec->capture_prepare = cxt5066_olpc_capture_prepare;
+		spec->capture_cleanup = cxt5066_olpc_capture_cleanup;
 		break;
 	case CXT5066_DELL_VOSTO:
+		codec->patch_ops.init = cxt5066_init;
 		codec->patch_ops.unsol_event = cxt5066_vostro_event;
 		spec->init_verbs[0] = cxt5066_init_verbs_vostro;
 		spec->mixers[spec->num_mixers++] = cxt5066_mixer_master_olpc;
@@ -2531,6 +2793,7 @@
 		spec->mixers[spec->num_mixers++] = cxt5066_vostro_mixers;
 		spec->port_d_mode = 0;
 		spec->dell_vostro = 1;
+		spec->mic_boost = 3; /* default 30dB gain */
 		snd_hda_attach_beep_device(codec, 0x13);
 
 		/* no S/PDIF out */
diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c
index c746505..141ff44 100644
--- a/sound/pci/hda/patch_realtek.c
+++ b/sound/pci/hda/patch_realtek.c
@@ -338,7 +338,7 @@
 	void (*init_hook)(struct hda_codec *codec);
 	void (*unsol_event)(struct hda_codec *codec, unsigned int res);
 #ifdef CONFIG_SND_HDA_POWER_SAVE
-	void (*power_hook)(struct hda_codec *codec, int power);
+	void (*power_hook)(struct hda_codec *codec);
 #endif
 
 	/* for pin sensing */
@@ -391,7 +391,7 @@
 	void (*init_hook)(struct hda_codec *);
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	struct hda_amp_list *loopbacks;
-	void (*power_hook)(struct hda_codec *codec, int power);
+	void (*power_hook)(struct hda_codec *codec);
 #endif
 };
 
@@ -633,6 +633,7 @@
 
 #define ALC_PIN_MODE(xname, nid, dir) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 	  .info = alc_pin_mode_info, \
 	  .get = alc_pin_mode_get, \
 	  .put = alc_pin_mode_put, \
@@ -684,6 +685,7 @@
 }
 #define ALC_GPIO_DATA_SWITCH(xname, nid, mask) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 	  .info = alc_gpio_data_info, \
 	  .get = alc_gpio_data_get, \
 	  .put = alc_gpio_data_put, \
@@ -738,6 +740,7 @@
 }
 #define ALC_SPDIF_CTRL_SWITCH(xname, nid, mask) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 	  .info = alc_spdif_ctrl_info, \
 	  .get = alc_spdif_ctrl_get, \
 	  .put = alc_spdif_ctrl_put, \
@@ -791,6 +794,7 @@
 
 #define ALC_EAPD_CTRL_SWITCH(xname, nid, mask) \
 	{ .iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, .index = 0,  \
+	  .subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 	  .info = alc_eapd_ctrl_info, \
 	  .get = alc_eapd_ctrl_get, \
 	  .put = alc_eapd_ctrl_put, \
@@ -1831,16 +1835,6 @@
 	spec->autocfg.speaker_pins[2] = 0x1b;
 }
 
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-static void alc889_power_eapd(struct hda_codec *codec, int power)
-{
-	snd_hda_codec_write(codec, 0x14, 0,
-			    AC_VERB_SET_EAPD_BTLENABLE, power ? 2 : 0);
-	snd_hda_codec_write(codec, 0x15, 0,
-			    AC_VERB_SET_EAPD_BTLENABLE, power ? 2 : 0);
-}
-#endif
-
 /*
  * ALC880 3-stack model
  *
@@ -2443,6 +2437,15 @@
  * build control elements
  */
 
+#define NID_MAPPING		(-1)
+
+#define SUBDEV_SPEAKER_		(0 << 6)
+#define SUBDEV_HP_		(1 << 6)
+#define SUBDEV_LINE_		(2 << 6)
+#define SUBDEV_SPEAKER(x)	(SUBDEV_SPEAKER_ | ((x) & 0x3f))
+#define SUBDEV_HP(x)		(SUBDEV_HP_ | ((x) & 0x3f))
+#define SUBDEV_LINE(x)		(SUBDEV_LINE_ | ((x) & 0x3f))
+
 static void alc_free_kctls(struct hda_codec *codec);
 
 #ifdef CONFIG_SND_HDA_INPUT_BEEP
@@ -2457,8 +2460,11 @@
 static int alc_build_controls(struct hda_codec *codec)
 {
 	struct alc_spec *spec = codec->spec;
-	int err;
-	int i;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new *knew;
+	int i, j, err;
+	unsigned int u;
+	hda_nid_t nid;
 
 	for (i = 0; i < spec->num_mixers; i++) {
 		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
@@ -2499,8 +2505,7 @@
 			if (!kctl)
 				return -ENOMEM;
 			kctl->private_value = spec->beep_amp;
-			err = snd_hda_ctl_add(codec,
-					get_amp_nid_(spec->beep_amp), kctl);
+			err = snd_hda_ctl_add(codec, 0, kctl);
 			if (err < 0)
 				return err;
 		}
@@ -2527,6 +2532,75 @@
 	}
 
 	alc_free_kctls(codec); /* no longer needed */
+
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Capture Source");
+	if (!kctl)
+		kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		hda_nid_t *nids = spec->capsrc_nids;
+		if (!nids)
+			nids = spec->adc_nids;
+		err = snd_hda_add_nid(codec, kctl, i, nids[i]);
+		if (err < 0)
+			return err;
+	}
+	if (spec->cap_mixer) {
+		const char *kname = kctl ? kctl->id.name : NULL;
+		for (knew = spec->cap_mixer; knew->name; knew++) {
+			if (kname && strcmp(knew->name, kname) == 0)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			for (i = 0; kctl && i < kctl->count; i++) {
+				err = snd_hda_add_nid(codec, kctl, i,
+						      spec->adc_nids[i]);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
+
+	/* other nid->control mapping */
+	for (i = 0; i < spec->num_mixers; i++) {
+		for (knew = spec->mixers[i]; knew->name; knew++) {
+			if (knew->iface != NID_MAPPING)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			if (kctl == NULL)
+				continue;
+			u = knew->subdevice;
+			for (j = 0; j < 4; j++, u >>= 8) {
+				nid = u & 0x3f;
+				if (nid == 0)
+					continue;
+				switch (u & 0xc0) {
+				case SUBDEV_SPEAKER_:
+					nid = spec->autocfg.speaker_pins[nid];
+					break;
+				case SUBDEV_LINE_:
+					nid = spec->autocfg.line_out_pins[nid];
+					break;
+				case SUBDEV_HP_:
+					nid = spec->autocfg.hp_pins[nid];
+					break;
+				default:
+					continue;
+				}
+				err = snd_hda_add_nid(codec, kctl, 0, nid);
+				if (err < 0)
+					return err;
+			}
+			u = knew->private_value;
+			for (j = 0; j < 4; j++, u >>= 8) {
+				nid = u & 0xff;
+				if (nid == 0)
+					continue;
+				err = snd_hda_add_nid(codec, kctl, 0, nid);
+				if (err < 0)
+					return err;
+			}
+		}
+	}
 	return 0;
 }
 
@@ -3609,6 +3683,11 @@
 	return 0;
 }
 
+static inline void alc_shutup(struct hda_codec *codec)
+{
+	snd_hda_shutup_pins(codec);
+}
+
 static void alc_free_kctls(struct hda_codec *codec)
 {
 	struct alc_spec *spec = codec->spec;
@@ -3629,17 +3708,47 @@
 	if (!spec)
 		return;
 
+	alc_shutup(codec);
 	alc_free_kctls(codec);
 	kfree(spec);
 	snd_hda_detach_beep_device(codec);
 }
 
 #ifdef CONFIG_SND_HDA_POWER_SAVE
+static void alc_power_eapd(struct hda_codec *codec)
+{
+	/* We currently only handle front, HP */
+	switch (codec->vendor_id) {
+	case 0x10ec0260:
+		snd_hda_codec_write(codec, 0x0f, 0,
+				    AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+		snd_hda_codec_write(codec, 0x10, 0,
+				    AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+		break;
+	case 0x10ec0262:
+	case 0x10ec0267:
+	case 0x10ec0268:
+	case 0x10ec0269:
+	case 0x10ec0272:
+	case 0x10ec0660:
+	case 0x10ec0662:
+	case 0x10ec0663:
+	case 0x10ec0862:
+	case 0x10ec0889:
+		snd_hda_codec_write(codec, 0x14, 0,
+				    AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+		snd_hda_codec_write(codec, 0x15, 0,
+				    AC_VERB_SET_EAPD_BTLENABLE, 0x00);
+		break;
+	}
+}
+
 static int alc_suspend(struct hda_codec *codec, pm_message_t state)
 {
 	struct alc_spec *spec = codec->spec;
+	alc_shutup(codec);
 	if (spec && spec->power_hook)
-		spec->power_hook(codec, 0);
+		spec->power_hook(codec);
 	return 0;
 }
 #endif
@@ -3647,16 +3756,9 @@
 #ifdef SND_HDA_NEEDS_RESUME
 static int alc_resume(struct hda_codec *codec)
 {
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-	struct alc_spec *spec = codec->spec;
-#endif
 	codec->patch_ops.init(codec);
 	snd_hda_codec_resume_amp(codec);
 	snd_hda_codec_resume_cache(codec);
-#ifdef CONFIG_SND_HDA_POWER_SAVE
-	if (spec && spec->power_hook)
-		spec->power_hook(codec, 1);
-#endif
 	return 0;
 }
 #endif
@@ -3676,6 +3778,7 @@
 	.suspend = alc_suspend,
 	.check_power_status = alc_check_power_status,
 #endif
+	.reboot_notify = alc_shutup,
 };
 
 
@@ -3832,6 +3935,7 @@
 #define PIN_CTL_TEST(xname,nid) {			\
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,	\
 			.name = xname,		       \
+			.subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 			.info = alc_test_pin_ctl_info, \
 			.get = alc_test_pin_ctl_get,   \
 			.put = alc_test_pin_ctl_put,   \
@@ -3841,6 +3945,7 @@
 #define PIN_SRC_TEST(xname,nid) {			\
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,	\
 			.name = xname,		       \
+			.subdevice = HDA_SUBDEV_NID_FLAG | nid, \
 			.info = alc_test_pin_src_info, \
 			.get = alc_test_pin_src_get,   \
 			.put = alc_test_pin_src_put,   \
@@ -4380,7 +4485,7 @@
 	if (!knew->name)
 		return -ENOMEM;
 	if (get_amp_nid_(val))
-		knew->subdevice = HDA_SUBDEV_NID_FLAG | get_amp_nid_(val);
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
 	knew->private_value = val;
 	return 0;
 }
@@ -5131,6 +5236,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x11,
 		.info = snd_ctl_boolean_mono_info,
 		.get = alc260_hp_master_sw_get,
 		.put = alc260_hp_master_sw_put,
@@ -5169,6 +5275,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_NID_FLAG | 0x11,
 		.info = snd_ctl_boolean_mono_info,
 		.get = alc260_hp_master_sw_get,
 		.put = alc260_hp_master_sw_put,
@@ -9452,7 +9559,7 @@
 		.setup = alc889_acer_aspire_8930g_setup,
 		.init_hook = alc_automute_amp,
 #ifdef CONFIG_SND_HDA_POWER_SAVE
-		.power_hook = alc889_power_eapd,
+		.power_hook = alc_power_eapd,
 #endif
 	},
 	[ALC888_ACER_ASPIRE_7730G] = {
@@ -10248,8 +10355,14 @@
 		.info = snd_ctl_boolean_mono_info,		\
 		.get = alc262_hp_master_sw_get,			\
 		.put = alc262_hp_master_sw_put,			\
+	}, \
+	{							\
+		.iface = NID_MAPPING,				\
+		.name = "Master Playback Switch",		\
+		.private_value = 0x15 | (0x16 << 8) | (0x1b << 16),	\
 	}
 
+
 static struct snd_kcontrol_new alc262_HP_BPC_mixer[] = {
 	ALC262_HP_MASTER_SWITCH,
 	HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x0, HDA_OUTPUT),
@@ -10407,6 +10520,12 @@
 		.info = snd_ctl_boolean_mono_info,		\
 		.get = alc262_hippo_master_sw_get,		\
 		.put = alc262_hippo_master_sw_put,		\
+	},							\
+	{							\
+		.iface = NID_MAPPING,				\
+		.name = "Master Playback Switch",		\
+		.subdevice = SUBDEV_HP(0) | (SUBDEV_LINE(0) << 8) | \
+			     (SUBDEV_SPEAKER(0) << 16), \
 	}
 
 static struct snd_kcontrol_new alc262_hippo_mixer[] = {
@@ -10887,11 +11006,17 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc262_fujitsu_master_sw_put,
 		.private_value = HDA_COMPOSE_AMP_VAL(0x14, 3, 0, HDA_OUTPUT),
 	},
+	{
+		.iface = NID_MAPPING,
+		.name = "Master Playback Switch",
+		.private_value = 0x1b,
+	},
 	HDA_CODEC_VOLUME("CD Playback Volume", 0x0b, 0x04, HDA_INPUT),
 	HDA_CODEC_MUTE("CD Playback Switch", 0x0b, 0x04, HDA_INPUT),
 	HDA_CODEC_VOLUME("Mic Boost", 0x18, 0, HDA_INPUT),
@@ -10922,6 +11047,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc262_lenovo_3000_master_sw_put,
@@ -11076,6 +11202,11 @@
 		.get = alc_mux_enum_get,
 		.put = alc262_ultra_mux_enum_put,
 	},
+	{
+		.iface = NID_MAPPING,
+		.name = "Capture Source",
+		.private_value = 0x15,
+	},
 	{ } /* end */
 };
 
@@ -12094,6 +12225,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc268_acer_master_sw_put,
@@ -12109,6 +12241,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc268_acer_master_sw_put,
@@ -12126,6 +12259,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc268_acer_master_sw_put,
@@ -13078,6 +13212,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc268_acer_master_sw_put,
@@ -13098,6 +13233,7 @@
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Master Playback Switch",
+		.subdevice = HDA_SUBDEV_AMP_FLAG,
 		.info = snd_hda_mixer_amp_switch_info,
 		.get = snd_hda_mixer_amp_switch_get,
 		.put = alc268_acer_master_sw_put,
@@ -14860,9 +14996,13 @@
 	spec->vmaster_nid = 0x03;
 
 	codec->patch_ops = alc_patch_ops;
-	if (board_config == ALC861_AUTO)
+	if (board_config == ALC861_AUTO) {
 		spec->init_hook = alc861_auto_init;
 #ifdef CONFIG_SND_HDA_POWER_SAVE
+		spec->power_hook = alc_power_eapd;
+#endif
+	}
+#ifdef CONFIG_SND_HDA_POWER_SAVE
 	if (!spec->loopback.amplist)
 		spec->loopback.amplist = alc861_loopbacks;
 #endif
diff --git a/sound/pci/hda/patch_si3054.c b/sound/pci/hda/patch_si3054.c
index 43b436c..f419ee8 100644
--- a/sound/pci/hda/patch_si3054.c
+++ b/sound/pci/hda/patch_si3054.c
@@ -122,6 +122,7 @@
 #define SI3054_KCONTROL(kname,reg,mask) { \
 	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
 	.name = kname, \
+	.subdevice = HDA_SUBDEV_NID_FLAG | reg, \
 	.info = si3054_switch_info, \
 	.get  = si3054_switch_get, \
 	.put  = si3054_switch_put, \
diff --git a/sound/pci/hda/patch_sigmatel.c b/sound/pci/hda/patch_sigmatel.c
index 2291a83..117919a 100644
--- a/sound/pci/hda/patch_sigmatel.c
+++ b/sound/pci/hda/patch_sigmatel.c
@@ -2688,7 +2688,7 @@
 stac_control_new(struct sigmatel_spec *spec,
 		 struct snd_kcontrol_new *ktemp,
 		 const char *name,
-		 hda_nid_t nid)
+		 unsigned int subdev)
 {
 	struct snd_kcontrol_new *knew;
 
@@ -2704,8 +2704,7 @@
 		spec->kctls.alloced--;
 		return NULL;
 	}
-	if (nid)
-		knew->subdevice = HDA_SUBDEV_NID_FLAG | nid;
+	knew->subdevice = subdev;
 	return knew;
 }
 
@@ -2715,7 +2714,7 @@
 				     unsigned long val)
 {
 	struct snd_kcontrol_new *knew = stac_control_new(spec, ktemp, name,
-							 get_amp_nid_(val));
+							 HDA_SUBDEV_AMP_FLAG);
 	if (!knew)
 		return -ENOMEM;
 	knew->index = idx;
@@ -4160,34 +4159,52 @@
 static void stac_toggle_power_map(struct hda_codec *codec, hda_nid_t nid,
 				  int enable);
 
+static inline int get_int_hint(struct hda_codec *codec, const char *key,
+			       int *valp)
+{
+	const char *p;
+	p = snd_hda_get_hint(codec, key);
+	if (p) {
+		unsigned long val;
+		if (!strict_strtoul(p, 0, &val)) {
+			*valp = val;
+			return 1;
+		}
+	}
+	return 0;
+}
+
 /* override some hints from the hwdep entry */
 static void stac_store_hints(struct hda_codec *codec)
 {
 	struct sigmatel_spec *spec = codec->spec;
-	const char *p;
 	int val;
 
 	val = snd_hda_get_bool_hint(codec, "hp_detect");
 	if (val >= 0)
 		spec->hp_detect = val;
-	p = snd_hda_get_hint(codec, "gpio_mask");
-	if (p) {
-		spec->gpio_mask = simple_strtoul(p, NULL, 0);
+	if (get_int_hint(codec, "gpio_mask", &spec->gpio_mask)) {
 		spec->eapd_mask = spec->gpio_dir = spec->gpio_data =
 			spec->gpio_mask;
 	}
-	p = snd_hda_get_hint(codec, "gpio_dir");
-	if (p)
-		spec->gpio_dir = simple_strtoul(p, NULL, 0) & spec->gpio_mask;
-	p = snd_hda_get_hint(codec, "gpio_data");
-	if (p)
-		spec->gpio_data = simple_strtoul(p, NULL, 0) & spec->gpio_mask;
-	p = snd_hda_get_hint(codec, "eapd_mask");
-	if (p)
-		spec->eapd_mask = simple_strtoul(p, NULL, 0) & spec->gpio_mask;
+	if (get_int_hint(codec, "gpio_dir", &spec->gpio_dir))
+		spec->gpio_mask &= spec->gpio_mask;
+	if (get_int_hint(codec, "gpio_data", &spec->gpio_data))
+		spec->gpio_dir &= spec->gpio_mask;
+	if (get_int_hint(codec, "eapd_mask", &spec->eapd_mask))
+		spec->eapd_mask &= spec->gpio_mask;
+	if (get_int_hint(codec, "gpio_mute", &spec->gpio_mute))
+		spec->gpio_mute &= spec->gpio_mask;
 	val = snd_hda_get_bool_hint(codec, "eapd_switch");
 	if (val >= 0)
 		spec->eapd_switch = val;
+	get_int_hint(codec, "gpio_led_polarity", &spec->gpio_led_polarity);
+	if (get_int_hint(codec, "gpio_led", &spec->gpio_led)) {
+		spec->gpio_mask |= spec->gpio_led;
+		spec->gpio_dir |= spec->gpio_led;
+		if (spec->gpio_led_polarity)
+			spec->gpio_data |= spec->gpio_led;
+	}
 }
 
 static int stac92xx_init(struct hda_codec *codec)
@@ -4372,18 +4389,8 @@
 static void stac92xx_shutup(struct hda_codec *codec)
 {
 	struct sigmatel_spec *spec = codec->spec;
-	int i;
-	hda_nid_t nid;
 
-	/* reset each pin before powering down DAC/ADC to avoid click noise */
-	nid = codec->start_nid;
-	for (i = 0; i < codec->num_nodes; i++, nid++) {
-		unsigned int wcaps = get_wcaps(codec, nid);
-		unsigned int wid_type = get_wcaps_type(wcaps);
-		if (wid_type == AC_WID_PIN)
-			snd_hda_codec_read(codec, nid, 0,
-				AC_VERB_SET_PIN_WIDGET_CONTROL, 0);
-	}
+	snd_hda_shutup_pins(codec);
 
 	if (spec->eapd_mask)
 		stac_gpio_set(codec, spec->gpio_mask,
@@ -5404,6 +5411,54 @@
 		return 0;
 }
 
+/* HP dv7 bass switch - GPIO5 */
+#define stac_hp_bass_gpio_info	snd_ctl_boolean_mono_info
+static int stac_hp_bass_gpio_get(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	ucontrol->value.integer.value[0] = !!(spec->gpio_data & 0x20);
+	return 0;
+}
+
+static int stac_hp_bass_gpio_put(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
+	struct sigmatel_spec *spec = codec->spec;
+	unsigned int gpio_data;
+
+	gpio_data = (spec->gpio_data & ~0x20) |
+		(ucontrol->value.integer.value[0] ? 0x20 : 0);
+	if (gpio_data == spec->gpio_data)
+		return 0;
+	spec->gpio_data = gpio_data;
+	stac_gpio_set(codec, spec->gpio_mask, spec->gpio_dir, spec->gpio_data);
+	return 1;
+}
+
+static struct snd_kcontrol_new stac_hp_bass_sw_ctrl = {
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.info = stac_hp_bass_gpio_info,
+	.get = stac_hp_bass_gpio_get,
+	.put = stac_hp_bass_gpio_put,
+};
+
+static int stac_add_hp_bass_switch(struct hda_codec *codec)
+{
+	struct sigmatel_spec *spec = codec->spec;
+
+	if (!stac_control_new(spec, &stac_hp_bass_sw_ctrl,
+			      "Bass Speaker Playback Switch", 0))
+		return -ENOMEM;
+
+	spec->gpio_mask |= 0x20;
+	spec->gpio_dir |= 0x20;
+	spec->gpio_data |= 0x20;
+	return 0;
+}
+
 static int patch_stac92hd71bxx(struct hda_codec *codec)
 {
 	struct sigmatel_spec *spec;
@@ -5645,6 +5700,15 @@
 		return err;
 	}
 
+	/* enable bass on HP dv7 */
+	if (spec->board_config == STAC_HP_DV5) {
+		unsigned int cap;
+		cap = snd_hda_param_read(codec, 0x1, AC_PAR_GPIO_CAP);
+		cap &= AC_GPIO_IO_COUNT;
+		if (cap >= 6)
+			stac_add_hp_bass_switch(codec);
+	}
+
 	codec->proc_widget_hook = stac92hd7x_proc_hook;
 
 	return 0;
diff --git a/sound/pci/hda/patch_via.c b/sound/pci/hda/patch_via.c
index b70e26a..9ddc373 100644
--- a/sound/pci/hda/patch_via.c
+++ b/sound/pci/hda/patch_via.c
@@ -54,6 +54,8 @@
 #include "hda_codec.h"
 #include "hda_local.h"
 
+#define NID_MAPPING		(-1)
+
 /* amp values */
 #define AMP_VAL_IDX_SHIFT	19
 #define AMP_VAL_IDX_MASK	(0x0f<<19)
@@ -157,6 +159,19 @@
 #endif
 };
 
+static struct via_spec * via_new_spec(struct hda_codec *codec)
+{
+	struct via_spec *spec;
+
+	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	if (spec == NULL)
+		return NULL;
+
+	codec->spec = spec;
+	spec->codec = codec;
+	return spec;
+}
+
 static enum VIA_HDA_CODEC get_codec_type(struct hda_codec *codec)
 {
 	u32 vendor_id = codec->vendor_id;
@@ -443,11 +458,27 @@
 	if (!knew->name)
 		return -ENOMEM;
 	if (get_amp_nid_(val))
-		knew->subdevice = HDA_SUBDEV_NID_FLAG | get_amp_nid_(val);
+		knew->subdevice = HDA_SUBDEV_AMP_FLAG;
 	knew->private_value = val;
 	return 0;
 }
 
+static struct snd_kcontrol_new *via_clone_control(struct via_spec *spec,
+						struct snd_kcontrol_new *tmpl)
+{
+	struct snd_kcontrol_new *knew;
+
+	snd_array_init(&spec->kctls, sizeof(*knew), 32);
+	knew = snd_array_new(&spec->kctls);
+	if (!knew)
+		return NULL;
+	*knew = *tmpl;
+	knew->name = kstrdup(tmpl->name, GFP_KERNEL);
+	if (!knew->name)
+		return NULL;
+	return 0;
+}
+
 static void via_free_kctls(struct hda_codec *codec)
 {
 	struct via_spec *spec = codec->spec;
@@ -1088,24 +1119,9 @@
 				  struct snd_ctl_elem_value *ucontrol)
 {
 	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
-	struct via_spec *spec = codec->spec;
-	hda_nid_t nid;
+	hda_nid_t nid = kcontrol->private_value;
 	unsigned int pinsel;
 
-	switch (spec->codec_type) {
-	case VT1718S:
-		nid = 0x34;
-		break;
-	case VT2002P:
-		nid = 0x35;
-		break;
-	case VT1812:
-		nid = 0x3d;
-		break;
-	default:
-		nid = spec->autocfg.hp_pins[0];
-		break;
-	}
 	/* use !! to translate conn sel 2 for VT1718S */
 	pinsel = !!snd_hda_codec_read(codec, nid, 0,
 				      AC_VERB_GET_CONNECT_SEL,
@@ -1127,29 +1143,24 @@
 	}
 }
 
+static hda_nid_t side_mute_channel(struct via_spec *spec)
+{
+	switch (spec->codec_type) {
+	case VT1708:		return 0x1b;
+	case VT1709_10CH:	return 0x29;
+	case VT1708B_8CH:	/* fall thru */
+	case VT1708S:		return 0x27;
+	default:		return 0;
+	}
+}
+
 static int update_side_mute_status(struct hda_codec *codec)
 {
 	/* mute side channel */
 	struct via_spec *spec = codec->spec;
 	unsigned int parm = spec->hp_independent_mode
 		? AMP_OUT_MUTE : AMP_OUT_UNMUTE;
-	hda_nid_t sw3;
-
-	switch (spec->codec_type) {
-	case VT1708:
-		sw3 = 0x1b;
-		break;
-	case VT1709_10CH:
-		sw3 = 0x29;
-		break;
-	case VT1708B_8CH:
-	case VT1708S:
-		sw3 = 0x27;
-		break;
-	default:
-		sw3 = 0;
-		break;
-	}
+	hda_nid_t sw3 = side_mute_channel(spec);
 
 	if (sw3)
 		snd_hda_codec_write(codec, sw3, 0, AC_VERB_SET_AMP_GAIN_MUTE,
@@ -1162,28 +1173,11 @@
 {
 	struct hda_codec *codec = snd_kcontrol_chip(kcontrol);
 	struct via_spec *spec = codec->spec;
-	hda_nid_t nid = spec->autocfg.hp_pins[0];
+	hda_nid_t nid = kcontrol->private_value;
 	unsigned int pinsel = ucontrol->value.enumerated.item[0];
 	/* Get Independent Mode index of headphone pin widget */
 	spec->hp_independent_mode = spec->hp_independent_mode_index == pinsel
 		? 1 : 0;
-
-	switch (spec->codec_type) {
-	case VT1718S:
-		nid = 0x34;
-		pinsel = pinsel ? 2 : 0; /* indep HP use AOW4 (index 2) */
-		spec->multiout.num_dacs = 4;
-		break;
-	case VT2002P:
-		nid = 0x35;
-		break;
-	case VT1812:
-		nid = 0x3d;
-		break;
-	default:
-		nid = spec->autocfg.hp_pins[0];
-		break;
-	}
 	snd_hda_codec_write(codec, nid, 0, AC_VERB_SET_CONNECT_SEL, pinsel);
 
 	if (spec->multiout.hp_nid && spec->multiout.hp_nid
@@ -1207,18 +1201,55 @@
 	return 0;
 }
 
-static struct snd_kcontrol_new via_hp_mixer[] = {
+static struct snd_kcontrol_new via_hp_mixer[2] = {
 	{
 		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 		.name = "Independent HP",
-		.count = 1,
 		.info = via_independent_hp_info,
 		.get = via_independent_hp_get,
 		.put = via_independent_hp_put,
 	},
-	{ } /* end */
+	{
+		.iface = NID_MAPPING,
+		.name = "Independent HP",
+	},
 };
 
+static int via_hp_build(struct via_spec *spec)
+{
+	struct snd_kcontrol_new *knew;
+	hda_nid_t nid;
+
+	knew = via_clone_control(spec, &via_hp_mixer[0]);
+	if (knew == NULL)
+		return -ENOMEM;
+
+	switch (spec->codec_type) {
+	case VT1718S:
+		nid = 0x34;
+		break;
+	case VT2002P:
+		nid = 0x35;
+		break;
+	case VT1812:
+		nid = 0x3d;
+		break;
+	default:
+		nid = spec->autocfg.hp_pins[0];
+		break;
+	}
+
+	knew->subdevice = HDA_SUBDEV_NID_FLAG | nid;
+	knew->private_value = nid;
+
+	knew = via_clone_control(spec, &via_hp_mixer[1]);
+	if (knew == NULL)
+		return -ENOMEM;
+	knew->subdevice = side_mute_channel(spec);
+
+	return 0;
+}
+
 static void notify_aa_path_ctls(struct hda_codec *codec)
 {
 	int i;
@@ -1376,7 +1407,7 @@
 	return 1;
 }
 
-static struct snd_kcontrol_new via_smart51_mixer[] = {
+static struct snd_kcontrol_new via_smart51_mixer[2] = {
 	{
 	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 	 .name = "Smart 5.1",
@@ -1385,9 +1416,36 @@
 	 .get = via_smart51_get,
 	 .put = via_smart51_put,
 	 },
-	{}			/* end */
+	{
+	 .iface = NID_MAPPING,
+	 .name = "Smart 5.1",
+	}
 };
 
+static int via_smart51_build(struct via_spec *spec)
+{
+	struct snd_kcontrol_new *knew;
+	int index[] = { AUTO_PIN_MIC, AUTO_PIN_FRONT_MIC, AUTO_PIN_LINE };
+	hda_nid_t nid;
+	int i;
+
+	knew = via_clone_control(spec, &via_smart51_mixer[0]);
+	if (knew == NULL)
+		return -ENOMEM;
+
+	for (i = 0; i < ARRAY_SIZE(index); i++) {
+		nid = spec->autocfg.input_pins[index[i]];
+		if (nid) {
+			knew = via_clone_control(spec, &via_smart51_mixer[1]);
+			if (knew == NULL)
+				return -ENOMEM;
+			knew->subdevice = nid;
+		}
+	}
+
+	return 0;
+}
+
 /* capture mixer elements */
 static struct snd_kcontrol_new vt1708_capture_mixer[] = {
 	HDA_CODEC_VOLUME("Capture Volume", 0x15, 0x0, HDA_INPUT),
@@ -1819,8 +1877,9 @@
 static int via_build_controls(struct hda_codec *codec)
 {
 	struct via_spec *spec = codec->spec;
-	int err;
-	int i;
+	struct snd_kcontrol *kctl;
+	struct snd_kcontrol_new *knew;
+	int err, i;
 
 	for (i = 0; i < spec->num_mixers; i++) {
 		err = snd_hda_add_new_ctls(codec, spec->mixers[i]);
@@ -1845,6 +1904,27 @@
 			return err;
 	}
 
+	/* assign Capture Source enums to NID */
+	kctl = snd_hda_find_mixer_ctl(codec, "Input Source");
+	for (i = 0; kctl && i < kctl->count; i++) {
+		err = snd_hda_add_nid(codec, kctl, i, spec->mux_nids[i]);
+		if (err < 0)
+			return err;
+	}
+
+	/* other nid->control mapping */
+	for (i = 0; i < spec->num_mixers; i++) {
+		for (knew = spec->mixers[i]; knew->name; knew++) {
+			if (knew->iface != NID_MAPPING)
+				continue;
+			kctl = snd_hda_find_mixer_ctl(codec, knew->name);
+			if (kctl == NULL)
+				continue;
+			err = snd_hda_add_nid(codec, kctl, 0,
+					      knew->subdevice);
+		}
+	}
+
 	/* init power states */
 	set_jack_power_state(codec);
 	analog_low_current_mode(codec, 1);
@@ -2481,9 +2561,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 	return 1;
 }
 
@@ -2554,12 +2634,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1708_parse_auto_config(codec);
 	if (err < 0) {
@@ -2597,7 +2675,6 @@
 #ifdef CONFIG_SND_HDA_POWER_SAVE
 	spec->loopback.amplist = vt1708_loopbacks;
 #endif
-	spec->codec = codec;
 	INIT_DELAYED_WORK(&spec->vt1708_hp_work, vt1708_update_hp_jack_state);
 	return 0;
 }
@@ -3010,9 +3087,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 	return 1;
 }
 
@@ -3032,12 +3109,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	err = vt1709_parse_auto_config(codec);
 	if (err < 0) {
 		via_free(codec);
@@ -3126,12 +3201,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	err = vt1709_parse_auto_config(codec);
 	if (err < 0) {
 		via_free(codec);
@@ -3581,9 +3654,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 	return 1;
 }
 
@@ -3605,12 +3678,10 @@
 	if (get_codec_type(codec) == VT1708BCE)
 		return patch_vt1708S(codec);
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1708B_parse_auto_config(codec);
 	if (err < 0) {
@@ -3657,12 +3728,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1708B_parse_auto_config(codec);
 	if (err < 0) {
@@ -4071,9 +4140,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 	return 1;
 }
 
@@ -4103,12 +4172,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1708S_parse_auto_config(codec);
 	if (err < 0) {
@@ -4443,7 +4510,7 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
 	return 1;
 }
@@ -4464,12 +4531,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1702_parse_auto_config(codec);
 	if (err < 0) {
@@ -4865,9 +4930,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 
 	return 1;
 }
@@ -4888,12 +4953,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1718S_parse_auto_config(codec);
 	if (err < 0) {
@@ -5014,6 +5077,7 @@
 	{
 	 .iface = SNDRV_CTL_ELEM_IFACE_MIXER,
 	 .name = "Digital Mic Capture Switch",
+	 .subdevice = HDA_SUBDEV_NID_FLAG | 0x26,
 	 .count = 1,
 	 .info = vt1716s_dmic_info,
 	 .get = vt1716s_dmic_get,
@@ -5361,9 +5425,9 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
-	spec->mixers[spec->num_mixers++] = via_smart51_mixer;
+	via_smart51_build(spec);
 
 	return 1;
 }
@@ -5384,12 +5448,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1716S_parse_auto_config(codec);
 	if (err < 0) {
@@ -5719,7 +5781,7 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
 	return 1;
 }
@@ -5741,12 +5803,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt2002P_parse_auto_config(codec);
 	if (err < 0) {
@@ -6070,7 +6130,7 @@
 	spec->input_mux = &spec->private_imux[0];
 
 	if (spec->hp_mux)
-		spec->mixers[spec->num_mixers++] = via_hp_mixer;
+		via_hp_build(spec);
 
 	return 1;
 }
@@ -6092,12 +6152,10 @@
 	int err;
 
 	/* create a codec specific record */
-	spec = kzalloc(sizeof(*spec), GFP_KERNEL);
+	spec = via_new_spec(codec);
 	if (spec == NULL)
 		return -ENOMEM;
 
-	codec->spec = spec;
-
 	/* automatic parse from the BIOS config */
 	err = vt1812_parse_auto_config(codec);
 	if (err < 0) {
diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile
index 389941cf..acd8f15 100644
--- a/sound/pci/oxygen/Makefile
+++ b/sound/pci/oxygen/Makefile
@@ -2,7 +2,7 @@
 snd-hifier-objs := hifier.o
 snd-oxygen-objs := oxygen.o
 snd-virtuoso-objs := virtuoso.o xonar_lib.o \
-	xonar_pcm179x.o xonar_cs43xx.o xonar_hdmi.o
+	xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o
 
 obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o
 obj-$(CONFIG_SND_HIFIER) += snd-hifier.o
diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c
index 6accaf9..563b6f5 100644
--- a/sound/pci/oxygen/virtuoso.c
+++ b/sound/pci/oxygen/virtuoso.c
@@ -49,6 +49,7 @@
 	{ OXYGEN_PCI_SUBID(0x1043, 0x834f) },
 	{ OXYGEN_PCI_SUBID(0x1043, 0x835c) },
 	{ OXYGEN_PCI_SUBID(0x1043, 0x835d) },
+	{ OXYGEN_PCI_SUBID(0x1043, 0x838e) },
 	{ OXYGEN_PCI_SUBID_BROKEN_EEPROM },
 	{ }
 };
@@ -61,6 +62,8 @@
 		return 0;
 	if (get_xonar_cs43xx_model(chip, id) >= 0)
 		return 0;
+	if (get_xonar_wm87x6_model(chip, id) >= 0)
+		return 0;
 	return -EINVAL;
 }
 
diff --git a/sound/pci/oxygen/wm8766.h b/sound/pci/oxygen/wm8766.h
new file mode 100644
index 0000000..e0e849a
--- /dev/null
+++ b/sound/pci/oxygen/wm8766.h
@@ -0,0 +1,73 @@
+#ifndef WM8766_H_INCLUDED
+#define WM8766_H_INCLUDED
+
+#define WM8766_LDA1		0x00
+#define WM8766_RDA1		0x01
+#define WM8766_DAC_CTRL		0x02
+#define WM8766_INT_CTRL		0x03
+#define WM8766_LDA2		0x04
+#define WM8766_RDA2		0x05
+#define WM8766_LDA3		0x06
+#define WM8766_RDA3		0x07
+#define WM8766_MASTDA		0x08
+#define WM8766_DAC_CTRL2	0x09
+#define WM8766_DAC_CTRL3	0x0a
+#define WM8766_MUTE1		0x0c
+#define WM8766_MUTE2		0x0f
+#define WM8766_RESET		0x1f
+
+/* LDAx/RDAx/MASTDA */
+#define WM8766_ATT_MASK		0x0ff
+#define WM8766_UPDATE		0x100
+/* DAC_CTRL */
+#define WM8766_MUTEALL		0x001
+#define WM8766_DEEMPALL		0x002
+#define WM8766_PWDN		0x004
+#define WM8766_ATC		0x008
+#define WM8766_IZD		0x010
+#define WM8766_PL_LEFT_MASK	0x060
+#define WM8766_PL_LEFT_MUTE	0x000
+#define WM8766_PL_LEFT_LEFT	0x020
+#define WM8766_PL_LEFT_RIGHT	0x040
+#define WM8766_PL_LEFT_LRMIX	0x060
+#define WM8766_PL_RIGHT_MASK	0x180
+#define WM8766_PL_RIGHT_MUTE	0x000
+#define WM8766_PL_RIGHT_LEFT	0x080
+#define WM8766_PL_RIGHT_RIGHT	0x100
+#define WM8766_PL_RIGHT_LRMIX	0x180
+/* INT_CTRL */
+#define WM8766_FMT_MASK		0x003
+#define WM8766_FMT_RJUST	0x000
+#define WM8766_FMT_LJUST	0x001
+#define WM8766_FMT_I2S		0x002
+#define WM8766_FMT_DSP		0x003
+#define WM8766_LRP		0x004
+#define WM8766_BCP		0x008
+#define WM8766_IWL_MASK		0x030
+#define WM8766_IWL_16		0x000
+#define WM8766_IWL_20		0x010
+#define WM8766_IWL_24		0x020
+#define WM8766_IWL_32		0x030
+#define WM8766_PHASE_MASK	0x1c0
+/* DAC_CTRL2 */
+#define WM8766_ZCD		0x001
+#define WM8766_DZFM_MASK	0x006
+#define WM8766_DMUTE_MASK	0x038
+#define WM8766_DEEMP_MASK	0x1c0
+/* DAC_CTRL3 */
+#define WM8766_DACPD_MASK	0x00e
+#define WM8766_PWRDNALL		0x010
+#define WM8766_MS		0x020
+#define WM8766_RATE_MASK	0x1c0
+#define WM8766_RATE_128		0x000
+#define WM8766_RATE_192		0x040
+#define WM8766_RATE_256		0x080
+#define WM8766_RATE_384		0x0c0
+#define WM8766_RATE_512		0x100
+#define WM8766_RATE_768		0x140
+/* MUTE1 */
+#define WM8766_MPD1		0x040
+/* MUTE2 */
+#define WM8766_MPD2		0x020
+
+#endif
diff --git a/sound/pci/oxygen/wm8776.h b/sound/pci/oxygen/wm8776.h
new file mode 100644
index 0000000..1a96f56
--- /dev/null
+++ b/sound/pci/oxygen/wm8776.h
@@ -0,0 +1,177 @@
+#ifndef WM8776_H_INCLUDED
+#define WM8776_H_INCLUDED
+
+/*
+ * the following register names are from:
+ * wm8776.h  --  WM8776 ASoC driver
+ *
+ * Copyright 2009 Wolfson Microelectronics plc
+ *
+ * Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#define WM8776_HPLVOL		0x00
+#define WM8776_HPRVOL		0x01
+#define WM8776_HPMASTER		0x02
+#define WM8776_DACLVOL		0x03
+#define WM8776_DACRVOL		0x04
+#define WM8776_DACMASTER	0x05
+#define WM8776_PHASESWAP	0x06
+#define WM8776_DACCTRL1		0x07
+#define WM8776_DACMUTE		0x08
+#define WM8776_DACCTRL2		0x09
+#define WM8776_DACIFCTRL	0x0a
+#define WM8776_ADCIFCTRL	0x0b
+#define WM8776_MSTRCTRL		0x0c
+#define WM8776_PWRDOWN		0x0d
+#define WM8776_ADCLVOL		0x0e
+#define WM8776_ADCRVOL		0x0f
+#define WM8776_ALCCTRL1		0x10
+#define WM8776_ALCCTRL2		0x11
+#define WM8776_ALCCTRL3		0x12
+#define WM8776_NOISEGATE	0x13
+#define WM8776_LIMITER		0x14
+#define WM8776_ADCMUX		0x15
+#define WM8776_OUTMUX		0x16
+#define WM8776_RESET		0x17
+
+
+/* HPLVOL/HPRVOL/HPMASTER */
+#define WM8776_HPATT_MASK	0x07f
+#define WM8776_HPZCEN		0x080
+#define WM8776_UPDATE		0x100
+
+/* DACLVOL/DACRVOL/DACMASTER */
+#define WM8776_DATT_MASK	0x0ff
+/*#define WM8776_UPDATE		0x100*/
+
+/* PHASESWAP */
+#define WM8776_PH_MASK		0x003
+
+/* DACCTRL1 */
+#define WM8776_DZCEN		0x001
+#define WM8776_ATC		0x002
+#define WM8776_IZD		0x004
+#define WM8776_TOD		0x008
+#define WM8776_PL_LEFT_MASK	0x030
+#define WM8776_PL_LEFT_MUTE	0x000
+#define WM8776_PL_LEFT_LEFT	0x010
+#define WM8776_PL_LEFT_RIGHT	0x020
+#define WM8776_PL_LEFT_LRMIX	0x030
+#define WM8776_PL_RIGHT_MASK	0x0c0
+#define WM8776_PL_RIGHT_MUTE	0x000
+#define WM8776_PL_RIGHT_LEFT	0x040
+#define WM8776_PL_RIGHT_RIGHT	0x080
+#define WM8776_PL_RIGHT_LRMIX	0x0c0
+
+/* DACMUTE */
+#define WM8776_DMUTE		0x001
+
+/* DACCTRL2 */
+#define WM8776_DEEMPH		0x001
+#define WM8776_DZFM_MASK	0x006
+#define WM8776_DZFM_NONE	0x000
+#define WM8776_DZFM_LR		0x002
+#define WM8776_DZFM_BOTH	0x004
+#define WM8776_DZFM_EITHER	0x006
+
+/* DACIFCTRL */
+#define WM8776_DACFMT_MASK	0x003
+#define WM8776_DACFMT_RJUST	0x000
+#define WM8776_DACFMT_LJUST	0x001
+#define WM8776_DACFMT_I2S	0x002
+#define WM8776_DACFMT_DSP	0x003
+#define WM8776_DACLRP		0x004
+#define WM8776_DACBCP		0x008
+#define WM8776_DACWL_MASK	0x030
+#define WM8776_DACWL_16		0x000
+#define WM8776_DACWL_20		0x010
+#define WM8776_DACWL_24		0x020
+#define WM8776_DACWL_32		0x030
+
+/* ADCIFCTRL */
+#define WM8776_ADCFMT_MASK	0x003
+#define WM8776_ADCFMT_RJUST	0x000
+#define WM8776_ADCFMT_LJUST	0x001
+#define WM8776_ADCFMT_I2S	0x002
+#define WM8776_ADCFMT_DSP	0x003
+#define WM8776_ADCLRP		0x004
+#define WM8776_ADCBCP		0x008
+#define WM8776_ADCWL_MASK	0x030
+#define WM8776_ADCWL_16		0x000
+#define WM8776_ADCWL_20		0x010
+#define WM8776_ADCWL_24		0x020
+#define WM8776_ADCWL_32		0x030
+#define WM8776_ADCMCLK		0x040
+#define WM8776_ADCHPD		0x100
+
+/* MSTRCTRL */
+#define WM8776_ADCRATE_MASK	0x007
+#define WM8776_ADCRATE_256	0x002
+#define WM8776_ADCRATE_384	0x003
+#define WM8776_ADCRATE_512	0x004
+#define WM8776_ADCRATE_768	0x005
+#define WM8776_ADCOSR		0x008
+#define WM8776_DACRATE_MASK	0x070
+#define WM8776_DACRATE_128	0x000
+#define WM8776_DACRATE_192	0x010
+#define WM8776_DACRATE_256	0x020
+#define WM8776_DACRATE_384	0x030
+#define WM8776_DACRATE_512	0x040
+#define WM8776_DACRATE_768	0x050
+#define WM8776_DACMS		0x080
+#define WM8776_ADCMS		0x100
+
+/* PWRDOWN */
+#define WM8776_PDWN		0x001
+#define WM8776_ADCPD		0x002
+#define WM8776_DACPD		0x004
+#define WM8776_HPPD		0x008
+#define WM8776_AINPD		0x040
+
+/* ADCLVOL/ADCRVOL */
+#define WM8776_AGMASK		0x0ff
+#define WM8776_ZCA		0x100
+
+/* ALCCTRL1 */
+#define WM8776_LCT_MASK		0x00f
+#define WM8776_MAXGAIN_MASK	0x070
+#define WM8776_LCSEL_MASK	0x180
+#define WM8776_LCSEL_LIMITER	0x000
+#define WM8776_LCSEL_ALC_RIGHT 0x080
+#define WM8776_LCSEL_ALC_LEFT	0x100
+#define WM8776_LCSEL_ALC_STEREO	0x180
+
+/* ALCCTRL2 */
+#define WM8776_HLD_MASK		0x00f
+#define WM8776_ALCZC		0x080
+#define WM8776_LCEN		0x100
+
+/* ALCCTRL3 */
+#define WM8776_ATK_MASK		0x00f
+#define WM8776_DCY_MASK		0x0f0
+
+/* NOISEGATE */
+#define WM8776_NGAT		0x001
+#define WM8776_NGTH_MASK	0x01c
+
+/* LIMITER */
+#define WM8776_MAXATTEN_MASK	0x00f
+#define WM8776_TRANWIN_MASK	0x070
+
+/* ADCMUX */
+#define WM8776_AMX_MASK		0x01f
+#define WM8776_MUTERA		0x040
+#define WM8776_MUTELA		0x080
+#define WM8776_LRBOTH		0x100
+
+/* OUTMUX */
+#define WM8776_MX_DAC		0x001
+#define WM8776_MX_AUX		0x002
+#define WM8776_MX_BYPASS	0x004
+
+#endif
diff --git a/sound/pci/oxygen/xonar.h b/sound/pci/oxygen/xonar.h
index 89b3ed8..b35343b 100644
--- a/sound/pci/oxygen/xonar.h
+++ b/sound/pci/oxygen/xonar.h
@@ -35,6 +35,8 @@
 			    const struct pci_device_id *id);
 int get_xonar_cs43xx_model(struct oxygen *chip,
 			   const struct pci_device_id *id);
+int get_xonar_wm87x6_model(struct oxygen *chip,
+			   const struct pci_device_id *id);
 
 /* HDMI helper functions */
 
diff --git a/sound/pci/oxygen/xonar_wm87x6.c b/sound/pci/oxygen/xonar_wm87x6.c
new file mode 100644
index 0000000..7754db1
--- /dev/null
+++ b/sound/pci/oxygen/xonar_wm87x6.c
@@ -0,0 +1,1021 @@
+/*
+ * card driver for models with WM8776/WM8766 DACs (Xonar DS)
+ *
+ * Copyright (c) Clemens Ladisch <clemens@ladisch.de>
+ *
+ *
+ *  This driver is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License, version 2.
+ *
+ *  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 driver; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Xonar DS
+ * --------
+ *
+ * CMI8788:
+ *
+ * SPI 0 -> WM8766 (surround, center/LFE, back)
+ * SPI 1 -> WM8776 (front, input)
+ *
+ * GPIO 4 <- headphone detect
+ * GPIO 6 -> route input jack to input 1/2 (1/0)
+ * GPIO 7 -> enable output to speakers
+ * GPIO 8 -> enable output to speakers
+ */
+
+#include <linux/pci.h>
+#include <linux/delay.h>
+#include <sound/control.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+#include "xonar.h"
+#include "wm8776.h"
+#include "wm8766.h"
+
+#define GPIO_DS_HP_DETECT	0x0010
+#define GPIO_DS_INPUT_ROUTE	0x0040
+#define GPIO_DS_OUTPUT_ENABLE	0x0180
+
+#define LC_CONTROL_LIMITER	0x40000000
+#define LC_CONTROL_ALC		0x20000000
+
+struct xonar_wm87x6 {
+	struct xonar_generic generic;
+	u16 wm8776_regs[0x17];
+	u16 wm8766_regs[0x10];
+	struct snd_kcontrol *lc_controls[13];
+};
+
+static void wm8776_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (1 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8776_regs)) {
+		if (reg >= WM8776_HPLVOL || reg <= WM8776_DACMASTER)
+			value &= ~WM8776_UPDATE;
+		data->wm8776_regs[reg] = value;
+	}
+}
+
+static void wm8776_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8776_regs) ||
+	    value != data->wm8776_regs[reg])
+		wm8776_write(chip, reg, value);
+}
+
+static void wm8766_write(struct oxygen *chip,
+			 unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER |
+			 OXYGEN_SPI_DATA_LENGTH_2 |
+			 OXYGEN_SPI_CLOCK_160 |
+			 (0 << OXYGEN_SPI_CODEC_SHIFT) |
+			 OXYGEN_SPI_CEN_LATCH_CLOCK_LO,
+			 (reg << 9) | value);
+	if (reg < ARRAY_SIZE(data->wm8766_regs))
+		data->wm8766_regs[reg] = value;
+}
+
+static void wm8766_write_cached(struct oxygen *chip,
+				unsigned int reg, unsigned int value)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (reg >= ARRAY_SIZE(data->wm8766_regs) ||
+	    value != data->wm8766_regs[reg]) {
+		if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) ||
+		    (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA))
+			value &= ~WM8766_UPDATE;
+		wm8766_write(chip, reg, value);
+	}
+}
+
+static void wm8776_registers_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	wm8776_write(chip, WM8776_RESET, 0);
+	wm8776_write(chip, WM8776_DACCTRL1, WM8776_DZCEN |
+		     WM8776_PL_LEFT_LEFT | WM8776_PL_RIGHT_RIGHT);
+	wm8776_write(chip, WM8776_DACMUTE, chip->dac_mute ? WM8776_DMUTE : 0);
+	wm8776_write(chip, WM8776_DACIFCTRL,
+		     WM8776_DACFMT_LJUST | WM8776_DACWL_24);
+	wm8776_write(chip, WM8776_ADCIFCTRL,
+		     data->wm8776_regs[WM8776_ADCIFCTRL]);
+	wm8776_write(chip, WM8776_MSTRCTRL, data->wm8776_regs[WM8776_MSTRCTRL]);
+	wm8776_write(chip, WM8776_PWRDOWN, data->wm8776_regs[WM8776_PWRDOWN]);
+	wm8776_write(chip, WM8776_HPLVOL, data->wm8776_regs[WM8776_HPLVOL]);
+	wm8776_write(chip, WM8776_HPRVOL, data->wm8776_regs[WM8776_HPRVOL] |
+		     WM8776_UPDATE);
+	wm8776_write(chip, WM8776_ADCLVOL, data->wm8776_regs[WM8776_ADCLVOL]);
+	wm8776_write(chip, WM8776_ADCRVOL, data->wm8776_regs[WM8776_ADCRVOL]);
+	wm8776_write(chip, WM8776_ADCMUX, data->wm8776_regs[WM8776_ADCMUX]);
+	wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0]);
+	wm8776_write(chip, WM8776_DACRVOL, chip->dac_volume[1] | WM8776_UPDATE);
+}
+
+static void wm8766_registers_init(struct oxygen *chip)
+{
+	wm8766_write(chip, WM8766_RESET, 0);
+	wm8766_write(chip, WM8766_INT_CTRL, WM8766_FMT_LJUST | WM8766_IWL_24);
+	wm8766_write(chip, WM8766_DAC_CTRL2,
+		     WM8766_ZCD | (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+	wm8766_write(chip, WM8766_LDA1, chip->dac_volume[2]);
+	wm8766_write(chip, WM8766_RDA1, chip->dac_volume[3]);
+	wm8766_write(chip, WM8766_LDA2, chip->dac_volume[4]);
+	wm8766_write(chip, WM8766_RDA2, chip->dac_volume[5]);
+	wm8766_write(chip, WM8766_LDA3, chip->dac_volume[6]);
+	wm8766_write(chip, WM8766_RDA3, chip->dac_volume[7] | WM8766_UPDATE);
+}
+
+static void wm8776_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->wm8776_regs[WM8776_HPLVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_HPRVOL] = (0x79 - 60) | WM8776_HPZCEN;
+	data->wm8776_regs[WM8776_ADCIFCTRL] =
+		WM8776_ADCFMT_LJUST | WM8776_ADCWL_24 | WM8776_ADCMCLK;
+	data->wm8776_regs[WM8776_MSTRCTRL] =
+		WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	data->wm8776_regs[WM8776_PWRDOWN] = WM8776_HPPD;
+	data->wm8776_regs[WM8776_ADCLVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCRVOL] = 0xa5 | WM8776_ZCA;
+	data->wm8776_regs[WM8776_ADCMUX] = 0x001;
+	wm8776_registers_init(chip);
+}
+
+static void xonar_ds_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	data->generic.anti_pop_delay = 300;
+	data->generic.output_enable_bit = GPIO_DS_OUTPUT_ENABLE;
+
+	wm8776_init(chip);
+	wm8766_registers_init(chip);
+
+	oxygen_write16_masked(chip, OXYGEN_GPIO_CONTROL, GPIO_DS_INPUT_ROUTE,
+			      GPIO_DS_HP_DETECT | GPIO_DS_INPUT_ROUTE);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_DS_INPUT_ROUTE);
+	oxygen_set_bits16(chip, OXYGEN_GPIO_INTERRUPT_MASK, GPIO_DS_HP_DETECT);
+	chip->interrupt_mask |= OXYGEN_INT_GPIO;
+
+	xonar_enable_output(chip);
+
+	snd_component_add(chip->card, "WM8776");
+	snd_component_add(chip->card, "WM8766");
+}
+
+static void xonar_ds_cleanup(struct oxygen *chip)
+{
+	xonar_disable_output(chip);
+}
+
+static void xonar_ds_suspend(struct oxygen *chip)
+{
+	xonar_ds_cleanup(chip);
+}
+
+static void xonar_ds_resume(struct oxygen *chip)
+{
+	wm8776_registers_init(chip);
+	wm8766_registers_init(chip);
+	xonar_enable_output(chip);
+}
+
+static void wm8776_adc_hardware_filter(unsigned int channel,
+				       struct snd_pcm_hardware *hardware)
+{
+	if (channel == PCM_A) {
+		hardware->rates = SNDRV_PCM_RATE_32000 |
+				  SNDRV_PCM_RATE_44100 |
+				  SNDRV_PCM_RATE_48000 |
+				  SNDRV_PCM_RATE_64000 |
+				  SNDRV_PCM_RATE_88200 |
+				  SNDRV_PCM_RATE_96000;
+		hardware->rate_max = 96000;
+	}
+}
+
+static void set_wm87x6_dac_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+}
+
+static void set_wm8776_adc_params(struct oxygen *chip,
+				  struct snd_pcm_hw_params *params)
+{
+	u16 reg;
+
+	reg = WM8776_ADCRATE_256 | WM8776_DACRATE_256;
+	if (params_rate(params) > 48000)
+		reg |= WM8776_ADCOSR;
+	wm8776_write_cached(chip, WM8776_MSTRCTRL, reg);
+}
+
+static void update_wm8776_volume(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_change;
+
+	if (chip->dac_volume[0] == chip->dac_volume[1]) {
+		if (chip->dac_volume[0] != data->wm8776_regs[WM8776_DACLVOL] ||
+		    chip->dac_volume[1] != data->wm8776_regs[WM8776_DACRVOL]) {
+			wm8776_write(chip, WM8776_DACMASTER,
+				     chip->dac_volume[0] | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_DACLVOL] = chip->dac_volume[0];
+			data->wm8776_regs[WM8776_DACRVOL] = chip->dac_volume[0];
+		}
+	} else {
+		to_change = (chip->dac_volume[0] !=
+			     data->wm8776_regs[WM8776_DACLVOL]) << 0;
+		to_change |= (chip->dac_volume[1] !=
+			      data->wm8776_regs[WM8776_DACLVOL]) << 1;
+		if (to_change & 1)
+			wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0] |
+				     ((to_change & 2) ? 0 : WM8776_UPDATE));
+		if (to_change & 2)
+			wm8776_write(chip, WM8776_DACRVOL,
+				     chip->dac_volume[1] | WM8776_UPDATE);
+	}
+}
+
+static void update_wm87x6_volume(struct oxygen *chip)
+{
+	static const u8 wm8766_regs[6] = {
+		WM8766_LDA1, WM8766_RDA1,
+		WM8766_LDA2, WM8766_RDA2,
+		WM8766_LDA3, WM8766_RDA3,
+	};
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	u8 to_change;
+
+	update_wm8776_volume(chip);
+	if (chip->dac_volume[2] == chip->dac_volume[3] &&
+	    chip->dac_volume[2] == chip->dac_volume[4] &&
+	    chip->dac_volume[2] == chip->dac_volume[5] &&
+	    chip->dac_volume[2] == chip->dac_volume[6] &&
+	    chip->dac_volume[2] == chip->dac_volume[7]) {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			if (chip->dac_volume[2] !=
+			    data->wm8766_regs[wm8766_regs[i]])
+				to_change = 1;
+		if (to_change) {
+			wm8766_write(chip, WM8766_MASTDA,
+				     chip->dac_volume[2] | WM8766_UPDATE);
+			for (i = 0; i < 6; ++i)
+				data->wm8766_regs[wm8766_regs[i]] =
+					chip->dac_volume[2];
+		}
+	} else {
+		to_change = 0;
+		for (i = 0; i < 6; ++i)
+			to_change |= (chip->dac_volume[2 + i] !=
+				      data->wm8766_regs[wm8766_regs[i]]) << i;
+		for (i = 0; i < 6; ++i)
+			if (to_change & (1 << i))
+				wm8766_write(chip, wm8766_regs[i],
+					     chip->dac_volume[2 + i] |
+					     ((to_change & (0x3e << i))
+					      ? 0 : WM8766_UPDATE));
+	}
+}
+
+static void update_wm8776_mute(struct oxygen *chip)
+{
+	wm8776_write_cached(chip, WM8776_DACMUTE,
+			    chip->dac_mute ? WM8776_DMUTE : 0);
+}
+
+static void update_wm87x6_mute(struct oxygen *chip)
+{
+	update_wm8776_mute(chip);
+	wm8766_write_cached(chip, WM8766_DAC_CTRL2, WM8766_ZCD |
+			    (chip->dac_mute ? WM8766_DMUTE_MASK : 0));
+}
+
+static void xonar_ds_gpio_changed(struct oxygen *chip)
+{
+	u16 bits;
+
+	bits = oxygen_read16(chip, OXYGEN_GPIO_DATA);
+	snd_printk(KERN_INFO "HP detect: %d\n", !!(bits & GPIO_DS_HP_DETECT));
+}
+
+static int wm8776_bit_switch_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+
+	value->value.integer.value[0] =
+		((data->wm8776_regs[reg_index] & bit) != 0) ^ invert;
+	return 0;
+}
+
+static int wm8776_bit_switch_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u16 bit = ctl->private_value & 0xffff;
+	u16 reg_value;
+	unsigned int reg_index = (ctl->private_value >> 16) & 0xff;
+	bool invert = (ctl->private_value >> 24) & 1;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg_value = data->wm8776_regs[reg_index] & ~bit;
+	if (value->value.integer.value[0] ^ invert)
+		reg_value |= bit;
+	changed = reg_value != data->wm8776_regs[reg_index];
+	if (changed)
+		wm8776_write(chip, reg_index, reg_value);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_info(struct snd_kcontrol *ctl,
+				  struct snd_ctl_elem_info *info)
+{
+	static const char *const hld[16] = {
+		"0 ms", "2.67 ms", "5.33 ms", "10.6 ms",
+		"21.3 ms", "42.7 ms", "85.3 ms", "171 ms",
+		"341 ms", "683 ms", "1.37 s", "2.73 s",
+		"5.46 s", "10.9 s", "21.8 s", "43.7 s",
+	};
+	static const char *const atk_lim[11] = {
+		"0.25 ms", "0.5 ms", "1 ms", "2 ms",
+		"4 ms", "8 ms", "16 ms", "32 ms",
+		"64 ms", "128 ms", "256 ms",
+	};
+	static const char *const atk_alc[11] = {
+		"8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms",
+		"134 ms", "269 ms", "538 ms", "1.08 s",
+		"2.15 s", "4.3 s", "8.6 s",
+	};
+	static const char *const dcy_lim[11] = {
+		"1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms",
+		"19.2 ms", "38.4 ms", "76.8 ms", "154 ms",
+		"307 ms", "614 ms", "1.23 s",
+	};
+	static const char *const dcy_alc[11] = {
+		"33.5 ms", "67.0 ms", "134 ms", "268 ms",
+		"536 ms", "1.07 s", "2.14 s", "4.29 s",
+		"8.58 s", "17.2 s", "34.3 s",
+	};
+	static const char *const tranwin[8] = {
+		"0 us", "62.5 us", "125 us", "250 us",
+		"500 us", "1 ms", "2 ms", "4 ms",
+	};
+	u8 max;
+	const char *const *names;
+
+	max = (ctl->private_value >> 12) & 0xf;
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = max + 1;
+	if (info->value.enumerated.item > max)
+		info->value.enumerated.item = max;
+	switch ((ctl->private_value >> 24) & 0x1f) {
+	case WM8776_ALCCTRL2:
+		names = hld;
+		break;
+	case WM8776_ALCCTRL3:
+		if (((ctl->private_value >> 20) & 0xf) == 0) {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = atk_lim;
+			else
+				names = atk_alc;
+		} else {
+			if (ctl->private_value & LC_CONTROL_LIMITER)
+				names = dcy_lim;
+			else
+				names = dcy_alc;
+		}
+		break;
+	case WM8776_LIMITER:
+		names = tranwin;
+		break;
+	default:
+		return -ENXIO;
+	}
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int wm8776_field_volume_info(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 1;
+	info->value.integer.min = (ctl->private_value >> 8) & 0xf;
+	info->value.integer.max = (ctl->private_value >> 12) & 0xf;
+	return 0;
+}
+
+static void wm8776_field_set_from_ctl(struct snd_kcontrol *ctl)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int value, reg_index, mode;
+	u8 min, max, shift;
+	u16 mask, reg_value;
+	bool invert;
+
+	if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+	    WM8776_LCSEL_LIMITER)
+		mode = LC_CONTROL_LIMITER;
+	else
+		mode = LC_CONTROL_ALC;
+	if (!(ctl->private_value & mode))
+		return;
+
+	value = ctl->private_value & 0xf;
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	mask = (ctl->private_value >> 16) & 0xf;
+	shift = (ctl->private_value >> 20) & 0xf;
+	reg_index = (ctl->private_value >> 24) & 0x1f;
+	invert = (ctl->private_value >> 29) & 0x1;
+
+	if (invert)
+		value = max - (value - min);
+	reg_value = data->wm8776_regs[reg_index];
+	reg_value &= ~(mask << shift);
+	reg_value |= value << shift;
+	wm8776_write_cached(chip, reg_index, reg_value);
+}
+
+static int wm8776_field_set(struct snd_kcontrol *ctl, unsigned int value)
+{
+	struct oxygen *chip = ctl->private_data;
+	u8 min, max;
+	int changed;
+
+	min = (ctl->private_value >> 8) & 0xf;
+	max = (ctl->private_value >> 12) & 0xf;
+	if (value < min || value > max)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value != (ctl->private_value & 0xf);
+	if (changed) {
+		ctl->private_value = (ctl->private_value & ~0xf) | value;
+		wm8776_field_set_from_ctl(ctl);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_field_enum_get(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	value->value.enumerated.item[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_volume_get(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	value->value.integer.value[0] = ctl->private_value & 0xf;
+	return 0;
+}
+
+static int wm8776_field_enum_put(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.enumerated.item[0]);
+}
+
+static int wm8776_field_volume_put(struct snd_kcontrol *ctl,
+				   struct snd_ctl_elem_value *value)
+{
+	return wm8776_field_set(ctl, value->value.integer.value[0]);
+}
+
+static int wm8776_hp_vol_info(struct snd_kcontrol *ctl,
+			      struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0x79 - 60;
+	info->value.integer.max = 0x7f;
+	return 0;
+}
+
+static int wm8776_hp_vol_get(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_hp_vol_put(struct snd_kcontrol *ctl,
+			     struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	u8 to_update;
+
+	mutex_lock(&chip->mutex);
+	to_update = (value->value.integer.value[0] !=
+		     (data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK))
+		<< 0;
+	to_update |= (value->value.integer.value[1] !=
+		      (data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK))
+		<< 1;
+	if (value->value.integer.value[0] == value->value.integer.value[1]) {
+		if (to_update) {
+			wm8776_write(chip, WM8776_HPMASTER,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+			data->wm8776_regs[WM8776_HPLVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+			data->wm8776_regs[WM8776_HPRVOL] =
+				value->value.integer.value[0] | WM8776_HPZCEN;
+		}
+	} else {
+		if (to_update & 1)
+			wm8776_write(chip, WM8776_HPLVOL,
+				     value->value.integer.value[0] |
+				     WM8776_HPZCEN |
+				     ((to_update & 2) ? 0 : WM8776_UPDATE));
+		if (to_update & 2)
+			wm8776_write(chip, WM8776_HPRVOL,
+				     value->value.integer.value[1] |
+				     WM8776_HPZCEN | WM8776_UPDATE);
+	}
+	mutex_unlock(&chip->mutex);
+	return to_update != 0;
+}
+
+static int wm8776_input_mux_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mux_bit = ctl->private_value;
+
+	value->value.integer.value[0] =
+		!!(data->wm8776_regs[WM8776_ADCMUX] & mux_bit);
+	return 0;
+}
+
+static int wm8776_input_mux_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mux_bit = ctl->private_value;
+	u16 reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCMUX];
+	if (value->value.integer.value[0]) {
+		reg &= ~0x003;
+		reg |= mux_bit;
+	} else
+		reg &= ~mux_bit;
+	changed = reg != data->wm8776_regs[WM8776_ADCMUX];
+	if (changed) {
+		oxygen_write16_masked(chip, OXYGEN_GPIO_DATA,
+				      reg & 1 ? GPIO_DS_INPUT_ROUTE : 0,
+				      GPIO_DS_INPUT_ROUTE);
+		wm8776_write(chip, WM8776_ADCMUX, reg);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_input_vol_info(struct snd_kcontrol *ctl,
+				 struct snd_ctl_elem_info *info)
+{
+	info->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	info->count = 2;
+	info->value.integer.min = 0xa5;
+	info->value.integer.max = 0xff;
+	return 0;
+}
+
+static int wm8776_input_vol_get(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	mutex_lock(&chip->mutex);
+	value->value.integer.value[0] =
+		data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK;
+	value->value.integer.value[1] =
+		data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK;
+	mutex_unlock(&chip->mutex);
+	return 0;
+}
+
+static int wm8776_input_vol_put(struct snd_kcontrol *ctl,
+				struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	int changed = 0;
+
+	mutex_lock(&chip->mutex);
+	changed = (value->value.integer.value[0] !=
+		   (data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK)) ||
+		  (value->value.integer.value[1] !=
+		   (data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK));
+	wm8776_write_cached(chip, WM8776_ADCLVOL,
+			    value->value.integer.value[0] | WM8776_ZCA);
+	wm8776_write_cached(chip, WM8776_ADCRVOL,
+			    value->value.integer.value[1] | WM8776_ZCA);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int wm8776_level_control_info(struct snd_kcontrol *ctl,
+				     struct snd_ctl_elem_info *info)
+{
+	static const char *const names[3] = {
+		"None", "Peak Limiter", "Automatic Level Control"
+	};
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 3;
+	if (info->value.enumerated.item >= 3)
+		info->value.enumerated.item = 2;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int wm8776_level_control_get(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	if (!(data->wm8776_regs[WM8776_ALCCTRL2] & WM8776_LCEN))
+		value->value.enumerated.item[0] = 0;
+	else if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) ==
+		 WM8776_LCSEL_LIMITER)
+		value->value.enumerated.item[0] = 1;
+	else
+		value->value.enumerated.item[0] = 2;
+	return 0;
+}
+
+static void activate_control(struct oxygen *chip,
+			     struct snd_kcontrol *ctl, unsigned int mode)
+{
+	unsigned int access;
+
+	if (ctl->private_value & mode)
+		access = 0;
+	else
+		access = SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+	if ((ctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != access) {
+		ctl->vd[0].access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE;
+		snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &ctl->id);
+	}
+}
+
+static int wm8776_level_control_put(struct snd_kcontrol *ctl,
+				    struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int mode = 0, i;
+	u16 ctrl1, ctrl2;
+	int changed;
+
+	if (value->value.enumerated.item[0] >= 3)
+		return -EINVAL;
+	mutex_lock(&chip->mutex);
+	changed = value->value.enumerated.item[0] != ctl->private_value;
+	if (changed) {
+		ctl->private_value = value->value.enumerated.item[0];
+		ctrl1 = data->wm8776_regs[WM8776_ALCCTRL1];
+		ctrl2 = data->wm8776_regs[WM8776_ALCCTRL2];
+		switch (value->value.enumerated.item[0]) {
+		default:
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 & ~WM8776_LCEN);
+			break;
+		case 1:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_LIMITER);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_LIMITER;
+			break;
+		case 2:
+			wm8776_write_cached(chip, WM8776_ALCCTRL1,
+					    (ctrl1 & ~WM8776_LCSEL_MASK) |
+					    WM8776_LCSEL_ALC_STEREO);
+			wm8776_write_cached(chip, WM8776_ALCCTRL2,
+					    ctrl2 | WM8776_LCEN);
+			mode = LC_CONTROL_ALC;
+			break;
+		}
+		for (i = 0; i < ARRAY_SIZE(data->lc_controls); ++i)
+			activate_control(chip, data->lc_controls[i], mode);
+	}
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info)
+{
+	static const char *const names[2] = {
+		"None", "High-pass Filter"
+	};
+
+	info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	info->count = 1;
+	info->value.enumerated.items = 2;
+	if (info->value.enumerated.item >= 2)
+		info->value.enumerated.item = 1;
+	strcpy(info->value.enumerated.name, names[info->value.enumerated.item]);
+	return 0;
+}
+
+static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+
+	value->value.enumerated.item[0] =
+		!(data->wm8776_regs[WM8776_ADCIFCTRL] & WM8776_ADCHPD);
+	return 0;
+}
+
+static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value)
+{
+	struct oxygen *chip = ctl->private_data;
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int reg;
+	int changed;
+
+	mutex_lock(&chip->mutex);
+	reg = data->wm8776_regs[WM8776_ADCIFCTRL] & ~WM8776_ADCHPD;
+	if (!value->value.enumerated.item[0])
+		reg |= WM8776_ADCHPD;
+	changed = reg != data->wm8776_regs[WM8776_ADCIFCTRL];
+	if (changed)
+		wm8776_write(chip, WM8776_ADCIFCTRL, reg);
+	mutex_unlock(&chip->mutex);
+	return changed;
+}
+
+#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.info = snd_ctl_boolean_mono_info, \
+	.get = wm8776_bit_switch_get, \
+	.put = wm8776_bit_switch_put, \
+	.private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \
+}
+#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \
+	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, \
+	.name = xname, \
+	.private_value = (initval) | ((min) << 8) | ((max) << 12) | \
+	((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags)
+#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\
+	_WM8776_FIELD_CTL(xname " Capture Enum", \
+			  reg, shift, init, min, max, mask, flags), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE, \
+	.info = wm8776_field_enum_info, \
+	.get = wm8776_field_enum_get, \
+	.put = wm8776_field_enum_put, \
+}
+#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \
+	_WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \
+	.access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \
+		  SNDRV_CTL_ELEM_ACCESS_INACTIVE | \
+		  SNDRV_CTL_ELEM_ACCESS_TLV_READ, \
+	.info = wm8776_field_volume_info, \
+	.get = wm8776_field_volume_get, \
+	.put = wm8776_field_volume_put, \
+	.tlv = { .p = tlv_p }, \
+}
+
+static const DECLARE_TLV_DB_SCALE(wm87x6_dac_db_scale, -6000, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_adc_db_scale, -2100, 50, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_hp_db_scale, -6000, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_lct_db_scale, -1600, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_db_scale, 0, 400, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_ngth_db_scale, -7800, 600, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_db_scale, -1200, 100, 0);
+static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_db_scale, -2100, 400, 0);
+
+static const struct snd_kcontrol_new ds_controls[] = {
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Headphone Playback Volume",
+		.info = wm8776_hp_vol_info,
+		.get = wm8776_hp_vol_get,
+		.put = wm8776_hp_vol_put,
+		.tlv = { .p = wm8776_hp_db_scale },
+	},
+	WM8776_BIT_SWITCH("Headphone Playback Switch",
+			  WM8776_PWRDOWN, WM8776_HPPD, 1, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Capture Volume",
+		.info = wm8776_input_vol_info,
+		.get = wm8776_input_vol_get,
+		.put = wm8776_input_vol_put,
+		.tlv = { .p = wm8776_adc_db_scale },
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Line Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 0,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Mic Capture Switch",
+		.info = snd_ctl_boolean_mono_info,
+		.get = wm8776_input_mux_get,
+		.put = wm8776_input_mux_put,
+		.private_value = 1 << 1,
+	},
+	WM8776_BIT_SWITCH("Aux", WM8776_ADCMUX, 1 << 2, 0, 0),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "ADC Filter Capture Enum",
+		.info = hpf_info,
+		.get = hpf_get,
+		.put = hpf_put,
+	},
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Level Control Capture Enum",
+		.info = wm8776_level_control_info,
+		.get = wm8776_level_control_get,
+		.put = wm8776_level_control_put,
+		.private_value = 0,
+	},
+};
+static const struct snd_kcontrol_new lc_controls[] = {
+	WM8776_FIELD_CTL_VOLUME("Limiter Threshold",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_LIMITER, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("Limiter Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_ENUM("Limiter Transient Window",
+			      WM8776_LIMITER, 4, 2, 0, 7, 0x7,
+			      LC_CONTROL_LIMITER),
+	WM8776_FIELD_CTL_VOLUME("Limiter Maximum Attenuation",
+				WM8776_LIMITER, 0, 6, 3, 12, 0xf,
+				LC_CONTROL_LIMITER,
+				wm8776_maxatten_lim_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Target Level",
+				WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_lct_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Attack Time",
+			      WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_ENUM("ALC Decay Time",
+			      WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Gain",
+				WM8776_ALCCTRL1, 4, 7, 1, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_maxgain_db_scale),
+	WM8776_FIELD_CTL_VOLUME("ALC Maximum Attenuation",
+				WM8776_LIMITER, 0, 10, 10, 15, 0xf,
+				LC_CONTROL_ALC, wm8776_maxatten_alc_db_scale),
+	WM8776_FIELD_CTL_ENUM("ALC Hold Time",
+			      WM8776_ALCCTRL2, 0, 0, 0, 15, 0xf,
+			      LC_CONTROL_ALC),
+	WM8776_BIT_SWITCH("Noise Gate Capture Switch",
+			  WM8776_NOISEGATE, WM8776_NGAT, 0,
+			  LC_CONTROL_ALC),
+	WM8776_FIELD_CTL_VOLUME("Noise Gate Threshold",
+				WM8776_NOISEGATE, 2, 0, 0, 7, 0x7,
+				LC_CONTROL_ALC, wm8776_ngth_db_scale),
+};
+
+static int xonar_ds_control_filter(struct snd_kcontrol_new *template)
+{
+	if (!strncmp(template->name, "CD Capture ", 11))
+		return 1; /* no CD input */
+	return 0;
+}
+
+static int xonar_ds_mixer_init(struct oxygen *chip)
+{
+	struct xonar_wm87x6 *data = chip->model_data;
+	unsigned int i;
+	struct snd_kcontrol *ctl;
+	int err;
+
+	for (i = 0; i < ARRAY_SIZE(ds_controls); ++i) {
+		ctl = snd_ctl_new1(&ds_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+	}
+	BUILD_BUG_ON(ARRAY_SIZE(lc_controls) != ARRAY_SIZE(data->lc_controls));
+	for (i = 0; i < ARRAY_SIZE(lc_controls); ++i) {
+		ctl = snd_ctl_new1(&lc_controls[i], chip);
+		if (!ctl)
+			return -ENOMEM;
+		err = snd_ctl_add(chip->card, ctl);
+		if (err < 0)
+			return err;
+		data->lc_controls[i] = ctl;
+	}
+	return 0;
+}
+
+static const struct oxygen_model model_xonar_ds = {
+	.shortname = "Xonar DS",
+	.longname = "Asus Virtuoso 200",
+	.chip = "AV200",
+	.init = xonar_ds_init,
+	.control_filter = xonar_ds_control_filter,
+	.mixer_init = xonar_ds_mixer_init,
+	.cleanup = xonar_ds_cleanup,
+	.suspend = xonar_ds_suspend,
+	.resume = xonar_ds_resume,
+	.pcm_hardware_filter = wm8776_adc_hardware_filter,
+	.get_i2s_mclk = oxygen_default_i2s_mclk,
+	.set_dac_params = set_wm87x6_dac_params,
+	.set_adc_params = set_wm8776_adc_params,
+	.update_dac_volume = update_wm87x6_volume,
+	.update_dac_mute = update_wm87x6_mute,
+	.gpio_changed = xonar_ds_gpio_changed,
+	.dac_tlv = wm87x6_dac_db_scale,
+	.model_data_size = sizeof(struct xonar_wm87x6),
+	.device_config = PLAYBACK_0_TO_I2S |
+			 PLAYBACK_1_TO_SPDIF |
+			 CAPTURE_0_FROM_I2S_1,
+	.dac_channels = 8,
+	.dac_volume_min = 255 - 2*60,
+	.dac_volume_max = 255,
+	.function_flags = OXYGEN_FUNCTION_SPI,
+	.dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+	.adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST,
+};
+
+int __devinit get_xonar_wm87x6_model(struct oxygen *chip,
+				     const struct pci_device_id *id)
+{
+	switch (id->subdevice) {
+	case 0x838e:
+		chip->model = model_xonar_ds;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c
index 4963def..d01ec18 100644
--- a/sound/usb/usbaudio.c
+++ b/sound/usb/usbaudio.c
@@ -3327,6 +3327,32 @@
 }
 
 /*
+ * This call will put the synth in "USB send" mode, i.e it will send MIDI
+ * messages through USB (this is disabled at startup). The synth will
+ * acknowledge by sending a sysex on endpoint 0x85 and by displaying a USB
+ * sign on its LCD. Values here are chosen based on sniffing USB traffic
+ * under Windows.
+ */
+static int snd_usb_accessmusic_boot_quirk(struct usb_device *dev)
+{
+	int err, actual_length;
+
+	/* "midi send" enable */
+	static const u8 seq[] = { 0x4e, 0x73, 0x52, 0x01 };
+
+	void *buf = kmemdup(seq, ARRAY_SIZE(seq), GFP_KERNEL);
+	if (!buf)
+		return -ENOMEM;
+	err = usb_interrupt_msg(dev, usb_sndintpipe(dev, 0x05), buf,
+			ARRAY_SIZE(seq), &actual_length, 1000);
+	kfree(buf);
+	if (err < 0)
+		return err;
+
+	return 0;
+}
+
+/*
  * Setup quirks
  */
 #define AUDIOPHILE_SET			0x01 /* if set, parse device_setup */
@@ -3624,6 +3650,12 @@
 			goto __err_val;
 	}
 
+	/* Access Music VirusTI Desktop */
+	if (id == USB_ID(0x133e, 0x0815)) {
+		if (snd_usb_accessmusic_boot_quirk(dev) < 0)
+			goto __err_val;
+	}
+
 	/*
 	 * found a config.  now register to ALSA
 	 */
diff --git a/sound/usb/usbmidi.c b/sound/usb/usbmidi.c
index aae50df..b2da478 100644
--- a/sound/usb/usbmidi.c
+++ b/sound/usb/usbmidi.c
@@ -1419,6 +1419,12 @@
 	EXTERNAL_PORT(0x086a, 0x0001, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0002, 8, "%s Broadcast"),
 	EXTERNAL_PORT(0x086a, 0x0003, 4, "%s Broadcast"),
+	/* Access Music Virus TI */
+	EXTERNAL_PORT(0x133e, 0x0815, 0, "%s MIDI"),
+	PORT_INFO(0x133e, 0x0815, 1, "%s Synth", 0,
+		SNDRV_SEQ_PORT_TYPE_MIDI_GENERIC |
+		SNDRV_SEQ_PORT_TYPE_HARDWARE |
+		SNDRV_SEQ_PORT_TYPE_SYNTHESIZER),
 };
 
 static struct port_info *find_port_info(struct snd_usb_midi* umidi, int number)
diff --git a/sound/usb/usbmixer.c b/sound/usb/usbmixer.c
index c998220..c72ad0c 100644
--- a/sound/usb/usbmixer.c
+++ b/sound/usb/usbmixer.c
@@ -123,6 +123,7 @@
 	int channels;
 	int val_type;
 	int min, max, res;
+	int dBmin, dBmax;
 	int cached;
 	int cache_val[MAX_CHANNELS];
 	u8 initialized;
@@ -194,42 +195,50 @@
  */
 #include "usbmixer_maps.c"
 
-/* get the mapped name if the unit matches */
-static int check_mapped_name(struct mixer_build *state, int unitid, int control, char *buf, int buflen)
+static const struct usbmix_name_map *
+find_map(struct mixer_build *state, int unitid, int control)
 {
-	const struct usbmix_name_map *p;
+	const struct usbmix_name_map *p = state->map;
 
-	if (! state->map)
-		return 0;
+	if (!p)
+		return NULL;
 
 	for (p = state->map; p->id; p++) {
-		if (p->id == unitid && p->name &&
-		    (! control || ! p->control || control == p->control)) {
-			buflen--;
-			return strlcpy(buf, p->name, buflen);
-		}
+		if (p->id == unitid &&
+		    (!control || !p->control || control == p->control))
+			return p;
 	}
-	return 0;
+	return NULL;
+}
+
+/* get the mapped name if the unit matches */
+static int
+check_mapped_name(const struct usbmix_name_map *p, char *buf, int buflen)
+{
+	if (!p || !p->name)
+		return 0;
+
+	buflen--;
+	return strlcpy(buf, p->name, buflen);
 }
 
 /* check whether the control should be ignored */
-static int check_ignored_ctl(struct mixer_build *state, int unitid, int control)
+static inline int
+check_ignored_ctl(const struct usbmix_name_map *p)
 {
-	const struct usbmix_name_map *p;
-
-	if (! state->map)
+	if (!p || p->name || p->dB)
 		return 0;
-	for (p = state->map; p->id; p++) {
-		if (p->id == unitid && ! p->name &&
-		    (! control || ! p->control || control == p->control)) {
-			/*
-			printk(KERN_DEBUG "ignored control %d:%d\n",
-			       unitid, control);
-			*/
-			return 1;
-		}
+	return 1;
+}
+
+/* dB mapping */
+static inline void check_mapped_dB(const struct usbmix_name_map *p,
+				   struct usb_mixer_elem_info *cval)
+{
+	if (p && p->dB) {
+		cval->dBmin = p->dB->min;
+		cval->dBmax = p->dB->max;
 	}
-	return 0;
 }
 
 /* get the mapped selector source name */
@@ -466,20 +475,8 @@
 
 	if (size < sizeof(scale))
 		return -ENOMEM;
-	/* USB descriptions contain the dB scale in 1/256 dB unit
-	 * while ALSA TLV contains in 1/100 dB unit
-	 */
-	scale[2] = (convert_signed_value(cval, cval->min) * 100) / 256;
-	scale[3] = (convert_signed_value(cval, cval->max) * 100) / 256;
-	if (scale[3] <= scale[2]) {
-		/* something is wrong; assume it's either from/to 0dB */
-		if (scale[2] < 0)
-			scale[3] = 0;
-		else if (scale[2] > 0)
-			scale[2] = 0;
-		else /* totally crap, return an error */
-			return -EINVAL;
-	}
+	scale[2] = cval->dBmin;
+	scale[3] = cval->dBmax;
 	if (copy_to_user(_tlv, scale, sizeof(scale)))
 		return -EFAULT;
 	return 0;
@@ -720,6 +717,7 @@
 	cval->min = default_min;
 	cval->max = cval->min + 1;
 	cval->res = 1;
+	cval->dBmin = cval->dBmax = 0;
 
 	if (cval->val_type == USB_MIXER_BOOLEAN ||
 	    cval->val_type == USB_MIXER_INV_BOOLEAN) {
@@ -787,6 +785,24 @@
 
 		cval->initialized = 1;
 	}
+
+	/* USB descriptions contain the dB scale in 1/256 dB unit
+	 * while ALSA TLV contains in 1/100 dB unit
+	 */
+	cval->dBmin = (convert_signed_value(cval, cval->min) * 100) / 256;
+	cval->dBmax = (convert_signed_value(cval, cval->max) * 100) / 256;
+	if (cval->dBmin > cval->dBmax) {
+		/* something is wrong; assume it's either from/to 0dB */
+		if (cval->dBmin < 0)
+			cval->dBmax = 0;
+		else if (cval->dBmin > 0)
+			cval->dBmin = 0;
+		if (cval->dBmin > cval->dBmax) {
+			/* totally crap, return an error */
+			return -EINVAL;
+		}
+	}
+
 	return 0;
 }
 
@@ -912,6 +928,7 @@
 	int nameid = desc[desc[0] - 1];
 	struct snd_kcontrol *kctl;
 	struct usb_mixer_elem_info *cval;
+	const struct usbmix_name_map *map;
 
 	control++; /* change from zero-based to 1-based value */
 
@@ -920,7 +937,8 @@
 		return;
 	}
 
-	if (check_ignored_ctl(state, unitid, control))
+	map = find_map(state, unitid, control);
+	if (check_ignored_ctl(map))
 		return;
 
 	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
@@ -954,10 +972,11 @@
 	}
 	kctl->private_free = usb_mixer_elem_free;
 
-	len = check_mapped_name(state, unitid, control, kctl->id.name, sizeof(kctl->id.name));
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
 	mapped_name = len != 0;
 	if (! len && nameid)
-		len = snd_usb_copy_string_desc(state, nameid, kctl->id.name, sizeof(kctl->id.name));
+		len = snd_usb_copy_string_desc(state, nameid,
+				kctl->id.name, sizeof(kctl->id.name));
 
 	switch (control) {
 	case USB_FEATURE_MUTE:
@@ -995,6 +1014,7 @@
 			kctl->vd[0].access |= 
 				SNDRV_CTL_ELEM_ACCESS_TLV_READ |
 				SNDRV_CTL_ELEM_ACCESS_TLV_CALLBACK;
+			check_mapped_dB(map, cval);
 		}
 		break;
 
@@ -1122,8 +1142,10 @@
 	unsigned int num_outs = desc[5 + input_pins];
 	unsigned int i, len;
 	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
 
-	if (check_ignored_ctl(state, unitid, 0))
+	map = find_map(state, unitid, 0);
+	if (check_ignored_ctl(map))
 		return;
 
 	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
@@ -1152,7 +1174,7 @@
 	}
 	kctl->private_free = usb_mixer_elem_free;
 
-	len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name));
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
 	if (! len)
 		len = get_term_name(state, iterm, kctl->id.name, sizeof(kctl->id.name), 0);
 	if (! len)
@@ -1342,6 +1364,7 @@
 	int i, err, nameid, type, len;
 	struct procunit_info *info;
 	struct procunit_value_info *valinfo;
+	const struct usbmix_name_map *map;
 	static struct procunit_value_info default_value_info[] = {
 		{ 0x01, "Switch", USB_MIXER_BOOLEAN },
 		{ 0 }
@@ -1371,7 +1394,8 @@
 		/* FIXME: bitmap might be longer than 8bit */
 		if (! (dsc[12 + num_ins] & (1 << (valinfo->control - 1))))
 			continue;
-		if (check_ignored_ctl(state, unitid, valinfo->control))
+		map = find_map(state, unitid, valinfo->control);
+		if (check_ignored_ctl(map))
 			continue;
 		cval = kzalloc(sizeof(*cval), GFP_KERNEL);
 		if (! cval) {
@@ -1402,8 +1426,9 @@
 		}
 		kctl->private_free = usb_mixer_elem_free;
 
-		if (check_mapped_name(state, unitid, cval->control, kctl->id.name, sizeof(kctl->id.name)))
-			;
+		if (check_mapped_name(map, kctl->id.name,
+						sizeof(kctl->id.name)))
+			/* nothing */ ;
 		else if (info->name)
 			strlcpy(kctl->id.name, info->name, sizeof(kctl->id.name));
 		else {
@@ -1542,6 +1567,7 @@
 	int err;
 	struct usb_mixer_elem_info *cval;
 	struct snd_kcontrol *kctl;
+	const struct usbmix_name_map *map;
 	char **namelist;
 
 	if (! num_ins || desc[0] < 5 + num_ins) {
@@ -1557,7 +1583,8 @@
 	if (num_ins == 1) /* only one ? nonsense! */
 		return 0;
 
-	if (check_ignored_ctl(state, unitid, 0))
+	map = find_map(state, unitid, 0);
+	if (check_ignored_ctl(map))
 		return 0;
 
 	cval = kzalloc(sizeof(*cval), GFP_KERNEL);
@@ -1612,7 +1639,7 @@
 	kctl->private_free = usb_mixer_selector_elem_free;
 
 	nameid = desc[desc[0] - 1];
-	len = check_mapped_name(state, unitid, 0, kctl->id.name, sizeof(kctl->id.name));
+	len = check_mapped_name(map, kctl->id.name, sizeof(kctl->id.name));
 	if (len)
 		;
 	else if (nameid)
diff --git a/sound/usb/usbmixer_maps.c b/sound/usb/usbmixer_maps.c
index 77c3588..79e903a 100644
--- a/sound/usb/usbmixer_maps.c
+++ b/sound/usb/usbmixer_maps.c
@@ -19,11 +19,16 @@
  *
  */
 
+struct usbmix_dB_map {
+	u32 min;
+	u32 max;
+};
 
 struct usbmix_name_map {
 	int id;
 	const char *name;
 	int control;
+	struct usbmix_dB_map *dB;
 };
 
 struct usbmix_selector_map {
@@ -72,7 +77,7 @@
 	{ 8, "Line Playback" }, /* FU */
 	/* 9: IT mic */
 	{ 10, "Mic Playback" }, /* FU */
-	{ 11, "Capture Input Source" }, /* SU */
+	{ 11, "Capture Source" }, /* SU */
 	{ 12, "Capture" }, /* FU */
 	/* 13: OT pcm capture */
 	/* 14: MU (w/o controls) */
@@ -102,6 +107,9 @@
  * e.g. no Master and fake PCM volume
  *			Pavel Mihaylov <bin@bash.info>
  */
+static struct usbmix_dB_map mp3plus_dB_1 = {-4781, 0};	/* just guess */
+static struct usbmix_dB_map mp3plus_dB_2 = {-1781, 618}; /* just guess */
+
 static struct usbmix_name_map mp3plus_map[] = {
 	/* 1: IT pcm */
 	/* 2: IT mic */
@@ -110,16 +118,19 @@
 	/* 5: OT digital out */
 	/* 6: OT speaker */
 	/* 7: OT pcm capture */
-	{ 8, "Capture Input Source" }, /* FU, default PCM Capture Source */
+	{ 8, "Capture Source" }, /* FU, default PCM Capture Source */
 		/* (Mic, Input 1 = Line input, Input 2 = Optical input) */
 	{ 9, "Master Playback" }, /* FU, default Speaker 1 */
 	/* { 10, "Mic Capture", 1 }, */ /* FU, Mic Capture */
-	/* { 10, "Mic Capture", 2 }, */ /* FU, Mic Capture */
+	{ 10, /* "Mic Capture", */ NULL, 2, .dB = &mp3plus_dB_2 },
+		/* FU, Mic Capture */
 	{ 10, "Mic Boost", 7 }, /* FU, default Auto Gain Input */
-	{ 11, "Line Capture" }, /* FU, default PCM Capture */
+	{ 11, "Line Capture", .dB = &mp3plus_dB_2 },
+		/* FU, default PCM Capture */
 	{ 12, "Digital In Playback" }, /* FU, default PCM 1 */
-	/* { 13, "Mic Playback" }, */ /* FU, default Mic Playback */
-	{ 14, "Line Playback" }, /* FU, default Speaker */
+	{ 13, /* "Mic Playback", */ .dB = &mp3plus_dB_1 },
+		/* FU, default Mic Playback */
+	{ 14, "Line Playback", .dB = &mp3plus_dB_1 }, /* FU, default Speaker */
 	/* 15: MU */
 	{ 0 } /* terminator */
 };
diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h
index a892bda..406b74b 100644
--- a/sound/usb/usbquirks.h
+++ b/sound/usb/usbquirks.h
@@ -2073,6 +2073,33 @@
 	}
 },
 
+/* Access Music devices */
+{
+	/* VirusTI Desktop */
+	USB_DEVICE_VENDOR_SPEC(0x133e, 0x0815),
+	.driver_info = (unsigned long) &(const struct snd_usb_audio_quirk) {
+		.ifnum = QUIRK_ANY_INTERFACE,
+		.type = QUIRK_COMPOSITE,
+		.data = &(const struct snd_usb_audio_quirk[]) {
+			{
+				.ifnum = 3,
+				.type = QUIRK_MIDI_FIXED_ENDPOINT,
+				.data = &(const struct snd_usb_midi_endpoint_info) {
+					.out_cables = 0x0003,
+					.in_cables  = 0x0003
+				}
+			},
+			{
+				.ifnum = 4,
+				.type = QUIRK_IGNORE_INTERFACE
+			},
+			{
+				.ifnum = -1
+			}
+		}
+	}
+},
+
 /* */
 {
 	/* aka. Serato Scratch Live DJ Box */