patch-2.4.14 linux/drivers/sound/waveartist.c

Next file: linux/drivers/sound/waveartist.h
Previous file: linux/drivers/sound/trident.c
Back to the patch index
Back to the overall index

diff -u --recursive --new-file v2.4.13/linux/drivers/sound/waveartist.c linux/drivers/sound/waveartist.c
@@ -39,26 +39,22 @@
 #include <linux/sched.h>
 #include <linux/interrupt.h>
 #include <linux/delay.h>
-#include <linux/smp.h>
 #include <linux/spinlock.h>
+#include <linux/bitops.h>
 
-#include <asm/hardware.h>
-#include <asm/mach-types.h>
 #include <asm/system.h>
 
 #include "sound_config.h"
 #include "waveartist.h"
 
-#ifndef _ISA_DMA
-#define _ISA_DMA(x) (x)
-#endif
-#ifndef _ISA_IRQ
-#define _ISA_IRQ(x) (x)
+#ifdef CONFIG_ARM
+#include <asm/hardware.h>
+#include <asm/mach-types.h>
 #endif
 
-#define POSSIBLE_RECORDING_DEVICES	(SOUND_MASK_LINE       |\
-					 SOUND_MASK_MIC	       |\
-					 SOUND_MASK_LINE1)
+#ifndef NO_DMA
+#define NO_DMA	255
+#endif
 
 #define SUPPORTED_MIXER_DEVICES		(SOUND_MASK_SYNTH      |\
 					 SOUND_MASK_PCM        |\
@@ -75,15 +71,15 @@
 	0x0000,		/* Treble		 */
 	0x2323,		/* Synth (FM)		 */
 	0x4b4b,		/* PCM			 */
-	0x0000,		/* PC Speaker		 */
+	0x6464,		/* PC Speaker		 */
 	0x0000,		/* Ext Line		 */
 	0x0000,		/* Mic			 */
 	0x0000,		/* CD			 */
-	0x0000,		/* Recording monitor	 */
+	0x6464,		/* Recording monitor	 */
 	0x0000,		/* SB PCM (ALT PCM)	 */
 	0x0000,		/* Recording level	 */
-	0x0000,		/* Input gain		 */
-	0x0000,		/* Output gain		 */
+	0x6464,		/* Input gain		 */
+	0x6464,		/* Output gain		 */
 	0x0000,		/* Line1 (Aux1)		 */
 	0x0000,		/* Line2 (Aux2)		 */
 	0x0000,		/* Line3 (Aux3)		 */
@@ -91,7 +87,7 @@
 	0x0000,		/* Digital2		 */
 	0x0000,		/* Digital3		 */
 	0x0000,		/* Phone In		 */
-	0x0000,		/* Phone Out		 */
+	0x6464,		/* Phone Out		 */
 	0x0000,		/* Video		 */
 	0x0000,		/* Radio		 */
 	0x0000		/* Monitor		 */
@@ -110,10 +106,10 @@
 	int		dev_no;
 
 	/* Mixer parameters */
+	const struct waveartist_mixer_info *mix;
+
 	unsigned short	*levels;	   /* cache of volume settings   */
 	int		recmask;	   /* currently enabled recording device! */
-	int             supported_devices; /* SUPPORTED_MIXER_DEVICES    */
-	int             rec_devices;	   /* POSSIBLE_RECORDING_DEVICES */
 
 #ifdef CONFIG_ARCH_NETWINDER
 	signed int	slider_vol;	   /* hardware slider volume     */
@@ -126,6 +122,21 @@
 #endif
 } wavnc_info;
 
+/*
+ * This is the implementation specific mixer information.
+ */
+struct waveartist_mixer_info {
+	unsigned int	supported_devs;	   /* Supported devices */
+	unsigned int	recording_devs;	   /* Recordable devies */
+	unsigned int	stereo_devs;	   /* Stereo devices	*/
+
+	unsigned int	(*select_input)(wavnc_info *, unsigned int,
+					unsigned char *, unsigned char *);
+	int		(*decode_mixer)(wavnc_info *, int,
+					unsigned char, unsigned char);
+	int		(*get_mixer)(wavnc_info *, int);
+};
+
 typedef struct wavnc_port_info {
 	int		open_mode;
 	int		speed;
@@ -137,14 +148,14 @@
 static wavnc_info	adev_info[MAX_AUDIO_DEV];
 static spinlock_t	waveartist_lock = SPIN_LOCK_UNLOCKED;
 
-#ifndef machine_is_netwinder
+#ifndef CONFIG_ARCH_NETWINDER
 #define machine_is_netwinder() 0
-#endif
-
+#else
 static struct timer_list vnc_timer;
-static void vnc_configure_mixer(wavnc_info *devc);
+static void vnc_configure_mixer(wavnc_info *devc, unsigned int input_mask);
 static int vnc_private_ioctl(int dev, unsigned int cmd, caddr_t arg);
 static void vnc_slider_tick(unsigned long data);
+#endif
 
 static inline void
 waveartist_set_ctlr(struct address_info *hw, unsigned char clear, unsigned char set)
@@ -176,7 +187,7 @@
 	unsigned int timeout = timeout_ms * 10 * HZ / 100;
 
 	do {
-		current->state = TASK_INTERRUPTIBLE;
+		set_current_state(TASK_INTERRUPTIBLE);
 		timeout = schedule_timeout(timeout);
 	} while (timeout);
 
@@ -804,21 +815,21 @@
 }
 
 static struct audio_driver waveartist_audio_driver = {
-	owner:		THIS_MODULE,
-	open:		waveartist_open,
-	close:		waveartist_close,
-	output_block:	waveartist_output_block,
-	start_input:	waveartist_start_input,
-	ioctl:		waveartist_ioctl,
+	owner:			THIS_MODULE,
+	open:			waveartist_open,
+	close:			waveartist_close,
+	output_block:		waveartist_output_block,
+	start_input:		waveartist_start_input,
+	ioctl:			waveartist_ioctl,
 	prepare_for_input:	waveartist_prepare_for_input,
 	prepare_for_output:	waveartist_prepare_for_output,
-	halt_io:	waveartist_halt,
-	halt_input:	waveartist_halt_input,
-	halt_output:	waveartist_halt_output,
-	trigger:	waveartist_trigger,
-	set_speed:	waveartist_set_speed,
-	set_bits:	waveartist_set_bits,
-	set_channels:	waveartist_set_channels
+	halt_io:		waveartist_halt,
+	halt_input:		waveartist_halt_input,
+	halt_output:		waveartist_halt_output,
+	trigger:		waveartist_trigger,
+	set_speed:		waveartist_set_speed,
+	set_bits:		waveartist_set_bits,
+	set_channels:		waveartist_set_channels
 };
 
 
