Subject: port-sparc/32481: audiocs(4) mixer does not label inputs correctly, does not control output ports
To: None <port-sparc-maintainer@netbsd.org, gnats-admin@netbsd.org,>
From: None <svs+pr@grep.ru>
List: netbsd-bugs
Date: 01/08/2006 20:05:00
>Number:         32481
>Category:       port-sparc
>Synopsis:       audiocs(4) mixer does not label inputs correctly, does not control output ports
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    port-sparc-maintainer
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Jan 08 20:05:00 +0000 2006
>Originator:     Sergey Svishchev
>Release:        3.0
>Organization:
>Environment:
>Description:
audiocs(4) labels some mixer inputs incorrectly: CD input (which is wired to AUX1) is labeled inputs.dac, Mono input (meant for PC Speaker) is labeled inputs.mic, and AUX2 input is labeled inputs.cd.  AUX2 and Mono inputs may not be connected to any audio source at all.

Also, there are usually three output ports -- internal speaker, line out and headphone out; current code can mute the speaker, but not the other outputs.
>How-To-Repeat:

>Fix:
Tested on a SparcStation 5.

--- dev/ic/ad1848.c	15 Jan 2005 15:19:52 -0000	1.20
+++ dev/ic/ad1848.c	8 Jan 2006 16:55:52 -0000
@@ -440,7 +440,6 @@
   { CS_LEFT_LINE_CONTROL, CS_RIGHT_LINE_CONTROL, LINE_INPUT_ATTEN_BITS,
     LINE_INPUT_ATTEN_MASK },
   { CS_MONO_IO_CONTROL, 0, MONO_INPUT_ATTEN_BITS, MONO_INPUT_ATTEN_MASK },
-  { CS_MONO_IO_CONTROL, 0, 0, 0 },
   { SP_DIGITAL_MIX, 0, OUTPUT_ATTEN_BITS, MIX_ATTEN_MASK }
 };
 
@@ -464,10 +463,7 @@
 				ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 0);
 			ad_write(sc, mixer_channel_info[device].left_reg,
 				 reg & ~DIGITAL_MIX1_ENABLE);
-		} else if (device == AD1848_OUT_CHANNEL)
-			ad_write(sc, mixer_channel_info[device].left_reg,
-				 reg | MONO_OUTPUT_MUTE);
-		else
+		} else
 			ad_write(sc, mixer_channel_info[device].left_reg,
 				 reg | 0x80);
 	} else if (!(sc->mute[device] & MUTE_LEFT)) {
@@ -476,10 +472,7 @@
 				 reg | DIGITAL_MIX1_ENABLE);
 			if (sc->open_mode & FREAD)
 				ad1848_mute_wave_output(sc, WAVE_UNMUTE1, 1);
-		} else if (device == AD1848_OUT_CHANNEL)
-			ad_write(sc, mixer_channel_info[device].left_reg,
-				 reg & ~MONO_OUTPUT_MUTE);
-		else
+		} else
 			ad_write(sc, mixer_channel_info[device].left_reg,
 				 reg & ~0x80);
 	}
@@ -702,6 +695,12 @@
 		error = 0;
 		break;
 
+	case AD1848_KIND_OUTPUTPORT:
+		if (cp->type != AUDIO_MIXER_SET) break;
+		cp->un.mask = cs4231_get_play_port(ac);
+		error = 0;
+		break;
+
 	default:
 		printf ("Invalid kind\n");
 		break;
@@ -770,6 +769,12 @@
 		error = ad1848_set_rec_port(ac,  cp->un.ord);
 		break;
 
+	case AD1848_KIND_OUTPUTPORT:
+		if (cp->type != AUDIO_MIXER_SET) break;
+
+		error = cs4231_set_play_port(ac, cp->un.mask);
+		break;
+
 	default:
 		printf ("Invalid kind\n");
 		break;
@@ -1015,6 +1020,45 @@
 	return sc->rec_port;
 }
 
