[PARISC] Update harmony from parisc tree

o Added a control for the input source (which can be either
  "line" or "mic")

o Mute the speaker/line-out/headphone outputs by default.

o Increased the buffer size from 10 pages to 16.

Signed-off-by: Stuart Brady <sdb@parisc-linux.org>

ALSA Harmony was resetting the capture position when
preparing the capture substream, which it shouldn't do.
This should fix the problem.

Signed-off-by: Stuart Brady <sdb@parisc-linux.org>

ALSA Harmony should no longer play junk (left in the buffers
from a previous stream) at the start of a new stream.

Implement the monitor mixer channel for ALSA Harmony.

Also prevent snd_harmony_volume_get from returning negative values.

Signed-off-by: Stuart Brady <sdb@parisc-linux.org>

Use the graveyard/silence buffers in ALSA Harmony.

Signed-off-by: Stuart Brady <sdb@parisc-linux.org>

Signed-off-by: Kyle McMartin <kyle@parisc-linux.org>
diff --git a/sound/parisc/harmony.c b/sound/parisc/harmony.c
index 8b3ea26..d833349 100644
--- a/sound/parisc/harmony.c
+++ b/sound/parisc/harmony.c
@@ -197,7 +197,7 @@
 	spin_unlock(&h->lock);
 
 	if (dstatus & HARMONY_DSTATUS_PN) {
-		if (h->psubs) {
+		if (h->psubs && h->st.playing) {
 			spin_lock(&h->lock);
 			h->pbuf.buf += h->pbuf.count; /* PAGE_SIZE */
 			h->pbuf.buf %= h->pbuf.size; /* MAX_BUFS*PAGE_SIZE */
@@ -216,7 +216,7 @@
 	}
 
 	if (dstatus & HARMONY_DSTATUS_RN) {
-		if (h->csubs) {
+		if (h->csubs && h->st.capturing) {
 			spin_lock(&h->lock);
 			h->cbuf.buf += h->cbuf.count;
 			h->cbuf.buf %= h->cbuf.size;
@@ -316,6 +316,7 @@
 	case SNDRV_PCM_TRIGGER_STOP:
 		h->st.playing = 0;
 		harmony_mute(h);
+		harmony_write(h, HARMONY_PNXTADD, h->sdma.addr);
 		harmony_disable_interrupts(h);
 		break;
 	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
@@ -351,8 +352,9 @@
 		break;
         case SNDRV_PCM_TRIGGER_STOP:
 		h->st.capturing = 0;
-                harmony_mute(h);
-                harmony_disable_interrupts(h);
+		harmony_mute(h);
+		harmony_write(h, HARMONY_RNXTADD, h->gdma.addr);
+		harmony_disable_interrupts(h);
 		break;
         case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
         case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
@@ -408,7 +410,8 @@
 	
 	h->pbuf.size = snd_pcm_lib_buffer_bytes(ss);
 	h->pbuf.count = snd_pcm_lib_period_bytes(ss);
-	h->pbuf.buf = 0;
+	if (h->pbuf.buf >= h->pbuf.size)
+		h->pbuf.buf = 0;
 	h->st.playing = 0;
 
 	h->st.rate = snd_harmony_rate_bits(rt->rate);
@@ -437,7 +440,8 @@
 
         h->cbuf.size = snd_pcm_lib_buffer_bytes(ss);
         h->cbuf.count = snd_pcm_lib_period_bytes(ss);
-        h->cbuf.buf = 0;
+	if (h->cbuf.buf >= h->cbuf.size)
+	        h->cbuf.buf = 0;
 	h->st.capturing = 0;
 
         h->st.rate = snd_harmony_rate_bits(rt->rate);
@@ -712,13 +716,14 @@
 
 	left = (h->st.gain >> shift_left) & mask;
 	right = (h->st.gain >> shift_right) & mask;
-
 	if (invert) {
 		left = mask - left;
 		right = mask - right;
 	}
+	
 	ucontrol->value.integer.value[0] = left;
-	ucontrol->value.integer.value[1] = right;
+	if (shift_left != shift_right)
+		ucontrol->value.integer.value[1] = right;
 
 	spin_unlock_irqrestore(&h->mixer_lock, flags);
 
@@ -738,22 +743,82 @@
 	int old_gain = h->st.gain;
 	unsigned long flags;
 	
-	left = ucontrol->value.integer.value[0] & mask;
-	right = ucontrol->value.integer.value[1] & mask;
-	if (invert) {
-		left = mask - left;
-		right = mask - right;
-	}
-	
 	spin_lock_irqsave(&h->mixer_lock, flags);
 
-	h->st.gain &= ~( (mask << shift_right) | (mask << shift_left) );
- 	h->st.gain |=  ( (left << shift_left) | (right << shift_right) );
+	left = ucontrol->value.integer.value[0] & mask;
+	if (invert)
+		left = mask - left;
+	h->st.gain &= ~( (mask << shift_left ) );
+ 	h->st.gain |= (left << shift_left);
+
+	if (shift_left != shift_right) {
+		right = ucontrol->value.integer.value[1] & mask;
+		if (invert)
+			right = mask - right;
+		h->st.gain &= ~( (mask << shift_right) );
+		h->st.gain |= (right << shift_right);
+	}
+
 	snd_harmony_set_new_gain(h);
 
 	spin_unlock_irqrestore(&h->mixer_lock, flags);
 	
-	return (old_gain - h->st.gain);
+	return h->st.gain != old_gain;
+}
+
+static int 
+snd_harmony_captureroute_info(snd_kcontrol_t *kc, 
+			      snd_ctl_elem_info_t *uinfo)
+{
+	static char *texts[2] = { "Line", "Mic" };
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED;
+	uinfo->count = 1;
+	uinfo->value.enumerated.items = 2;
+	if (uinfo->value.enumerated.item > 1)
+		uinfo->value.enumerated.item = 1;
+	strcpy(uinfo->value.enumerated.name,
+	       texts[uinfo->value.enumerated.item]);
+	return 0;
+}
+
+static int 
+snd_harmony_captureroute_get(snd_kcontrol_t *kc, 
+			     snd_ctl_elem_value_t *ucontrol)
+{
+	harmony_t *h = snd_kcontrol_chip(kc);
+	int value;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&h->mixer_lock, flags);
+
+	value = (h->st.gain >> HARMONY_GAIN_IS_SHIFT) & 1;
+	ucontrol->value.enumerated.item[0] = value;
+
+	spin_unlock_irqrestore(&h->mixer_lock, flags);
+
+	return 0;
+}  
+
+static int 
+snd_harmony_captureroute_put(snd_kcontrol_t *kc, 
+			     snd_ctl_elem_value_t *ucontrol)
+{
+	harmony_t *h = snd_kcontrol_chip(kc);
+	int value;
+	int old_gain = h->st.gain;
+	unsigned long flags;
+	
+	spin_lock_irqsave(&h->mixer_lock, flags);
+
+	value = ucontrol->value.enumerated.item[0] & 1;
+	h->st.gain &= ~HARMONY_GAIN_IS_MASK;
+ 	h->st.gain |= value << HARMONY_GAIN_IS_SHIFT;
+
+	snd_harmony_set_new_gain(h);
+
+	spin_unlock_irqrestore(&h->mixer_lock, flags);
+	
+	return h->st.gain != old_gain;
 }
 
 #define HARMONY_CONTROLS (sizeof(snd_harmony_controls)/ \
@@ -767,10 +832,25 @@
                    ((mask) << 16) | ((invert) << 24)) }
 
 static snd_kcontrol_new_t snd_harmony_controls[] = {
-	HARMONY_VOLUME("Playback Volume", HARMONY_GAIN_LO_SHIFT, 
+	HARMONY_VOLUME("Master Playback Volume", HARMONY_GAIN_LO_SHIFT, 
 		       HARMONY_GAIN_RO_SHIFT, HARMONY_GAIN_OUT, 1),
 	HARMONY_VOLUME("Capture Volume", HARMONY_GAIN_LI_SHIFT,
 		       HARMONY_GAIN_RI_SHIFT, HARMONY_GAIN_IN, 0),
+	HARMONY_VOLUME("Monitor Volume", HARMONY_GAIN_MA_SHIFT,
+		       HARMONY_GAIN_MA_SHIFT, HARMONY_GAIN_MA, 1),
+	{
+		.iface = SNDRV_CTL_ELEM_IFACE_MIXER,
+		.name = "Input Route",
+		.info = snd_harmony_captureroute_info,
+		.get = snd_harmony_captureroute_get,
+		.put = snd_harmony_captureroute_put
+	},
+	HARMONY_VOLUME("Internal Speaker Switch", HARMONY_GAIN_SE_SHIFT,
+		       HARMONY_GAIN_SE_SHIFT, 1, 0),
+	HARMONY_VOLUME("Line-Out Switch", HARMONY_GAIN_LE_SHIFT,
+		       HARMONY_GAIN_LE_SHIFT, 1, 0),
+	HARMONY_VOLUME("Headphones Switch", HARMONY_GAIN_HE_SHIFT,
+		       HARMONY_GAIN_HE_SHIFT, 1, 0),
 };
 
 static void __init