patch-2.4.18 linux/drivers/sound/dmasound/dmasound_awacs.c

Next file: linux/drivers/sound/dmasound/dmasound_core.c
Previous file: linux/drivers/sound/dmasound/dmasound_atari.c
Back to the patch index
Back to the overall index

diff -Naur -X /home/marcelo/lib/dontdiff linux.orig/drivers/sound/dmasound/dmasound_awacs.c linux/drivers/sound/dmasound/dmasound_awacs.c
@@ -1,13 +1,60 @@
-
 /*
  *  linux/drivers/sound/dmasound/dmasound_awacs.c
  *
  *  PowerMac `AWACS' and `Burgundy' DMA Sound Driver
+ *  with some limited support for DACA & Tumbler
  *
- *  See linux/drivers/sound/dmasound/dmasound_core.c for copyright and credits
- */
-
+ *  See linux/drivers/sound/dmasound/dmasound_core.c for copyright and
+ *  history prior to 2001/01/26.
+ *
+ *	26/01/2001 ed 0.1 Iain Sandoe
+ *		- added version info.
+ *		- moved dbdma command buffer allocation to PMacXXXSqSetup()
+ *		- fixed up beep dbdma cmd buffers
+ *
+ *	08/02/2001 [0.2]
+ *		- make SNDCTL_DSP_GETFMTS return the correct info for the h/w
+ *		- move soft format translations to a separate file
+ *		- [0.3] make SNDCTL_DSP_GETCAPS return correct info.
+ *		- [0.4] more informative machine name strings.
+ *		- [0.5]
+ *		- record changes.
+ *		- made the default_hard/soft entries.
+ *	04/04/2001 [0.6]
+ *		- minor correction to bit assignments in awacs_defs.h
+ *		- incorporate mixer changes from 2.2.x back-port.
+ *		- take out passthru as a rec input (it isn't).
+ *              - make Input Gain slider work the 'right way up'.
+ *              - try to make the mixer sliders more logical - so now the
+ *                input selectors are just two-state (>50% == ON) and the
+ *                Input Gain slider handles the rest of the gain issues.
+ *              - try to pick slider representations that most closely match
+ *                the actual use - e.g. IGain for input gain... 
+ *              - first stab at over/under-run detection.
+ *		- minor cosmetic changes to IRQ identification.
+ *		- fix bug where rates > max would be reported as supported.
+ *              - first stab at over/under-run detection.
+ *              - make use of i2c for mixer settings conditional on perch
+ *                rather than cuda (some machines without perch have cuda).
+ *              - fix bug where TX stops when dbdma status comes up "DEAD"
+ *		  so far only reported on PowerComputing clones ... but.
+ *		- put in AWACS/Screamer register write timeouts.
+ *		- part way to partitioning the init() stuff
+ *		- first pass at 'tumbler' stuff (not support - just an attempt
+ *		  to allow the driver to load on new G4s).
+ *      01/02/2002 [0.7] - BenH
+ *	        - all sort of minor bits went in since the latest update, I
+ *	          bumped the version number for that reason
+*/
+
+/* GENERAL FIXME/TODO: check that the assumptions about what is written to
+   mac-io is valid for DACA & Tumbler.
+
+   This driver is in bad need of a rewrite. The dbdma code has to be split,
+   some proper device-tree parsing code has to be written, etc...
+*/
 
+#include <linux/types.h>
 #include <linux/module.h>
 #include <linux/config.h>
 #include <linux/slab.h>
@@ -18,6 +65,9 @@
 #include <linux/nvram.h>
 #include <linux/tty.h>
 #include <linux/vt_kern.h>
+#include <linux/irq.h>
+#include <linux/kmod.h>
+#include <asm/semaphore.h>
 #ifdef CONFIG_ADB_CUDA
 #include <linux/cuda.h>
 #endif
@@ -25,20 +75,30 @@
 #include <linux/pmu.h>
 #endif
 
+#include <linux/i2c-dev.h>
+
 #include <asm/uaccess.h>
 #include <asm/prom.h>
 #include <asm/machdep.h>
 #include <asm/io.h>
 #include <asm/dbdma.h>
-#include <asm/feature.h>
+#include <asm/pmac_feature.h>
 #include <asm/irq.h>
+#include <asm/nvram.h>
 
 #include "awacs_defs.h"
 #include "dmasound.h"
 
+#define DMASOUND_AWACS_REVISION	0
+#define DMASOUND_AWACS_EDITION	7
 
+#define AWACS_BURGUNDY	100	/* fake revision # for burgundy */
+#define AWACS_TUMBLER    90	/* fake revision # for tumbler */
+#define AWACS_DACA	 80	/* fake revision # for daca (ibook) */
+#define AWACS_AWACS       2     /* holding revision for AWACS */
+#define AWACS_SCREAMER    3     /* holding revision for Screamer */
 /*
- * Interrupt numbers and addresses, obtained from the device tree.
+ * Interrupt numbers and addresses, & info obtained from the device tree.
  */
 static int awacs_irq, awacs_tx_irq, awacs_rx_irq;
 static volatile struct awacs_regs *awacs;
@@ -50,28 +110,58 @@
 
 static char awacs_name[64];
 static int awacs_revision;
-int awacs_is_screamer = 0;
-int awacs_device_id = 0;
-int awacs_has_iic = 0;
-#define AWACS_BURGUNDY	100		/* fake revision # for burgundy */
+static int awacs_sleeping;
+static DECLARE_MUTEX(dmasound_sem);
+
+static int sound_device_id;		/* exists after iMac revA */
+static int hw_can_byteswap = 1 ;	/* most pmac sound h/w can */
+
+/* model info */
+/* To be replaced with better interaction with pmac_feature.c */
+static int is_pbook_3X00;
+static int is_pbook_g3;
+
+/* expansion info */
+static int has_perch;
+static int has_ziva;
+
+/* for earlier powerbooks which need fiddling with mac-io to enable
+ * cd etc.
+*/
+static unsigned char *latch_base;
+static unsigned char *macio_base;
 
 /*
  * Space for the DBDMA command blocks.
  */
 static void *awacs_tx_cmd_space;
 static volatile struct dbdma_cmd *awacs_tx_cmds;
+static int number_of_tx_cmd_buffers = 0;
 
 static void *awacs_rx_cmd_space;
 static volatile struct dbdma_cmd *awacs_rx_cmds;
+static int number_of_rx_cmd_buffers = 0;
 
 /*
  * Cached values of AWACS registers (we can't read them).
- * Except on the burgundy. XXX
+ * Except on the burgundy (and screamer). XXX
  */
+
 int awacs_reg[8];
+int awacs_reg1_save;
+
+/* tracking values for the mixer contents
+*/
 
-#define HAS_16BIT_TABLES
-#undef HAS_8BIT_TABLES
+static int spk_vol = 0 ;
+static int line_vol = 0 ;
+static int passthru_vol = 0 ;
+
+static int ip_gain = 0 ; /* mic preamp settings */
+static int rec_lev = 0x4545 ; /* default CD gain 69 % */
+static int mic_lev = 0 ;
+static int cd_lev = 0x6363 ; /* 99 % */
+static int line_lev = 0 ;
 
 /*
  * Stuff for outputting a beep.  The values range from -327 to +327
@@ -113,20 +203,18 @@
 	-269,	-245,	-218,	-187,	-153,	-117,	-79,	-40,
 };
 
+/* beep support */
 #define BEEP_SRATE	22050	/* 22050 Hz sample rate */
 #define BEEP_BUFLEN	512
 #define BEEP_VOLUME	15	/* 0 - 100 */
 
-static int beep_volume = BEEP_VOLUME;
+static int beep_vol = BEEP_VOLUME;
 static int beep_playing = 0;
 static int awacs_beep_state = 0;
 static short *beep_buf;
+static void *beep_dbdma_cmd_space;
 static volatile struct dbdma_cmd *beep_dbdma_cmd;
 static void (*orig_mksound)(unsigned int, unsigned int);
-static int is_pbook_3400;
-static unsigned char *latch_base;
-static int is_pbook_G3;
-static unsigned char *macio_base;
 
 /* Burgundy functions */
 static void awacs_burgundy_wcw(unsigned addr,unsigned newval);
@@ -136,6 +224,18 @@
 static void awacs_burgundy_write_mvolume(unsigned address, int volume);
 static int awacs_burgundy_read_mvolume(unsigned address);
 
+/* we will allocate a single 'emergency' dbdma cmd block to use if the
+   tx status comes up "DEAD".  This happens on some PowerComputing Pmac
+   clones, either owing to a bug in dbdma or some interaction between
+   IDE and sound.  However, this measure would deal with DEAD status if
+   if appeared elsewhere.
+
+   for the sake of memory efficiency we'll allocate this cmd as part of
+   the beep cmd stuff.
+*/
+
+static volatile struct dbdma_cmd *emergency_dbdma_cmd;
+
 #ifdef CONFIG_PMAC_PBOOK
 /*
  * Stuff for restoring after a sleep.
@@ -146,78 +246,11 @@
 };
 #endif /* CONFIG_PMAC_PBOOK */
 
-static int expand_bal;	/* Balance factor for expanding (not volume!) */
-static int expand_data;	/* Data for expanding */
-
-
-/*** Translations ************************************************************/
-
-
-/* ++TeSche: radically changed for new expanding purposes...
- *
- * These two routines now deal with copying/expanding/translating the samples
- * from user space into our buffer at the right frequency. They take care about
- * how much data there's actually to read, how much buffer space there is and
- * to convert samples into the right frequency/encoding. They will only work on
- * complete samples so it may happen they leave some bytes in the input stream
- * if the user didn't write a multiple of the current sample size. They both
- * return the number of bytes they've used from both streams so you may detect
- * such a situation. Luckily all programs should be able to cope with that.
- *
- * I think I've optimized anything as far as one can do in plain C, all
- * variables should fit in registers and the loops are really short. There's
- * one loop for every possible situation. Writing a more generalized and thus
- * parameterized loop would only produce slower code. Feel free to optimize
- * this in assembler if you like. :)
- *
- * I think these routines belong here because they're not yet really hardware
- * independent, especially the fact that the Falcon can play 16bit samples
- * only in stereo is hardcoded in both of them!
- *
- * ++geert: split in even more functions (one per format)
- */
-
-static ssize_t pmac_ct_law(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ct_s8(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft);
-static ssize_t pmac_ct_u8(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft);
-static ssize_t pmac_ct_s16(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ct_u16(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ctx_law(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft);
-static ssize_t pmac_ctx_s8(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ctx_u8(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ctx_s16(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft);
-static ssize_t pmac_ctx_u16(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft);
-static ssize_t pmac_ct_s16_read(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-static ssize_t pmac_ct_u16_read(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft);
-
+/* for (soft) sample rate translations */
+int expand_bal;		/* Balance factor for expanding (not volume!) */
 
 /*** Low level stuff *********************************************************/
 
-
 static void PMacOpen(void);
 static void PMacRelease(void);
 static void *PMacAlloc(unsigned int size, int flags);
@@ -244,565 +277,214 @@
 
 /*** Mid level stuff **********************************************************/
 
-
 static int PMacMixerIoctl(u_int cmd, u_long arg);
-static void PMacWriteSqSetup(void);
-static void PMacReadSqSetup(void);
+static int PMacWriteSqSetup(void);
+static int PMacReadSqSetup(void);
 static void PMacAbortRead(void);
 
+extern TRANS transAwacsNormal ;
+extern TRANS transAwacsExpand ;
+extern TRANS transAwacsNormalRead ;
+
+extern int daca_init(void);
+extern int daca_cleanup(void);
+extern int daca_set_volume(uint left_vol, uint right_vol);
+extern void daca_get_volume(uint * left_vol, uint  *right_vol);
+extern int daca_enter_sleep(void);
+extern int daca_leave_sleep(void);
+
+extern int tas_init(void);
+extern int tas_cleanup(void);
+extern int tumbler_set_volume(uint left_vol, uint right_vol);
+extern void tumbler_get_volume(uint * left_vol, uint  *right_vol);
+extern void tumbler_set_treble(int treble);
+extern void tumbler_get_treble(int *treble);
+extern void tumbler_set_bass(int bass);
+extern void tumbler_get_bass(int *bass);
+extern void tumbler_set_pcm_lvl(int pcm_lvl);
+extern void tumbler_get_pcm_lvl(int *pcm_lvl);
+extern int tumbler_enter_sleep(void);
+extern int tumbler_leave_sleep(void);
+
+#define TRY_LOCK()	\
+	if ((rc = down_interruptible(&dmasound_sem)) != 0)	\
+		return rc;
+#define LOCK()		down(&dmasound_sem);
+
+#define UNLOCK()	up(&dmasound_sem);
+
+/* We use different versions that the ones provided in dmasound.h
+ * 
+ * FIXME: Use different names ;)
+ */
+#undef IOCTL_IN
+#undef IOCTL_OUT
 
-/*** Translations ************************************************************/
-
-
-static ssize_t pmac_ct_law(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	short *table = dmasound.soft.format == AFMT_MU_LAW
-		? dmasound_ulaw2dma16 : dmasound_alaw2dma16;
-	ssize_t count, used;
-	short *p = (short *) &frame[*frameUsed];
-	int val, stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		u_char data;
-		if (get_user(data, userPtr++))
-			return -EFAULT;
-		val = table[data];
-		*p++ = val;
-		if (stereo) {
-			if (get_user(data, userPtr++))
-				return -EFAULT;
-			val = table[data];
-		}
-		*p++ = val;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 2: used;
-}
-
-
-static ssize_t pmac_ct_s8(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft)
-{
-	ssize_t count, used;
-	short *p = (short *) &frame[*frameUsed];
-	int val, stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		u_char data;
-		if (get_user(data, userPtr++))
-			return -EFAULT;
-		val = data << 8;
-		*p++ = val;
-		if (stereo) {
-			if (get_user(data, userPtr++))
-				return -EFAULT;
-			val = data << 8;
-		}
-		*p++ = val;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 2: used;
-}
-
-
-static ssize_t pmac_ct_u8(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft)
-{
-	ssize_t count, used;
-	short *p = (short *) &frame[*frameUsed];
-	int val, stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		u_char data;
-		if (get_user(data, userPtr++))
-			return -EFAULT;
-		val = (data ^ 0x80) << 8;
-		*p++ = val;
-		if (stereo) {
-			if (get_user(data, userPtr++))
-				return -EFAULT;
-			val = (data ^ 0x80) << 8;
-		}
-		*p++ = val;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 2: used;
-}
-
-
-static ssize_t pmac_ct_s16(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	ssize_t count, used;
-	int stereo = dmasound.soft.stereo;
-	short *fp = (short *) &frame[*frameUsed];
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	if (!stereo) {
-		short *up = (short *) userPtr;
-		while (count > 0) {
-			short data;
-			if (get_user(data, up++))
-				return -EFAULT;
-			*fp++ = data;
-			*fp++ = data;
-			count--;
-		}
-	} else {
-		if (copy_from_user(fp, userPtr, count * 4))
-			return -EFAULT;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 4: used * 2;
-}
+#define IOCTL_IN(arg, ret)	\
+	rc = get_user(ret, (int *)(arg)); \
+	if (rc) break;
+#define IOCTL_OUT(arg, ret)	\
+	ioctl_return2((int *)(arg), ret)
 
-static ssize_t pmac_ct_u16(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	ssize_t count, used;
-	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
-	int stereo = dmasound.soft.stereo;
-	short *fp = (short *) &frame[*frameUsed];
-	short *up = (short *) userPtr;
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		int data;
-		if (get_user(data, up++))
-			return -EFAULT;
-		data ^= mask;
-		*fp++ = data;
-		if (stereo) {
-			if (get_user(data, up++))
-				return -EFAULT;
-			data ^= mask;
-		}
-		*fp++ = data;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 4: used * 2;
+static inline int ioctl_return2(int *addr, int value)
+{
+	return value < 0 ? value : put_user(value, addr);
 }
 
 
-static ssize_t pmac_ctx_law(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft)
-{
-	unsigned short *table = (unsigned short *)
-		(dmasound.soft.format == AFMT_MU_LAW
-		 ? dmasound_ulaw2dma16 : dmasound_alaw2dma16);
-	unsigned int data = expand_data;
-	unsigned int *p = (unsigned int *) &frame[*frameUsed];
-	int bal = expand_bal;
-	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
-	int utotal, ftotal;
-	int stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	ftotal = frameLeft;
-	utotal = userCount;
-	while (frameLeft) {
-		u_char c;
-		if (bal < 0) {
-			if (userCount == 0)
-				break;
-			if (get_user(c, userPtr++))
-				return -EFAULT;
-			data = table[c];
-			if (stereo) {
-				if (get_user(c, userPtr++))
-					return -EFAULT;
-				data = (data << 16) + table[c];
-			} else
-				data = (data << 16) + data;
-			userCount--;
-			bal += hSpeed;
-		}
-		*p++ = data;
-		frameLeft--;
-		bal -= sSpeed;
-	}
-	expand_bal = bal;
-	expand_data = data;
-	*frameUsed += (ftotal - frameLeft) * 4;
-	utotal -= userCount;
-	return stereo? utotal * 2: utotal;
-}
+/*** AE - TUMBLER START *********************************************************/
 
 
-static ssize_t pmac_ctx_s8(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	unsigned int *p = (unsigned int *) &frame[*frameUsed];
-	unsigned int data = expand_data;
-	int bal = expand_bal;
-	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
-	int stereo = dmasound.soft.stereo;
-	int utotal, ftotal;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	ftotal = frameLeft;
-	utotal = userCount;
-	while (frameLeft) {
-		u_char c;
-		if (bal < 0) {
-			if (userCount == 0)
-				break;
-			if (get_user(c, userPtr++))
-				return -EFAULT;
-			data = c << 8;
-			if (stereo) {
-				if (get_user(c, userPtr++))
-					return -EFAULT;
-				data = (data << 16) + (c << 8);
-			} else
-				data = (data << 16) + data;
-			userCount--;
-			bal += hSpeed;
-		}
-		*p++ = data;
-		frameLeft--;
-		bal -= sSpeed;
-	}
-	expand_bal = bal;
-	expand_data = data;
-	*frameUsed += (ftotal - frameLeft) * 4;
-	utotal -= userCount;
-	return stereo? utotal * 2: utotal;
-}
+int gpio_audio_reset, gpio_audio_reset_pol;
+int gpio_amp_mute, gpio_amp_mute_pol;
+int gpio_headphone_mute, gpio_headphone_mute_pol;
+int gpio_headphone_detect, gpio_headphone_detect_pol;
+int gpio_headphone_irq;
 
+int
+setup_audio_gpio(const char *name, const char* compatible, int *gpio_addr, int* gpio_pol)
+{
+	struct device_node *np;
+	u32* pp;
+	
+	np = find_devices("gpio");
+	if (!np)
+		return -ENODEV;
 
-static ssize_t pmac_ctx_u8(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	unsigned int *p = (unsigned int *) &frame[*frameUsed];
-	unsigned int data = expand_data;
-	int bal = expand_bal;
-	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
-	int stereo = dmasound.soft.stereo;
-	int utotal, ftotal;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	ftotal = frameLeft;
-	utotal = userCount;
-	while (frameLeft) {
-		u_char c;
-		if (bal < 0) {
-			if (userCount == 0)
+	np = np->child;
+	while(np != 0) {
+		if (name) {
+			char *property = get_property(np,"audio-gpio",NULL);
+			if (property != 0 && strcmp(property,name) == 0)
 				break;
-			if (get_user(c, userPtr++))
-				return -EFAULT;
-			data = (c ^ 0x80) << 8;
-			if (stereo) {
-				if (get_user(c, userPtr++))
-					return -EFAULT;
-				data = (data << 16) + ((c ^ 0x80) << 8);
-			} else
-				data = (data << 16) + data;
-			userCount--;
-			bal += hSpeed;
-		}
-		*p++ = data;
-		frameLeft--;
-		bal -= sSpeed;
-	}
-	expand_bal = bal;
-	expand_data = data;
-	*frameUsed += (ftotal - frameLeft) * 4;
-	utotal -= userCount;
-	return stereo? utotal * 2: utotal;
+		} else if (compatible && device_is_compatible(np, compatible))
+			break;
+		np = np->sibling;
+	}
+	if (!np)
+		return -ENODEV;
+	pp = (u32 *)get_property(np, "AAPL,address", NULL);
+	if (!pp)
+		return -ENODEV;
+	*gpio_addr = (*pp) & 0x0000ffff;
+	pp = (u32 *)get_property(np, "audio-gpio-active-state", NULL);
+	if (pp)
+		*gpio_pol = *pp;
+	else
+		*gpio_pol = 1;
+	if (np->n_intrs > 0)
+		return np->intrs[0].line;
+	
+	return 0;
 }
 
-
-static ssize_t pmac_ctx_s16(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft)
-{
-	unsigned int *p = (unsigned int *) &frame[*frameUsed];
-	unsigned int data = expand_data;
-	unsigned short *up = (unsigned short *) userPtr;
-	int bal = expand_bal;
-	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
-	int stereo = dmasound.soft.stereo;
-	int utotal, ftotal;
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	ftotal = frameLeft;
-	utotal = userCount;
-	while (frameLeft) {
-		unsigned short c;
-		if (bal < 0) {
-			if (userCount == 0)
-				break;
-			if (get_user(data, up++))
-				return -EFAULT;
-			if (stereo) {
-				if (get_user(c, up++))
-					return -EFAULT;
-				data = (data << 16) + c;
-			} else
-				data = (data << 16) + data;
-			userCount--;
-			bal += hSpeed;
-		}
-		*p++ = data;
-		frameLeft--;
-		bal -= sSpeed;
-	}
-	expand_bal = bal;
-	expand_data = data;
-	*frameUsed += (ftotal - frameLeft) * 4;
-	utotal -= userCount;
-	return stereo? utotal * 4: utotal * 2;
+static inline void
+write_audio_gpio(int gpio_addr, int data)
+{
+	if (!gpio_addr)
+		return;
+	pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_addr, data ? 0x05 : 0x04);
 }
 
+static inline int
+read_audio_gpio(int gpio_addr)
+{
+	if (!gpio_addr)
+		return 0;
+	return ((pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_addr, 0) & 0x02) !=0);
+}
 
-static ssize_t pmac_ctx_u16(const u_char *userPtr, size_t userCount,
-			    u_char frame[], ssize_t *frameUsed,
-			    ssize_t frameLeft)
-{
-	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
-	unsigned int *p = (unsigned int *) &frame[*frameUsed];
-	unsigned int data = expand_data;
-	unsigned short *up = (unsigned short *) userPtr;
-	int bal = expand_bal;
-	int hSpeed = dmasound.hard.speed, sSpeed = dmasound.soft.speed;
-	int stereo = dmasound.soft.stereo;
-	int utotal, ftotal;
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	ftotal = frameLeft;
-	utotal = userCount;
-	while (frameLeft) {
-		unsigned short c;
-		if (bal < 0) {
-			if (userCount == 0)
-				break;
-			if (get_user(data, up++))
-				return -EFAULT;
-			data ^= mask;
-			if (stereo) {
-				if (get_user(c, up++))
-					return -EFAULT;
-				data = (data << 16) + (c ^ mask);
-			} else
-				data = (data << 16) + data;
-			userCount--;
-			bal += hSpeed;
-		}
-		*p++ = data;
-		frameLeft--;
-		bal -= sSpeed;
-	}
-	expand_bal = bal;
-	expand_data = data;
-	*frameUsed += (ftotal - frameLeft) * 4;
-	utotal -= userCount;
-	return stereo? utotal * 4: utotal * 2;
-}
-
-static ssize_t pmac_ct_s8_read(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft)
-{
-	ssize_t count, used;
-	short *p = (short *) &frame[*frameUsed];
-	int val, stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		u_char data;
-
-		val = *p++;
-		data = val >> 8;
-		if (put_user(data, (u_char *)userPtr++))
-			return -EFAULT;
-		if (stereo) {
-			val = *p;
-			data = val >> 8;
-			if (put_user(data, (u_char *)userPtr++))
-				return -EFAULT;
-		}
-		p++;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 2: used;
-}
-
-
-static ssize_t pmac_ct_u8_read(const u_char *userPtr, size_t userCount,
-			  u_char frame[], ssize_t *frameUsed,
-			  ssize_t frameLeft)
-{
-	ssize_t count, used;
-	short *p = (short *) &frame[*frameUsed];
-	int val, stereo = dmasound.soft.stereo;
-
-	frameLeft >>= 2;
-	if (stereo)
-		userCount >>= 1;
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		u_char data;
-
-		val = *p++;
-		data = (val >> 8) ^ 0x80;
-		if (put_user(data, (u_char *)userPtr++))
-			return -EFAULT;
-		if (stereo) {
-			val = *p;
-			data = (val >> 8) ^ 0x80;
-			if (put_user(data, (u_char *)userPtr++))
-				return -EFAULT;
-		}
-		p++;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 2: used;
-}
-
-
-static ssize_t pmac_ct_s16_read(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	ssize_t count, used;
-	int stereo = dmasound.soft.stereo;
-	short *fp = (short *) &frame[*frameUsed];
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	if (!stereo) {
-		short *up = (short *) userPtr;
-		while (count > 0) {
-			short data;
-			data = *fp;
-			if (put_user(data, up++))
-				return -EFAULT;
-			fp+=2;
-			count--;
-		}
+static void
+headphone_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	if (read_audio_gpio(gpio_headphone_detect) == gpio_headphone_detect_pol) {
+		printk(KERN_INFO "Audio jack plugged, muting speakers.\n");
+		write_audio_gpio(gpio_amp_mute, gpio_amp_mute_pol);
+		write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol);
 	} else {
-		if (copy_to_user((u_char *)userPtr, fp, count * 4))
-			return -EFAULT;
+		printk(KERN_INFO "Audio jack unplugged, enabling speakers.\n");
+		write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol);
+		write_audio_gpio(gpio_headphone_mute, gpio_headphone_mute_pol);
 	}
-	*frameUsed += used * 4;
-	return stereo? used * 4: used * 2;
 }
 
-static ssize_t pmac_ct_u16_read(const u_char *userPtr, size_t userCount,
-			   u_char frame[], ssize_t *frameUsed,
-			   ssize_t frameLeft)
-{
-	ssize_t count, used;
-	int mask = (dmasound.soft.format == AFMT_U16_LE? 0x0080: 0x8000);
-	int stereo = dmasound.soft.stereo;
-	short *fp = (short *) &frame[*frameUsed];
-	short *up = (short *) userPtr;
-
-	frameLeft >>= 2;
-	userCount >>= (stereo? 2: 1);
-	used = count = min_t(unsigned long, userCount, frameLeft);
-	while (count > 0) {
-		int data;
-
-		data = *fp++;
-		data ^= mask;
-		if (put_user(data, up++))
-			return -EFAULT;
-		if (stereo) {
-			data = *fp;
-			data ^= mask;
-			if (put_user(data, up++))
-				return -EFAULT;
-		}
-		fp++;
-		count--;
-	}
-	*frameUsed += used * 4;
-	return stereo? used * 4: used * 2;
+
+/* Initialize tumbler */
+
+static int
+awacs_tumbler_init(void)
+{
+	setup_audio_gpio(
+		"audio-hw-reset",
+		NULL,
+		&gpio_audio_reset,
+		&gpio_audio_reset_pol);
+	setup_audio_gpio(
+		"amp-mute",
+		NULL,
+		&gpio_amp_mute,
+		&gpio_amp_mute_pol);
+	setup_audio_gpio("headphone-mute",
+		NULL,
+		&gpio_headphone_mute,
+		&gpio_headphone_mute_pol);
+	gpio_headphone_irq = setup_audio_gpio(
+		"headphone-detect",
+		NULL,
+		&gpio_headphone_detect,
+		&gpio_headphone_detect_pol);
+	/* Fix some broken OF entries in desktop machines */
+	if (!gpio_headphone_irq)
+		gpio_headphone_irq = setup_audio_gpio(
+			NULL,
+			"keywest-gpio15",
+			&gpio_headphone_detect,
+			&gpio_headphone_detect_pol);
+
+	write_audio_gpio(gpio_audio_reset, gpio_audio_reset_pol);
+	wait_ms(100);
+	write_audio_gpio(gpio_audio_reset, !gpio_audio_reset_pol);
+	wait_ms(100);
+  	if (gpio_headphone_irq) {
+		if (request_irq(gpio_headphone_irq,headphone_intr,0,"Headphone detect",0) < 0) {
+    			printk(KERN_ERR "tumbler: Can't request headphone interrupt\n");
+    			gpio_headphone_irq = 0;
+    		} else {
+			u8 val;
+			/* Activate headphone status interrupts */
+			val = pmac_call_feature(PMAC_FTR_READ_GPIO, NULL, gpio_headphone_detect, 0);
+			pmac_call_feature(PMAC_FTR_WRITE_GPIO, NULL, gpio_headphone_detect, val | 0x80);
+			/* Trigger it */
+  			headphone_intr(0,0,0);
+  		}
+  	}
+  	if (!gpio_headphone_irq) {
+  		/* Some machine enter this case ? */
+  		printk(KERN_WARNING "tumbler: Headphone detect IRQ not found, enabling all outputs !\n");
+  		write_audio_gpio(gpio_amp_mute, !gpio_amp_mute_pol);
+  		write_audio_gpio(gpio_headphone_mute, !gpio_headphone_mute_pol);
+  	}
+	return 0;
 }
 
 
-static TRANS transAwacsNormal = {
-	ct_ulaw:	pmac_ct_law,
-	ct_alaw:	pmac_ct_law,
-	ct_s8:		pmac_ct_s8,
-	ct_u8:		pmac_ct_u8,
-	ct_s16be:	pmac_ct_s16,
-	ct_u16be:	pmac_ct_u16,
-	ct_s16le:	pmac_ct_s16,
-	ct_u16le:	pmac_ct_u16,
-};
+static int
+awacs_tumbler_cleanup(void)
+{
+	if (gpio_headphone_irq)
+		free_irq(gpio_headphone_irq, 0);
+	return 0;
+}
 
-static TRANS transAwacsExpand = {
-	ct_ulaw:	pmac_ctx_law,
-	ct_alaw:	pmac_ctx_law,
-	ct_s8:		pmac_ctx_s8,
-	ct_u8:		pmac_ctx_u8,
-	ct_s16be:	pmac_ctx_s16,
-	ct_u16be:	pmac_ctx_u16,
-	ct_s16le:	pmac_ctx_s16,
-	ct_u16le:	pmac_ctx_u16,
-};
 
-static TRANS transAwacsNormalRead = {
-	ct_s8:		pmac_ct_s8_read,
-	ct_u8:		pmac_ct_u8_read,
-	ct_s16be:	pmac_ct_s16_read,
-	ct_u16be:	pmac_ct_u16_read,
-	ct_s16le:	pmac_ct_s16_read,
-	ct_u16le:	pmac_ct_u16_read,
-};
+/*** AE - TUMBLER END *********************************************************/
 
-/*** Low level stuff *********************************************************/
 
 
+/*** Low level stuff *********************************************************/
 
 /*
- * PCI PowerMac, with AWACS and DBDMA.
+ * PCI PowerMac, with AWACS, Screamer, Burgundy, DACA or Tumbler and DBDMA.
  */
 
 static void PMacOpen(void)
@@ -827,9 +509,9 @@
 
 static int __init PMacIrqInit(void)
 {
-	if (request_irq(awacs_irq, pmac_awacs_intr, 0, "AWACS", 0)
-	    || request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "AWACS out", 0)
-	    || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "AWACS in", 0))
+	if (request_irq(awacs_irq, pmac_awacs_intr, 0, "Built-in Sound misc", 0)
+	    || request_irq(awacs_tx_irq, pmac_awacs_tx_intr, 0, "Built-in Sound out", 0)
+	    || request_irq(awacs_rx_irq, pmac_awacs_rx_intr, 0, "Built-in Sound in", 0))
 		return 0;
 	return 1;
 }
@@ -837,25 +519,44 @@
 #ifdef MODULE
 static void PMacIrqCleanup(void)
 {
-	/* turn off output dma */
-	out_le32(&awacs_txdma->control, RUN<<16);
+	/* turn off input & output dma */
+	DBDMA_DO_STOP(awacs_txdma);
+	DBDMA_DO_STOP(awacs_rxdma);
+
 	/* disable interrupts from awacs interface */
 	out_le32(&awacs->control, in_le32(&awacs->control) & 0xfff);
-#ifdef CONFIG_PMAC_PBOOK
-	if (is_pbook_G3) {
-		feature_clear(awacs_node, FEATURE_Sound_power);
-		feature_clear(awacs_node, FEATURE_Sound_CLK_enable);
+
+	/* Switch off the sound clock */
+	pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
+	/* Make sure proper bits are set on pismo & tipb */
+	if (machine_is_compatible("PowerBook3,1") ||
+	    machine_is_compatible("PowerBook3,2")) {
+		awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+		awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		wait_ms(200);
 	}
-#endif
 	free_irq(awacs_irq, 0);
 	free_irq(awacs_tx_irq, 0);
 	free_irq(awacs_rx_irq, 0);
-	kfree(awacs_tx_cmd_space);
+	/* all OF versions I've seen use this value */
+	iounmap((void *)awacs);
+	iounmap((void *)awacs_txdma);
+	iounmap((void *)awacs_rxdma);
+
+	release_OF_resource(awacs_node, 0);
+	release_OF_resource(awacs_node, 1);
+	release_OF_resource(awacs_node, 2);
+
+	if (awacs_tx_cmd_space)
+		kfree(awacs_tx_cmd_space);
 	if (awacs_rx_cmd_space)
 		kfree(awacs_rx_cmd_space);
-	if (beep_buf)
+	if (beep_dbdma_cmd_space)
+		kfree(beep_dbdma_cmd_space);
+	if (beep_buf) {
 		kfree(beep_buf);
-	kd_mksound = orig_mksound;
+		kd_mksound = orig_mksound;
+	}
 #ifdef CONFIG_PMAC_PBOOK
 	pmu_unregister_sleep_notifier(&awacs_sleep_notifier);
 #endif
@@ -865,7 +566,32 @@
 static void PMacSilence(void)
 {
 	/* turn off output dma */
-	out_le32(&awacs_txdma->control, RUN<<16);
+	DBDMA_DO_STOP(awacs_txdma);
+}
+
+static int tumbler_freqs[2] = { 48000, 44100 } ;
+static int tumbler_freqs_ok[2] = { 1, 1 } ;
+
+/* don't know what to do really - just have to leave it where
+ * OF left things
+*/
+
+static int tumbler_set_frame_rate(void)
+{
+	dmasound.hard.speed = 44100 ;
+	awacs_rate_index = 0 ;
+	return 44100 ;
+}
+
+/* don't know what to do really - just have to leave it where
+ * OF left things
+*/
+
+static int daca_set_frame_rate(void)
+{
+	dmasound.hard.speed = 44100 ;
+	awacs_rate_index = 0 ;
+	return 44100 ;
 }
 
 static int awacs_freqs[8] = {
@@ -873,59 +599,115 @@
 };
 static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
 
-static void PMacInit(void)
+static int awacs_set_frame_rate(int desired, int catch_r)
 {
-	int i, tolerance;
-
-	switch (dmasound.soft.format) {
-	case AFMT_S16_LE:
-	case AFMT_U16_LE:
-		dmasound.hard.format = AFMT_S16_LE;
-		break;
-	default:
-		dmasound.hard.format = AFMT_S16_BE;
-		break;
-	}
-	dmasound.hard.stereo = 1;
-	dmasound.hard.size = 16;
-
+	int tolerance, i = 8 ;
 	/*
 	 * If we have a sample rate which is within catchRadius percent
 	 * of the requested value, we don't have to expand the samples.
 	 * Otherwise choose the next higher rate.
-	 * N.B.: burgundy awacs (iMac and later) only works at 44100 Hz.
+	 * N.B.: burgundy awacs only works at 44100 Hz.
 	 */
-	i = 8;
 	do {
-		tolerance = catchRadius * awacs_freqs[--i] / 100;
+		tolerance = catch_r * awacs_freqs[--i] / 100;
 		if (awacs_freqs_ok[i]
 		    && dmasound.soft.speed <= awacs_freqs[i] + tolerance)
 			break;
 	} while (i > 0);
-	if (dmasound.soft.speed >= awacs_freqs[i] - tolerance)
-		dmasound.trans_write = &transAwacsNormal;
-	else
-		dmasound.trans_write = &transAwacsExpand;
-	dmasound.trans_read = &transAwacsNormalRead;
 	dmasound.hard.speed = awacs_freqs[i];
 	awacs_rate_index = i;
 
-	/* XXX disable error interrupt on burgundy for now */
-	out_le32(&awacs->control, MASK_IEPC | (i << 8) | 0x11
-		 | (awacs_revision < AWACS_BURGUNDY? MASK_IEE: 0));
+	out_le32(&awacs->control, MASK_IEPC | (i << 8) | 0x11 );
 	awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) | (i << 3);
 	awacs_write(awacs_reg[1] | MASK_ADDR1);
-	out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
+	return dmasound.hard.speed;
+}
+
+static int burgundy_frame_rates = 1 ;
+static int burgundy_set_frame_rate(void)
+{
+#ifdef DEBUG_DMASOUND
+if (burgundy_frame_rates > 1)
+	printk("dmasound_pmac: warning Burgundy had more than one frame rate\n");
+#endif
+	awacs_rate_index = 0 ;
+	awacs_reg[1] = (awacs_reg[1] & ~MASK_SAMPLERATE) ;
+	/* XXX disable error interrupt on burgundy for now */
+	out_le32(&awacs->control, MASK_IEPC | 0 | 0x11 | MASK_IEE);
+	return 44100 ;
+}
+
+static int set_frame_rate(int desired, int catch_r)
+{
+	switch (awacs_revision) {
+		case AWACS_BURGUNDY:
+			dmasound.hard.speed =
+			  burgundy_set_frame_rate();
+			break ;
+		case AWACS_TUMBLER:
+			dmasound.hard.speed =
+			  tumbler_set_frame_rate();
+			break ;
+		case AWACS_DACA:
+			dmasound.hard.speed =
+			  daca_set_frame_rate();
+			break ;
+		default:
+			dmasound.hard.speed =
+			  awacs_set_frame_rate(desired, catch_r);
+			break ;
+	}
+	return dmasound.hard.speed ;
+}
 
-	/* We really want to execute a DMA stop command, after the AWACS
-	 * is initialized.
-	 * For reasons I don't understand, it stops the hissing noise
-	 * common to many PowerBook G3 systems (like mine :-).
+static void
+awacs_recalibrate(void)
+{
+	/* Sorry for the horrible delays... I hope to get that improved
+	 * by making the whole PM process asynchronous in a future version
 	 */
-	out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
-	st_le16(&beep_dbdma_cmd->command, DBDMA_STOP);
-	out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
-	out_le32(&awacs_txdma->control, RUN | (RUN << 16));
+	wait_ms(750);
+	awacs_reg[1] |= MASK_CMUTE | MASK_AMUTE;
+	awacs_write(awacs_reg[1] | MASK_RECALIBRATE | MASK_ADDR1);
+	wait_ms(1000);
+	awacs_write(awacs_reg[1] | MASK_ADDR1);
+}
+
+static void PMacInit(void)
+{
+	int tolerance;
+
+	switch (dmasound.soft.format) {
+	    case AFMT_S16_LE:
+	    case AFMT_U16_LE:
+		if (hw_can_byteswap)
+			dmasound.hard.format = AFMT_S16_LE;
+		else
+			dmasound.hard.format = AFMT_S16_BE;
+		break;
+	default:
+		dmasound.hard.format = AFMT_S16_BE;
+		break;
+	}
+	dmasound.hard.stereo = 1;
+	dmasound.hard.size = 16;
+
+	/* set dmasound.hard.speed - on the basis of what we want (soft)
+	 * and the tolerance we'll allow.
+	*/
+	set_frame_rate(dmasound.soft.speed, catchRadius) ;
+
+	tolerance = (catchRadius * dmasound.hard.speed) / 100;
+	if (dmasound.soft.speed >= dmasound.hard.speed - tolerance)
+		dmasound.trans_write = &transAwacsNormal;
+	else
+		dmasound.trans_write = &transAwacsExpand;
+	dmasound.trans_read = &transAwacsNormalRead;
+
+	if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+		out_le32(&awacs->byteswap, BS_VAL);
+	else
+		out_le32(&awacs->byteswap, 0);
 
 	expand_bal = -dmasound.soft.speed;
 }
@@ -933,7 +715,8 @@
 static int PMacSetFormat(int format)
 {
 	int size;
-
+	int req_format = format;
+		
 	switch (format) {
 	case AFMT_QUERY:
 		return dmasound.soft.format;
@@ -943,10 +726,16 @@
 	case AFMT_S8:
 		size = 8;
 		break;
-	case AFMT_S16_BE:
-	case AFMT_U16_BE:
 	case AFMT_S16_LE:
+		if(!hw_can_byteswap)
+			format = AFMT_S16_BE;
+	case AFMT_S16_BE:
+		size = 16;
+		break;
 	case AFMT_U16_LE:
+		if(!hw_can_byteswap)
+			format = AFMT_U16_BE;
+	case AFMT_U16_BE:
 		size = 16;
 		break;
 	default: /* :-) */
@@ -955,16 +744,16 @@
 		size = 8;
 		format = AFMT_U8;
 	}
-
-	dmasound.soft.format = format;
-	dmasound.soft.size = size;
-	if (dmasound.minDev == SND_DEV_DSP) {
-		dmasound.dsp.format = format;
-		dmasound.dsp.size = size;
+	
+	if (req_format == format) {
+		dmasound.soft.format = format;
+		dmasound.soft.size = size;
+		if (dmasound.minDev == SND_DEV_DSP) {
+			dmasound.dsp.format = format;
+			dmasound.dsp.size = size;
+		}
 	}
 
-	PMacInit();
-
 	return format;
 }
 
@@ -1007,48 +796,91 @@
 	return awacs_volume_setter(volume, 2, MASK_AMUTE, 6);
 }
 
-static void PMacPlay(void)
+static void __PMacPlay(void)
 {
 	volatile struct dbdma_cmd *cp;
-	int i, count;
+	int next_frg, count;
 	unsigned long flags;
 
+	/* CHECK: how much of this *really* needs IRQs masked? */
+
 	save_flags(flags); cli();
+	count = 300 ; /* > two cycles at the lowest sample rate */
+
+	/* what we want to send next */
+	next_frg = (write_sq.front + write_sq.active) % write_sq.max_count;
+
 	if (awacs_beep_state) {
 		/* sound takes precedence over beeps */
+		/* stop the dma channel */
 		out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+		while ( (in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1);
+		/* FIXME: check that this is OK for other chip sets */
 		out_le32(&awacs->control,
 			 (in_le32(&awacs->control) & ~0x1f00)
 			 | (awacs_rate_index << 8));
-		out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
-		out_le32(&awacs_txdma->cmdptr, virt_to_bus(&(awacs_tx_cmds[(write_sq.front+write_sq.active) % write_sq.max_count])));
+
+		if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+			out_le32(&awacs->byteswap, BS_VAL);
+		else
+			out_le32(&awacs->byteswap, 0);
+		out_le32(&awacs_txdma->cmdptr,
+			 virt_to_bus(&(awacs_tx_cmds[next_frg])));
 
 		beep_playing = 0;
 		awacs_beep_state = 0;
 	}
-	i = write_sq.front + write_sq.active;
-	if (i >= write_sq.max_count)
-		i -= write_sq.max_count;
+	/* this won't allow more than two frags to be in the output queue at
+	   once. (or one, if the max frags is 2 - because count can't exceed
+	   2 in that case)
+	*/
 	while (write_sq.active < 2 && write_sq.active < write_sq.count) {
-		count = (write_sq.count == write_sq.active + 1)?write_sq.rear_size:write_sq.block_size;
-		if (count < write_sq.block_size && !write_sq.syncing)
-			/* last block not yet filled, and we're not syncing. */
-			break;
-		cp = &awacs_tx_cmds[i];
+		count = (write_sq.count == write_sq.active + 1) ?
+				write_sq.rear_size:write_sq.block_size ;
+		if (count < write_sq.block_size) {
+			if (!write_sq.syncing) /* last block not yet filled,*/
+				break; 	/* and we're not syncing or POST-ed */
+			else {
+				/* pretend the block is full to force a new
+				   block to be started on the next write */
+				write_sq.rear_size = write_sq.block_size ;
+				write_sq.syncing &= ~2 ; /* clear POST */
+			}
+		}
+		cp = &awacs_tx_cmds[next_frg];
 		st_le16(&cp->req_count, count);
 		st_le16(&cp->xfer_status, 0);
-		if (++i >= write_sq.max_count)
-			i = 0;
-		out_le16(&awacs_tx_cmds[i].command, DBDMA_STOP);
-		out_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS);
+		st_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS);
+		/* put a STOP at the end of the queue - but only if we have
+		   space for it.  This means that, if we under-run and we only
+		   have two fragments, we might re-play sound from an existing
+		   queued frag.  I guess the solution to that is not to set two
+		   frags if you are likely to under-run...
+		*/
+		if (write_sq.count < write_sq.max_count) {
+			if (++next_frg >= write_sq.max_count)
+				next_frg = 0 ; /* wrap */
+			/* if we get here then we've underrun so we will stop*/
+			st_le16(&awacs_tx_cmds[next_frg].command, DBDMA_STOP);
+		}
+		/* set the dbdma controller going, if it is not already */
 		if (write_sq.active == 0)
 			out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp));
+		(void)in_le32(&awacs_txdma->status);
 		out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
 		++write_sq.active;
 	}
 	restore_flags(flags);
 }
 
+static void PMacPlay(void)
+{
+	LOCK();
+	if (!awacs_sleeping)
+		__PMacPlay();
+	UNLOCK();
+}
 
 static void PMacRecord(void)
 {
@@ -1067,6 +899,26 @@
 	restore_flags(flags);
 }
 
+/* if the TX status comes up "DEAD" - reported on some Power Computing machines
+   we need to re-start the dbdma - but from a different physical start address
+   and with a different transfer length.  It would get very messy to do this
+   with the normal dbdma_cmd blocks - we would have to re-write the buffer start
+   addresses each time.  So, we will keep a single dbdma_cmd block which can be
+   fiddled with.
+   When DEAD status is first reported the content of the faulted dbdma block is
+   copied into the emergency buffer and we note that the buffer is in use.
+   we then bump the start physical address by the amount that was successfully
+   output before it died.
+   On any subsequent DEAD result we just do the bump-ups (we know that we are
+   already using the emergency dbdma_cmd).
+   CHECK: this just tries to "do it".  It is possible that we should abandon
+   xfers when the number of residual bytes gets below a certain value - I can
+   see that this might cause a loop-forever if too small a transfer causes
+   DEAD status.  However this is a TODO for now - we'll see what gets reported.
+   When we get a successful transfer result with the emergency buffer we just
+   pretend that it completed using the original dmdma_cmd and carry on.  The
+   'next_cmd' field will already point back to the original loop of blocks.
+*/
 
 static void
 pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs)
@@ -1074,35 +926,92 @@
 	int i = write_sq.front;
 	int stat;
 	volatile struct dbdma_cmd *cp;
+	/* != 0 when we are dealing with a DEAD xfer */
+	static int emergency_in_use = 0 ;
 
-	while (write_sq.active > 0) {
-		cp = &awacs_tx_cmds[i];
+	while (write_sq.active > 0) { /* we expect to have done something*/
+		if (emergency_in_use) /* we are dealing with DEAD xfer */
+			cp = emergency_dbdma_cmd ;
+		else
+			cp = &awacs_tx_cmds[i];
 		stat = ld_le16(&cp->xfer_status);
+		if (stat & DEAD) {
+			unsigned short req, res ;
+			unsigned int phy ;
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: tx-irq: xfer died - patching it up...\n") ;
+#endif
+			/* to clear DEAD status we must first clear RUN
+			   set it to quiescent to be on the safe side */
+			(void)in_le32(&awacs_txdma->status);
+			out_le32(&awacs_txdma->control,
+				(RUN|PAUSE|FLUSH|WAKE) << 16);
+			write_sq.died++ ;
+			if (!emergency_in_use) { /* new problem */
+				memcpy((void *)emergency_dbdma_cmd, (void *)cp,
+					sizeof(struct dbdma_cmd));
+				emergency_in_use = 1;
+				cp = emergency_dbdma_cmd;
+			}
+			/* now bump the values to reflect the amount
+			   we haven't yet shifted */
+			req = ld_le16(&cp->req_count);
+			res = ld_le16(&cp->res_count);
+			phy = ld_le32(&cp->phy_addr);
+			phy += (req - res);
+			st_le16(&cp->req_count, res);
+			st_le16(&cp->res_count, 0);
+			st_le16(&cp->xfer_status, 0);
+			st_le32(&cp->phy_addr, phy);
+			st_le16(&cp->command, OUTPUT_MORE + INTR_ALWAYS);
+			/* point at our patched up command block */
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp));
+			/* we must re-start the controller */
+			(void)in_le32(&awacs_txdma->status);
+			/* should complete clearing the DEAD status */
+			out_le32(&awacs_txdma->control,
+				((RUN|WAKE) << 16) + (RUN|WAKE));
+			break; /* this block is still going */
+		}
 		if ((stat & ACTIVE) == 0)
 			break;	/* this frame is still going */
+		if (emergency_in_use)
+			emergency_in_use = 0 ; /* done that */
 		--write_sq.count;
 		--write_sq.active;
 		if (++i >= write_sq.max_count)
 			i = 0;
 	}
+
+	/* if we stopped and we were not sync-ing - then we under-ran */
+	if( write_sq.syncing == 0 ){
+		stat = in_le32(&awacs_txdma->status) ;
+		/* we hit the dbdma_stop */
+		if( (stat & ACTIVE) == 0 ) write_sq.xruns++ ;
+	}
+
+	/* if we used some data up then wake the writer to supply some more*/
 	if (i != write_sq.front)
 		WAKE_UP(write_sq.action_queue);
 	write_sq.front = i;
 
-	PMacPlay();
-
-	if (!write_sq.active)
-		WAKE_UP(write_sq.sync_queue);
+	/* but make sure we funnel what we've already got */\
+	 if (!awacs_sleeping)
+		__PMacPlay();
+
+	/* make the wake-on-empty conditional on syncing */
+	if (!write_sq.active && (write_sq.syncing & 1))
+		WAKE_UP(write_sq.sync_queue); /* any time we're empty */
 }
 
 
 static void
 pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs)
 {
-
+	int stat ;
 	/* For some reason on my PowerBook G3, I get one interrupt
 	 * when the interrupt vector is installed (like something is
-	 * pending).  This happens before the dbdma is initialize by
+	 * pending).  This happens before the dbdma is initialized by
 	 * us, so I just check the command pointer and if it is zero,
 	 * just blow it off.
 	 */
@@ -1118,8 +1027,35 @@
 	 * interrupt processing for a long time.  Geeze, I really hope
 	 * this doesn't happen.
 	 */
-	while (awacs_rx_cmds[read_sq.rear].xfer_status) {
+	while ((stat=awacs_rx_cmds[read_sq.rear].xfer_status)) {
 
+		/* if we got a "DEAD" status then just log it for now.
+		   and try to restart dma.
+		   TODO: figure out how best to fix it up
+		*/
+		if (stat & DEAD){
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: rx-irq: DIED - attempting resurection\n");
+#endif
+			/* to clear DEAD status we must first clear RUN
+			   set it to quiescent to be on the safe side */
+			(void)in_le32(&awacs_txdma->status);
+			out_le32(&awacs_txdma->control,
+				(RUN|PAUSE|FLUSH|WAKE) << 16);
+			awacs_rx_cmds[read_sq.rear].xfer_status = 0;
+			awacs_rx_cmds[read_sq.rear].res_count = 0;
+			read_sq.died++ ;
+			(void)in_le32(&awacs_txdma->status);
+			/* re-start the same block */
+			out_le32(&awacs_rxdma->cmdptr,
+				virt_to_bus(&awacs_rx_cmds[read_sq.rear]));
+			/* we must re-start the controller */
+			(void)in_le32(&awacs_rxdma->status);
+			/* should complete clearing the DEAD status */
+			out_le32(&awacs_rxdma->control,
+				((RUN|WAKE) << 16) + (RUN|WAKE));
+			return; /* try this block again */
+		}
 		/* Clear status and move on to next buffer.
 		*/
 		awacs_rx_cmds[read_sq.rear].xfer_status = 0;
@@ -1138,6 +1074,7 @@
 		 */
 		if (read_sq.rear == read_sq.front) {
 			read_sq.front++;
+			read_sq.xruns++ ; /* we overan */
 			if (read_sq.front >= read_sq.max_active)
 				read_sq.front = 0;
 		}
@@ -1157,8 +1094,9 @@
 	}
 	if (ctrl & MASK_CNTLERR) {
 		int err = (in_le32(&awacs->codec_stat) & MASK_ERRCODE) >> 16;
-		if (err != 0 && awacs_revision < AWACS_BURGUNDY)
-			printk(KERN_ERR "AWACS: error %x\n", err);
+		/* CHECK: we just swallow burgundy errors at the moment..*/
+		if (err != 0 && awacs_revision != AWACS_BURGUNDY)
+			printk(KERN_ERR "dmasound_pmac: error %x\n", err);
 	}
 	/* Writing 1s to the CNTLERR and PORTCHG bits clears them... */
 	out_le32(&awacs->control, ctrl);
@@ -1167,25 +1105,38 @@
 static void
 awacs_write(int val)
 {
-	if (awacs_revision >= AWACS_BURGUNDY)
-		return;
-	while (in_le32(&awacs->codec_ctrl) & MASK_NEWECMD)
-		;	/* XXX should have timeout */
+	int count = 300 ;
+	if (awacs_revision >= AWACS_DACA)
+		return ;
+
+	while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--)
+		udelay(1) ;	/* timeout is > 2 samples at lowest rate */
 	out_le32(&awacs->codec_ctrl, val | (awacs_subframe << 22));
+	(void)in_le32(&awacs->byteswap);
 }
 
+/* this is called when the beep timer expires... it will be called even
+   if the beep has been overidden by other sound output.
+*/
 static void awacs_nosound(unsigned long xx)
 {
 	unsigned long flags;
+	int count = 600 ; /* > four samples at lowest rate */
 
 	save_flags(flags); cli();
 	if (beep_playing) {
 		st_le16(&beep_dbdma_cmd->command, DBDMA_STOP);
 		out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+		while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1);
+		/* FIXME: check this is OK for DACA, Tumbler */
 		out_le32(&awacs->control,
 			 (in_le32(&awacs->control) & ~0x1f00)
 			 | (awacs_rate_index << 8));
-		out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
+		if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+			out_le32(&awacs->byteswap, BS_VAL);
+		else
+			out_le32(&awacs->byteswap, 0);
 		beep_playing = 0;
 	}
 	restore_flags(flags);
@@ -1195,6 +1146,11 @@
 	function: awacs_nosound
 };
 