@@ -864,220 +875,338 @@
 /* -------------------------------------------------------------------------
  * Mixer stuff
  */
+struct mix_ent {
+	unsigned char	reg_l;
+	unsigned char	reg_r;
+	unsigned char	shift;
+	unsigned char	max;
+};
+
+static const struct mix_ent mix_devs[SOUND_MIXER_NRDEVICES] = {
+	{ 2, 6, 1,  7 }, /* SOUND_MIXER_VOLUME   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_BASS     */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_TREBLE   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_SYNTH    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PCM      */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_SPEAKER  */
+	{ 0, 4, 6, 31 }, /* SOUND_MIXER_LINE     */
+	{ 2, 6, 4,  3 }, /* SOUND_MIXER_MIC      */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_CD       */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_IMIX     */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_ALTPCM   */
+#if 0
+	{ 3, 7, 0, 10 }, /* SOUND_MIXER_RECLEV   */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_IGAIN    */
+#else
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_RECLEV   */
+	{ 3, 7, 0,  7 }, /* SOUND_MIXER_IGAIN    */
+#endif
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_OGAIN    */
+	{ 0, 4, 1, 31 }, /* SOUND_MIXER_LINE1    */
+	{ 1, 5, 6, 31 }, /* SOUND_MIXER_LINE2    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_LINE3    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL1 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL2 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_DIGITAL3 */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PHONEIN  */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_PHONEOUT */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_VIDEO    */
+	{ 0, 0, 0,  0 }, /* SOUND_MIXER_RADIO    */
+	{ 0, 0, 0,  0 }  /* SOUND_MIXER_MONITOR  */
+};
+
 static void
 waveartist_mixer_update(wavnc_info *devc, int whichDev)
 {
-	unsigned int mask, reg_l, reg_r;
 	unsigned int lev_left, lev_right;
-	unsigned int vals[3];
 
 	lev_left  = devc->levels[whichDev] & 0xff;
 	lev_right = devc->levels[whichDev] >> 8;
 
+	if (lev_left > 100)
+		lev_left = 100;
+	if (lev_right > 100)
+		lev_right = 100;
+
 #define SCALE(lev,max)	((lev) * (max) / 100)
 
 	if (machine_is_netwinder() && whichDev == SOUND_MIXER_PHONEOUT)
 		whichDev = SOUND_MIXER_VOLUME;
 
-	switch(whichDev) {
-	case SOUND_MIXER_VOLUME:
-		mask  = 0x000e;
-		reg_l = 0x200;
-		reg_r = 0x600;
-		lev_left  = SCALE(lev_left,  7) << 1;
-		lev_right = SCALE(lev_right, 7) << 1;
-		break;
+	if (mix_devs[whichDev].reg_l || mix_devs[whichDev].reg_r) {
+		const struct mix_ent *mix = mix_devs + whichDev;
+		unsigned int mask, left, right;
+
+		mask = mix->max << mix->shift;
+		lev_left  = SCALE(lev_left,  mix->max) << mix->shift;
+		lev_right = SCALE(lev_right, mix->max) << mix->shift;
+
+		/* read left setting */
+		left  = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+					       mix->reg_l << 8);
+
+		/* read right setting */
+		right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL |
+						mix->reg_r << 8);
+
+		left  = (left  & ~mask) | (lev_left  & mask);
+		right = (right & ~mask) | (lev_right & mask);
+
+		/* write left,right back */
+		waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+	} else {
+		switch(whichDev) {
+		case SOUND_MIXER_PCM:
+			waveartist_cmd3(devc, WACMD_SET_LEVEL,
+					SCALE(lev_left,  32767),
+					SCALE(lev_right, 32767));
+			break;
 
-	case SOUND_MIXER_LINE:
-		if ((devc->recmask & SOUND_MASK_LINE) == 0)
-			return;
-		mask  = 0x07c0;
-		reg_l = 0x000;
-		reg_r = 0x400;
-		lev_left  = SCALE(lev_left,  31) << 6;
-		lev_right = SCALE(lev_right, 31) << 6;
-		break;
+		case SOUND_MIXER_SYNTH:
+			waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL,
+					SCALE(lev_left,  32767),
+					SCALE(lev_right, 32767));
+			break;
+		}
+	}
+}
 