+/*
+ *  audiocs(4)-specific code
+ */
+
+int
+cs4231_set_play_port(struct ad1848_softc *sc, int port)
+{
+	u_char xctl = XCTL0_ENABLE | XCTL1_ENABLE, reg;
+
+	DPRINTF(("cs4231_set_play_port: 0x%x\n", port));
+
+	if (port > 7 || port < 0)
+		return(EINVAL);
+	if (port & 2)
+		xctl &= ~XCTL1_ENABLE;
+	if (port & 4)
+		xctl &= ~XCTL0_ENABLE;
+
+	reg = ad_read(sc, SP_PIN_CONTROL);
+	reg &= XCTL_MASK;
+	ad_write(sc, SP_PIN_CONTROL, reg | xctl);
+
+	reg = ad_read(sc, CS_MONO_IO_CONTROL);
+	if (port & 1)
+		ad_write(sc, CS_MONO_IO_CONTROL, reg & ~MONO_OUTPUT_MUTE);
+	else
+		ad_write(sc, CS_MONO_IO_CONTROL, reg | MONO_OUTPUT_MUTE);
+
+	sc->play_port = port;
+
+	return (0);
+}
+
+int
+cs4231_get_play_port(struct ad1848_softc *sc)
+{
+	return (sc->play_port);
+}
+
 int
 ad1848_round_blocksize(void *addr, int blk,
     int mode, const audio_params_t *param)
--- dev/ic/ad1848reg.h	6 Apr 2003 18:20:12 -0000	1.10
+++ dev/ic/ad1848reg.h	8 Jan 2006 16:55:52 -0000
@@ -154,6 +154,7 @@
 #define INTERRUPT_ENABLE	0x02
 #define XCTL0_ENABLE		0x40
 #define XCTL1_ENABLE		0x80
+#define XCTL_MASK		0x3F
 
 /* Test and init reg bits - register I11 (read-only) */
 #define OVERRANGE_LEFT_MASK	0xfc
diff -u -r1.12 ad1848var.h
--- dev/ic/ad1848var.h	15 Jan 2005 15:19:52 -0000	1.12
+++ dev/ic/ad1848var.h	8 Jan 2006 16:55:52 -0000
@@ -118,6 +118,7 @@
 	struct ad1848_volume rec_gain;
 
 	int	rec_port;		/* recording port */
+	int	play_port;		/* playback port */
 
 	/* ad1848 */
 	u_char	MCE_bit;
@@ -140,7 +141,7 @@
 };
 
 /*
- * Ad1848 registers.
+ * AD1848 registers.
  */
 #define MIC_IN_PORT	0
 #define LINE_IN_PORT	1
@@ -152,6 +153,7 @@
 #define AD1848_KIND_RECORDGAIN	2
 #define AD1848_KIND_MICGAIN	3
 #define AD1848_KIND_RECORDSOURCE 4