+/* we generate the beep with a single dbdma command that loops a buffer
+   forever - without generating interrupts.
+   So, to stop it you have to stop dma output as per awacs_nosound.
+*/
+
 static void awacs_mksound(unsigned int hz, unsigned int ticks)
 {
 	unsigned long flags;
@@ -1207,11 +1163,19 @@
 	static int beep_nsamples_cache;
 	static int beep_volume_cache;
 
-	for (i = 0; i < 8 && awacs_freqs[i] >= BEEP_SRATE; ++i)
-		if (awacs_freqs_ok[i])
-			beep_speed = i;
-	srate = awacs_freqs[beep_speed];
+	if (beep_buf == NULL)
+		return;
+
+	/* quick-hack fix for DACA, Burgundy & Tumbler */
 
+	if (awacs_revision >= AWACS_DACA){
+		srate = 44100 ;
+	} else {
+		for (i = 0; i < 8 && awacs_freqs[i] >= BEEP_SRATE; ++i)
+			if (awacs_freqs_ok[i])
+				beep_speed = i;
+		srate = awacs_freqs[beep_speed];
+	}
 	if (hz <= srate / BEEP_BUFLEN || hz > srate / 2) {
 #if 1
 		/* this is a hack for broken X server code */
@@ -1237,7 +1201,7 @@
 	st_le16(&beep_dbdma_cmd->command, OUTPUT_MORE + BR_ALWAYS);
 	restore_flags(flags);
 
-	if (hz == beep_hz_cache && beep_volume == beep_volume_cache) {
+	if (hz == beep_hz_cache && beep_vol == beep_volume_cache) {
 		nsamples = beep_nsamples_cache;
 	} else {
 		period = srate * 256 / hz;	/* fixed point */
@@ -1247,11 +1211,11 @@
 		j = 0;
 		p = beep_buf;
 		for (i = 0; i < nsamples; ++i, p += 2) {
-			p[0] = p[1] = beep_wform[j >> 8] * beep_volume;
+			p[0] = p[1] = beep_wform[j >> 8] * beep_vol;
 			j = (j + f) & 0xffff;
 		}
 		beep_hz_cache = hz;
-		beep_volume_cache = beep_volume;
+		beep_volume_cache = beep_vol;
 		beep_nsamples_cache = nsamples;
 	}
 
@@ -1263,78 +1227,159 @@
 
 	save_flags(flags); cli();
 	if (beep_playing) {	/* i.e. haven't been terminated already */
+		int count = 300 ;
 		out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
+		while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+			udelay(1); /* timeout > 2 samples at lowest rate*/
+		/* FIXME: check this is OK on DACA, Tumbler */
 		out_le32(&awacs->control,
 			 (in_le32(&awacs->control) & ~0x1f00)
 			 | (beep_speed << 8));
-		out_le32(&awacs->byteswap, 0);
+		out_le32(&awacs->byteswap, 0); /* force BE */
 		out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+		(void)in_le32(&awacs_txdma->status);
 		out_le32(&awacs_txdma->control, RUN | (RUN << 16));
 	}
 	restore_flags(flags);
 }
 
+/* used in init and for wake-up */
+
+static void
+load_awacs(void)
+{
+	awacs_write(awacs_reg[0] + MASK_ADDR0);
+	awacs_write(awacs_reg[1] + MASK_ADDR1);
+	awacs_write(awacs_reg[2] + MASK_ADDR2);
+	awacs_write(awacs_reg[4] + MASK_ADDR4);
+
+	if (awacs_revision == AWACS_SCREAMER) {
+		awacs_write(awacs_reg[5] + MASK_ADDR5);
+		wait_ms(100);
+		awacs_write(awacs_reg[6] + MASK_ADDR6);
+		wait_ms(2);
+		awacs_write(awacs_reg[1] + MASK_ADDR1);
+		awacs_write(awacs_reg[7] + MASK_ADDR7);
+	}
+	if (hw_can_byteswap && (dmasound.hard.format == AFMT_S16_LE))
+		out_le32(&awacs->byteswap, BS_VAL);
+	else
+		out_le32(&awacs->byteswap, 0);
+}
+
 #ifdef CONFIG_PMAC_PBOOK
 /*
  * Save state when going to sleep, restore it afterwards.
  */
+/* FIXME: sort out disabling/re-enabling of read stuff as well */
 static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when)
 {
 	switch (when) {
-	case PBOOK_SLEEP_NOW:
-		/* XXX we should stop any dma in progress when going to sleep
-		   and restart it when we wake. */
+	case PBOOK_SLEEP_NOW:		
+		LOCK();
+		awacs_sleeping = 1;
+		/* Tell the rest of the driver we are now going to sleep */
+		mb();
+		if (awacs_revision == AWACS_SCREAMER ||
+		    awacs_revision == AWACS_AWACS) {
+			awacs_reg1_save = awacs_reg[1];
+			awacs_reg[1] |= MASK_AMUTE | MASK_CMUTE;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+
 		PMacSilence();
+		/* stop rx - if going - a bit of a daft user... but */
+		out_le32(&awacs_rxdma->control, (RUN|WAKE|FLUSH << 16));
+		/* deny interrupts */
+		switch (awacs_revision) {
+			case AWACS_TUMBLER:
+				tumbler_enter_sleep(); /* Stub for now */
+				break ;
+			case AWACS_DACA:
+				daca_enter_sleep();
+				break ;
+			case AWACS_BURGUNDY:
+				break ;
+			case AWACS_SCREAMER:
+			case AWACS_AWACS:
+			default:
+				out_le32(&awacs->control, 0x11) ;
+				break ;
+		}
 		disable_irq(awacs_irq);
 		disable_irq(awacs_tx_irq);
-		if (is_pbook_G3) {
-			feature_clear(awacs_node, FEATURE_Sound_CLK_enable);
-			feature_clear(awacs_node, FEATURE_Sound_power);
+		disable_irq(awacs_rx_irq);
+		/* Disable sound clock */
+		pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 0);
+		/* According to Darwin, we do that after turning off the sound
+		 * chip clock. All this will have to be cleaned up once we properly
+		 * parse the OF sound-objects
+		 */
+		if (machine_is_compatible("PowerBook3,1") ||
+		    machine_is_compatible("PowerBook3,2")) {
+			awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+			wait_ms(200);
 		}
 		break;
 	case PBOOK_WAKE:
-		/* There is still a problem on wake. Sound seems to work fine
-		   if I launch mpg123 and resumes fine if mpg123 was playing,
-		   but the console beep is dead until I do something with the
-		   mixer. Probably yet another timing issue */
-		if (!feature_test(awacs_node, FEATURE_Sound_CLK_enable)
-		    || !feature_test(awacs_node, FEATURE_Sound_power)) {
-			/* these aren't present on the 3400 AFAIK -- paulus */
-			feature_set(awacs_node, FEATURE_Sound_CLK_enable);
-			feature_set(awacs_node, FEATURE_Sound_power);
-			mdelay(1000);
-		}
-		out_le32(&awacs->control, MASK_IEPC
-			 | (awacs_rate_index << 8) | 0x11
-			 | (awacs_revision < AWACS_BURGUNDY? MASK_IEE: 0));
-		awacs_write(awacs_reg[0] | MASK_ADDR0);
-		awacs_write(awacs_reg[1] | MASK_ADDR1);
-		awacs_write(awacs_reg[2] | MASK_ADDR2);
-		awacs_write(awacs_reg[4] | MASK_ADDR4);
-		if (awacs_is_screamer) {
-			awacs_write(awacs_reg[5] + MASK_ADDR5);
-			awacs_write(awacs_reg[6] + MASK_ADDR6);
-			awacs_write(awacs_reg[7] + MASK_ADDR7);
-		}
-		out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
+		/* Enable sound clock */
+		pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, awacs_node, 0, 1);
+		if (machine_is_compatible("PowerBook3,1") ||
+		    machine_is_compatible("PowerBook3,2")) {
+			wait_ms(100);
+			awacs_reg[1] &= ~(MASK_PAROUT0 | MASK_PAROUT1);
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+			wait_ms(300);
+		} else
+			wait_ms(1000);
+ 		/* restore settings */
+		switch (awacs_revision) {
+			case AWACS_TUMBLER:
+				headphone_intr(0,0,0);
+				tumbler_leave_sleep(); /* Stub for now */
+				break;
+			case AWACS_DACA:
+				wait_ms(10); /* Check this !!! */
+				daca_leave_sleep();
+				break ;		/* dont know how yet */
+			case AWACS_BURGUNDY:
+				break ;
+			case AWACS_SCREAMER:
+			case AWACS_AWACS:
+			default:
+		 		load_awacs() ;
+				break ;
+		}
+		/* Recalibrate chip */
+		if (awacs_revision == AWACS_SCREAMER)
+			awacs_recalibrate();
+		/* Make sure dma is stopped */
+		PMacSilence();
 		enable_irq(awacs_irq);
 		enable_irq(awacs_tx_irq);
-		if (awacs_revision == 3) {
-			mdelay(100);
-			awacs_write(0x6000);
-			mdelay(2);
-			awacs_write(awacs_reg[1] | MASK_ADDR1);
-		}
-		/* enable CD sound input */
-		if (macio_base && is_pbook_G3) {
+ 		enable_irq(awacs_rx_irq);
+ 		/* OK, allow ints back again */
+ 		out_le32(&awacs->control, MASK_IEPC
+ 		 	| (awacs_rate_index << 8) | 0x11
+ 			 | (awacs_revision < AWACS_DACA ? MASK_IEE: 0));
+ 		if (macio_base && is_pbook_g3) {
+			/* FIXME: should restore the setup we had...*/
 			out_8(macio_base + 0x37, 3);
-		} else if (is_pbook_3400) {
-			feature_set(awacs_node, FEATURE_IOBUS_enable);
-			udelay(10);
+ 		} else if (is_pbook_3X00) {
 			in_8(latch_base + 0x190);
 		}
+		/* Remove mute */
+		if (awacs_revision == AWACS_SCREAMER ||
+		    awacs_revision == AWACS_AWACS) {
+			awacs_reg[1] = awacs_reg1_save;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+ 		awacs_sleeping = 0;
 		/* Resume pending sounds. */
-		PMacPlay();
+		/* we don't try to restart input... */
+		__PMacPlay();
+		UNLOCK();
 	}
 	return PBOOK_SLEEP_OK;
 }
@@ -1347,17 +1392,20 @@
 inline static void
 awacs_burgundy_busy_wait(void)
 {
-	while (in_le32(&awacs->codec_ctrl) & MASK_NEWECMD)
-		;
+	int count = 50; /* > 2 samples at 44k1 */
+	while ((in_le32(&awacs->codec_ctrl) & MASK_NEWECMD) && count--)
+		udelay(1) ;
 }
 
 inline static void
 awacs_burgundy_extend_wait(void)
 {
-	while (!(in_le32(&awacs->codec_stat) & MASK_EXTEND))
-		;
-	while (in_le32(&awacs->codec_stat) & MASK_EXTEND)
-		;
+	int count = 50 ; /* > 2 samples at 44k1 */
+	while ((!(in_le32(&awacs->codec_stat) & MASK_EXTEND)) && count--)
+		udelay(1) ;
+	count = 50;
+	while ((in_le32(&awacs->codec_stat) & MASK_EXTEND) && count--)
+		udelay(1);
 }
 
 static void
@@ -1447,7 +1495,7 @@
 awacs_burgundy_init(void)
 {
 	if (awacs_burgundy_check()) {
-		printk(KERN_WARNING "AWACS: disabled by MacOS :-(\n");
+		printk(KERN_WARNING "dmasound_pmac: burgundy not working :-(\n");
 		return 1;
 	}
 
@@ -1515,9 +1563,6 @@
 	return softvolume > 0 ? softvolume : 0;
 }
 
-
-
-
 static int
 awacs_burgundy_read_mvolume(unsigned address)
 {
@@ -1533,7 +1578,6 @@
 	return lvolume + (rvolume << 8);
 }
 
-
 static void
 awacs_burgundy_write_mvolume(unsigned address, int volume)
 {
@@ -1550,21 +1594,23 @@
 
 /* End burgundy functions */
 
+/* Set up output volumes on machines with the 'perch/whisper' extension card.
+ * this has an SGS i2c chip (7433) which is accessed using the cuda.
+ *
+ * TODO: split this out and make use of the other parts of the SGS chip to
+ * do Bass, Treble etc.
+ */
 
-
-
-
-/* Turn on sound output, needed on G3 desktop powermacs */
 static void
 awacs_enable_amp(int spkr_vol)
 {
+#ifdef CONFIG_ADB_CUDA
 	struct adb_request req;
 
 	awacs_spkr_vol = spkr_vol;
 	if (sys_ctrler != SYS_CTRLER_CUDA)
 		return;
 
-#ifdef CONFIG_ADB_CUDA
 	/* turn on headphones */
 	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
 		     0x8a, 4, 0);
@@ -1595,146 +1641,229 @@
  * /dev/mixer abstraction
  */
 
-static int awacs_mixer_ioctl(u_int cmd, u_long arg)
+static void do_line_lev(int data)
 {
-	int data;
-
-	switch (cmd) {
-	case SOUND_MIXER_READ_DEVMASK:
-		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
-			| SOUND_MASK_LINE | SOUND_MASK_MIC
-			| SOUND_MASK_CD | SOUND_MASK_RECLEV
-			| SOUND_MASK_ALTPCM
-			| SOUND_MASK_MONITOR;
-		return IOCTL_OUT(arg, data);
-	case SOUND_MIXER_READ_RECMASK:
-		data = SOUND_MASK_LINE | SOUND_MASK_MIC
-			| SOUND_MASK_CD;
-		return IOCTL_OUT(arg, data);
-	case SOUND_MIXER_READ_RECSRC:
-		data = 0;
-		if (awacs_reg[0] & MASK_MUX_AUDIN)
-			data |= SOUND_MASK_LINE;
-		if (awacs_reg[0] & MASK_MUX_MIC)
-			data |= SOUND_MASK_MIC;
-		if (awacs_reg[0] & MASK_MUX_CD)
-			data |= SOUND_MASK_CD;
-		if (awacs_reg[1] & MASK_LOOPTHRU)
-			data |= SOUND_MASK_MONITOR;
-		return IOCTL_OUT(arg, data);
-	case SOUND_MIXER_WRITE_RECSRC:
-		IOCTL_IN(arg, data);
-		data &= (SOUND_MASK_LINE
-			 | SOUND_MASK_MIC | SOUND_MASK_CD
-			 | SOUND_MASK_MONITOR);
-		awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC
-				  | MASK_MUX_AUDIN);
-		awacs_reg[1] &= ~MASK_LOOPTHRU;
-		if (data & SOUND_MASK_LINE)
+		line_lev = data ;
+		awacs_reg[0] &= ~MASK_MUX_AUDIN;
+		if ((data & 0xff) >= 50)
 			awacs_reg[0] |= MASK_MUX_AUDIN;
-		if (data & SOUND_MASK_MIC)
-			awacs_reg[0] |= MASK_MUX_MIC;
-		if (data & SOUND_MASK_CD)
-			awacs_reg[0] |= MASK_MUX_CD;
-		if (data & SOUND_MASK_MONITOR)
+		awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_ip_gain(int data)
+{
+	ip_gain = data ;
+	data &= 0xff;
+	awacs_reg[0] &= ~MASK_GAINLINE;
+	if (awacs_revision == AWACS_SCREAMER) {
+		awacs_reg[6] &= ~MASK_MIC_BOOST ;
+		if (data >= 33) {
+			awacs_reg[0] |= MASK_GAINLINE;
+			if( data >= 66)
+				awacs_reg[6] |= MASK_MIC_BOOST ;
+		}
+		awacs_write(MASK_ADDR6 | awacs_reg[6]) ;
+	} else {
+		if (data >= 50)
+			awacs_reg[0] |= MASK_GAINLINE;
+	}
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_mic_lev(int data)
+{
+	mic_lev = data ;
+	data &= 0xff;
+	awacs_reg[0] &= ~MASK_MUX_MIC;
+	if (data >= 50)
+		awacs_reg[0] |= MASK_MUX_MIC;
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_cd_lev(int data)
+{
+	cd_lev = data ;
+	awacs_reg[0] &= ~MASK_MUX_CD;
+	if ((data & 0xff) >= 50)
+		awacs_reg[0] |= MASK_MUX_CD;
+	awacs_write(MASK_ADDR0 | awacs_reg[0]);
+}
+
+static void do_rec_lev(int data)
+{
+	int left, right ;
+	rec_lev = data ;
+	/* need to fudge this to use the volume setter routine */
+	left = 100 - (data & 0xff) ; if( left < 0 ) left = 0 ;
+	right = 100 - ((data >> 8) & 0xff) ; if( right < 0 ) right = 0 ;
+	left |= (right << 8 );
+	left = awacs_volume_setter(left, 0, 0, 4);
+}
+
+static void do_passthru_vol(int data)
+{
+	passthru_vol = data ;
+	awacs_reg[1] &= ~MASK_LOOPTHRU;
+	if (awacs_revision == AWACS_SCREAMER) {
+		if( data ) { /* switch it on for non-zero */
 			awacs_reg[1] |= MASK_LOOPTHRU;
+			awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		}
+		data = awacs_volume_setter(data, 5, 0, 6) ;
+	} else {
+		if ((data & 0xff) >= 50)
+			awacs_reg[1] |= MASK_LOOPTHRU;
+		awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		data = (awacs_reg[1] & MASK_LOOPTHRU)? 100: 0;
+	}
+}
+
+static int awacs_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	switch (cmd) {
+	case SOUND_MIXER_READ_CAPS:
+		/* say we will allow multiple inputs?  prob. wrong
+			so I'm switching it to single */
+		return IOCTL_OUT(arg, 1);
+	case SOUND_MIXER_READ_DEVMASK:
+		data  = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
+			| SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD
+			| SOUND_MASK_IGAIN | SOUND_MASK_RECLEV
+			| SOUND_MASK_ALTPCM
+			| SOUND_MASK_MONITOR;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		if (awacs_reg[0] & MASK_MUX_AUDIN)
+			data |= SOUND_MASK_LINE;
+		if (awacs_reg[0] & MASK_MUX_MIC)
+			data |= SOUND_MASK_MIC;
+		if (awacs_reg[0] & MASK_MUX_CD)
+			data |= SOUND_MASK_CD;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data &= (SOUND_MASK_LINE | SOUND_MASK_MIC | SOUND_MASK_CD);
+		awacs_reg[0] &= ~(MASK_MUX_CD | MASK_MUX_MIC
+				  | MASK_MUX_AUDIN);
+		if (data & SOUND_MASK_LINE)
+			awacs_reg[0] |= MASK_MUX_AUDIN;
+		if (data & SOUND_MASK_MIC)
+			awacs_reg[0] |= MASK_MUX_MIC;
+		if (data & SOUND_MASK_CD)
+			awacs_reg[0] |= MASK_MUX_CD;
 		awacs_write(awacs_reg[0] | MASK_ADDR0);
-		awacs_write(awacs_reg[1] | MASK_ADDR1);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_STEREODEVS:
-		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
-			| SOUND_MASK_RECLEV;
-		return IOCTL_OUT(arg, data);
-	case SOUND_MIXER_READ_CAPS:
-		return IOCTL_OUT(arg, 0);
-	case SOUND_MIXER_READ_VOLUME:
-		data = (awacs_reg[1] & MASK_AMUTE)? 0:
-			awacs_get_volume(awacs_reg[2], 6);
-		return IOCTL_OUT(arg, data);
+		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER| SOUND_MASK_RECLEV  ;
+		if (awacs_revision == AWACS_SCREAMER)
+			data |= SOUND_MASK_MONITOR ;
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_WRITE_VOLUME:
 		IOCTL_IN(arg, data);
-		return IOCTL_OUT(arg, PMacSetVolume(data));
-	case SOUND_MIXER_READ_SPEAKER:
-		if (awacs_revision == 3
-		    && sys_ctrler == SYS_CTRLER_CUDA)
-			data = awacs_spkr_vol;
-		else
-			data = (awacs_reg[1] & MASK_CMUTE)? 0:
-				awacs_get_volume(awacs_reg[4], 6);
-		return IOCTL_OUT(arg, data);
+		line_vol = data ;
+		awacs_volume_setter(data, 2, 0, 6);
+		/* fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		rc = IOCTL_OUT(arg, line_vol);
+		break;
 	case SOUND_MIXER_WRITE_SPEAKER:
 		IOCTL_IN(arg, data);
-		if (awacs_revision == 3
-		    && sys_ctrler == SYS_CTRLER_CUDA)
+		spk_vol = data ;
+		if (has_perch)
 			awacs_enable_amp(data);
 		else
-			data = awacs_volume_setter(data, 4, MASK_CMUTE, 6);
-		return IOCTL_OUT(arg, data);
+			(void)awacs_volume_setter(data, 4, MASK_CMUTE, 6);
+		/* fall though */
+	case SOUND_MIXER_READ_SPEAKER:
+		rc = IOCTL_OUT(arg, spk_vol);
+		break;
 	case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
 		IOCTL_IN(arg, data);
-		beep_volume = data & 0xff;
-				/* fall through */
+		beep_vol = data & 0xff;
+		/* fall through */
 	case SOUND_MIXER_READ_ALTPCM:
-		return IOCTL_OUT(arg, beep_volume);
+		rc = IOCTL_OUT(arg, beep_vol);
+		break;
 	case SOUND_MIXER_WRITE_LINE:
 		IOCTL_IN(arg, data);
-		awacs_reg[0] &= ~MASK_MUX_AUDIN;
-		if ((data & 0xff) >= 50)
-			awacs_reg[0] |= MASK_MUX_AUDIN;
-		awacs_write(MASK_ADDR0 | awacs_reg[0]);
-				/* fall through */
+		do_line_lev(data) ;
+		/* fall through */
 	case SOUND_MIXER_READ_LINE:
-		data = (awacs_reg[0] & MASK_MUX_AUDIN)? 100: 0;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, line_lev);
+		break;
+	case SOUND_MIXER_WRITE_IGAIN:
+		IOCTL_IN(arg, data);
+		do_ip_gain(data) ;
+		/* fall through */
+	case SOUND_MIXER_READ_IGAIN:
+		rc = IOCTL_OUT(arg, ip_gain);
+		break;
 	case SOUND_MIXER_WRITE_MIC:
 		IOCTL_IN(arg, data);
-		data &= 0xff;
-		awacs_reg[0] &= ~(MASK_MUX_MIC | MASK_GAINLINE);
-		if (data >= 25) {
-			awacs_reg[0] |= MASK_MUX_MIC;
-			if (data >= 75)
-				awacs_reg[0] |= MASK_GAINLINE;
-		}
-		awacs_write(MASK_ADDR0 | awacs_reg[0]);
-				/* fall through */
+		do_mic_lev(data);
+		/* fall through */
 	case SOUND_MIXER_READ_MIC:
-		data = (awacs_reg[0] & MASK_MUX_MIC)?
-			(awacs_reg[0] & MASK_GAINLINE? 100: 50): 0;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, mic_lev);
+		break;
 	case SOUND_MIXER_WRITE_CD:
 		IOCTL_IN(arg, data);
-		awacs_reg[0] &= ~MASK_MUX_CD;
-		if ((data & 0xff) >= 50)
-			awacs_reg[0] |= MASK_MUX_CD;
-		awacs_write(MASK_ADDR0 | awacs_reg[0]);
-				/* fall through */
+		do_cd_lev(data);
+		/* fall through */
 	case SOUND_MIXER_READ_CD:
-		data = (awacs_reg[0] & MASK_MUX_CD)? 100: 0;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, cd_lev);
+		break;
 	case SOUND_MIXER_WRITE_RECLEV:
 		IOCTL_IN(arg, data);
-		data = awacs_volume_setter(data, 0, 0, 4);
-		return IOCTL_OUT(arg, data);
+		do_rec_lev(data) ;
+		/* fall through */
 	case SOUND_MIXER_READ_RECLEV:
-		data = awacs_get_volume(awacs_reg[0], 4);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, rec_lev);
+		break;
 	case MIXER_WRITE(SOUND_MIXER_MONITOR):
 		IOCTL_IN(arg, data);
-		awacs_reg[1] &= ~MASK_LOOPTHRU;
-		if ((data & 0xff) >= 50)
-			awacs_reg[1] |= MASK_LOOPTHRU;
-		awacs_write(MASK_ADDR1 | awacs_reg[1]);
+		do_passthru_vol(data) ;
 		/* fall through */
 	case MIXER_READ(SOUND_MIXER_MONITOR):
-		data = (awacs_reg[1] & MASK_LOOPTHRU)? 100: 0;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, passthru_vol);
+		break;
+	default:
+		rc = -EINVAL;
 	}
-	return -EINVAL;
+	
+	return rc;
+}
+
+static void awacs_mixer_init(void)
+{
+	awacs_volume_setter(line_vol, 2, 0, 6);
+	if (has_perch)
+		awacs_enable_amp(spk_vol);
+	else
+		(void)awacs_volume_setter(spk_vol, 4, MASK_CMUTE, 6);
+	do_line_lev(line_lev) ;
+	do_ip_gain(ip_gain) ;
+	do_mic_lev(mic_lev) ;
+	do_cd_lev(cd_lev) ;
+	do_rec_lev(rec_lev) ;
+	do_passthru_vol(passthru_vol) ;
 }
 
 static int burgundy_mixer_ioctl(u_int cmd, u_long arg)
 {
 	int data;
+	int rc;
 
 	/* We are, we are, we are... Burgundy or better */
 	switch(cmd) {
@@ -1742,11 +1871,13 @@
 		data = SOUND_MASK_VOLUME | SOUND_MASK_CD |
 			SOUND_MASK_LINE | SOUND_MASK_MIC |
 			SOUND_MASK_SPEAKER | SOUND_MASK_ALTPCM;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_RECMASK:
 		data = SOUND_MASK_LINE | SOUND_MASK_MIC
 			| SOUND_MASK_CD;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_RECSRC:
 		data = 0;
 		if (awacs_reg[0] & MASK_MUX_AUDIN)
@@ -1755,7 +1886,8 @@
 			data |= SOUND_MASK_MIC;
 		if (awacs_reg[0] & MASK_MUX_CD)
 			data |= SOUND_MASK_CD;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_WRITE_RECSRC:
 		IOCTL_IN(arg, data);
 		data &= (SOUND_MASK_LINE
@@ -1769,23 +1901,26 @@
 		if (data & SOUND_MASK_CD)
 			awacs_reg[0] |= MASK_MUX_CD;
 		awacs_write(awacs_reg[0] | MASK_ADDR0);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_STEREODEVS:
 		data = SOUND_MASK_VOLUME | SOUND_MASK_SPEAKER
 			| SOUND_MASK_RECLEV | SOUND_MASK_CD
 			| SOUND_MASK_LINE;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_CAPS:
-		return IOCTL_OUT(arg, 0);
+		rc = IOCTL_OUT(arg, 0);
+		break;
 	case SOUND_MIXER_WRITE_VOLUME:
 		IOCTL_IN(arg, data);
 		awacs_burgundy_write_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME, data);
 				/* Fall through */
 	case SOUND_MIXER_READ_VOLUME:
-		return IOCTL_OUT(arg, awacs_burgundy_read_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME));
+		rc = IOCTL_OUT(arg, awacs_burgundy_read_mvolume(MASK_ADDR_BURGUNDY_MASTER_VOLUME));
+		break;
 	case SOUND_MIXER_WRITE_SPEAKER:
 		IOCTL_IN(arg, data);
-
 		if (!(data & 0xff)) {
 			/* Mute the left speaker */
 			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
@@ -1806,7 +1941,7 @@
 		}
 
 		data = (((data&0xff)*16)/100 > 0xf ? 0xf :
-			(((data&0xff)*16)/100)) + 
+			(((data&0xff)*16)/100)) +
 			((((data>>8)*16)/100 > 0xf ? 0xf :
 			  ((((data>>8)*16)/100)))<<4);
 
@@ -1815,21 +1950,24 @@
 	case SOUND_MIXER_READ_SPEAKER:
 		data = awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER);
 		data = (((data & 0xf)*100)/16) + ((((data>>4)*100)/16)<<8);