-	case SOUND_MIXER_MIC:
-		if ((devc->recmask & SOUND_MASK_MIC) == 0)
-			return;
-		mask  = 0x0030;
-		reg_l = 0x200;
-		reg_r = 0x600;
-		lev_left  = SCALE(lev_left,  3) << 4;
-		lev_right = SCALE(lev_right, 3) << 4;
-		break;
+/*
+ * Set the ADC MUX to the specified values.  We do NOT do any
+ * checking of the values passed, since we assume that the
+ * relevant *_select_input function has done that for us.
+ */
+static void
+waveartist_set_adc_mux(wavnc_info *devc, char left_dev, char right_dev)
+{
+	unsigned int reg_08, reg_09;
 
-	case SOUND_MIXER_RECLEV:
-		mask  = 0x000f;
-		reg_l = 0x300;
-		reg_r = 0x700;
-		lev_left  = SCALE(lev_left,  10);
-		lev_right = SCALE(lev_right, 10);
-		break;
+	reg_08 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0800);
+	reg_09 = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x0900);
 
-	case SOUND_MIXER_LINE1:
-		if ((devc->recmask & SOUND_MASK_LINE1) == 0)
-			return;
-		mask  = 0x003e;
-		reg_l = 0x000;
-		reg_r = 0x400;
-		lev_left  = SCALE(lev_left,  31) << 1;
-		lev_right = SCALE(lev_right, 31) << 1;
-		break;
+	reg_08 = (reg_08 & ~0x3f) | right_dev << 3 | left_dev;
 
-	case SOUND_MIXER_PCM:
-		waveartist_cmd3(devc, WACMD_SET_LEVEL,
-				SCALE(lev_left,  32767),
-				SCALE(lev_right, 32767));
-		return;
+	waveartist_cmd3(devc, WACMD_SET_MIXER, reg_08, reg_09);
+}
 
-	case SOUND_MIXER_SYNTH:
-		waveartist_cmd3(devc, 0x0100 | WACMD_SET_LEVEL,
-				SCALE(lev_left,  32767),
-				SCALE(lev_right, 32767));
-		return;
+/*
+ * Decode a recording mask into a mixer selection as follows:
+ *
+ *     OSS Source	WA Source	Actual source
+ *  SOUND_MASK_IMIX	Mixer		Mixer output (same as AD1848)
+ *  SOUND_MASK_LINE	Line		Line in
+ *  SOUND_MASK_LINE1	Aux 1		Aux 1 in
+ *  SOUND_MASK_LINE2	Aux 2		Aux 2 in
+ *  SOUND_MASK_MIC	Mic		Microphone
+ */
+static unsigned int
+waveartist_select_input(wavnc_info *devc, unsigned int recmask,
+			unsigned char *dev_l, unsigned char *dev_r)
+{
+	unsigned int recdev = ADC_MUX_NONE;
 
-	default:
-		return;
+	if (recmask & SOUND_MASK_IMIX) {
+		recmask = SOUND_MASK_IMIX;
+		recdev = ADC_MUX_MIXER;
+	} else if (recmask & SOUND_MASK_LINE2) {
+		recmask = SOUND_MASK_LINE2;
+		recdev = ADC_MUX_AUX2;
+	} else if (recmask & SOUND_MASK_LINE1) {
+		recmask = SOUND_MASK_LINE1;
+		recdev = ADC_MUX_AUX1;
+	} else if (recmask & SOUND_MASK_LINE) {
+		recmask = SOUND_MASK_LINE;
+		recdev = ADC_MUX_LINE;
+	} else if (recmask & SOUND_MASK_MIC) {
+		recmask = SOUND_MASK_MIC;
+		recdev = ADC_MUX_MIC;
 	}
 
-	/* read left setting */
-	vals[0] = reg_l + WACMD_GET_LEVEL;
-	waveartist_cmd(devc, 1, vals, 1, vals + 1);
+	*dev_l = *dev_r = recdev;
+
+	return recmask;
+}
+
+static int
+waveartist_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+			unsigned char lev_r)
+{
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_MIC:
+	case SOUND_MIXER_IGAIN:
+	case SOUND_MIXER_LINE1:
+	case SOUND_MIXER_LINE2:
+		devc->levels[dev] = lev_l | lev_r << 8;
+		break;
+
+	case SOUND_MIXER_IMIX:
+		break;
 
-	/* read right setting */
-	vals[0] = reg_r + 0x30;
-	waveartist_cmd(devc, 1, vals, 1, vals + 2);
+	default:
+		dev = -EINVAL;
+		break;
+	}
 
-	vals[1] = (vals[1] & ~mask) | (lev_left  & mask);
-	vals[2] = (vals[2] & ~mask) | (lev_right & mask);
+	return dev;
+}
 
-	/* write left,right back */
-	vals[0] = WACMD_SET_MIXER;
-	waveartist_cmd(devc, 3, vals, 0, NULL);
+static int waveartist_get_mixer(wavnc_info *devc, int dev)
+{
+	return devc->levels[dev];
 }
 
+static const struct waveartist_mixer_info waveartist_mixer = {
+	supported_devs:	SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN,
+	recording_devs:	SOUND_MASK_LINE  | SOUND_MASK_MIC   |
+			SOUND_MASK_LINE1 | SOUND_MASK_LINE2 |
+			SOUND_MASK_IMIX,
+	stereo_devs:	(SUPPORTED_MIXER_DEVICES | SOUND_MASK_IGAIN) & ~
+			(SOUND_MASK_SPEAKER | SOUND_MASK_IMIX),
+	select_input:	waveartist_select_input,
+	decode_mixer:	waveartist_decode_mixer,
+	get_mixer:	waveartist_get_mixer,
+};
+
 static void
