patch-2.3.99-pre4 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 -u --recursive --new-file v2.3.99-pre3/linux/drivers/sound/dmasound/dmasound_awacs.c linux/drivers/sound/dmasound/dmasound_awacs.c
@@ -0,0 +1,2113 @@
+
+/*
+ *  linux/drivers/sound/dmasound_awacs.c
+ *
+ *  PowerMac DMA Sound Driver
+ *
+ *  See linux/drivers/sound/dmasound_core.c for copyright and credits
+ */
+
+
+#include <linux/module.h>
+#include <linux/config.h>
+#include <linux/malloc.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/soundcard.h>
+#include <linux/adb.h>
+#include <linux/nvram.h>
+#include <linux/vt_kern.h>
+#include <linux/cuda.h>
+
+#include <asm/uaccess.h>
+#include <asm/prom.h>
+#include <asm/machdep.h>
+#include <asm/io.h>
+#include <asm/dbdma.h>
+
+#include "awacs_defs.h"
+#include "dmasound.h"
+
+
+/*
+ * Interrupt numbers and addresses, obtained from the device tree.
+ */
+static int awacs_irq, awacs_tx_irq, awacs_rx_irq;
+static volatile struct awacs_regs *awacs;
+static volatile struct dbdma_regs *awacs_txdma, *awacs_rxdma;
+static int awacs_rate_index;
+static int awacs_subframe;
+static int awacs_spkr_vol;
+static struct device_node* awacs_node;
+
+static char awacs_name[64];
+static int awacs_revision;
+#define AWACS_BURGUNDY	100		/* fake revision # for burgundy */
+
+/*
+ * Space for the DBDMA command blocks.
+ */
+static void *awacs_tx_cmd_space;
+static volatile struct dbdma_cmd *awacs_tx_cmds;
+
+static void *awacs_rx_cmd_space;
+static volatile struct dbdma_cmd *awacs_rx_cmds;
+
+/*
+ * Cached values of AWACS registers (we can't read them).
+ * Except on the burgundy. XXX
+ */
+int awacs_reg[5];
+
+#define HAS_16BIT_TABLES
+#undef HAS_8BIT_TABLES
+
+/*
+ * Stuff for outputting a beep.  The values range from -327 to +327
+ * so we can multiply by an amplitude in the range 0..100 to get a
+ * signed short value to put in the output buffer.
+ */
+static short beep_wform[256] = {
+	0,	40,	79,	117,	153,	187,	218,	245,
+	269,	288,	304,	316,	323,	327,	327,	324,
+	318,	310,	299,	288,	275,	262,	249,	236,
+	224,	213,	204,	196,	190,	186,	183,	182,
+	182,	183,	186,	189,	192,	196,	200,	203,
+	206,	208,	209,	209,	209,	207,	204,	201,
+	197,	193,	188,	183,	179,	174,	170,	166,
+	163,	161,	160,	159,	159,	160,	161,	162,
+	164,	166,	168,	169,	171,	171,	171,	170,
+	169,	167,	163,	159,	155,	150,	144,	139,
+	133,	128,	122,	117,	113,	110,	107,	105,
+	103,	103,	103,	103,	104,	104,	105,	105,
+	105,	103,	101,	97,	92,	86,	78,	68,
+	58,	45,	32,	18,	3,	-11,	-26,	-41,
+	-55,	-68,	-79,	-88,	-95,	-100,	-102,	-102,
+	-99,	-93,	-85,	-75,	-62,	-48,	-33,	-16,
+	0,	16,	33,	48,	62,	75,	85,	93,
+	99,	102,	102,	100,	95,	88,	79,	68,
+	55,	41,	26,	11,	-3,	-18,	-32,	-45,
+	-58,	-68,	-78,	-86,	-92,	-97,	-101,	-103,
+	-105,	-105,	-105,	-104,	-104,	-103,	-103,	-103,
+	-103,	-105,	-107,	-110,	-113,	-117,	-122,	-128,
+	-133,	-139,	-144,	-150,	-155,	-159,	-163,	-167,
+	-169,	-170,	-171,	-171,	-171,	-169,	-168,	-166,
+	-164,	-162,	-161,	-160,	-159,	-159,	-160,	-161,
+	-163,	-166,	-170,	-174,	-179,	-183,	-188,	-193,
+	-197,	-201,	-204,	-207,	-209,	-209,	-209,	-208,
+	-206,	-203,	-200,	-196,	-192,	-189,	-186,	-183,
+	-182,	-182,	-183,	-186,	-190,	-196,	-204,	-213,
+	-224,	-236,	-249,	-262,	-275,	-288,	-299,	-310,
+	-318,	-324,	-327,	-327,	-323,	-316,	-304,	-288,
+	-269,	-245,	-218,	-187,	-153,	-117,	-79,	-40,
+};
+
+#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_playing = 0;
+static int awacs_beep_state = 0;
+static short *beep_buf;
+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);
+static unsigned awacs_burgundy_rcw(unsigned addr);
+static void awacs_burgundy_write_volume(unsigned address, int volume);
+static int awacs_burgundy_read_volume(unsigned address);
+static void awacs_burgundy_write_mvolume(unsigned address, int volume);
+static int awacs_burgundy_read_mvolume(unsigned address);
+
+#ifdef CONFIG_PMAC_PBOOK
+/*
+ * Stuff for restoring after a sleep.
+ */
+static int awacs_sleep_notify(struct pmu_sleep_notifier *self, int when);
+struct pmu_sleep_notifier awacs_sleep_notifier = {
+	awacs_sleep_notify, SLEEP_LEVEL_SOUND,
+};
+#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);
+
+
+/*** Low level stuff *********************************************************/
+
+
+static void PMacOpen(void);
+static void PMacRelease(void);
+static void *PMacAlloc(unsigned int size, int flags);
+static void PMacFree(void *ptr, unsigned int size);
+static int PMacIrqInit(void);
+#ifdef MODULE
+static void PMacIrqCleanup(void);
+#endif
+static void PMacSilence(void);
+static void PMacInit(void);
+static int PMacSetFormat(int format);
+static int PMacSetVolume(int volume);
+static void PMacPlay(void);
+static void PMacRecord(void);
+static void pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs);
+static void pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs);
+static void pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs);
+static void awacs_write(int val);
+static int awacs_get_volume(int reg, int lshift);
+static int awacs_volume_setter(int volume, int n, int mute, int lshift);
+static void awacs_mksound(unsigned int hz, unsigned int ticks);
+static void awacs_nosound(unsigned long xx);
+
+
+/*** Mid level stuff **********************************************************/
+
+
+static int PMacMixerIoctl(u_int cmd, u_long arg);
+static void PMacWriteSqSetup(void);
+static void PMacReadSqSetup(void);
+static void PMacAbortRead(void);
+
+
+/*** 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(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(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(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(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;
+}
+
+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(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 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;
+}
+
+
+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;
+}
+
+
+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)
+				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;
+}
+
+
+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 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(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(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(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--;
+		}
+	} else {
+		if (copy_to_user((u_char *)userPtr, fp, count * 4))
+			return -EFAULT;
+	}
+	*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(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;
+}
+
+
+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 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,
+};
+
+/*** Low level stuff *********************************************************/
+
+
+
+/*
+ * PCI PowerMac, with AWACS and DBDMA.
+ */
+
+static void PMacOpen(void)
+{
+	MOD_INC_USE_COUNT;
+}
+
+static void PMacRelease(void)
+{
+	MOD_DEC_USE_COUNT;
+}
+
+static void *PMacAlloc(unsigned int size, int flags)
+{
+	return kmalloc(size, flags);
+}
+
+static void PMacFree(void *ptr, unsigned int size)
+{
+	kfree(ptr);
+}
+
+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))
+		return 0;
+	return 1;
+}
+
+#ifdef MODULE
+static void PMacIrqCleanup(void)
+{
+	/* turn off output dma */
+	out_le32(&awacs_txdma->control, RUN<<16);
+	/* 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);
+	}
+#endif
+	free_irq(awacs_irq, 0);
+	free_irq(awacs_tx_irq, 0);
+	free_irq(awacs_rx_irq, 0);
+	kfree(awacs_tx_cmd_space);
+	if (awacs_rx_cmd_space)
+		kfree(awacs_rx_cmd_space);
+	if (beep_buf)
+		kfree(beep_buf);
+	kd_mksound = orig_mksound;
+#ifdef CONFIG_PMAC_PBOOK
+	pmu_unregister_sleep_notifier(&awacs_sleep_notifier);
+#endif
+}
+#endif /* MODULE */
+
+static void PMacSilence(void)
+{
+	/* turn off output dma */
+	out_le32(&awacs_txdma->control, RUN<<16);
+}
+
+static int awacs_freqs[8] = {
+	44100, 29400, 22050, 17640, 14700, 11025, 8820, 7350
+};
+static int awacs_freqs_ok[8] = { 1, 1, 1, 1, 1, 1, 1, 1 };
+
+static void PMacInit(void)
+{
+	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;
+
+	/*
+	 * 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.
+	 */
+	i = 8;
+	do {
+		tolerance = catchRadius * 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));
+	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);
+
+	/* 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 :-).
+	 */
+	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));
+
+	expand_bal = -dmasound.soft.speed;
+}
+
+static int PMacSetFormat(int format)
+{
+	int size;
+
+	switch (format) {
+	case AFMT_QUERY:
+		return dmasound.soft.format;
+	case AFMT_MU_LAW:
+	case AFMT_A_LAW:
+	case AFMT_U8:
+	case AFMT_S8:
+		size = 8;
+		break;
+	case AFMT_S16_BE:
+	case AFMT_U16_BE:
+	case AFMT_S16_LE:
+	case AFMT_U16_LE:
+		size = 16;
+		break;
+	default: /* :-) */
+		printk(KERN_ERR "dmasound: unknown format 0x%x, using AFMT_U8\n",
+		       format);
+		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;
+	}
+
+	PMacInit();
+
+	return format;
+}
+
+#define AWACS_VOLUME_TO_MASK(x)	(15 - ((((x) - 1) * 15) / 99))
+#define AWACS_MASK_TO_VOLUME(y)	(100 - ((y) * 99 / 15))
+
+static int awacs_get_volume(int reg, int lshift)
+{
+	int volume;
+
+	volume = AWACS_MASK_TO_VOLUME((reg >> lshift) & 0xf);
+	volume |= AWACS_MASK_TO_VOLUME(reg & 0xf) << 8;
+	return volume;
+}
+
+static int awacs_volume_setter(int volume, int n, int mute, int lshift)
+{
+	int r1, rn;
+
+	if (mute && volume == 0) {
+		r1 = awacs_reg[1] | mute;
+	} else {
+		r1 = awacs_reg[1] & ~mute;
+		rn = awacs_reg[n] & ~(0xf | (0xf << lshift));
+		rn |= ((AWACS_VOLUME_TO_MASK(volume & 0xff) & 0xf) << lshift);
+		rn |= AWACS_VOLUME_TO_MASK((volume >> 8) & 0xff) & 0xf;
+		awacs_reg[n] = rn;
+		awacs_write((n << 12) | rn);
+		volume = awacs_get_volume(rn, lshift);
+	}
+	if (r1 != awacs_reg[1]) {
+		awacs_reg[1] = r1;
+		awacs_write(r1 | MASK_ADDR1);
+	}
+	return volume;
+}
+
+static int PMacSetVolume(int volume)
+{
+	return awacs_volume_setter(volume, 2, MASK_AMUTE, 6);
+}
+
+static void PMacPlay(void)
+{
+	volatile struct dbdma_cmd *cp;
+	int i, count;
+	unsigned long flags;
+
+	save_flags(flags); cli();
+	if (awacs_beep_state) {
+		/* sound takes precedence over beeps */
+		out_le32(&awacs_txdma->control, (RUN|PAUSE|FLUSH|WAKE) << 16);
+		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])));
+
+		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;
+	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];
+		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);
+		if (write_sq.active == 0)
+			out_le32(&awacs_txdma->cmdptr, virt_to_bus(cp));
+		out_le32(&awacs_txdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
+		++write_sq.active;
+	}
+	restore_flags(flags);
+}
+
+
+static void PMacRecord(void)
+{
+	unsigned long flags;
+
+	if (read_sq.active)
+		return;
+
+	save_flags(flags); cli();
+
+	/* This is all we have to do......Just start it up.
+	*/
+	out_le32(&awacs_rxdma->control, ((RUN|WAKE) << 16) + (RUN|WAKE));
+	read_sq.active = 1;
+
+	restore_flags(flags);
+}
+
+
+static void
+pmac_awacs_tx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	int i = write_sq.front;
+	int stat;
+	volatile struct dbdma_cmd *cp;
+
+	while (write_sq.active > 0) {
+		cp = &awacs_tx_cmds[i];
+		stat = ld_le16(&cp->xfer_status);
+		if ((stat & ACTIVE) == 0)
+			break;	/* this frame is still going */
+		--write_sq.count;
+		--write_sq.active;
+		if (++i >= write_sq.max_count)
+			i = 0;
+	}
+	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);
+}
+
+
+static void
+pmac_awacs_rx_intr(int irq, void *devid, struct pt_regs *regs)
+{
+
+	/* 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
+	 * us, so I just check the command pointer and if it is zero,
+	 * just blow it off.
+	 */
+	if (in_le32(&awacs_rxdma->cmdptr) == 0)
+		return;
+
+	/* We also want to blow 'em off when shutting down.
+	*/
+	if (read_sq.active == 0)
+		return;
+
+	/* Check multiple buffers in case we were held off from
+	 * interrupt processing for a long time.  Geeze, I really hope
+	 * this doesn't happen.
+	 */
+	while (awacs_rx_cmds[read_sq.rear].xfer_status) {
+
+		/* Clear status and move on to next buffer.
+		*/
+		awacs_rx_cmds[read_sq.rear].xfer_status = 0;
+		read_sq.rear++;
+
+		/* Wrap the buffer ring.
+		*/
+		if (read_sq.rear >= read_sq.max_active)
+			read_sq.rear = 0;
+
+		/* If we have caught up to the front buffer, bump it.
+		 * This will cause weird (but not fatal) results if the
+		 * read loop is currently using this buffer.  The user is
+		 * behind in this case anyway, so weird things are going
+		 * to happen.
+		 */
+		if (read_sq.rear == read_sq.front) {
+			read_sq.front++;
+			if (read_sq.front >= read_sq.max_active)
+				read_sq.front = 0;
+		}
+	}
+
+	WAKE_UP(read_sq.action_queue);
+}
+
+
+static void
+pmac_awacs_intr(int irq, void *devid, struct pt_regs *regs)
+{
+	int ctrl = in_le32(&awacs->control);
+
+	if (ctrl & MASK_PORTCHG) {
+		/* do something when headphone is plugged/unplugged? */
+	}
+	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);
+	}
+	/* Writing 1s to the CNTLERR and PORTCHG bits clears them... */
+	out_le32(&awacs->control, ctrl);
+}
+
+static void
+awacs_write(int val)
+{
+	if (awacs_revision >= AWACS_BURGUNDY)
+		return;
+	while (in_le32(&awacs->codec_ctrl) & MASK_NEWECMD)
+		;	/* XXX should have timeout */
+	out_le32(&awacs->codec_ctrl, val | (awacs_subframe << 22));
+}
+
+static void awacs_nosound(unsigned long xx)
+{
+	unsigned long flags;
+
+	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);
+		out_le32(&awacs->control,
+			 (in_le32(&awacs->control) & ~0x1f00)
+			 | (awacs_rate_index << 8));
+		out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
+		beep_playing = 0;
+	}
+	restore_flags(flags);
+}
+
+static struct timer_list beep_timer = {
+	NULL, NULL, 0, 0, awacs_nosound
+};
+
+static void awacs_mksound(unsigned int hz, unsigned int ticks)
+{
+	unsigned long flags;
+	int beep_speed = 0;
+	int srate;
+	int period, ncycles, nsamples;
+	int i, j, f;
+	short *p;
+	static int beep_hz_cache;
+	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 (hz <= srate / BEEP_BUFLEN || hz > srate / 2) {
+#if 1
+		/* this is a hack for broken X server code */
+		hz = 750;
+		ticks = 12;
+#else
+		/* cancel beep currently playing */
+		awacs_nosound(0);
+		return;
+#endif
+	}
+	save_flags(flags); cli();
+	del_timer(&beep_timer);
+	if (ticks) {
+		beep_timer.expires = jiffies + ticks;
+		add_timer(&beep_timer);
+	}
+	if (beep_playing || write_sq.active || beep_buf == NULL) {
+		restore_flags(flags);
+		return;		/* too hard, sorry :-( */
+	}
+	beep_playing = 1;
+	st_le16(&beep_dbdma_cmd->command, OUTPUT_MORE + BR_ALWAYS);
+	restore_flags(flags);
+
+	if (hz == beep_hz_cache && beep_volume == beep_volume_cache) {
+		nsamples = beep_nsamples_cache;
+	} else {
+		period = srate * 256 / hz;	/* fixed point */
+		ncycles = BEEP_BUFLEN * 256 / period;
+		nsamples = (period * ncycles) >> 8;
+		f = ncycles * 65536 / nsamples;
+		j = 0;
+		p = beep_buf;
+		for (i = 0; i < nsamples; ++i, p += 2) {
+			p[0] = p[1] = beep_wform[j >> 8] * beep_volume;
+			j = (j + f) & 0xffff;
+		}
+		beep_hz_cache = hz;
+		beep_volume_cache = beep_volume;
+		beep_nsamples_cache = nsamples;
+	}
+
+	st_le16(&beep_dbdma_cmd->req_count, nsamples*4);
+	st_le16(&beep_dbdma_cmd->xfer_status, 0);
+	st_le32(&beep_dbdma_cmd->cmd_dep, virt_to_bus(beep_dbdma_cmd));
+	st_le32(&beep_dbdma_cmd->phy_addr, virt_to_bus(beep_buf));
+	awacs_beep_state = 1;
+
+	save_flags(flags); cli();
+	if (beep_playing) {	/* i.e. haven't been terminated already */
+		out_le32(&awacs_txdma->control, (RUN|WAKE|FLUSH|PAUSE) << 16);
+		out_le32(&awacs->control,
+			 (in_le32(&awacs->control) & ~0x1f00)
+			 | (beep_speed << 8));
+		out_le32(&awacs->byteswap, 0);
+		out_le32(&awacs_txdma->cmdptr, virt_to_bus(beep_dbdma_cmd));
+		out_le32(&awacs_txdma->control, RUN | (RUN << 16));
+	}
+	restore_flags(flags);
+}
+
+#ifdef CONFIG_PMAC_PBOOK
+/*
+ * Save state when going to sleep, restore it afterwards.
+ */
+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. */
+		PMacSilence();
+		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);
+		}
+		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);
+		out_le32(&awacs->byteswap, dmasound.hard.format != AFMT_S16_BE);
+		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) {
+			out_8(macio_base + 0x37, 3);
+		} else if (is_pbook_3400) {
+			feature_set(awacs_node, FEATURE_IOBUS_enable);
+			udelay(10);
+			in_8(latch_base + 0x190);
+		}
+		/* Resume pending sounds. */
+		PMacPlay();
+	}
+	return PBOOK_SLEEP_OK;
+}
+#endif /* CONFIG_PMAC_PBOOK */
+
+
+/* All the burgundy functions: */
+
+/* Waits for busy flag to clear */
+inline static void
+awacs_burgundy_busy_wait(void)
+{
+	while (in_le32(&awacs->codec_ctrl) & MASK_NEWECMD)
+		;
+}
+
+inline static void
+awacs_burgundy_extend_wait(void)
+{
+	while (!(in_le32(&awacs->codec_stat) & MASK_EXTEND))
+		;
+	while (in_le32(&awacs->codec_stat) & MASK_EXTEND)
+		;
+}
+
+static void
+awacs_burgundy_wcw(unsigned addr, unsigned val)
+{
+	out_le32(&awacs->codec_ctrl, addr + 0x200c00 + (val & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200d00 +((val>>8) & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200e00 +((val>>16) & 0xff));
+	awacs_burgundy_busy_wait();
+	out_le32(&awacs->codec_ctrl, addr + 0x200f00 +((val>>24) & 0xff));
+	awacs_burgundy_busy_wait();
+}
+
+static unsigned
+awacs_burgundy_rcw(unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	/* should have timeouts here */
+	save_flags(flags); cli();
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100000);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += (in_le32(&awacs->codec_stat) >> 4) & 0xff;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100100);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<8;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100200);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<16;
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100300);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += ((in_le32(&awacs->codec_stat)>>4) & 0xff) <<24;
+
+	restore_flags(flags);
+
+	return val;
+}
+
+
+static void
+awacs_burgundy_wcb(unsigned addr, unsigned val)
+{
+	out_le32(&awacs->codec_ctrl, addr + 0x300000 + (val & 0xff));
+	awacs_burgundy_busy_wait();
+}
+
+static unsigned
+awacs_burgundy_rcb(unsigned addr)
+{
+	unsigned val = 0;
+	unsigned long flags;
+
+	/* should have timeouts here */
+	save_flags(flags); cli();
+
+	out_le32(&awacs->codec_ctrl, addr + 0x100000);
+	awacs_burgundy_busy_wait();
+	awacs_burgundy_extend_wait();
+	val += (in_le32(&awacs->codec_stat) >> 4) & 0xff;
+
+	restore_flags(flags);
+
+	return val;
+}
+
+static int
+awacs_burgundy_check(void)
+{
+	/* Checks to see the chip is alive and kicking */
+	int error = in_le32(&awacs->codec_ctrl) & MASK_ERRCODE;
+
+	return error == 0xf0000;
+}
+
+static int
+awacs_burgundy_init(void)
+{
+	if (awacs_burgundy_check()) {
+		printk(KERN_WARNING "AWACS: disabled by MacOS :-(\n");
+		return 1;
+	}
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_OUTPUTENABLES,
+			   DEF_BURGUNDY_OUTPUTENABLES);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+			   DEF_BURGUNDY_MORE_OUTPUTENABLES);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_OUTPUTSELECTS,
+			   DEF_BURGUNDY_OUTPUTSELECTS);
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL21,
+			   DEF_BURGUNDY_INPSEL21);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_INPSEL3,
+			   DEF_BURGUNDY_INPSEL3);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINCD,
+			   DEF_BURGUNDY_GAINCD);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINLINE,
+			   DEF_BURGUNDY_GAINLINE);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMIC,
+			   DEF_BURGUNDY_GAINMIC);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_GAINMODEM,
+			   DEF_BURGUNDY_GAINMODEM);
+
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER,
+			   DEF_BURGUNDY_ATTENSPEAKER);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENLINEOUT,
+			   DEF_BURGUNDY_ATTENLINEOUT);
+	awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENHP,
+			   DEF_BURGUNDY_ATTENHP);
+
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_MASTER_VOLUME,
+			   DEF_BURGUNDY_MASTER_VOLUME);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLCD,
+			   DEF_BURGUNDY_VOLCD);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLLINE,
+			   DEF_BURGUNDY_VOLLINE);
+	awacs_burgundy_wcw(MASK_ADDR_BURGUNDY_VOLMIC,
+			   DEF_BURGUNDY_VOLMIC);
+	return 0;
+}
+
+static void
+awacs_burgundy_write_volume(unsigned address, int volume)
+{
+	int hardvolume,lvolume,rvolume;
+
+	lvolume = (volume & 0xff) ? (volume & 0xff) + 155 : 0;
+	rvolume = ((volume >>8)&0xff) ? ((volume >> 8)&0xff ) + 155 : 0;
+
+	hardvolume = lvolume + (rvolume << 16);
+
+	awacs_burgundy_wcw(address, hardvolume);
+}
+
+static int
+awacs_burgundy_read_volume(unsigned address)
+{
+	int softvolume,wvolume;
+
+	wvolume = awacs_burgundy_rcw(address);
+
+	softvolume = (wvolume & 0xff) - 155;
+	softvolume += (((wvolume >> 16) & 0xff) - 155)<<8;
+
+	return softvolume > 0 ? softvolume : 0;
+}
+
+
+
+
+static int
+awacs_burgundy_read_mvolume(unsigned address)
+{
+	int lvolume,rvolume,wvolume;
+
+	wvolume = awacs_burgundy_rcw(address);
+
+	wvolume &= 0xffff;
+
+	rvolume = (wvolume & 0xff) - 155;
+	lvolume = ((wvolume & 0xff00)>>8) - 155;
+
+	return lvolume + (rvolume << 8);
+}
+
+
+static void
+awacs_burgundy_write_mvolume(unsigned address, int volume)
+{
+	int lvolume,rvolume,hardvolume;
+
+	lvolume = (volume &0xff) ? (volume & 0xff) + 155 :0;
+	rvolume = ((volume >>8) & 0xff) ? (volume >> 8) + 155 :0;
+
+	hardvolume = lvolume + (rvolume << 8);
+	hardvolume += (hardvolume << 16);
+
+	awacs_burgundy_wcw(address, hardvolume);
+}
+
+/* End burgundy functions */
+
+
+
+
+
+/* Turn on sound output, needed on G3 desktop powermacs */
+static void
+awacs_enable_amp(int spkr_vol)
+{
+	struct adb_request req;
+
+	awacs_spkr_vol = spkr_vol;
+	if (sys_ctrler != SYS_CTRLER_CUDA)
+		return;
+
+	/* turn on headphones */
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 4, 0);
+	while (!req.complete) cuda_poll();
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 6, 0);
+	while (!req.complete) cuda_poll();
+
+	/* turn on speaker */
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 3, (100 - (spkr_vol & 0xff)) * 32 / 100);
+	while (!req.complete) cuda_poll();
+	cuda_request(&req, NULL, 5, CUDA_PACKET, CUDA_GET_SET_IIC,
+		     0x8a, 5, (100 - ((spkr_vol >> 8) & 0xff)) * 32 / 100);
+	while (!req.complete) cuda_poll();
+
+	cuda_request(&req, NULL, 5, CUDA_PACKET,
+		     CUDA_GET_SET_IIC, 0x8a, 1, 0x29);
+	while (!req.complete) cuda_poll();
+}
+
+
+/*** Mid level stuff *********************************************************/
+
+
+/*
+ * /dev/mixer abstraction
+ */
+
+static int PMacMixerIoctl(u_int cmd, u_long arg)
+{
+	int data;
+	/* Different IOCTLS for burgundy*/
+	if (awacs_revision < AWACS_BURGUNDY) {
+		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)
+				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_reg[1] |= MASK_LOOPTHRU;
+			awacs_write(awacs_reg[0] | MASK_ADDR0);
+			awacs_write(awacs_reg[1] | MASK_ADDR1);
+			return IOCTL_OUT(arg, data);
+		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);
+		case SOUND_MIXER_WRITE_VOLUME:
+			IOCTL_IN(arg, data);
+			return IOCTL_OUT(arg, dmasound_set_volume(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);
+		case SOUND_MIXER_WRITE_SPEAKER:
+			IOCTL_IN(arg, data);
+			if (awacs_revision == 3
+			    && sys_ctrler == SYS_CTRLER_CUDA)
+				awacs_enable_amp(data);
+			else
+				data = awacs_volume_setter(data, 4, MASK_CMUTE, 6);
+			return IOCTL_OUT(arg, data);
+		case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
+			IOCTL_IN(arg, data);
+			beep_volume = data & 0xff;
+			/* fall through */
+		case SOUND_MIXER_READ_ALTPCM:
+			return IOCTL_OUT(arg, beep_volume);
+		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 */
+		case SOUND_MIXER_READ_LINE:
+			data = (awacs_reg[0] & MASK_MUX_AUDIN)? 100: 0;
+			return IOCTL_OUT(arg, data);
+		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 */
+		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);
+		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 */
+		case SOUND_MIXER_READ_CD:
+			data = (awacs_reg[0] & MASK_MUX_CD)? 100: 0;
+			return IOCTL_OUT(arg, data);
+		case SOUND_MIXER_WRITE_RECLEV:
+			IOCTL_IN(arg, data);
+			data = awacs_volume_setter(data, 0, 0, 4);
+			return IOCTL_OUT(arg, data);
+		case SOUND_MIXER_READ_RECLEV:
+			data = awacs_get_volume(awacs_reg[0], 4);
+			return IOCTL_OUT(arg, data);
+		}
+	} else {
+		/* We are, we are, we are... Burgundy or better */
+		switch(cmd) {
+		case SOUND_MIXER_READ_DEVMASK:
+			data = SOUND_MASK_VOLUME | SOUND_MASK_CD |
+				SOUND_MASK_LINE | SOUND_MASK_MIC |
+				SOUND_MASK_SPEAKER | SOUND_MASK_ALTPCM;
+			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;
+			return IOCTL_OUT(arg, data);
+		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);
+			return IOCTL_OUT(arg, data);
+		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);
+		case SOUND_MIXER_READ_CAPS:
+			return IOCTL_OUT(arg, 0);
+		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));
+		case SOUND_MIXER_WRITE_SPEAKER:
+			IOCTL_IN(arg, data);
+
+			if (!(data & 0xff)) {
+			  /* Mute the left speaker */
+			  awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					     awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x2);
+			} else {
+			  /* Unmute the left speaker */
+			  awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					     awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x2);
+			}
+			if (!(data & 0xff00)) {
+			  /* Mute the right speaker */
+			  awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					     awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) & ~0x4);
+			} else {
+			  /* Unmute the right speaker */
+			  awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES,
+					     awacs_burgundy_rcb(MASK_ADDR_BURGUNDY_MORE_OUTPUTENABLES) | 0x4);
+			}
+
+			data = (((data&0xff)*16)/100 > 0xf ? 0xf :
+				(((data&0xff)*16)/100)) +
+				 ((((data>>8)*16)/100 > 0xf ? 0xf :
+				((((data>>8)*16)/100)))<<4);
+
+			awacs_burgundy_wcb(MASK_ADDR_BURGUNDY_ATTENSPEAKER, ~data);
+			/* Fall through */
+		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);
+		case SOUND_MIXER_WRITE_ALTPCM:	/* really bell volume */
+			IOCTL_IN(arg, data);
+			beep_volume = data & 0xff;
+			/* fall through */
+		case SOUND_MIXER_READ_ALTPCM:
+			return IOCTL_OUT(arg, beep_volume);
+		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);
+		case SOUND_MIXER_WRITE_MIC:
+			IOCTL_IN(arg, data);
+			/* Mic is mono device */
+			data = (data << 8) + (data << 24);
+			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 <<= 24;
+			return IOCTL_OUT(arg, data);
+		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);
+		case SOUND_MIXER_WRITE_RECLEV:
+			IOCTL_IN(arg, data);
+			data = awacs_volume_setter(data, 0, 0, 4);
+			return IOCTL_OUT(arg, data);
+		case SOUND_MIXER_READ_RECLEV:
+			data = awacs_get_volume(awacs_reg[0], 4);
+			return IOCTL_OUT(arg, data);
+		case SOUND_MIXER_OUTMASK:
+			break;
+		case SOUND_MIXER_OUTSRC:
+			break;
+		}
+	}
+	return -EINVAL;
+}
+
+
+static void PMacWriteSqSetup(void)
+{
+	int i;
+	volatile struct dbdma_cmd *cp;
+
+	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) {
+		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);
+	out_le32(&awacs_txdma->cmdptr, virt_to_bus(awacs_tx_cmds));
+}
+
+static void PMacReadSqSetup(void)
+{
+	int i;
+	volatile struct dbdma_cmd *cp;
+
+	cp = awacs_rx_cmds;
+	memset((void *)cp, 0, (read_sq.numBufs+1) * sizeof(struct dbdma_cmd));
+
+	/* Set dma buffers up in a loop */
+	for (i = 0; i < read_sq.numBufs; 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);
+		st_le16(&cp->xfer_status, 0);
+	}
+
+	/* The next two lines make the thing loop around.
+	*/
+	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);
+	out_le32(&awacs_rxdma->cmdptr, virt_to_bus(awacs_rx_cmds));
+
+}
+
+static void PMacAbortRead(void)
+{
+	int i;
+	volatile struct dbdma_cmd *cp;
+
+	cp = awacs_rx_cmds;
+	for (i = 0; i < read_sq.numBufs; i++,cp++)
+		st_le16(&cp->command, DBDMA_STOP);
+	/*
+	 * We should probably wait for the thing to stop before we
+	 * release the memory
+	 */
+}
+
+
+/*** Machine definitions *****************************************************/
+
+
+static MACHINE machPMac = {
+	name:		awacs_name,
+	name2:		"AWACS",
+	open:		PMacOpen,
+	release:	PMacRelease,
+	dma_alloc:	PMacAlloc,
+	dma_free:	PMacFree,
+	irqinit:	PMacIrqInit,
+#ifdef MODULE
+	irqcleanup:	PMacIrqCleanup,
+#endif /* MODULE */
+	init:		PMacInit,
+	silence:	PMacSilence,
+	setFormat:	PMacSetFormat,
+	setVolume:	PMacSetVolume,
+	play:		PMacPlay,
+	record:		PMacRecord,
+	mixer_ioctl:	PMacMixerIoctl,
+	write_sq_setup:	PMacWriteSqSetup,
+	read_sq_setup:	PMacReadSqSetup,
+	abort_read:	PMacAbortRead,
+	min_dsp_speed:	8000
+};
+
+
+/*** Config & Setup **********************************************************/
+
+
+int __init dmasound_awacs_init(void)
+{
+	struct device_node *np;
+
+	if (_machine != _MACH_Pmac)
+		return -ENODEV;
+
+	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;
+
+			/* 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;
+						}
+					}
+				}
+			} 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;
+
+		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;
+		}
+		awacs_node = np;
+#ifdef CONFIG_PMAC_PBOOK
+		if (machine_is_compatible("PowerBook1,1")
+		    || machine_is_compatible("AAPL,PowerBook1998")) {
+			feature_set(np, FEATURE_Sound_CLK_enable);
+			feature_set(np, FEATURE_Sound_power);
+			/* Shorter delay will not work */
+			mdelay(1000);
+		}
+#endif
+		awacs_tx_cmds = (volatile struct dbdma_cmd *)
+			DBDMA_ALIGN(awacs_tx_cmd_space);
+
+
+		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");
+		}
+		awacs_rx_cmds = (volatile struct dbdma_cmd *)
+		  DBDMA_ALIGN(awacs_rx_cmd_space);
+
+
+
+		awacs_reg[0] = MASK_MUX_CD;
+		awacs_reg[1] = MASK_LOOPTHRU | 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);
+		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);
+
+		/* 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);
+			}
+		}
+		if (awacs_revision >= AWACS_BURGUNDY)
+			awacs_burgundy_init();
+
+		/* 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);
+#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;
+				}
+			}
+			/* enable CD sound input */
+			if (macio_base)
+				out_8(macio_base + 0x37, 3);
+		}
+		sprintf(awacs_name, "PowerMac (AWACS rev %d) ",
+			awacs_revision);
+		return dmasound_init();
+	}
+	return -ENODEV;
+}
+
+static void __exit dmasound_awacs_cleanup(void)
+{
+	dmasound_deinit();
+}
+
+module_init(dmasound_awacs_init);
+module_exit(dmasound_awacs_cleanup);

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