-		return IOCTL_OUT(arg, ~data);
+		rc = IOCTL_OUT(arg, ~data);
+		break;
 	case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
 		IOCTL_IN(arg, data);
-		beep_volume = data & 0xff;
+		beep_vol = data & 0xff;
 				/* fall through */
 	case SOUND_MIXER_READ_ALTPCM:
-		return IOCTL_OUT(arg, beep_volume);
+		rc = IOCTL_OUT(arg, beep_vol);
+		break;
 	case SOUND_MIXER_WRITE_LINE:
 		IOCTL_IN(arg, data);
 		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLLINE, data);
 
 				/* fall through */
 	case SOUND_MIXER_READ_LINE:
-		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLLINE);				
-		return IOCTL_OUT(arg, data);
+		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLLINE);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_WRITE_MIC:
 		IOCTL_IN(arg, data);
 				/* Mic is mono device */
@@ -1837,66 +1975,331 @@
 		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLMIC, data);
 				/* fall through */
 	case SOUND_MIXER_READ_MIC:
-		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLMIC);				
+		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLMIC);
 		data <<= 24;
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_WRITE_CD:
 		IOCTL_IN(arg, data);
 		awacs_burgundy_write_volume(MASK_ADDR_BURGUNDY_VOLCD, data);
 				/* fall through */
 	case SOUND_MIXER_READ_CD:
 		data = awacs_burgundy_read_volume(MASK_ADDR_BURGUNDY_VOLCD);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_WRITE_RECLEV:
 		IOCTL_IN(arg, data);
 		data = awacs_volume_setter(data, 0, 0, 4);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_READ_RECLEV:
 		data = awacs_get_volume(awacs_reg[0], 4);