-waveartist_select_input(wavnc_info *devc, unsigned int input)
+waveartist_set_recmask(wavnc_info *devc, unsigned int recmask)
 {
-	unsigned int vals[3];
+	unsigned char dev_l, dev_r;
 
-	/*
-	 * Get reg 9
-	 */
-	vals[0] = 0x0830;
-	waveartist_cmd(devc, 1, vals, 1, vals + 1);
+	recmask &= devc->mix->recording_devs;
 
 	/*
-	 * Get reg 10, only so that we can write it back.
+	 * If more than one recording device selected,
+	 * disable the device that is currently in use.
 	 */
-	vals[0] = 0x0930;
-	waveartist_cmd(devc, 1, vals, 1, vals + 2);
-
-	if (debug_flg & DEBUG_MIXER)
-		printk("RECSRC: old left: 0x%04X, old right: 0x%04X.\n",
-			vals[1] & 0x07, (vals[1] >> 3) & 0x07);
+	if (hweight32(recmask) > 1)
+		recmask &= ~devc->recmask;
 
 	/*
-	 * kill current left/right mux input select
+	 * Translate the recording device mask into
+	 * the ADC multiplexer settings.
 	 */
-	vals[1] &= ~0x03F;
+	devc->recmask = devc->mix->select_input(devc, recmask,
+						&dev_l, &dev_r);
 
-	switch (input) {
-	case SOUND_MASK_MIC:
-		/*
-		 * right=mic, left=mic
-		 */
-		vals[1] |= 0x002D;
-		break;
-
-	case SOUND_MASK_LINE1:
-		/*
-		 * right=none, left=Aux1;
-		 */
-		vals[1] |= 0x0004;
-		break;
+	waveartist_set_adc_mux(devc, dev_l, dev_r);
+}
 
-	case SOUND_MASK_LINE:
-		/*
-		 * right=Line, left=Line;
-		 */
-		vals[1] |= 0x0012;
-		break;
-	}
+static int
+waveartist_set_mixer(wavnc_info *devc, int dev, unsigned int level)
+{
+	unsigned int lev_left  = level & 0x00ff;
+	unsigned int lev_right = (level & 0xff00) >> 8;
 
-	if (debug_flg & DEBUG_MIXER)
-		printk("RECSRC %d: left=0x%04X, right=0x%04X.\n", input,
-			vals[1] & 0x07, (vals[1] >> 3) & 0x07);
+	if (lev_left > 100)
+		lev_left = 100;
+	if (lev_right > 100)
+		lev_right = 100;
 
 	/*
-	 * and finally - write the reg pair back....
+	 * Mono devices have their right volume forced to their
+	 * left volume.  (from ALSA driver OSS emulation).
 	 */
-	vals[0] = WACMD_SET_MIXER;
+	if (!(devc->mix->stereo_devs & (1 << dev)))
+		lev_right = lev_left;
 
-	waveartist_cmd(devc, 3, vals, 0, NULL);
+	dev = devc->mix->decode_mixer(devc, dev, lev_left, lev_right);
+
+	if (dev >= 0)
+		waveartist_mixer_update(devc, dev);
+
+	return dev < 0 ? dev : 0;
 }
 
 static int