+#define AD1848_KIND_OUTPUTPORT	5
 
 typedef struct ad1848_devmap {
 	int  id;
@@ -185,6 +187,8 @@
 int	ad1848_commit_settings(void *);
 int	ad1848_set_rec_port(struct ad1848_softc *, int);
 int	ad1848_get_rec_port(struct ad1848_softc *);
+int	cs4231_set_play_port(struct ad1848_softc *, int);
+int	cs4231_get_play_port(struct ad1848_softc *);
 int	ad1848_set_channel_gain(struct ad1848_softc *, int,
 	    struct ad1848_volume *);
 int	ad1848_get_device_gain(struct ad1848_softc *, int,
--- dev/ic/cs4231.c	15 Jan 2005 15:19:52 -0000	1.17
+++ dev/ic/cs4231.c	8 Jan 2006 17:06:26 -0000
@@ -60,24 +60,25 @@
 #include <dev/ic/cs4231var.h>
 
 /*---*/
-#define CSAUDIO_DAC_LVL		0
+#define CSAUDIO_CD_LVL		0
 #define CSAUDIO_LINE_IN_LVL	1
 #define CSAUDIO_MONO_LVL	2
-#define CSAUDIO_CD_LVL		3
-#define CSAUDIO_OUTPUT_LVL	4
+#define CSAUDIO_AUX_LVL		3
+#define CSAUDIO_LOOPBACK_LVL	4
 #define CSAUDIO_OUT_LVL		5
 #define CSAUDIO_LINE_IN_MUTE	6
-#define CSAUDIO_DAC_MUTE	7
-#define CSAUDIO_CD_MUTE		8
+#define CSAUDIO_CD_MUTE		7
+#define CSAUDIO_AUX_MUTE	8
 #define CSAUDIO_MONO_MUTE	9
-#define CSAUDIO_OUTPUT_MUTE	10
-#define CSAUDIO_OUT_MUTE	11
-#define CSAUDIO_REC_LVL		12
-#define CSAUDIO_RECORD_SOURCE	13
+#define CSAUDIO_LOOPBACK_MUTE	10
+#define CSAUDIO_REC_LVL		11
+#define CSAUDIO_RECORD_SOURCE	12
+#define CSAUDIO_OUTPUT_PORT	13
 
 #define CSAUDIO_INPUT_CLASS	14
-#define CSAUDIO_MONITOR_CLASS	15
-#define CSAUDIO_RECORD_CLASS	16
+#define CSAUDIO_OUTPUT_CLASS	15
+#define CSAUDIO_MONITOR_CLASS	16
+#define CSAUDIO_RECORD_CLASS	17
 
 #ifdef AUDIO_DEBUG
 int     cs4231_debug = 0;
@@ -383,20 +384,20 @@
 }
 
 static ad1848_devmap_t csmapping[] = {
-	{ CSAUDIO_DAC_LVL, AD1848_KIND_LVL, AD1848_AUX1_CHANNEL },
+	{ CSAUDIO_CD_LVL, AD1848_KIND_LVL, AD1848_AUX1_CHANNEL },
 	{ CSAUDIO_LINE_IN_LVL, AD1848_KIND_LVL, AD1848_LINE_CHANNEL },
 	{ CSAUDIO_MONO_LVL, AD1848_KIND_LVL, AD1848_MONO_CHANNEL },
-	{ CSAUDIO_CD_LVL, AD1848_KIND_LVL, AD1848_AUX2_CHANNEL },
-	{ CSAUDIO_OUTPUT_LVL, AD1848_KIND_LVL, AD1848_MONITOR_CHANNEL },
+	{ CSAUDIO_AUX_LVL, AD1848_KIND_LVL, AD1848_AUX2_CHANNEL },
+	{ CSAUDIO_LOOPBACK_LVL, AD1848_KIND_LVL, AD1848_MONITOR_CHANNEL },
 	{ CSAUDIO_OUT_LVL, AD1848_KIND_LVL, AD1848_DAC_CHANNEL },
-	{ CSAUDIO_DAC_MUTE, AD1848_KIND_MUTE, AD1848_AUX1_CHANNEL },
+	{ CSAUDIO_CD_MUTE, AD1848_KIND_MUTE, AD1848_AUX1_CHANNEL },
 	{ CSAUDIO_LINE_IN_MUTE, AD1848_KIND_MUTE, AD1848_LINE_CHANNEL },
 	{ CSAUDIO_MONO_MUTE, AD1848_KIND_MUTE, AD1848_MONO_CHANNEL },
-	{ CSAUDIO_CD_MUTE, AD1848_KIND_MUTE, AD1848_AUX2_CHANNEL },
-	{ CSAUDIO_OUTPUT_MUTE, AD1848_KIND_MUTE, AD1848_MONITOR_CHANNEL },
-	{ CSAUDIO_OUT_MUTE, AD1848_KIND_MUTE, AD1848_OUT_CHANNEL },
+	{ CSAUDIO_AUX_MUTE, AD1848_KIND_MUTE, AD1848_AUX2_CHANNEL },
+	{ CSAUDIO_LOOPBACK_MUTE, AD1848_KIND_MUTE, AD1848_MONITOR_CHANNEL },
 	{ CSAUDIO_REC_LVL, AD1848_KIND_RECORDGAIN, -1 },
-	{ CSAUDIO_RECORD_SOURCE, AD1848_KIND_RECORDSOURCE, -1 }
+	{ CSAUDIO_RECORD_SOURCE, AD1848_KIND_RECORDSOURCE, -1 },
+	{ CSAUDIO_OUTPUT_PORT, AD1848_KIND_OUTPUTPORT, -1},
 };
 
 static int nummap = sizeof(csmapping) / sizeof(csmapping[0]);
@@ -435,17 +436,17 @@
 
 	switch(dip->index) {
 
-	case CSAUDIO_DAC_LVL:		/*  dacout */
+	case CSAUDIO_CD_LVL:		/*  aux1 (cd) input */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->prev = AUDIO_MIXER_LAST;
-		dip->next = CSAUDIO_DAC_MUTE;
-		strcpy(dip->label.name, AudioNdac);
+		dip->next = CSAUDIO_CD_MUTE;
+		strcpy(dip->label.name, AudioNcd);
 		dip->un.v.num_channels = 2;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-	case CSAUDIO_LINE_IN_LVL:	/* line */
+	case CSAUDIO_LINE_IN_LVL:	/* line input */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->prev = AUDIO_MIXER_LAST;
@@ -455,92 +456,78 @@
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-	case CSAUDIO_MONO_LVL:	/* mono/microphone mixer */
+	/* is it even present on Sun workstations? */
+	case CSAUDIO_MONO_LVL:		/* mono (system beeper) input -- mixed into mixerout and passed to mono out */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->prev = AUDIO_MIXER_LAST;
 		dip->next = CSAUDIO_MONO_MUTE;
-		strcpy(dip->label.name, AudioNmicrophone);
+		strcpy(dip->label.name, AudioNmono);
 		dip->un.v.num_channels = 1;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-	case CSAUDIO_CD_LVL:		/* cd */
+	case CSAUDIO_AUX_LVL:		/* aux2 input -- mixed into mixerout */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->prev = AUDIO_MIXER_LAST;
-		dip->next = CSAUDIO_CD_MUTE;
-		strcpy(dip->label.name, AudioNcd);
+		dip->next = CSAUDIO_AUX_MUTE;
+		strcpy(dip->label.name, AudioNaux);
 		dip->un.v.num_channels = 2;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-
-	case CSAUDIO_OUTPUT_LVL:	/* monitor level */
+	case CSAUDIO_LOOPBACK_LVL:	/* digital loopback */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_MONITOR_CLASS;
-		dip->next = CSAUDIO_OUTPUT_MUTE;
+		dip->next = CSAUDIO_LOOPBACK_MUTE;
 		dip->prev = AUDIO_MIXER_LAST;
 		strcpy(dip->label.name, AudioNmonitor);
 		dip->un.v.num_channels = 1;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-	case CSAUDIO_OUT_LVL:		/* cs4231 output volume */
+	case CSAUDIO_OUT_LVL:		/* DAC output (host audio data + digital loopback, if enabled) */
 		dip->type = AUDIO_MIXER_VALUE;
-		dip->mixer_class = CSAUDIO_MONITOR_CLASS;
+		dip->mixer_class = CSAUDIO_OUTPUT_CLASS;
 		dip->next = dip->prev = AUDIO_MIXER_LAST;
 		strcpy(dip->label.name, AudioNmaster);
 		dip->un.v.num_channels = 2;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
 
-	case CSAUDIO_OUT_MUTE: /* mute built-in speaker */
-		dip->mixer_class = CSAUDIO_MONITOR_CLASS;
-		dip->type = AUDIO_MIXER_ENUM;
-		dip->prev = CSAUDIO_MONITOR_CLASS;
-		dip->next = AUDIO_MIXER_LAST;
-		strcpy(dip->label.name, AudioNmono);
-		/* names reversed, this is a "mute" value used as "mono enabled" */
-		dip->un.e.num_mem = 2;
-		strcpy(dip->un.e.member[0].label.name, AudioNon);
-		dip->un.e.member[0].ord = 0;
-		strcpy(dip->un.e.member[1].label.name, AudioNoff);
-		dip->un.e.member[1].ord = 1;
-		break;
-
-	case CSAUDIO_LINE_IN_MUTE:
+	case CSAUDIO_LINE_IN_MUTE:	/* line input */
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->type = AUDIO_MIXER_ENUM;
 		dip->prev = CSAUDIO_LINE_IN_LVL;
 		dip->next = AUDIO_MIXER_LAST;
 		goto mute;
 
-	case CSAUDIO_DAC_MUTE:
+	case CSAUDIO_CD_MUTE:		/* aux1 (cd) input */
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->type = AUDIO_MIXER_ENUM;
-		dip->prev = CSAUDIO_DAC_LVL;
+		dip->prev = CSAUDIO_CD_LVL;
 		dip->next = AUDIO_MIXER_LAST;
 		goto mute;
 
-	case CSAUDIO_CD_MUTE:
+	case CSAUDIO_AUX_MUTE:		/* aux2 input */
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->type = AUDIO_MIXER_ENUM;
-		dip->prev = CSAUDIO_CD_LVL;
+		dip->prev = CSAUDIO_AUX_LVL;
 		dip->next = AUDIO_MIXER_LAST;
 		goto mute;
 
-	case CSAUDIO_MONO_MUTE:
+	case CSAUDIO_MONO_MUTE:		/* mono output, muted separately from stereo outputs */
 		dip->mixer_class = CSAUDIO_INPUT_CLASS;
 		dip->type = AUDIO_MIXER_ENUM;
 		dip->prev = CSAUDIO_MONO_LVL;
 		dip->next = AUDIO_MIXER_LAST;
 		goto mute;
 
-	case CSAUDIO_OUTPUT_MUTE:
+	case CSAUDIO_LOOPBACK_MUTE:	/* digital loopback -- use only to mix mic input into output */
 		dip->mixer_class = CSAUDIO_MONITOR_CLASS;
 		dip->type = AUDIO_MIXER_ENUM;
-		dip->prev = CSAUDIO_OUTPUT_LVL;
+		dip->prev = CSAUDIO_LOOPBACK_LVL;
 		dip->next = AUDIO_MIXER_LAST;
 	mute:
 		strcpy(dip->label.name, AudioNmute);
@@ -551,12 +538,26 @@
 		dip->un.e.member[1].ord = 1;
 		break;
 
+	case CSAUDIO_OUTPUT_PORT:
+		dip->mixer_class = CSAUDIO_OUTPUT_CLASS;
+		dip->type = AUDIO_MIXER_SET;
+		dip->next = dip->prev = AUDIO_MIXER_LAST;
+		strcpy(dip->label.name, AudioNselect);
+		dip->un.s.num_mem = 3;
+		strcpy(dip->un.s.member[0].label.name, AudioNspeaker);
+		dip->un.s.member[0].mask = 1;
+		strcpy(dip->un.s.member[1].label.name, AudioNheadphone);
+		dip->un.s.member[1].mask = 2;
+		strcpy(dip->un.s.member[2].label.name, AudioNline);
+		dip->un.s.member[2].mask = 4;
+		break;
+
 	case CSAUDIO_REC_LVL:	/* record level */
 		dip->type = AUDIO_MIXER_VALUE;
 		dip->mixer_class = CSAUDIO_RECORD_CLASS;
 		dip->prev = AUDIO_MIXER_LAST;
 		dip->next = CSAUDIO_RECORD_SOURCE;
-		strcpy(dip->label.name, AudioNrecord);
+		strcpy(dip->label.name, AudioNmaster);
 		dip->un.v.num_channels = 2;
 		strcpy(dip->un.v.units.name, AudioNvolume);
 		break;
@@ -568,11 +569,11 @@
 		dip->next = AUDIO_MIXER_LAST;
 		strcpy(dip->label.name, AudioNsource);
 		dip->un.e.num_mem = 4;
-		strcpy(dip->un.e.member[0].label.name, AudioNoutput);
+		strcpy(dip->un.e.member[0].label.name, AudioNmixerout);
 		dip->un.e.member[0].ord = DAC_IN_PORT;
 		strcpy(dip->un.e.member[1].label.name, AudioNmicrophone);
 		dip->un.e.member[1].ord = MIC_IN_PORT;
-		strcpy(dip->un.e.member[2].label.name, AudioNdac);
+		strcpy(dip->un.e.member[2].label.name, AudioNcd);
 		dip->un.e.member[2].ord = AUX1_IN_PORT;
 		strcpy(dip->un.e.member[3].label.name, AudioNline);
 		dip->un.e.member[3].ord = LINE_IN_PORT;
@@ -592,6 +593,13 @@
 		strcpy(dip->label.name, AudioCmonitor);
 		break;
 
+	case CSAUDIO_OUTPUT_CLASS:		/* output ... class */
+		dip->type = AUDIO_MIXER_CLASS;
+		dip->mixer_class = CSAUDIO_OUTPUT_CLASS;
+		dip->next = dip->prev = AUDIO_MIXER_LAST;
+		strcpy(dip->label.name, AudioCoutputs);
+		break;
+
 	case CSAUDIO_RECORD_CLASS:		/* record source class */
 		dip->type = AUDIO_MIXER_CLASS;
 		dip->mixer_class = CSAUDIO_RECORD_CLASS;