-		return IOCTL_OUT(arg, data);
+		rc = IOCTL_OUT(arg, data);
+		break;
 	case SOUND_MIXER_OUTMASK:
+	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
+	}
+	
+	return rc;
+}
+
+static int tumbler_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	/* We are, we are, we are... Tumbler (and very dumb) */
+	/* Ok, we're not THAT dumb anymore, but still pretty dumb :-) */
+
+	switch(cmd) {
+	case SOUND_MIXER_READ_DEVMASK:
+		data =  SOUND_MASK_VOLUME | SOUND_MASK_ALTPCM |
+			SOUND_MASK_BASS | SOUND_MASK_TREBLE |
+			SOUND_MASK_PCM;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data =0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = SOUND_MASK_VOLUME | SOUND_MASK_PCM;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_CAPS:
+		rc = IOCTL_OUT(arg, 0);
+		break;
+	case SOUND_MIXER_WRITE_BASS:
+		IOCTL_IN(arg, data);
+		tumbler_set_bass(data);
+		/* Fall through */
+	case SOUND_MIXER_READ_BASS:
+		tumbler_get_bass(&data);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_TREBLE:
+		IOCTL_IN(arg, data);
+		tumbler_set_treble(data);
+		/* Fall through */
+	case SOUND_MIXER_READ_TREBLE:
+		tumbler_get_treble(&data);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_PCM:
+		IOCTL_IN(arg, data);
+		tumbler_set_pcm_lvl(data);
+		/* Fall through */
+	case SOUND_MIXER_READ_PCM:
+		tumbler_get_pcm_lvl(&data);
+		IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_VOLUME:
+		IOCTL_IN(arg, data);
+		tumbler_set_volume(data, data);
+		/* Fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		tumbler_get_volume(& data, &data);
+		rc = IOCTL_OUT(arg, data);
 		break;
+	case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
+		IOCTL_IN(arg, data);
+		beep_vol = data & 0xff;
+		/* fall through */
+	case SOUND_MIXER_READ_ALTPCM:
+		rc = IOCTL_OUT(arg, beep_vol);
+		break;
+	case SOUND_MIXER_OUTMASK:
 	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int daca_mixer_ioctl(u_int cmd, u_long arg)
+{
+	int data;
+	int rc;
+
+	/* And the DACA's no genius either! */
+
+	switch(cmd) {
+	case SOUND_MIXER_READ_DEVMASK:
+		data = SOUND_MASK_VOLUME;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECMASK:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_RECSRC:
+		data = 0;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_WRITE_RECSRC:
+		IOCTL_IN(arg, data);
+		data =0;
+		rc = IOCTL_OUT(arg, data);
 		break;
+	case SOUND_MIXER_READ_STEREODEVS:
+		data = SOUND_MASK_VOLUME;
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_READ_CAPS:
+		rc = IOCTL_OUT(arg, 0);
+		break;
+	case SOUND_MIXER_WRITE_VOLUME:
+		IOCTL_IN(arg, data);
+		daca_set_volume(data, data);
+		/* Fall through */
+	case SOUND_MIXER_READ_VOLUME:
+		daca_get_volume(& data, &data);
+		rc = IOCTL_OUT(arg, data);
+		break;
+	case SOUND_MIXER_OUTMASK:
+	case SOUND_MIXER_OUTSRC:
+	default:
+		rc = -EINVAL;
 	}
-	return -EINVAL;
+	return rc;
 }
 
 static int PMacMixerIoctl(u_int cmd, u_long arg)
 {
-	/* Different IOCTLS for burgundy*/
-	if (awacs_revision >= AWACS_BURGUNDY)
-		return burgundy_mixer_ioctl(cmd, arg);
-	return awacs_mixer_ioctl(cmd, arg);
+	int rc;
+	
+	/* Different IOCTLS for burgundy and, eventually, DACA & Tumbler */
+
+	TRY_LOCK();
+	
+	switch (awacs_revision){
+		case AWACS_BURGUNDY:
+			rc = burgundy_mixer_ioctl(cmd, arg);
+			break ;
+		case AWACS_DACA:
+			rc = daca_mixer_ioctl(cmd, arg);
+			break;
+		case AWACS_TUMBLER:
+			rc = tumbler_mixer_ioctl(cmd, arg);
+			break ;
+		default: /* ;-)) */
+			rc = awacs_mixer_ioctl(cmd, arg);
+	}
+
+	UNLOCK();
+	
+	return rc;
+}
+
+static void PMacMixerInit(void)
+{
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+		  printk("AE-Init tumbler mixer\n");
+		  break ;
+		  
+		case AWACS_DACA:
+		case AWACS_BURGUNDY:
+			break ;	/* don't know yet */
+		case AWACS_AWACS:
+		case AWACS_SCREAMER:
+		default:
+			awacs_mixer_init() ;
+			break ;
+	}
 }
 