-waveartist_mixer_set(wavnc_info *devc, int whichDev, unsigned int level)
+waveartist_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg)
 {
-	unsigned int lev_left  = level & 0x007f;
-	unsigned int lev_right = (level & 0x7f00) >> 8;
-	int left, right, devmask;
-
-	left = level & 0x7f;
-	right = (level & 0x7f00) >> 8;
-
-	if (debug_flg & DEBUG_MIXER)
-		printk("wa_mixer_set(dev=%d, level=%X)\n",
-			whichDev, level);
-
-	switch (whichDev) {
-	case SOUND_MIXER_VOLUME:	/* master volume (0-7)       */
-	case SOUND_MIXER_LINE:		/* external line (0-31)      */
-	case SOUND_MIXER_MIC:		/* mono mic (0-3)            */
-	case SOUND_MIXER_RECLEV:	/* recording level (0-7)     */
-	case SOUND_MIXER_LINE1:		/* mono external aux1 (0-31) */
-	case SOUND_MIXER_PCM:		/* Waveartist PCM (0-32767)  */
-	case SOUND_MIXER_SYNTH:		/* internal synth (0-31)     */
-	case SOUND_MIXER_IMIX:		/* recording feedback        */
-		devc->levels[whichDev] = lev_left | lev_right << 8;
-		waveartist_mixer_update(devc, whichDev);
-		break;
+	wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
+	int ret = 0, val, nr;
 
-	/* Select recording input source
+	/*
+	 * All SOUND_MIXER_* ioctls use type 'M'
 	 */
-	case SOUND_MIXER_RECSRC:
-		devmask = level & devc->rec_devices;
+	if (((cmd >> 8) & 255) != 'M')
+		return -ENOIOCTLCMD;
 
 #ifdef CONFIG_ARCH_NETWINDER
-		if (machine_is_netwinder())
-			vnc_configure_mixer(devc);
+	if (machine_is_netwinder()) {
+		ret = vnc_private_ioctl(dev, cmd, arg);
+		if (ret != -ENOIOCTLCMD)
+			return ret;
 		else
+			ret = 0;
+	}
 #endif
-		{
-			waveartist_select_input(devc, level);
 
-			/*
-			 * if record monitoring is on, make sure the bit is set
-			 */
-			if (devc->levels[SOUND_MIXER_IMIX])
-				waveartist_mixer_update(devc, SOUND_MIXER_IMIX);
+	nr = cmd & 0xff;
+
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+		if (get_user(val, (int *)arg))
+			return -EFAULT;
+
+		switch (nr) {
+		case SOUND_MIXER_RECSRC:
+			waveartist_set_recmask(devc, val);
+			break;
+
+		default:
+			ret = -EINVAL;
+			if (nr < SOUND_MIXER_NRDEVICES &&
+			    devc->mix->supported_devs & (1 << nr))
+				ret = waveartist_set_mixer(devc, nr, val);
 		}
+	}
 
-		/*
-		 * do not save in "levels", return current setting
-		 */
-		return devc->recmask;
+	if (ret == 0 && _SIOC_DIR(cmd) & _SIOC_READ) {
+		ret = -EINVAL;
 
-	default:
-		return -EINVAL;
+		switch (nr) {
+		case SOUND_MIXER_RECSRC:
+			ret = devc->recmask;
+			break;
+
+		case SOUND_MIXER_DEVMASK:
+			ret = devc->mix->supported_devs;
+			break;
+
+		case SOUND_MIXER_STEREODEVS:
+			ret = devc->mix->stereo_devs;
+			break;
+
+		case SOUND_MIXER_RECMASK:
+			ret = devc->mix->recording_devs;
+			break;
+
+		case SOUND_MIXER_CAPS:
+			ret = SOUND_CAP_EXCL_INPUT;
+			break;
+
+		default:
+			if (nr < SOUND_MIXER_NRDEVICES)
+				ret = devc->mix->get_mixer(devc, nr);
+			break;
+		}
+
+		if (ret >= 0)
+			ret = put_user(ret, (int *)arg) ? -EFAULT : 0;
 	}
 
-	return devc->levels[whichDev];
+	return ret;
 }
 
+static struct mixer_operations waveartist_mixer_operations =
+{
+	owner:	THIS_MODULE,
+	id:	"WaveArtist",
+	name:	"WaveArtist",
+	ioctl:	waveartist_mixer_ioctl
+};
+
 static void
 waveartist_mixer_reset(wavnc_info *devc)
 {
@@ -1098,7 +1227,7 @@
 	waveartist_cmd3(devc, WACMD_SET_MIXER, 0x9800, 0xa836);
 
 	/*
-	 * set mixer input select to none, RX filter gains 0 db
+	 * set mixer input select to none, RX filter gains 0 dB
 	 */
 	waveartist_cmd3(devc, WACMD_SET_MIXER, 0x4c00, 0x8c00);
 
@@ -1110,90 +1239,12 @@
 	/* set default input device = internal mic
 	 * current recording device = none
 	 */
-	devc->recmask = 0;
+	waveartist_set_recmask(devc, 0);
 
 	for (i = 0; i < SOUND_MIXER_NRDEVICES; i++)
 		waveartist_mixer_update(devc, i);
-
-	devc->supported_devices = SUPPORTED_MIXER_DEVICES;
-	devc->rec_devices = POSSIBLE_RECORDING_DEVICES;
-
-	if (machine_is_netwinder()) {
-		devc->supported_devices |= SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT;
-		devc->rec_devices |= SOUND_MASK_PHONEIN;
-	}
 }
 
-static int
-waveartist_mixer_ioctl(int dev, unsigned int cmd, caddr_t arg)
-{
-	wavnc_info *devc = (wavnc_info *)audio_devs[dev]->devc;
-	int ret;
-
-#ifdef CONFIG_ARCH_NETWINDER
-	if (machine_is_netwinder()) {
-		ret = vnc_private_ioctl(dev, cmd, arg);
-		if (ret != -ENOIOCTLCMD)
-			return ret;
-	}
-#endif
-
-	if (((cmd >> 8) & 0xff) == 'M') {
-		if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
-			int val;
-
-			if (get_user(val, (int *)arg))
-				return -EFAULT;
-
-			return waveartist_mixer_set(devc, cmd & 0xff, val);
-		} else {
-			/*
-			 * Return parameters
-			 */
-			switch (cmd & 0xff) {
-			case SOUND_MIXER_RECSRC:
-				ret = devc->recmask;
-				break;
-
-			case SOUND_MIXER_DEVMASK:
-				ret = devc->supported_devices;
-				break;
-
-			case SOUND_MIXER_STEREODEVS:
-				ret = devc->supported_devices &
-					~(SOUND_MASK_SPEAKER|SOUND_MASK_IMIX);
-				break;
-
-			case SOUND_MIXER_RECMASK:
-				ret = devc->rec_devices;
-				break;
-
-			case SOUND_MIXER_CAPS:
-				ret = SOUND_CAP_EXCL_INPUT;
-				break;
-
-			default:
-				if ((cmd & 0xff) < SOUND_MIXER_NRDEVICES)
-					ret = devc->levels[cmd & 0xff];
-				else
-					return -EINVAL;
-			}
-
-			return put_user(ret, (int *)arg) ? -EFAULT : 0;
-		}
-	}
-
-	return -ENOIOCTLCMD;
-}
-
-static struct mixer_operations waveartist_mixer_operations =
-{
-	owner:	THIS_MODULE,
-	id:	"WaveArtist",
-	name:	"WaveArtist NetWinder",
-	ioctl:	waveartist_mixer_ioctl
-};
-
 static int __init waveartist_init(wavnc_info *devc)
 {
 	wavnc_port_info *portc;
@@ -1297,13 +1348,13 @@
 		return 0;
 	}
 
-	if (hw_config->irq > _ISA_IRQ(15) || hw_config->irq < _ISA_IRQ(0)) {
+	if (hw_config->irq > 15 || hw_config->irq < 0) {
 		printk(KERN_WARNING "WaveArtist: Bad IRQ %d\n",
 		       hw_config->irq);
 		return 0;
 	}
 
-	if (hw_config->dma != _ISA_DMA(3)) {
+	if (hw_config->dma != 3) {
 		printk(KERN_WARNING "WaveArtist: Bad DMA %d\n",
 		       hw_config->dma);
 		return 0;
@@ -1317,7 +1368,8 @@
 	return 1;
 }
 
-static void __init attach_waveartist(struct address_info *hw)
+static void __init
+attach_waveartist(struct address_info *hw, const struct waveartist_mixer_info *mix)
 {
 	wavnc_info *devc = &adev_info[nr_waveartist_devs];
 
@@ -1339,6 +1391,7 @@
 
 	request_region(hw->io_base, 15, devc->hw.name);
 
+	devc->mix = mix;
 	devc->dev_no = waveartist_init(devc);
 
 	if (devc->dev_no < 0)
@@ -1352,7 +1405,9 @@
 			vnc_timer.data     = nr_waveartist_devs;
 			add_timer(&vnc_timer);
 
-			vnc_configure_mixer(devc);
+			vnc_configure_mixer(devc, 0);
+
+			devc->no_autoselect = 1;
 		}
 #endif
 		nr_waveartist_devs += 1;
@@ -1408,10 +1463,14 @@
 		       "to unload\n");
 }
 
+#ifdef CONFIG_ARCH_NETWINDER
+
 /*
  * Rebel.com Netwinder specifics...
  */
 
+#include <asm/hardware/dec21285.h>
+ 
 #define	VNC_TIMER_PERIOD (HZ/4)	//check slider 4 times/sec
 
 #define	MIXER_PRIVATE3_RESET	0x53570000
@@ -1427,7 +1486,7 @@
 extern spinlock_t gpio_lock;
 
 static inline void
-vnc_update_spkr_mute(wavnc_info *devc)
+vnc_mute_spkr(wavnc_info *devc)
 {
 	unsigned long flags;
 
@@ -1437,8 +1496,22 @@
 }
 
 static void
-vnc_mute_lout(wavnc_info *devc, int mute)
+vnc_mute_lout(wavnc_info *devc)
 {
+	unsigned int left, right;
+
+	left  = waveartist_cmd1_r(devc, WACMD_GET_LEVEL);
+	right = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x400);
+
+	if (devc->line_mute_state) {
+		left &= ~1;
+		right &= ~1;
+	} else {
+		left |= 1;
+		right |= 1;
+	}
+	waveartist_cmd3(devc, WACMD_SET_MIXER, left, right);
+		
 }
 
 static int
@@ -1491,67 +1564,177 @@
 	return old_slider_volume;
 }
 
-static void
-vnc_configure_mixer(wavnc_info *devc)
+/*
+ * Decode a recording mask into a mixer selection on the NetWinder
+ * as follows:
+ *
+ *     OSS Source	WA Source	Actual source
+ *  SOUND_MASK_IMIX	Mixer		Mixer output (same as AD1848)
+ *  SOUND_MASK_LINE	Line		Line in
+ *  SOUND_MASK_LINE1	Left Mic	Handset
+ *  SOUND_MASK_PHONEIN	Left Aux	Telephone microphone
+ *  SOUND_MASK_MIC	Right Mic	Builtin microphone
+ */
+static unsigned int
+netwinder_select_input(wavnc_info *devc, unsigned int recmask,
+		       unsigned char *dev_l, unsigned char *dev_r)
 {
-	u_int vals[3];
+	unsigned int recdev_l = ADC_MUX_NONE, recdev_r = ADC_MUX_NONE;
 
-	if (!devc->no_autoselect) {
-		if (devc->handset_detect) {
-			devc->recmask = SOUND_MASK_LINE1;
-			devc->spkr_mute_state = devc->line_mute_state = 1;
-		} else if (devc->telephone_detect) {
-			devc->recmask = SOUND_MASK_PHONEIN;
-			devc->spkr_mute_state = devc->line_mute_state = 1;
-		} else {
-			/* unless someone has asked for LINE-IN,
-			 * we default to MIC
-			 */
-			if ((devc->recmask & SOUND_MASK_LINE) == 0)
-				devc->recmask = SOUND_MASK_MIC;
-			devc->spkr_mute_state = devc->line_mute_state = 0;
-		}
-		vnc_update_spkr_mute(devc);
-		vnc_mute_lout(devc, devc->spkr_mute_state);
+	if (recmask & SOUND_MASK_IMIX) {
+		recmask = SOUND_MASK_IMIX;
+		recdev_l = ADC_MUX_MIXER;
+		recdev_r = ADC_MUX_MIXER;
+	} else if (recmask & SOUND_MASK_LINE) {
+		recmask = SOUND_MASK_LINE;
+		recdev_l = ADC_MUX_LINE;
+		recdev_r = ADC_MUX_LINE;
+	} else if (recmask & SOUND_MASK_LINE1) {
+		recmask = SOUND_MASK_LINE1;
+		waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+		recdev_l = ADC_MUX_MIC;
+		recdev_r = ADC_MUX_NONE;
+	} else if (recmask & SOUND_MASK_PHONEIN) {
+		recmask = SOUND_MASK_PHONEIN;
+		waveartist_cmd1(devc, WACMD_SET_MONO); /* left */
+		recdev_l = ADC_MUX_AUX1;
+		recdev_r = ADC_MUX_NONE;
+	} else if (recmask & SOUND_MASK_MIC) {
+		recmask = SOUND_MASK_MIC;
+		waveartist_cmd1(devc, WACMD_SET_MONO | 0x100);	/* right */
+		recdev_l = ADC_MUX_NONE;
+		recdev_r = ADC_MUX_MIC;
 	}
 
-	/* Ok.  At this point, we have done the autoswitch logic, or we
-	 * have had a command from an ioctl.  We have a valid devc->recmask.
-	 * Now we have to connect up the hardware to reflect the recmask.
-	 */
-	vals[1] = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x800);
-	vals[2] = waveartist_cmd1_r(devc, WACMD_GET_LEVEL | 0x900);
+	*dev_l = recdev_l;
+	*dev_r = recdev_r;
 
-	vals[1] &= ~0x3f;
+	return recmask;
+}
 