+/* Write/Read sq setup functions:
+   Check to see if we have enough (or any) dbdma cmd buffers for the
+   user's fragment settings.  If not, allocate some. If this fails we will
+   point at the beep buffer - as an emergency provision - to stop dma tromping
+   on some random bit of memory (if someone lets it go anyway).
+   The command buffers are then set up to point to the fragment buffers
+   (allocated elsewhere).  We need n+1 commands the last of which holds
+   a NOP + loop to start.
+*/
 
-static void PMacWriteSqSetup(void)
+static int PMacWriteSqSetup(void)
 {
-	int i;
+	int i, count = 600 ;
 	volatile struct dbdma_cmd *cp;
 
+	LOCK();
+	
+	/* stop the controller from doing any output - if it isn't already.
+	   it _should_ be before this is called anyway */
+
+	out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	while ((in_le32(&awacs_txdma->status) & RUN) && count--)
+		udelay(1);
+#ifdef DEBUG_DMASOUND
+if (count <= 0)
+	printk("dmasound_pmac: write sq setup: timeout waiting for dma to stop\n");
+#endif
+
+	if ((write_sq.max_count + 1) > number_of_tx_cmd_buffers) {
+		if (awacs_tx_cmd_space)
+			kfree(awacs_tx_cmd_space);
+		number_of_tx_cmd_buffers = 0;
+
+		/* we need nbufs + 1 (for the loop) and we should request + 1
+		   again because the DBDMA_ALIGN might pull the start up by up
+		   to sizeof(struct dbdma_cmd) - 4.
+		*/
+
+		awacs_tx_cmd_space = kmalloc
+			((write_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd),
+			 GFP_KERNEL);
+		if (awacs_tx_cmd_space == NULL) {
+			/* don't leave it dangling - nasty but better than a
+			   random address */
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+			printk(KERN_ERR
+			   "dmasound_pmac: can't allocate dbdma cmd buffers"
+			   ", driver disabled\n");
+			UNLOCK();
+			return -ENOMEM;
+		}
+		awacs_tx_cmds = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(awacs_tx_cmd_space);
+		number_of_tx_cmd_buffers = write_sq.max_count + 1;
+	}
+
 	cp = awacs_tx_cmds;
-	memset((void *)cp, 0, (write_sq.numBufs+1) * sizeof(struct dbdma_cmd));
-	for (i = 0; i < write_sq.numBufs; ++i, ++cp) {
+	memset((void *)cp, 0, (write_sq.max_count+1) * sizeof(struct dbdma_cmd));
+	for (i = 0; i < write_sq.max_count; ++i, ++cp) {
 		st_le32(&cp->phy_addr, virt_to_bus(write_sq.buffers[i]));
 	}
 	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
 	st_le32(&cp->cmd_dep, virt_to_bus(awacs_tx_cmds));
-	out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	/* point the controller at the command stack - ready to go */
 	out_le32(&awacs_txdma->cmdptr, virt_to_bus(awacs_tx_cmds));
+	UNLOCK();
+	return 0;
 }
 
-static void PMacReadSqSetup(void)
+static int PMacReadSqSetup(void)
 {
-	int i;
+	int i, count = 600;
 	volatile struct dbdma_cmd *cp;
 
+	LOCK();
+	
+	/* stop the controller from doing any input - if it isn't already.
+	   it _should_ be before this is called anyway */
+	
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	while ((in_le32(&awacs_rxdma->status) & RUN) && count--)
+		udelay(1);
+#ifdef DEBUG_DMASOUND
+if (count <= 0)
+	printk("dmasound_pmac: read sq setup: timeout waiting for dma to stop\n");
+#endif
+
+	if ((read_sq.max_count+1) > number_of_rx_cmd_buffers ) {
+		if (awacs_rx_cmd_space)
+			kfree(awacs_rx_cmd_space);
+		number_of_rx_cmd_buffers = 0;
+
+		/* we need nbufs + 1 (for the loop) and we should request + 1 again
+		   because the DBDMA_ALIGN might pull the start up by up to
+		   sizeof(struct dbdma_cmd) - 4 (assuming kmalloc aligns 32 bits).
+		*/
+
+		awacs_rx_cmd_space = kmalloc
+			((read_sq.max_count + 1 + 1) * sizeof(struct dbdma_cmd),
+			 GFP_KERNEL);
+		if (awacs_rx_cmd_space == NULL) {
+			/* don't leave it dangling - nasty but better than a
+			   random address */
+			out_le32(&awacs_rxdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+			printk(KERN_ERR
+			   "dmasound_pmac: can't allocate dbdma cmd buffers"
+			   ", driver disabled\n");
+			UNLOCK();
+			return -ENOMEM;
+		}
+		awacs_rx_cmds = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(awacs_rx_cmd_space);
+		number_of_rx_cmd_buffers = read_sq.max_count + 1 ;
+	}
 	cp = awacs_rx_cmds;
-	memset((void *)cp, 0, (read_sq.numBufs+1) * sizeof(struct dbdma_cmd));
+	memset((void *)cp, 0, (read_sq.max_count+1) * sizeof(struct dbdma_cmd));
 
 	/* Set dma buffers up in a loop */
-	for (i = 0; i < read_sq.numBufs; i++,cp++) {
+	for (i = 0; i < read_sq.max_count; i++,cp++) {
 		st_le32(&cp->phy_addr, virt_to_bus(read_sq.buffers[i]));
 		st_le16(&cp->command, INPUT_MORE + INTR_ALWAYS);
 		st_le16(&cp->req_count, read_sq.block_size);
@@ -1907,37 +2310,105 @@
 	*/
 	st_le16(&cp->command, DBDMA_NOP + BR_ALWAYS);
 	st_le32(&cp->cmd_dep, virt_to_bus(awacs_rx_cmds));
-
-	/* Don't start until the first read is done.
-	 * This will also abort any operations in progress if the DMA
-	 * happens to be running (and it shouldn't).
-	 */
-	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	/* point the controller at the command stack - ready to go */
 	out_le32(&awacs_rxdma->cmdptr, virt_to_bus(awacs_rx_cmds));
 
+	UNLOCK();
+	return 0;
 }
 
+/* TODO: this needs work to guarantee that when it returns DMA has stopped
+   but in a more elegant way than is done here....
+*/
+
 static void PMacAbortRead(void)
 {
 	int i;
 	volatile struct dbdma_cmd *cp;
 
+	LOCK();
+	/* give it a chance to update the output and provide the IRQ
+	   that is expected.
+	*/
+
+	out_le32(&awacs_rxdma->control, ((FLUSH) << 16) + FLUSH );
+
 	cp = awacs_rx_cmds;
-	for (i = 0; i < read_sq.numBufs; i++,cp++)
+	for (i = 0; i < read_sq.max_count; i++,cp++)
 		st_le16(&cp->command, DBDMA_STOP);
 	/*
 	 * We should probably wait for the thing to stop before we
-	 * release the memory
+	 * release the memory.
 	 */
+
+	wait_ms(100) ; /* give it a (small) chance to act */
+
+	/* apply the sledgehammer approach - just stop it now */
+
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+	UNLOCK();
 }
 
+extern char *get_afmt_string(int);
+static int PMacStateInfo(char *b, size_t sp)
+{
+	int i, len = 0;
+	len = sprintf(b,"HW rates: ");
+	switch (awacs_revision){
+		case AWACS_DACA:
+		case AWACS_BURGUNDY:
+			len += sprintf(b,"44100 ") ;
+			break ;
+		case AWACS_TUMBLER:
+			for (i=0; i<2; i++){
+				if (tumbler_freqs_ok[i])
+					len += sprintf(b+len,"%d ", tumbler_freqs[i]) ;
+			}
+			break ;
+
+		case AWACS_AWACS:
+		case AWACS_SCREAMER:
+		default:
+			for (i=0; i<8; i++){
+				if (awacs_freqs_ok[i])
+					len += sprintf(b+len,"%d ", awacs_freqs[i]) ;
+			}
+			break ;
+	}
+	len += sprintf(b+len,"s/sec\n") ;
+	if (len < sp) {
+		len += sprintf(b+len,"HW AFMTS: ");
+		i = AFMT_U16_BE ;
+		while (i) {
+			if (i & dmasound.mach.hardware_afmts)
+				len += sprintf(b+len,"%s ",
+					get_afmt_string(i & dmasound.mach.hardware_afmts));
+			i >>= 1 ;
+		}
+		len += sprintf(b+len,"\n") ;
+	}
+	return len ;
+}
 
 /*** Machine definitions *****************************************************/
 
+static SETTINGS def_hard = {
+	format: AFMT_S16_BE,
+	stereo: 1,
+	size: 16,
+	speed: 44100
+} ;
+
+static SETTINGS def_soft = {
+	format: AFMT_S16_BE,
+	stereo: 1,
+	size: 16,
+	speed: 44100
+} ;
 
 static MACHINE machPMac = {
 	name:		awacs_name,
-	name2:		"AWACS",
+	name2:		"PowerMac Built-in Sound",
 	open:		PMacOpen,
 	release:	PMacRelease,
 	dma_alloc:	PMacAlloc,
@@ -1951,233 +2422,660 @@
 	setFormat:	PMacSetFormat,
 	setVolume:	PMacSetVolume,
 	play:		PMacPlay,
-	record:		PMacRecord,
+	record:		NULL,		/* default to no record */
+	mixer_init:	PMacMixerInit,
 	mixer_ioctl:	PMacMixerIoctl,
 	write_sq_setup:	PMacWriteSqSetup,
 	read_sq_setup:	PMacReadSqSetup,
+	state_info:	PMacStateInfo,
 	abort_read:	PMacAbortRead,
-	min_dsp_speed:	8000
+	min_dsp_speed:	7350,
+	max_dsp_speed:	44100,
+	version:	((DMASOUND_AWACS_REVISION<<8) + DMASOUND_AWACS_EDITION)
 };
 
 
 /*** Config & Setup **********************************************************/
 
+/* Check for pmac models that we care about in terms of special actions.
+*/
 
-int __init dmasound_awacs_init(void)
+void __init
+set_model(void)
 {
-	struct device_node *np;
+	/* portables/lap-tops */
 
-	if (_machine != _MACH_Pmac)
-		return -ENODEV;
+	if (machine_is_compatible("AAPL,3400/2400") ||
+	    machine_is_compatible("AAPL,3500"))	{
+		is_pbook_3X00 = 1 ;
+	}
+	if (machine_is_compatible("PowerBook1,1")  || /* lombard */
+	    machine_is_compatible("AAPL,PowerBook1998")){ /* wallstreet */
+		is_pbook_g3 = 1 ;
+		return ;
+	}
+}
 
-	awacs_subframe = 0;
-	awacs_revision = 0;
-	np = find_devices("awacs");
-	if (np == 0) {
-		/*
-		 * powermac G3 models have a node called "davbus"
-		 * with a child called "sound".
-		 */
-		struct device_node *sound;
-		np = find_devices("davbus");
-		sound = find_devices("sound");
-		if (sound != 0 && sound->parent == np) {
-			unsigned int *prop, l, i;
-			prop = (unsigned int *)
-				get_property(sound, "sub-frame", 0);
-			if (prop != 0 && *prop >= 0 && *prop < 16)
-				awacs_subframe = *prop;
-			if (device_is_compatible(sound, "burgundy"))
-				awacs_revision = AWACS_BURGUNDY;
-			/* This should be verified on older screamers */
-			if (device_is_compatible(sound, "screamer"))
-				awacs_is_screamer = 1;
-			prop = (unsigned int *)get_property(sound, "device-id", 0);
-			if (prop != 0)
-				awacs_device_id = *prop;
-			awacs_has_iic = (find_devices("perch") != NULL);
-
-			/* look for a property saying what sample rates
-			   are available */
-			for (i = 0; i < 8; ++i)
-				awacs_freqs_ok[i] = 0;
-			prop = (unsigned int *) get_property
-				(sound, "sample-rates", &l);
-			if (prop == 0)
-				prop = (unsigned int *) get_property
-					(sound, "output-frame-rates", &l);
-			if (prop != 0) {
-				for (l /= sizeof(int); l > 0; --l) {
-					/* sometimes the rate is in the
-					   high-order 16 bits (?) */
-					unsigned int r = *prop++;
-					if (r >= 0x10000)
-						r >>= 16;
-					for (i = 0; i < 8; ++i) {
-						if (r == awacs_freqs[i]) {
-							awacs_freqs_ok[i] = 1;
-							break;
-						}
-					}
+/* Get the OF node that tells us about the registers, interrupts etc. to use
+   for sound IO.
+
+   On most machines the sound IO OF node is the 'davbus' node.  On newer pmacs
+   with DACA (& Tumbler) the node to use is i2s-a.  On much older machines i.e.
+   before 9500 there is no davbus node and we have to use the 'awacs' property.
+
+  In the latter case we signal this by setting the codec value - so that the
+  code that looks for chip properties knows how to go about it.
+*/
+
+static struct device_node
+__init *get_snd_io_node(void)
+{
+	struct device_node *np = NULL;
+
+	/* set up awacs_node for early OF which doesn't have a full set of
+	 * properties on davbus
+	*/
+
+	awacs_node = find_devices("awacs");
+	if (awacs_node)
+		awacs_revision = AWACS_AWACS;
+
+	/* powermac models after 9500 (other than those which use DACA or
+	 * Tumbler) have a node called "davbus".
+	 */
+	np = find_devices("davbus");
+	/*
+	 * if we didn't find a davbus device, try 'i2s-a' since
+	 * this seems to be what iBooks (& Tumbler) have.
+	 */
+	if (np == NULL)
+		np = find_devices("i2s-a");
+
+	/* if we didn't find this - perhaps we are on an early model
+	 * which _only_ has an 'awacs' node
+	*/
+	if (np == NULL && awacs_node)
+		np = awacs_node ;
+
+	/* if we failed all these return null - this will cause the
+	 * driver to give up...
+	*/
+	return np ;
+}
+
+/* Get the OF node that contains the info about the sound chip, inputs s-rates
+   etc.
+   This node does not exist (or contains much reduced info) on earlier machines
+   we have to deduce the info other ways for these.
+*/
+
+static struct device_node
+__init *get_snd_info_node(struct device_node *io)
+{
+	struct device_node *info;
+
+	info = find_devices("sound");
+	while (info != 0 && info->parent != io)
+		info = info->next;
+
+	return info ;
+}
+
+/* Find out what type of codec we have.
+*/
+
+static int
+__init get_codec_type(struct device_node *info)
+{
+	/* already set if pre-davbus model and info will be NULL */
+	int codec = awacs_revision ;
+
+	if (info) {
+		/* must do awacs first to allow screamer to overide it */
+		if (device_is_compatible(info, "awacs"))
+			codec = AWACS_AWACS ;
+		if (device_is_compatible(info, "screamer"))
+			codec = AWACS_SCREAMER;
+		if (device_is_compatible(info, "burgundy"))
+			codec = AWACS_BURGUNDY ;
+		if (device_is_compatible(info, "daca"))
+			codec = AWACS_DACA;
+		if (device_is_compatible(info, "tumbler"))
+			codec = AWACS_TUMBLER;
+	}
+	return codec ;
+}
+
+/* find out what type, if any, of expansion card we have
+*/
+static void
+__init get_expansion_type(void)
+{
+	if (find_devices("perch") != NULL)
+		has_perch = 1;
+
+	if (find_devices("pb-ziva-pc") != NULL)
+		has_ziva = 1;
+	/* need to work out how we deal with iMac SRS module */
+}
+
+/* set up frame rates.
+ * I suspect that these routines don't quite go about it the right way:
+ * - where there is more than one rate - I think that the first property
+ * value is the number of rates.
+ * TODO: check some more device trees and modify accordingly
+ *       Set dmasound.mach.max_dsp_rate on the basis of these routines.
+*/
+
+static void
+__init init_awacs_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int i ;
+	if (prop) {
+		for (i=0; i<8; i++)
+			awacs_freqs_ok[i] = 0 ;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < 8; ++i) {
+				if (r == awacs_freqs[i]) {
+					awacs_freqs_ok[i] = 1;
+					break;
 				}
-			} else {
-				/* assume just 44.1k is OK */
-				awacs_freqs_ok[0] = 1;
 			}
 		}
 	}
-	if (np != NULL && np->n_addrs >= 3 && np->n_intrs >= 3) {
-		int vol;
-		dmasound.mach = machPMac;
-
-		awacs = (volatile struct awacs_regs *)
-			ioremap(np->addrs[0].address, 0x80);
-		awacs_txdma = (volatile struct dbdma_regs *)
-			ioremap(np->addrs[1].address, 0x100);
-		awacs_rxdma = (volatile struct dbdma_regs *)
-			ioremap(np->addrs[2].address, 0x100);
-
-		awacs_irq = np->intrs[0].line;
-		awacs_tx_irq = np->intrs[1].line;
-		awacs_rx_irq = np->intrs[2].line;
+	/* else we assume that all the rates are available */
+}
 
-		awacs_tx_cmd_space = kmalloc((write_sq.numBufs + 4) * sizeof(struct dbdma_cmd),
-					     GFP_KERNEL);
-		if (awacs_tx_cmd_space == NULL) {
-			printk(KERN_ERR "DMA sound driver: Not enough buffer memory, driver disabled!\n");
-			return -ENOMEM;
+static void
+__init init_tumbler_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int i ;
+	if (prop) {
+		for (i=0; i<2; i++)
+			tumbler_freqs_ok[i] = 0;
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			for (i = 0; i < 2; ++i) {
+				if (r == tumbler_freqs[i]) {
+					tumbler_freqs_ok[i] = 1;
+					break;
+				}
+			}
 		}
-		awacs_node = np;
-#ifdef CONFIG_PMAC_PBOOK
-		if (machine_is_compatible("PowerBook1,1")
-		    || machine_is_compatible("AAPL,PowerBook1998")) {
-			pmu_suspend();
-			feature_set(np, FEATURE_Sound_CLK_enable);
-			feature_set(np, FEATURE_Sound_power);
-			/* Shorter delay will not work */
-			mdelay(1000);
-			pmu_resume();
+	}
+	/* else we assume that all the rates are available */
+}
+
+static void
+__init init_burgundy_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int temp[9] ;
+	int i = 0 ;
+	if (prop) {
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			temp[i] = r ;
+			i++ ; if(i>=9) i=8;
+		}
+	}
+#ifdef DEBUG_DMASOUND
+if (i > 1){
+	int j;
+	printk("dmasound_pmac: burgundy with multiple frame rates\n");
+	for(j=0; j<i; j++)
+		printk("%d ", temp[j]) ;
+	printk("\n") ;
+}
+#endif
+}
+
+static void
+__init init_daca_frame_rates(unsigned int *prop, unsigned int l)
+{
+	int temp[9] ;
+	int i = 0 ;
+	if (prop) {
+		for (l /= sizeof(int); l > 0; --l) {
+			unsigned int r = *prop++;
+			/* Apple 'Fixed' format */
+			if (r >= 0x10000)
+				r >>= 16;
+			temp[i] = r ;
+			i++ ; if(i>=9) i=8;
+
 		}
+	}
+#ifdef DEBUG_DMASOUND
+if (i > 1){
+	int j;
+	printk("dmasound_pmac: DACA with multiple frame rates\n");
+	for(j=0; j<i; j++)
+		printk("%d ", temp[j]) ;
+	printk("\n") ;
+}
 #endif
-		awacs_tx_cmds = (volatile struct dbdma_cmd *)
-			DBDMA_ALIGN(awacs_tx_cmd_space);
+}
+
+static void
+__init init_frame_rates(unsigned int *prop, unsigned int l)
+{
+	switch (awacs_revision){
+		case AWACS_TUMBLER:
+			init_tumbler_frame_rates(prop, l);
+			break ;
+		case AWACS_DACA:
+			init_daca_frame_rates(prop, l);
+			break ;
+		case AWACS_BURGUNDY:
+			init_burgundy_frame_rates(prop, l);
+			break ;
+		default: /* ;-))) */
+			init_awacs_frame_rates(prop, l);
+			break ;
+	}
+}
 
+/* find things/machines that can't do mac-io byteswap
+*/
 
-		awacs_rx_cmd_space = kmalloc((read_sq.numBufs + 4) * sizeof(struct dbdma_cmd),
-					     GFP_KERNEL);
-		if (awacs_rx_cmd_space == NULL) {
-		  printk("DMA sound driver: No memory for input");
+static void
+__init set_hw_byteswap(struct device_node *io)
+{
+	struct device_node *mio ;
+	unsigned int *p, kl = 0 ;
+
+	/* if seems that Keylargo can't byte-swap  */
+
+	for (mio = io->parent; mio ; mio = mio->parent) {
+		if (strcmp(mio->name, "mac-io") == 0) {
+			if (device_is_compatible(mio, "Keylargo"))
+				kl = 1;
+			break;
 		}
-		awacs_rx_cmds = (volatile struct dbdma_cmd *)
-		  DBDMA_ALIGN(awacs_rx_cmd_space);
+	}
+	hw_can_byteswap = !kl;
+}
 
+/* Allocate the resources necessary for beep generation.  This cannot be (quite)
+   done statically (yet) because we cannot do virt_to_bus() on static vars when
+   the code is loaded as a module.
 
+   for the sake of saving the possibility that two allocations will incur the
+   overhead of two pull-ups in DBDMA_ALIGN() we allocate the 'emergency' dmdma
+   command here as well... even tho' it is not part of the beep process.
+*/
 
-		awacs_reg[0] = MASK_MUX_CD;
-		/* FIXME: Only machines with external SRS module need MASK_PAROUT */
-		awacs_reg[1] = MASK_LOOPTHRU;
-		if (awacs_has_iic || awacs_device_id == 0x5 || /*awacs_device_id == 0x8
-			|| */awacs_device_id == 0xb)
-			awacs_reg[1] |= MASK_PAROUT;
-		/* get default volume from nvram */
-		vol = (~nvram_read_byte(0x1308) & 7) << 1;
-		awacs_reg[2] = vol + (vol << 6);
-		awacs_reg[4] = vol + (vol << 6);
-		awacs_reg[5] = 0;
-		awacs_reg[6] = 0;
-		awacs_reg[7] = 0;
-		out_le32(&awacs->control, 0x11);
-		awacs_write(awacs_reg[0] + MASK_ADDR0);
-		awacs_write(awacs_reg[1] + MASK_ADDR1);
-		awacs_write(awacs_reg[2] + MASK_ADDR2);
-		awacs_write(awacs_reg[4] + MASK_ADDR4);
-		if (awacs_is_screamer) {
-			awacs_write(awacs_reg[5] + MASK_ADDR5);
-			awacs_write(awacs_reg[6] + MASK_ADDR6);
-			awacs_write(awacs_reg[7] + MASK_ADDR7);
-		}
-
-		/* Initialize recent versions of the awacs */
-		if (awacs_revision == 0) {
-			awacs_revision =
-				(in_le32(&awacs->codec_stat) >> 12) & 0xf;
-			if (awacs_revision == 3) {
-				mdelay(100);
-				awacs_write(0x6000);
-				mdelay(2);
-				awacs_write(awacs_reg[1] + MASK_ADDR1);
-				awacs_enable_amp(100 * 0x101);
+int32_t
+__init setup_beep(void)
+{
+	/* Initialize beep stuff */
+	/* want one cmd buffer for beeps, and a second one for emergencies
+	   - i.e. dbdma error conditions.
+	   ask for three to allow for pull up in DBDMA_ALIGN().
+	*/
+	beep_dbdma_cmd_space =
+		kmalloc((2 + 1) * sizeof(struct dbdma_cmd), GFP_KERNEL);
+	if(beep_dbdma_cmd_space == NULL) {
+		printk(KERN_ERR "dmasound_pmac: no beep dbdma cmd space\n") ;
+		return -ENOMEM ;
+	}
+	beep_dbdma_cmd = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(beep_dbdma_cmd_space);
+	/* set up emergency dbdma cmd */
+	emergency_dbdma_cmd = beep_dbdma_cmd+1 ;
+	beep_buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL);
+	if (beep_buf == NULL) {
+		printk(KERN_ERR "dmasound_pmac: no memory for beep buffer\n");
+		if( beep_dbdma_cmd_space ) kfree(beep_dbdma_cmd_space) ;
+		return -ENOMEM ;
+	}
+	/* OK, we should be safe to claim the mksound vector now */
+	orig_mksound = kd_mksound;
+	kd_mksound = awacs_mksound;
+	return 0 ;
+}
+
+int __init dmasound_awacs_init(void)
+{
+	struct device_node *io = NULL, *info = NULL;
+	int vol, res;
+
+	if (_machine != _MACH_Pmac)
+		return -ENODEV;
+
+	awacs_subframe = 0;
+	awacs_revision = 0;
+	hw_can_byteswap = 1 ; /* most can */
+
+	/* look for models we need to handle specially */
+	set_model() ;
+
+	/* find the OF node that tells us about the dbdma stuff
+	*/
+	io = get_snd_io_node();
+	if (io == NULL) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find sound io OF node\n");
+#endif
+		return -ENODEV ;
+	}
+
+	/* find the OF node that tells us about the sound sub-system
+	 * this doesn't exist on pre-davbus machines (earlier than 9500)
+	*/
+	if (awacs_revision != AWACS_AWACS) { /* set for pre-davbus */
+		info = get_snd_info_node(io) ;
+		if (info == NULL){
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find 'sound' OF node\n");
+#endif
+			return -ENODEV ;
+		}
+	}
+
+	awacs_revision = get_codec_type(info) ;
+	if (awacs_revision == 0) {
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: couldn't find a Codec we can handle\n");
+#endif
+		return -ENODEV ; /* we don't know this type of h/w */
+	}
+
+	/* set up perch, ziva, SRS or whatever else we have as sound
+	 *  expansion.
+	*/
+	get_expansion_type();
+
+	/* we've now got enough information to make up the audio topology.
+	 * we will map the sound part of mac-io now so that we can probe for
+	 * other info if necessary (early AWACS we want to read chip ids)
+	 */
+
+	if (io->n_addrs < 3 || io->n_intrs < 3) {
+		/* OK - maybe we need to use the 'awacs' node (on earlier
+		 * machines).
+		*/
+		if (awacs_node) {
+			io = awacs_node ;
+			if (io->n_addrs < 3 || io->n_intrs < 3) {
+				printk("dmasound_pmac: can't use %s"
+					" (%d addrs, %d intrs)\n",
+		      		 io->full_name, io->n_addrs, io->n_intrs);
+				return -ENODEV;
 			}
+		} else {
+			printk("dmasound_pmac: can't use %s (%d addrs, %d intrs)\n",
+		 	      io->full_name, io->n_addrs, io->n_intrs);
 		}
-		if (awacs_revision >= AWACS_BURGUNDY)
+	}
+
+	if (!request_OF_resource(io, 0, NULL)) {
+		printk(KERN_ERR "dmasound: can't request IO resource !\n");
+		return -ENODEV;
+	}
+	if (!request_OF_resource(io, 1, " (tx dma)")) {
+		release_OF_resource(io, 0);
+		printk(KERN_ERR "dmasound: can't request TX DMA resource !\n");
+		return -ENODEV;
+	}
+
+	if (!request_OF_resource(io, 2, " (rx dma)")) {
+		release_OF_resource(io, 0);
+		release_OF_resource(io, 1);
+		printk(KERN_ERR "dmasound: can't request RX DMA resource !\n");
+		return -ENODEV;
+	}
+
+	/* all OF versions I've seen use this value */
+	awacs = (volatile struct awacs_regs *)
+		ioremap(io->addrs[0].address, 0x1000);
+	awacs_txdma = (volatile struct dbdma_regs *)
+		ioremap(io->addrs[1].address, 0x100);
+	awacs_rxdma = (volatile struct dbdma_regs *)
+		ioremap(io->addrs[2].address, 0x100);
+
+#ifdef CONFIG_PMAC_PBOOK
+	/* first of all make sure that the chip is powered up....*/
+	pmac_call_feature(PMAC_FTR_SOUND_CHIP_ENABLE, io, 0, 1);
+	if (awacs_revision == AWACS_SCREAMER)
+		awacs_recalibrate();
+#endif
+	awacs_irq = io->intrs[0].line;
+	awacs_tx_irq = io->intrs[1].line;
+	awacs_rx_irq = io->intrs[2].line;
+
+	awacs_node = io;
+
+	/* if we have an awacs or screamer - probe the chip to make
+	 * sure we have the right revision.
+	*/
+
+	if (awacs_revision <= AWACS_SCREAMER){
+		uint32_t temp, rev, mfg ;
+		/* find out the awacs revision from the chip */
+		temp = in_le32(&awacs->codec_stat);
+		rev = (temp >> 12) & 0xf;
+		mfg = (temp >>  8) & 0xf;
+#ifdef DEBUG_DMASOUND
+printk("dmasound_pmac: Awacs/Screamer Codec Mfct: %d Rev %d\n", mfg, rev);
+#endif
+		if (rev >= AWACS_SCREAMER)
+			awacs_revision = AWACS_SCREAMER ;
+		else
+			awacs_revision = rev ;
+	}
+
+	dmasound.mach = machPMac;
+
+	/* find out other bits & pieces from OF, these may be present
+	   only on some models ... so be careful.
+	*/
+
+	/* in the absence of a frame rates property we will use the defaults
+	*/
+
+	if (info) {
+		unsigned int *prop, l;
+
+		sound_device_id = 0;
+		/* device ID appears post g3 b&w */
+		prop = (unsigned int *)get_property(info, "device-id", 0);
+		if (prop != 0)
+			sound_device_id = *prop;
+
+		/* look for a property saying what sample rates
+		   are available */
+
+		prop = (unsigned int *)get_property(info, "sample-rates", &l);
+		if (prop == 0)
+			prop = (unsigned int *) get_property
+				(info, "output-frame-rates", &l);
+
+		/* if it's there use it to set up frame rates */
+		init_frame_rates(prop, l) ;
+	}
+	
+	out_le32(&awacs->control, 0x11); /* set everything quiesent */
+
+	set_hw_byteswap(io) ; /* figure out if the h/w can do it */
+
+	/* get default volume from nvram
+	 * vol = (~nvram_read_byte(0x1308) & 7) << 1;
+	*/
+	vol = ((pmac_xpram_read( 8 ) & 7 ) << 1 );
+	/* set up tracking values */
+	spk_vol = vol * 100 ;
+	spk_vol /= 7 ; /* get set value to a percentage */
+	spk_vol |= (spk_vol << 8) ; /* equal left & right */
+ 	line_vol = passthru_vol = spk_vol ;
+
+	/* fill regs that are shared between AWACS & Burgundy */
+
+	awacs_reg[2] = vol + (vol << 6);
+	awacs_reg[4] = vol + (vol << 6);
+	awacs_reg[5] = vol + (vol << 6); /* screamer has loopthru vol control */
+	awacs_reg[6] = 0; /* maybe should be vol << 3 for PCMCIA speaker */
+	awacs_reg[7] = 0;
+
+	awacs_reg[0] = MASK_MUX_CD;
+	awacs_reg[1] = MASK_LOOPTHRU;
+
+	/* FIXME: Only machines with external SRS module need MASK_PAROUT */
+	if (has_perch || sound_device_id == 0x5
+	    || /*sound_device_id == 0x8 ||*/ sound_device_id == 0xb)
+		awacs_reg[1] |= MASK_PAROUT0 | MASK_PAROUT1;
+
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+#ifdef CONFIG_KMOD
+			request_module("i2c-keywest");
+#endif /* CONFIG_KMOD */	
+			awacs_tumbler_init();
+			tas_init();
+			break ;
+		case AWACS_DACA:
+#ifdef CONFIG_KMOD
+			request_module("i2c-keywest");
+#endif /* CONFIG_KMOD */
+			daca_init();
+			break ;		/* dont know how yet */
+		case AWACS_BURGUNDY:
 			awacs_burgundy_init();
+			break ;
+		case AWACS_SCREAMER:
+		case AWACS_AWACS:
+		default:
+			load_awacs() ;
+			break ;
+	}
+
+	/* enable/set-up external modules - when we know how */
+
+	if (has_perch)
+		awacs_enable_amp(100 * 0x101);
+
+	/* Reset dbdma channels */
+	out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	while (in_le32(&awacs_txdma->status) & RUN)
+		udelay(1);
+	out_le32(&awacs_rxdma->control, (RUN|PAUSE|FLUSH|WAKE|DEAD) << 16);
+	while (in_le32(&awacs_rxdma->status) & RUN)
+		udelay(1);
+
+	/* Initialize beep stuff */
+	if ((res=setup_beep()))
+		return res ;
 
-		/* Initialize beep stuff */
-		beep_dbdma_cmd = awacs_tx_cmds + (write_sq.numBufs + 1);
-		orig_mksound = kd_mksound;
-		kd_mksound = awacs_mksound;
-		beep_buf = (short *) kmalloc(BEEP_BUFLEN * 4, GFP_KERNEL);
-		if (beep_buf == NULL)
-			printk(KERN_WARNING "dmasound: no memory for "
-			       "beep buffer\n");
 #ifdef CONFIG_PMAC_PBOOK
-		pmu_register_sleep_notifier(&awacs_sleep_notifier);
+	pmu_register_sleep_notifier(&awacs_sleep_notifier);
 #endif /* CONFIG_PMAC_PBOOK */
 
-		/* Powerbooks have odd ways of enabling inputs such as
-		   an expansion-bay CD or sound from an internal modem
-		   or a PC-card modem. */
-		if (machine_is_compatible("AAPL,3400/2400")
-			|| machine_is_compatible("AAPL,3500")) {
-			is_pbook_3400 = 1;
-			/*
-			 * Enable CD and PC-card sound inputs.
-			 * This is done by reading from address
-			 * f301a000, + 0x10 to enable the expansion-bay
-			 * CD sound input, + 0x80 to enable the PC-card
-			 * sound input.  The 0x100 enables the SCSI bus
-			 * terminator power.
-			 */
-			latch_base = (unsigned char *) ioremap
-				(0xf301a000, 0x1000);
-			in_8(latch_base + 0x190);
-		} else if (machine_is_compatible("PowerBook1,1")
-			   || machine_is_compatible("AAPL,PowerBook1998")) {
-			struct device_node* mio;
-			macio_base = 0;
-			is_pbook_G3 = 1;
-			for (mio = np->parent; mio; mio = mio->parent) {
-				if (strcmp(mio->name, "mac-io") == 0
-				    && mio->n_addrs > 0) {
-					macio_base = (unsigned char *) ioremap
-						(mio->addrs[0].address, 0x40);
-					break;
-				}
+	/* Powerbooks have odd ways of enabling inputs such as
+	   an expansion-bay CD or sound from an internal modem
+	   or a PC-card modem. */
+	if (is_pbook_3X00) {
+		/*
+		 * Enable CD and PC-card sound inputs.
+		 * This is done by reading from address
+		 * f301a000, + 0x10 to enable the expansion-bay
+		 * CD sound input, + 0x80 to enable the PC-card
+		 * sound input.  The 0x100 enables the SCSI bus
+		 * terminator power.
+		 */
+		latch_base = (unsigned char *) ioremap (0xf301a000, 0x1000);
+		in_8(latch_base + 0x190);
+
+	} else if (is_pbook_g3) {
+		struct device_node* mio;
+		macio_base = 0;
+		for (mio = io->parent; mio; mio = mio->parent) {
+			if (strcmp(mio->name, "mac-io") == 0
+			    && mio->n_addrs > 0) {
+				macio_base = (unsigned char *) ioremap
+					(mio->addrs[0].address, 0x40);
+				break;
 			}
-			/*
-			 * Enable CD sound input.
-			 * The relevant bits for writing to this byte are 0x8f.
-			 * I haven't found out what the 0x80 bit does.
-			 * For the 0xf bits, writing 3 or 7 enables the CD
-			 * input, any other value disables it.  Values
-			 * 1, 3, 5, 7 enable the microphone.  Values 0, 2,
-			 * 4, 6, 8 - f enable the input from the modem.
-			 */
-			if (macio_base)
-				out_8(macio_base + 0x37, 3);
-		}
-		sprintf(awacs_name, "PowerMac (AWACS rev %d) ",
-			awacs_revision);
-		return dmasound_init();
+		}
+		/*
+		 * Enable CD sound input.
+		 * The relevant bits for writing to this byte are 0x8f.
+		 * I haven't found out what the 0x80 bit does.
+		 * For the 0xf bits, writing 3 or 7 enables the CD
+		 * input, any other value disables it.  Values
+		 * 1, 3, 5, 7 enable the microphone.  Values 0, 2,
+		 * 4, 6, 8 - f enable the input from the modem.
+		 *  -- paulus.
+		 */
+		if (macio_base)
+			out_8(macio_base + 0x37, 3);
+	}
+
+	if (hw_can_byteswap)
+ 		dmasound.mach.hardware_afmts = (AFMT_S16_BE | AFMT_S16_LE) ;
+ 	else
+		dmasound.mach.hardware_afmts = AFMT_S16_BE ;
+
+	/* shut out chips that do output only.
+	   may need to extend this to machines which have no inputs - even tho'
+	   they use screamer - IIRC one of the powerbooks is like this.
+	*/
+
+	if (awacs_revision != AWACS_TUMBLER && awacs_revision != AWACS_DACA) {
+		dmasound.mach.capabilities = DSP_CAP_DUPLEX ;
+		dmasound.mach.record = PMacRecord ;
+	}
+
+	dmasound.mach.default_hard = def_hard ;
+	dmasound.mach.default_soft = def_soft ;
+
+	switch (awacs_revision) {
+		case AWACS_BURGUNDY:
+			sprintf(awacs_name, "PowerMac Burgundy ") ;
+			break ;
+		case AWACS_DACA:
+			sprintf(awacs_name, "PowerMac DACA ") ;
+			break ;
+		case AWACS_TUMBLER:
+			sprintf(awacs_name, "PowerMac Tumbler ") ;
+			break ;
+		case AWACS_SCREAMER:
+			sprintf(awacs_name, "PowerMac Screamer ") ;
+			break ;
+		case AWACS_AWACS:
+		default:
+			sprintf(awacs_name, "PowerMac AWACS rev %d ", awacs_revision) ;
+			break ;
 	}
-	return -ENODEV;
+
+	return dmasound_init();
 }
 
 static void __exit dmasound_awacs_cleanup(void)
 {
+	switch (awacs_revision) {
+		case AWACS_TUMBLER:
+			awacs_tumbler_cleanup();
+			tas_cleanup();
+			break ;
+		case AWACS_DACA:
+			daca_cleanup();
+			break;
+	}
 	dmasound_deinit();
 }
 
+MODULE_DESCRIPTION("PowerMac built-in audio driver.");
+MODULE_LICENSE("GPL");
+
 module_init(dmasound_awacs_init);
 module_exit(dmasound_awacs_cleanup);
-MODULE_LICENSE("GPL");

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