-	switch(devc->recmask) {
-	case SOUND_MASK_MIC:		/* builtin mic */
-		waveartist_cmd1(devc, WACMD_SET_MONO | 0x100);	/* right */
-		vals[1] |= 0x28;
+static int
+netwinder_decode_mixer(wavnc_info *devc, int dev, unsigned char lev_l,
+		       unsigned char lev_r)
+{
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_IGAIN:
+		devc->levels[dev] = lev_l | lev_r << 8;
 		break;
 
-	case SOUND_MASK_LINE1:		/* out handset */
-		waveartist_cmd1(devc, WACMD_SET_MONO);		/* left */
-		vals[1] |= 0x05;
+	case SOUND_MIXER_MIC:		/* right mic only */
+		devc->levels[SOUND_MIXER_MIC] &= 0xff;
+		devc->levels[SOUND_MIXER_MIC] |= lev_l << 8;
 		break;
 
-	case SOUND_MASK_PHONEIN:	/* our telephone mic */
-		waveartist_cmd1(devc, WACMD_SET_MONO);		/* left */
-		vals[1] |= 0x04;
+	case SOUND_MIXER_LINE1:		/* left mic only  */
+		devc->levels[SOUND_MIXER_MIC] &= 0xff00;
+		devc->levels[SOUND_MIXER_MIC] |= lev_l;
+		dev = SOUND_MIXER_MIC;
 		break;
 
-	case SOUND_MASK_LINE:		/* stereo line in */
-		vals[1] |= 12;
+	case SOUND_MIXER_PHONEIN:	/* left aux only  */
+		devc->levels[SOUND_MIXER_LINE1] = lev_l;
+		dev = SOUND_MIXER_LINE1;
+		break;
+
+	case SOUND_MIXER_IMIX:
+	case SOUND_MIXER_PHONEOUT:
 		break;
 
 	default:
-		return;
+		dev = -EINVAL;
+		break;
 	}
+	return dev;
+}
+
+static int netwinder_get_mixer(wavnc_info *devc, int dev)
+{
+	int levels;
+
+	switch (dev) {
+	case SOUND_MIXER_VOLUME:
+	case SOUND_MIXER_SYNTH:
+	case SOUND_MIXER_PCM:
+	case SOUND_MIXER_LINE:
+	case SOUND_MIXER_IGAIN:
+		levels = devc->levels[dev];
+		break;
+
+	case SOUND_MIXER_MIC:		/* builtin mic: right mic only */
+		levels = devc->levels[SOUND_MIXER_MIC] >> 8;
+		levels |= levels << 8;
+		break;
 
-	vals[0] = WACMD_SET_MIXER;
-	waveartist_cmd(devc, 3, vals, 0, NULL);
+	case SOUND_MIXER_LINE1:		/* handset mic: left mic only */
+		levels = devc->levels[SOUND_MIXER_MIC] & 0xff;
+		levels |= levels << 8;
+		break;
+
+	case SOUND_MIXER_PHONEIN:	/* phone mic: left aux1 only */
+		levels = devc->levels[SOUND_MIXER_LINE1] & 0xff;
+		levels |= levels << 8;
+		break;
 
-	waveartist_mixer_update(devc, SOUND_MIXER_IMIX);
+	default:
+		levels = 0;
+	}
+
+	return levels;
+}
+
+/*
+ * Waveartist specific mixer information.
+ */
+static const struct waveartist_mixer_info netwinder_mixer = {
+	supported_devs:	SOUND_MASK_VOLUME  | SOUND_MASK_SYNTH   |
+			SOUND_MASK_PCM     | SOUND_MASK_SPEAKER |
+			SOUND_MASK_LINE    | SOUND_MASK_MIC     |
+			SOUND_MASK_IMIX    | SOUND_MASK_LINE1   |
+			SOUND_MASK_PHONEIN | SOUND_MASK_PHONEOUT|
+			SOUND_MASK_IGAIN,
+
+	recording_devs:	SOUND_MASK_LINE    | SOUND_MASK_MIC     |
+			SOUND_MASK_IMIX    | SOUND_MASK_LINE1   |
+			SOUND_MASK_PHONEIN,
+
+	stereo_devs:	SOUND_MASK_VOLUME  | SOUND_MASK_SYNTH   |
+			SOUND_MASK_PCM     | SOUND_MASK_LINE    |
+			SOUND_MASK_IMIX    | SOUND_MASK_IGAIN,
+
+	select_input:	netwinder_select_input,
+	decode_mixer:	netwinder_decode_mixer,
+	get_mixer:	netwinder_get_mixer,
+};
+
+static void
+vnc_configure_mixer(wavnc_info *devc, unsigned int recmask)
+{
+	if (!devc->no_autoselect) {
+		if (devc->handset_detect) {
+			recmask = SOUND_MASK_LINE1;
+			devc->spkr_mute_state = devc->line_mute_state = 1;
+		} else if (devc->telephone_detect) {
+			recmask = SOUND_MASK_PHONEIN;
+			devc->spkr_mute_state = devc->line_mute_state = 1;
+		} else {
+			/* unless someone has asked for LINE-IN,
+			 * we default to MIC
+			 */
+			if ((devc->recmask & SOUND_MASK_LINE) == 0)
+				devc->recmask = SOUND_MASK_MIC;
+			devc->spkr_mute_state = devc->line_mute_state = 0;
+		}
+		vnc_mute_spkr(devc);
+		vnc_mute_lout(devc);
+
+		if (recmask != devc->recmask)
+			waveartist_set_recmask(devc, recmask);
+	}
 }
 
 static int
@@ -1562,10 +1745,10 @@
 
 	/*
 	 * read the "buttons" state.
-	 *  Bit 4 = handset present,
-	 *  Bit 5 = offhook
+	 *  Bit 4 = 0 means handset present
+	 *  Bit 5 = 1 means phone offhook
 	 */
-	temp = inb(0x201) & 0x30;
+	temp = inb(0x201);
 
 	old_hs = devc->handset_detect;
 	old_td = devc->telephone_detect;
@@ -1576,7 +1759,7 @@
 	if (!devc->no_autoselect &&
 	    (old_hs != devc->handset_detect ||
 	     old_td != devc->telephone_detect))
-		vnc_configure_mixer(devc);
+		vnc_configure_mixer(devc, devc->recmask);
 
 	slider_volume = vnc_volume_slider(devc);
 
@@ -1596,7 +1779,7 @@
 	if (slider_volume != temp && devc->use_slider) {
 		devc->slider_vol = slider_volume;
 
-		waveartist_mixer_set(devc, SOUND_MIXER_VOLUME,
+		waveartist_set_mixer(devc, SOUND_MIXER_VOLUME,
 			slider_volume | slider_volume << 8);
 
 		return 1;
@@ -1648,13 +1831,13 @@
 		devc->line_mute_state = (val & VNC_MUTE_LINE_OUT) ? 1 : 0;
 
 		if (prev_spkr_mute != devc->spkr_mute_state)
-			vnc_update_spkr_mute(devc);
+			vnc_mute_spkr(devc);
 
 		if (prev_line_mute != devc->line_mute_state)
-			vnc_mute_lout(devc, devc->line_mute_state);
+			vnc_mute_lout(devc);
 
 		if (prev_auto_state != devc->no_autoselect)
-			vnc_configure_mixer(devc);
+			vnc_configure_mixer(devc, devc->recmask);
 
 		return 0;
 	}
@@ -1735,32 +1918,40 @@
 		return put_user(val, (int *)arg) ? -EFAULT : 0;
 	}
 
-	if (((cmd >> 8) & 0xff) == 'M') {
-		if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
-			/*
-			 * special case for master volume: if we
-			 * received this call - switch from hw
-			 * volume control to a software volume
-			 * control, till the hw volume is modified
-			 * to signal that user wants to be back in
-			 * hardware...
-			 */
-			if ((cmd & 0xff) == SOUND_MIXER_VOLUME)
-				devc->use_slider = 0;
-		} else if ((cmd & 0xff) == SOUND_MIXER_STEREODEVS) {
-			val = devc->supported_devices &
-				~(SOUND_MASK_IMIX |
-				  SOUND_MASK_MIC |
-				  SOUND_MASK_LINE1 |
-				  SOUND_MASK_PHONEIN |
-				  SOUND_MASK_PHONEOUT);
-			return put_user(val, (int *)arg) ? -EFAULT : 0;
+	if (_SIOC_DIR(cmd) & _SIOC_WRITE) {
+		/*
+		 * special case for master volume: if we
+		 * received this call - switch from hw
+		 * volume control to a software volume
+		 * control, till the hw volume is modified
+		 * to signal that user wants to be back in
+		 * hardware...
+		 */
+		if ((cmd & 0xff) == SOUND_MIXER_VOLUME)
+			devc->use_slider = 0;
+
+		/* speaker output            */
+		if ((cmd & 0xff) == SOUND_MIXER_SPEAKER) {
+			unsigned int val, l, r;
+
+			if (get_user(val, (int *)arg))
+				return -EFAULT;
+
+			l = val & 0x7f;
+			r = (val & 0x7f00) >> 8;
+			val = (l + r) / 2;
+			devc->levels[SOUND_MIXER_SPEAKER] = val | (val << 8);
+			devc->spkr_mute_state = (val <= 50);
+			vnc_mute_spkr(devc);
+			return 0;
 		}
 	}
 
 	return -ENOIOCTLCMD;
 }
 
+#endif
+
 static struct address_info cfg;
 
 static int attached;
@@ -1771,13 +1962,10 @@
 static int __initdata dma2 = 0;
 
 
-MODULE_PARM(io, "i");		/* IO base */
-MODULE_PARM(irq, "i");		/* IRQ */
-MODULE_PARM(dma, "i");		/* DMA */
-MODULE_PARM(dma2, "i");		/* DMA2 */
-
 static int __init init_waveartist(void)
 {
+	const struct waveartist_mixer_info *mix;
+
 	if (!io && machine_is_netwinder()) {
 		/*
 		 * The NetWinder WaveArtist is at a fixed address.
@@ -1790,6 +1978,12 @@
 		dma2 = 7;
 	}
 
+	mix = &waveartist_mixer;
+#ifdef CONFIG_ARCH_NETWINDER
+	if (machine_is_netwinder())
+		mix = &netwinder_mixer;
+#endif
+
 	cfg.io_base = io;
 	cfg.irq = irq;
 	cfg.dma = dma;
@@ -1798,7 +1992,7 @@
 	if (!probe_waveartist(&cfg))
 		return -ENODEV;
 
-	attach_waveartist(&cfg);
+	attach_waveartist(&cfg, mix);
 	attached = 1;
 
 	return 0;
@@ -1830,3 +2024,10 @@
 }
 __setup("waveartist=", setup_waveartist);
 #endif
+
+MODULE_DESCRIPTION("Rockwell WaveArtist RWA-010 sound driver");
+MODULE_PARM(io, "i");		/* IO base */
+MODULE_PARM(irq, "i");		/* IRQ */
+MODULE_PARM(dma, "i");		/* DMA */
+MODULE_PARM(dma2, "i");		/* DMA2 */
+MODULE_LICENSE("GPL");

FUNET's LINUX-ADM group, linux-adm@nic.funet.fi
TCL-scripts by Sam Shen (who was at: slshen@lbl.gov)