[alsa-devel] [RFC] SGI O2 MACE audio ALSA module

TJ tj.trevelyan at gmail.com
Mon Jul 9 23:07:31 CEST 2007


Hi,

Here is another patch that moves towards addressing some of the issues
raised about the last patch. Also I had trouble mailing the last patch
to the linux mips list, mainly because I attached the patch as opposed
to putting inline, so this time its pasted in. (I guess we'll find out
if thats safe with Gmail).

This patch was built against Linux-mips.org 2.6.21.6

On 03/07/07, Takashi Iwai <tiwai at suse.de> wrote:
> At Mon, 2 Jul 2007 12:27:58 +0100,
> The patch includes old typdefed structs which were already removed
> from the upstream, such as, snd_card_t.  Please replace them
> appropriately.  You can find the replacement in
> include/sound/typedefs.h (in the old kernel tree).

Fixed, does not use typedefs for ALSA structs.

> Similarly, I'd recommend to avoid typedefs in your own code, too.

I still have two typedef structs of my own. However these aren't used
outside the single C source file.

>
> Other things I noticed through a quick glance:
>
> - Follow the standard coding style, e.g. 80 chars in a line, don't put
>   if-block in a single line, etc.

I tidily hard wrapped lines that i spotted over 80. How many people
still using 80 column terminals? Maybe 132 would be a better choice
these days?

Is something like:
if (0 > err) goto ERROR_EXIT;
really that bad? In two lines it isn't much clearer and just user more space.

> - Avoid non-ASCII letters, especially outside the comments

Outside of the umlaut in my own name, I think the only place I had
UTF-8 chars was in ad1843_dump_reg strings, I have fixed this, please
let me know if there are more.

> - You don't need *_irqsave() in get/put callbacks of the control API.
>   It's always schedulable, so, spin_lock_irq() suffices.

Done

> - ad1843_lock could be better implemented with mutex if you have long
>   delays inside the spinlock (except for the calls from irq handler)

Doing this would be something new to me, so it's on my research list/todo list.

> - please remove uneeded debug printks.  If they are useful, keep it in
>   another macro form.

Substituted for snd_printd (which requires CONFIG_SND_DEBUG), but
still with no indentation so I can find and delete them in future.

> Could you fix these and repost?
Yes =)

It still plays audio too fast.
Please let me know about your experience with it, and
tips/sugestions/bug fixes/etc you have.

Regards,

Thorben

Patch can also be found here:

http://www.linux-mips.org/pub/linux/mips/people/trevelyan/mace_audio/mace_audio-20070709.patch

-------------------------------------

--- linux-2.6.21.6-b/include/sound/ad1843.h	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.21.6/include/sound/ad1843.h	2007-07-09 19:14:18.000000000 +0100
@@ -0,0 +1,48 @@
+/*
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License.  See the file "COPYING" in the main directory of this archive
+ * for more details.
+ *
+ * Copyright 2003 Vivien Chappelier <vivien.chappelier at linux-mips.org>
+ */
+
+#ifndef __SOUND_AD1843_H
+#define __SOUND_AD1843_H
+
+typedef struct {
+  void *chip;
+  int (* read)(void *chip, int reg);
+  int (* write)(void *chip, int reg, int val);
+} ad1843_t;
+
+#define AD1843_GAIN_RECLEV 0
+#define AD1843_GAIN_LINE   1
+#define AD1843_GAIN_CD     2
+#define AD1843_GAIN_MIC    3
+#define AD1843_GAIN_PCM_0  4
+#define AD1843_GAIN_PCM_1  5
+#define AD1843_GAIN_SIZE   AD1843_GAIN_PCM_1+1
+
+int ad1843_get_gain(ad1843_t *ad1843, int id);
+int ad1843_set_gain(ad1843_t *ad1843, int id, int newval);
+int ad1843_get_recsrc(ad1843_t *ad1843);
+void ad1843_set_resample_mode(ad1843_t *ad1843, int onoff);
+int ad1843_set_recsrc(ad1843_t *ad1843, int newsrc);
+int ad1843_get_outsrc(ad1843_t *ad1843);
+int ad1843_set_outsrc(ad1843_t *ad1843, int mask);
+void ad1843_setup_dac(ad1843_t *ad1843,
+                      unsigned int id,
+                      unsigned int framerate,
+                      snd_pcm_format_t fmt,
+                      unsigned int channels);
+void ad1843_shutdown_dac(ad1843_t *ad1843,
+                         unsigned int id);
+void ad1843_setup_adc(ad1843_t *ad1843,
+                      unsigned int framerate,
+                      snd_pcm_format_t fmt,
+                      unsigned int channels);
+void ad1843_shutdown_adc(ad1843_t *ad1843);
+int ad1843_init(ad1843_t *ad1843);
+char *ad1843_dump_reg(ad1843_t *ad1843, int reg);
+
+#endif /* __SOUND_AD1843_H */
--- linux-2.6.21.6-b/sound/mips/ad1843.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.21.6/sound/mips/ad1843.c	2007-07-09 20:09:15.000000000 +0100
@@ -0,0 +1,911 @@
+/*
+ *   AD1843 low level driver
+ *
+ *   Copyright 2003 Vivien Chappelier <vivien.chappelier at linux-mips.org>
+ *
+ *   inspired from vwsnd.c (SGI VW audio driver)
+ *     Copyright 1999 Silicon Graphics, Inc.  All rights reserved.
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+
+#include <linux/slab.h>
+
+#include <linux/init.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/vmalloc.h>
+#include <sound/driver.h>
+#include <sound/core.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/ad1843.h>
+
+// find and remove any OSS stuff
+#include <linux/soundcard.h>
+
+/*
+ * AD1843 bitfield definitions.  All are named as in the AD1843 data
+ * sheet, with ad1843_ prepended and individual bit numbers removed.
+ *
+ * E.g., bits LSS0 through LSS2 become ad1843_LSS.
+ *
+ * Only the bitfields we need are defined.
+ */
+
+typedef struct ad1843_bitfield {
+       char reg;
+       char lo_bit;
+       char nbits;
+} ad1843_bitfield_t;
+
+static const ad1843_bitfield_t
+	ad1843_PDNO   = {  0, 14,  1 }, /* Converter Power-Down Flag */
+	ad1843_INIT   = {  0, 15,  1 }, /* Clock Initialization Flag */
+	ad1843_RIG    = {  2,  0,  4 }, /* Right ADC Input Gain */
+	ad1843_RMGE   = {  2,  4,  1 }, /* Right ADC Mic Gain Enable */
+	ad1843_RSS    = {  2,  5,  3 }, /* Right ADC Source Select */
+	ad1843_LIG    = {  2,  8,  4 }, /* Left ADC Input Gain */
+	ad1843_LMGE   = {  2, 12,  1 }, /* Left ADC Mic Gain Enable */
+	ad1843_LSS    = {  2, 13,  3 }, /* Left ADC Source Select */
+	ad1843_RX1M   = {  4,  0,  5 }, /* Right Aux 1 Mix Gain/Atten */
+	ad1843_RX1MM  = {  4,  7,  1 }, /* Right Aux 1 Mix Mute */
+	ad1843_LX1M   = {  4,  8,  5 }, /* Left Aux 1 Mix Gain/Atten */
+	ad1843_LX1MM  = {  4, 15,  1 }, /* Left Aux 1 Mix Mute */
+	ad1843_RX2M   = {  5,  0,  5 }, /* Right Aux 2 Mix Gain/Atten */
+	ad1843_RX2MM  = {  5,  7,  1 }, /* Right Aux 2 Mix Mute */
+	ad1843_LX2M   = {  5,  8,  5 }, /* Left Aux 2 Mix Gain/Atten */
+	ad1843_LX2MM  = {  5, 15,  1 }, /* Left Aux 2 Mix Mute */
+	ad1843_RMCM   = {  7,  0,  5 }, /* Right Mic Mix Gain/Atten */
+	ad1843_RMCMM  = {  7,  7,  1 }, /* Right Mic Mix Mute */
+	ad1843_LMCM   = {  7,  8,  5 }, /* Left Mic Mix Gain/Atten */
+	ad1843_LMCMM  = {  7, 15,  1 }, /* Left Mic Mix Mute */
+	ad1843_HPOS   = {  8,  4,  1 }, /* Headphone Output Voltage Swing */
+	ad1843_HPOM   = {  8,  5,  1 }, /* Headphone Output Mute */
+	ad1843_MPOM   = {  8,  6,  1 }, /* Mono Output Mute */
+	ad1843_RDA1G  = {  9,  0,  6 }, /* Right DAC1 Analog/Digital Gain */
+	ad1843_RDA1GM = {  9,  7,  1 }, /* Right DAC1 Analog Mute */
+	ad1843_LDA1G  = {  9,  8,  6 }, /* Left DAC1 Analog/Digital Gain */
+	ad1843_LDA1GM = {  9, 15,  1 }, /* Left DAC1 Analog Mute */
+	ad1843_RDA2G  = { 10,  0,  6 }, /* Right DAC2 Analog/Digital Gain */
+	ad1843_RDA2GM = { 10,  7,  1 }, /* Right DAC2 Analog Mute */
+	ad1843_LDA2G  = { 10,  8,  6 }, /* Left DAC2 Analog/Digital Gain */
+	ad1843_LDA2GM = { 10, 15,  1 }, /* Left DAC2 Analog Mute */
+	ad1843_RDA1AM = { 11,  7,  1 }, /* Right DAC1 Digital Mute */
+	ad1843_LDA1AM = { 11, 15,  1 }, /* Left DAC1 Digital Mute */
+	ad1843_RDA2AM = { 12,  7,  1 }, /* Right DAC1 Digital Mute */
+	ad1843_LDA2AM = { 12, 15,  1 }, /* Left DAC1 Digital Mute */
+	ad1843_ADLC   = { 15,  0,  2 }, /* ADC Left Sample Rate Source */
+	ad1843_ADRC   = { 15,  2,  2 }, /* ADC Right Sample Rate Source */
+	ad1843_DA1C   = { 15,  8,  2 }, /* DAC1 Sample Rate Source */
+	ad1843_DA2C   = { 15, 10,  2 }, /* DAC2 Sample Rate Source */
+	ad1843_C1M    = { 16,  0,  8 }, /* Clock 1 Rate Modifier */
+	ad1843_C1C128 = { 16, 10,  1 }, /* Clock 1 CONV1 Freq select */
+	ad1843_C1C    = { 17,  0, 16 }, /* Clock 1 Sample Rate Select */
+	ad1843_C2M    = { 19,  0,  8 }, /* Clock 2 Rate Modifier */
+	ad1843_C2C    = { 20,  0, 16 }, /* Clock 2 Sample Rate Select */
+	ad1843_C3M    = { 22,  0,  8 }, /* Clock 3 Rate Modifier */
+	ad1843_C3C    = { 23,  0, 16 }, /* Clock 3 Sample Rate Select */
+	ad1843_DAADL  = { 25,  4,  2 }, /* Digital ADC Left Source Select */
+	ad1843_DAADR  = { 25,  6,  2 }, /* Digital ADC Right Source Select */
+	ad1843_DRSFLT = { 25, 15,  1 }, /* Digital Reampler Filter Mode */
+	ad1843_ADLF   = { 26,  0,  2 }, /* ADC Left Channel Data Format */
+	ad1843_ADRF   = { 26,  2,  2 }, /* ADC Right Channel Data Format */
+	ad1843_ADTLK  = { 26,  4,  1 }, /* ADC Transmit Lock Mode Select */
+	ad1843_FRS    = { 26,  6,  1 }, /* Frame size select */
+	ad1843_SCF    = { 26,  7,  1 }, /* SCLK Frequency Select */
+	ad1843_DA1F   = { 26,  8,  2 }, /* DAC1 Data Format Select */
+	ad1843_DA2F   = { 26, 10,  2 }, /* DAC2 Data Format Select */
+	ad1843_DA1SM  = { 26, 14,  1 }, /* DAC1 Stereo/Mono Mode Select */
+	ad1843_DA2SM  = { 26, 15,  1 }, /* DAC2 Stereo/Mono Mode Select */
+	ad1843_ADLEN  = { 27,  0,  1 }, /* ADC Left Channel Enable */
+	ad1843_ADREN  = { 27,  1,  1 }, /* ADC Right Channel Enable */
+	ad1843_AAMEN  = { 27,  4,  1 }, /* Analog to Analog Mix Enable */
+	ad1843_ANAEN  = { 27,  7,  1 }, /* Analog Channel Enable */
+	ad1843_DA1EN  = { 27,  8,  1 }, /* DAC1 Enable */
+	ad1843_DA2EN  = { 27,  9,  1 }, /* DAC2 Enable */
+	ad1843_ENBT1  = { 28,  2,  1 }, /* BIT1 Pin Enable */
+	ad1843_ENCV1  = { 28,  3,  1 }, /* CONV1 Pin Enable */
+	ad1843_ENBT2  = { 28,  4,  1 }, /* BIT2 Pin Enable */
+	ad1843_ENCV2  = { 28,  5,  1 }, /* CONV2 Pin Enable */
+	ad1843_ENBT3  = { 28,  6,  1 }, /* BIT3 Pin Enable */
+	ad1843_ENCV3  = { 28,  7,  1 }, /* CONV3 Pin Enable */
+	ad1843_ENCLKO = { 28, 10,  1 }, /* ClockOut Pin Enable */
+	ad1843_C1EN   = { 28, 11,  1 }, /* Clock Generator 1 Enable */
+	ad1843_C2EN   = { 28, 12,  1 }, /* Clock Generator 2 Enable */
+	ad1843_C3EN   = { 28, 13,  1 }, /* Clock Generator 3 Enable */
+	ad1843_ACEN   = { 28, 14,  1 }, /* Autocalibration enable */
+	ad1843_PDNI   = { 28, 15,  1 }; /* Converter Power Down */
+
+
+/*
+ * The various registers of the AD1843 use three different formats for
+ * specifying gain.  The ad1843_gain structure parameterizes the
+ * formats.
+ */
+
+typedef struct ad1843_gain {
+       int     negative;               /* nonzero if gain is negative. */
+       const ad1843_bitfield_t *lfield;
+       const ad1843_bitfield_t *rfield;
+} ad1843_gain_t;
+
+const ad1843_gain_t ad1843_gain_RECLEV
+                               = { 0, &ad1843_LIG,   &ad1843_RIG };
+const ad1843_gain_t ad1843_gain_LINE
+                               = { 1, &ad1843_LX1M,  &ad1843_RX1M };
+const ad1843_gain_t ad1843_gain_CD
+                               = { 1, &ad1843_LX2M,  &ad1843_RX2M };
+const ad1843_gain_t ad1843_gain_MIC
+                               = { 1, &ad1843_LMCM,  &ad1843_RMCM };
+const ad1843_gain_t ad1843_gain_PCM_0
+                               = { 1, &ad1843_LDA1G, &ad1843_RDA1G };
+const ad1843_gain_t ad1843_gain_PCM_1
+                               = { 1, &ad1843_LDA2G, &ad1843_RDA2G };
+
+const ad1843_gain_t *ad1843_gain[AD1843_GAIN_SIZE] = {
+  &ad1843_gain_RECLEV,
+  &ad1843_gain_LINE,
+  &ad1843_gain_CD,
+  &ad1843_gain_MIC,
+  &ad1843_gain_PCM_0,
+  &ad1843_gain_PCM_1,
+};
+
+/* read the current value of an AD1843 bitfield. */
+
+static int ad1843_read_bits(ad1843_t *ad1843, const ad1843_bitfield_t *field)
+{
+       int w = ad1843->read(ad1843->chip, field->reg);
+       int val = w >> field->lo_bit & ((1 << field->nbits) - 1);
+
+       return val;
+}
+
+/*
+ * write a new value to an AD1843 bitfield and return the old value.
+ */
+
+static int ad1843_write_bits(ad1843_t *ad1843,
+                            const ad1843_bitfield_t *field,
+                            int newval)
+{
+       int w = ad1843->read(ad1843->chip, field->reg);
+       int mask = ((1 << field->nbits) - 1) << field->lo_bit;
+       int oldval = (w & mask) >> field->lo_bit;
+       int newbits = (newval << field->lo_bit) & mask;
+       w = (w & ~mask) | newbits;
+       (void) ad1843->write(ad1843->chip, field->reg, w);
+
+       return oldval;
+}
+
+/*
+ * ad1843_read_multi reads multiple bitfields from the same AD1843
+ * register.  It uses a single read cycle to do it.  (Reading the
+ * ad1843 requires 256 bit times at 12.288 MHz, or nearly 20
+ * microseconds.)
+ *
+ * Called like this.
+ *
+ *  ad1843_read_multi(ad1843, nfields,
+ *                   &ad1843_FIELD1, &val1,
+ *                   &ad1843_FIELD2, &val2, ...);
+ */
+
+static void ad1843_read_multi(ad1843_t *ad1843, int argcount, ...)
+{
+       va_list ap;
+       const ad1843_bitfield_t *fp;
+       int w = 0, mask, *value, reg = -1;
+
+       va_start(ap, argcount);
+       while (--argcount >= 0) {
+               fp = va_arg(ap, const ad1843_bitfield_t *);
+               value = va_arg(ap, int *);
+
+               if (reg == -1) {
+                       reg = fp->reg;
+                       w = ad1843->read(ad1843->chip, reg);
+               }
+
+               mask = (1 << fp->nbits) - 1;
+               *value = w >> fp->lo_bit & mask;
+       }
+       va_end(ap);
+
+}
+
+/*
+ * ad1843_write_multi stores multiple bitfields into the same AD1843
+ * register.  It uses one read and one write cycle to do it.
+ *
+ * Called like this.
+ *
+ *  ad1843_write_multi(ad1843, nfields,
+ *                    &ad1843_FIELD1, val1,
+ *                    &ad1843_FIELF2, val2, ...);
+ */
+
+static void ad1843_write_multi(ad1843_t *ad1843, int argcount, ...)
+{
+       va_list ap;
+       int reg;
+       const ad1843_bitfield_t *fp;
+       int value;
+       int w, m, mask, bits;
+
+       mask = 0;
+       bits = 0;
+       reg = -1;
+
+       va_start(ap, argcount);
+       while (--argcount >= 0) {
+               fp = va_arg(ap, const ad1843_bitfield_t *);
+               value = va_arg(ap, int);
+               if (reg == -1)
+                       reg = fp->reg;
+
+               m = ((1 << fp->nbits) - 1) << fp->lo_bit;
+               mask |= m;
+               bits |= (value << fp->lo_bit) & m;
+       }
+       va_end(ap);
+
+       if (~mask & 0xFFFF)
+               w = ad1843->read(ad1843->chip, reg);
+       else
+               w = 0;
+       w = (w & ~mask) | bits;
+       (void) ad1843->write(ad1843->chip, reg, w);
+}
+
+/*
+ * ad1843_get_gain reads the specified register and extracts the gain value
+ * using the supplied gain type.  It returns the gain in OSS format.
+ */
+
+int ad1843_get_gain(ad1843_t *ad1843, int id)
+{
+       int lg, rg;
+        const ad1843_gain_t *gp = ad1843_gain[id];
+       unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+       ad1843_read_multi(ad1843, 2, gp->lfield, &lg, gp->rfield, &rg);
+       if (gp->negative) {
+               lg = mask - lg;
+               rg = mask - rg;
+       }
+       lg = (lg * 100 + (mask >> 1)) / mask;
+       rg = (rg * 100 + (mask >> 1)) / mask;
+       return lg << 0 | rg << 8;
+}
+
+/*
+ * Set an audio channel's gain. Converts from OSS format to AD1843's
+ * format.
+ *
+ * Returns the new gain, which may be lower than the old gain.
+ */
+
+int ad1843_set_gain(ad1843_t *ad1843,
+                    int id,
+                    int newval)
+{
+        const ad1843_gain_t *gp = ad1843_gain[id];
+       unsigned short mask = (1 << gp->lfield->nbits) - 1;
+
+       int lg = newval >> 0 & 0xFF;
+       int rg = newval >> 8;
+       if (lg < 0 || lg > 100 || rg < 0 || rg > 100)
+               return -EINVAL;
+       lg = (lg * mask + (mask >> 1)) / 100;
+       rg = (rg * mask + (mask >> 1)) / 100;
+       if (gp->negative) {
+               lg = mask - lg;
+               rg = mask - rg;
+       }
+       ad1843_write_multi(ad1843, 2, gp->lfield, lg, gp->rfield, rg);
+       return ad1843_get_gain(ad1843, id);
+}
+
+/* Returns the current recording source, in OSS format. */
+
+int ad1843_get_recsrc(ad1843_t *ad1843)
+{
+       int ls = ad1843_read_bits(ad1843, &ad1843_LSS);
+
+       switch (ls) {
+       case 1:
+               return SOUND_MASK_MIC;
+       case 2:
+               return SOUND_MASK_LINE;
+       case 3:
+               return SOUND_MASK_CD;
+       case 6:
+               return SOUND_MASK_PCM;
+       default:
+               return -1;
+       }
+}
+
+/*
+ * Enable/disable digital resample mode in the AD1843.
+ *
+ * The AD1843 requires that ADL, ADR, DA1 and DA2 be powered down
+ * while switching modes.  So we save DA's state, power them down,
+ * switch into/out of resample mode, power them up, and restore state.
+ *
+ * This will cause audible glitches if D/A or A/D is going on, so the
+ * driver disallows that (in mixer_write_ioctl()).
+ *
+ * The open question is, is this worth doing?  I'm leaving it in,
+ * because it's written, but...
+ */
+
+void ad1843_set_resample_mode(ad1843_t *ad1843, int onoff)
+{
+       /* Save DA's mute and gain (addr 9/10 is analog gain/attenuation) */
+       int save_da1 = ad1843->read(ad1843->chip, 9);
+       int save_da2 = ad1843->read(ad1843->chip, 10);
+
+       /* Power down A/D and D/A. */
+       ad1843_write_multi(ad1843, 4,
+                          &ad1843_DA1EN, 0,
+                          &ad1843_DA2EN, 0,
+                          &ad1843_ADLEN, 0,
+                          &ad1843_ADREN, 0);
+
+       /* Switch mode */
+       ad1843_write_bits(ad1843, &ad1843_DRSFLT, onoff);
+
+       /* Power up A/D and D/A. */
+       ad1843_write_multi(ad1843, 3,
+                          &ad1843_DA1EN, 1,
+                          &ad1843_DA2EN, 1,
+                          &ad1843_ADLEN, 1,
+                          &ad1843_ADREN, 1);
+
+       /* Restore DA's mute and gain. */
+       ad1843->write(ad1843->chip, 9, save_da1);
+       ad1843->write(ad1843->chip, 10, save_da2);
+}
+
+/*
+ * Set recording source.  Arg newsrc specifies an OSS channel mask.
+ *
+ * The complication is that when we switch into/out of loopback mode
+ * (i.e., src = SOUND_MASK_PCM), we change the AD1843 into/out of
+ * digital resampling mode.
+ *
+ * Returns newsrc on success, -errno on failure.
+ */
+
+int ad1843_set_recsrc(ad1843_t *ad1843, int newsrc)
+{
+       int bits;
+       int oldbits;
+
+       switch (newsrc) {
+       case SOUND_MASK_PCM:
+               bits = 6;
+               break;
+
+       case SOUND_MASK_MIC:
+               bits = 1;
+               break;
+
+       case SOUND_MASK_LINE:
+               bits = 2;
+               break;
+
+       case SOUND_MASK_CD:
+               bits = 3;
+               break;
+
+       default:
+               return -EINVAL;
+       }
+       oldbits = ad1843_read_bits(ad1843, &ad1843_LSS);
+       if (newsrc == SOUND_MASK_PCM && oldbits != 6) {
+
+               ad1843_set_resample_mode(ad1843, 1);
+               ad1843_write_multi(ad1843, 2,
+                                  &ad1843_DAADL, 2,
+                                  &ad1843_DAADR, 2);
+       } else if (newsrc != SOUND_MASK_PCM && oldbits == 6) {
+
+               ad1843_set_resample_mode(ad1843, 0);
+               ad1843_write_multi(ad1843, 2,
+                                  &ad1843_DAADL, 0,
+                                  &ad1843_DAADR, 0);
+       }
+       ad1843_write_multi(ad1843, 2, &ad1843_LSS, bits, &ad1843_RSS, bits);
+       return newsrc;
+}
+
+/*
+ * Return current output sources, in OSS format.
+ */
+
+int ad1843_get_outsrc(ad1843_t *ad1843)
+{
+       int pcm, line, mic, cd;
+
+       pcm  = ad1843_read_bits(ad1843, &ad1843_LDA1GM) ? 0 : SOUND_MASK_PCM;
+       line = ad1843_read_bits(ad1843, &ad1843_LX1MM)  ? 0 : SOUND_MASK_LINE;
+       cd   = ad1843_read_bits(ad1843, &ad1843_LX2MM)  ? 0 : SOUND_MASK_CD;
+       mic  = ad1843_read_bits(ad1843, &ad1843_LMCMM)  ? 0 : SOUND_MASK_MIC;
+
+       return pcm | line | cd | mic;
+}
+
+/*
+ * Set output sources.  Arg is a mask of active sources in OSS format.
+ *
+ * Returns source mask on success, -errno on failure.
+ */
+
+int ad1843_set_outsrc(ad1843_t *ad1843, int mask)
+{
+       int pcm, line, mic, cd;
+
+       if (mask & ~(SOUND_MASK_PCM | SOUND_MASK_LINE |
+                    SOUND_MASK_CD | SOUND_MASK_MIC))
+               return -EINVAL;
+       pcm  = (mask & SOUND_MASK_PCM)  ? 0 : 1;
+       line = (mask & SOUND_MASK_LINE) ? 0 : 1;
+       mic  = (mask & SOUND_MASK_MIC)  ? 0 : 1;
+       cd   = (mask & SOUND_MASK_CD)   ? 0 : 1;
+
+       ad1843_write_multi(ad1843, 2, &ad1843_LDA1GM, pcm, &ad1843_RDA1GM, pcm);
+       ad1843_write_multi(ad1843, 2, &ad1843_LX1MM, line, &ad1843_RX1MM, line);
+       ad1843_write_multi(ad1843, 2, &ad1843_LX2MM, cd,   &ad1843_RX2MM, cd);
+       ad1843_write_multi(ad1843, 2, &ad1843_LMCMM, mic,  &ad1843_RMCMM, mic);
+
+       return mask;
+}
+
+/* Setup ad1843 for D/A conversion. */
+
+void ad1843_setup_dac(ad1843_t *ad1843,
+                      unsigned int id,
+                      unsigned int framerate,
+                      snd_pcm_format_t fmt,
+                      unsigned int channels)
+{
+       int ad_fmt = 1, ad_mode = 0;
+
+       switch (fmt) {
+       case SNDRV_PCM_FORMAT_S8:       ad_fmt = 0; break;
+       case SNDRV_PCM_FORMAT_U8:       ad_fmt = 0; break;
+       case SNDRV_PCM_FORMAT_S16_BE:   ad_fmt = 1; break;
+       case SNDRV_PCM_FORMAT_S24_BE:   ad_fmt = 1; break;
+       case SNDRV_PCM_FORMAT_MU_LAW:   ad_fmt = 2; break;
+       case SNDRV_PCM_FORMAT_A_LAW:    ad_fmt = 3; break;
+       default: printk("errk!\n"); break;
+       }
+
+       switch (channels) {
+       case 2:                 ad_mode = 0; break;
+       case 1:                 ad_mode = 1; break;
+       default: printk("errk!\n"); break;
+       }
+
+        if(1 == id) {
+		ad1843_write_bits(ad1843, &ad1843_C1C, framerate);
+		//ad1843_write_bits(ad1843, &ad1843_C1M, 0x00);
+		//ad1843_write_bits(ad1843, &ad1843_C1C128, 1);
+		//ad1843_write_bits(ad1843, &ad1843_FRS, 0);
+		ad1843_write_multi(ad1843, 2,
+			&ad1843_DA1SM, ad_mode,
+			&ad1843_DA1F, ad_fmt);
+		//ad1843_write_bits(ad1843, &ad1843_DA1EN, 1);
+
+        } else {
+		ad1843_write_bits(ad1843, &ad1843_C3C, framerate);
+		ad1843_write_multi(ad1843, 2,
+			&ad1843_DA2SM, ad_mode,
+			&ad1843_DA2F, ad_fmt);
+		//ad1843_write_bits(ad1843, &ad1843_DA2EN, 1);
+       }
+
+/*FIXME Debugging, will remove */
+ad1843_write_bits(ad1843, &ad1843_C1C, framerate);
+ad1843_write_bits(ad1843, &ad1843_C2C, framerate);
+ad1843_write_bits(ad1843, &ad1843_C3C, framerate);
+//ad1843_write_bits(ad1843, &ad1843_C1M, 0x00);
+//ad1843_write_bits(ad1843, &ad1843_C2M, 0x00);
+//ad1843_write_bits(ad1843, &ad1843_C3M, 0x00);
+
+}
+
+void ad1843_shutdown_dac(ad1843_t *ad1843, unsigned int id)
+{
+       if(id)
+               ad1843_write_bits(ad1843, &ad1843_DA2F, 1);
+        else
+               ad1843_write_bits(ad1843, &ad1843_DA1F, 1);
+}
+
+void ad1843_setup_adc(ad1843_t *ad1843,
+                      unsigned int framerate,
+                      snd_pcm_format_t fmt,
+                      unsigned int channels)
+{
+       int da_fmt = 0;
+
+       switch (fmt) {
+       case SNDRV_PCM_FORMAT_S8:       da_fmt = 0; break;
+       case SNDRV_PCM_FORMAT_U8:       da_fmt = 0; break;
+       case SNDRV_PCM_FORMAT_S16_BE:   da_fmt = 1; break;
+       case SNDRV_PCM_FORMAT_MU_LAW:   da_fmt = 2; break;
+       case SNDRV_PCM_FORMAT_A_LAW:    da_fmt = 3; break;
+       default: printk("errk!\n"); break;
+       }
+
+       ad1843_write_bits(ad1843, &ad1843_C3C, framerate);
+       ad1843_write_multi(ad1843, 2,
+                          &ad1843_ADLF, da_fmt, &ad1843_ADRF, da_fmt);
+}
+
+void ad1843_shutdown_adc(ad1843_t *ad1843)
+{
+       /* nothing to do */
+}
+
+/*
+ * Fully initialize the ad1843.  As described in the AD1843 data
+ * sheet, section "START-UP SEQUENCE".  The numbered comments are
+ * subsection headings from the data sheet.  See the data sheet, pages
+ * 52-54, for more info.
+ *
+ * return 0 on success, -errno on failure.  */
+
+int ad1843_init(ad1843_t *ad1843)
+{
+       unsigned long later;
+
+	/* 1. Power up in hardware */
+
+	/* 2. Assert ^RESET^ to 0, wait 100ns
+	 * 3. Deassert ^RESET^ _400 to 800us, poll INIT to see when ready
+	 * Done in calling driver */
+	udelay(800);
+
+	/* 3. Check that the clocks are stable */
+	if (ad1843_read_bits(ad1843, &ad1843_INIT) != 0) {
+		printk(KERN_ERR "ad1843: AD1843 Clocks not yet stable\n");
+		return -EIO;
+	}
+
+	/*set serial bus speed */
+	ad1843_write_bits(ad1843, &ad1843_SCF, 0);
+
+	/* Power down conv resources */
+	/*ad1843_write_bits(ad1843, &ad1843_PDNI, 1);
+	udelay(800);*/
+
+	/* 4. Put the conversion resources into standby PDNI to 0
+	 * Force Enable auto calibrate ACEN to 1 */
+	ad1843_write_multi(ad1843, 2,
+		&ad1843_PDNI, 0, &ad1843_ACEN, 1);
+	later = jiffies + HZ / 2;  /* roughly half a second */
+
+	/* 5. While in standby
+	 * Power up the clock generators and enable clock output pins. */
+	ad1843_write_multi(ad1843, 10, &ad1843_ENCLKO, 1,
+		&ad1843_C1EN, 1, &ad1843_ENCV1, 1, &ad1843_ENBT1, 1,
+		&ad1843_C2EN, 1, &ad1843_ENCV2, 1, &ad1843_ENBT2, 1,
+		&ad1843_C3EN, 1, &ad1843_ENCV3, 1, &ad1843_ENBT3, 1);
+
+	/* 6. Configure conversion resources while they are in standby. */
+
+
+
+	/* Did we come out of standby ? */
+	while (ad1843_read_bits(ad1843, &ad1843_PDNO)) {
+		if (time_after(jiffies, later)) {
+			printk(KERN_ERR "ad1843: AD1843 won't power up\n");
+                       return -EIO;
+		}
+		schedule();
+	}
+
+
+	/* DAC1 = Clock1, ADC = Clock2, DAC2 = Clock3  Always.
+	 * Based an ad1843 state from startup jingle */
+	ad1843_write_multi(ad1843, 4,
+		&ad1843_DA1C, 1, &ad1843_DA2C, 3,
+		&ad1843_ADLC, 2, &ad1843_ADRC, 2);
+
+	/* 7. Enable conversion resources. */
+	ad1843_write_bits(ad1843, &ad1843_ADTLK, 1);
+	ad1843_write_multi(ad1843, 6,
+		&ad1843_ANAEN, 1, &ad1843_AAMEN, 1,
+		&ad1843_DA1EN, 1, &ad1843_DA2EN, 1,
+		&ad1843_ADLEN, 1, &ad1843_ADREN, 1);
+
+       /* 8. Configure conversion resources while they are enabled. */
+
+
+       /* Unmute all channels. */
+
+       ad1843_set_outsrc(ad1843,
+                         (SOUND_MASK_PCM | SOUND_MASK_LINE |
+                          SOUND_MASK_MIC | SOUND_MASK_CD));
+       ad1843_write_multi(ad1843, 2,
+		&ad1843_LDA1AM, 0, &ad1843_RDA1AM, 0);
+
+	ad1843_write_multi(ad1843, 2,
+		&ad1843_LDA2AM, 0, &ad1843_RDA2AM, 0);
+
+	ad1843_write_multi(ad1843, 2,
+		&ad1843_LDA1GM, 0, &ad1843_RDA1GM, 0);
+
+       /* Set default recording source to Line In and set
+        * mic gain to +20 dB.
+        */
+       ad1843_set_recsrc(ad1843, SOUND_MASK_LINE);
+       ad1843_write_multi(ad1843, 2, &ad1843_LMGE, 1, &ad1843_RMGE, 1);
+
+       /* Set Speaker Out level to +/- 4V and unmute it. */
+       ad1843_write_multi(ad1843, 3,
+                           &ad1843_HPOS, 1,
+                           &ad1843_HPOM, 0,
+                           &ad1843_MPOM, 0);
+
+       return 0;
+}
+
+char *ad1843_dump_reg(ad1843_t *ad1843, int reg)
+{
+	static const char *reg_fmt[32] = {
+		"Reg  0: Codec Status and Revision Identification\n"
+		"|INIT   %u |PDNO   %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|ID3    %u ,ID2    %u ,ID1    %u ,ID0    %u | (%u)\n",
+		/*reg 1*/
+		"Reg  1: Channel Status Flags\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |SU2    %u |SU1    %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|OVR1   %u ,OVR0   %u |OVL1   %u ,OVL0   %u | (%u)\n",
+		/*reg 2*/
+		"Reg  2: Input Control - ADC Source and Gain/Attenuation\n"
+		"|LSS2   %u ,LSS1   %u ,LSS0   %u |LMGE   %u "
+		"|LIG3   %u ,LIG2   %u ,LIG1   %u ,LIG0   %u | (%u)\n"
+		"|RSS2   %u ,RSS1   %u ,RSS0   %u |RMGE   %u "
+		"|RIG3   %u ,RIG2   %u ,RIG1   %u ,RIG0   %u | (%u)\n",
+		/*reg 3*/
+		"Reg  3: Mix Control - DAC2 to Mixer\n"
+		"|LD2MM  %u |RES    %u |RES    %u |LD2M4  %u "
+		",LD2M3  %u ,LD2M2  %u ,LD2M1  %u ,LD2M0  %u | (%u)\n"
+		"|RD2MM  %u |RES    %u |RES    %u |RD2M4  %u "
+		",RD2M3  %u ,RD2M2  %u ,RD2M1  %u ,RD2M0  %u | (%u)\n",
+		/*reg 4*/
+		"Reg  4: Auxiliary 1 to DAC1\n"
+		"|LX1MM  %u |RES    %u |RES    %u |LX1M4  %u "
+		",LX1M3  %u ,LX1M2  %u ,LX1M1  %u ,LX1M0  %u | (%u)\n"
+		"|RX1MM  %u |RES    %u |RES    %u |RX1M4  %u "
+		",RX1M3  %u ,RX1M2  %u ,RX1M1  %u ,RX1M0  %u | (%u)\n",
+		/*reg 5 */
+		"Reg  5: Mix Control - Auxiliary 2 to Mixer\n"
+		"|LX2MM  %u |RES    %u |RES    %u |LX2M4  %u "
+		",LX2M3  %u ,LX2M2  %u ,LX2M1  %u ,LX2M0  %u | (%u)\n"
+		"|RX2MM  %u |RES    %u |RES    %u |RX2M4  %u "
+		",RX2M3  %u ,RX2M2  %u ,RX2M1  %u ,RX2M0  %u | (%u)\n",
+		/*reg 6*/
+		"Reg  6: Mix Control - Auxiliary 3 to Mixer\n"
+		"|LX3MM  %u |RES    %u |RES    %u |LX3M4  %u "
+		",LX3M3  %u ,LX3M2  %u ,LX3M1  %u ,LX3M0  %u | (%u)\n"
+		"|RX3MM  %u |RES    %u |RES    %u |RX3M4  %u "
+		",RX3M3  %u ,RX3M2  %u ,RX3M1  %u ,RX3M0  %u | (%u)\n",
+		/*reg 7*/
+		"Reg  7: Mix Control - Mic to Mixer\n"
+		"|LMCMM  %u |RES    %u |RES    %u |LMCM4  %u "
+		",LMCM3  %u ,LMCM2  %u ,LMCM1  %u ,LMCM0  %u | (%u)\n"
+		"|RMCMM  %u |RES    %u |RES    %u |RMCM4  %u "
+		",RMCM3  %u ,RMCM2  %u ,RMCM1  %u ,RMCM0  %u | (%u)\n",
+		/*reg 8*/
+		"Reg  8: Mix/Misc Control - "
+			"Mono In to Mixer and Misc Settings\n"
+		"|MNMM   %u |RES    %u |RES    %u |MNM4   %u "
+		",MNM3   %u ,MNM2   %u ,MNM1   %u ,MNM0   %u | (%u)\n"
+		"|ALLMM  %u |MNOM   %u |HPOM   %u |HPOS   %u "
+		"|SUMM   %u |RES    %u |DAC2T  %u |DAC1T  %u | (%u)\n",
+		/*reg 9*/
+		"Reg  9: Output Control - DAC1 Analog Gain/Attenuation\n"
+		"|LDA1GM %u |RES    %u |LDA1G5 %u ,LDA1G4 %u "
+		",LDA1G3 %u ,LDA1G2 %u ,LDA1G1 %u ,LDA1G0 %u | (%u)\n"
+		"|RDA1GM %u |RES    %u |RDA1G5 %u ,RDA1G4 %u "
+		",RDA1G3 %u ,RDA1G2 %u ,RDA1G1 %u ,RDA1G0 %u | (%u)\n",
+		/*reg 10*/
+		"Reg 10: Output Control - DAC2 Analog Gain/Attenuation\n"
+		"|LDA2GM %u |RES    %u |LDA2G5 %u ,LDA2G4 %u "
+		",LDA2G3 %u ,LDA2G2 %u ,LDA2G1 %u ,LDA2G0 %u | (%u)\n"
+		"|RDA2GM %u |RES    %u |RDA2G5 %u ,RDA2G4 %u "
+		",RDA2G3 %u ,RDA2G2 %u ,RDA2G1 %u ,RDA2G0 %u | (%u)\n",
+		/*reg 11*/
+		"Reg 11: Output Control - DAC1 Digital Attenuation\n"
+		"|LDA1AM %u |RES    %u |LDA1A5 %u ,LDA1A4 %u "
+		",LDA1A3 %u ,LDA1A2 %u ,LDA1A1 %u ,LDA1A0 %u | (%u)\n"
+		"|RDA1AM %u |RES    %u |RDA1A5 %u ,RDA1A4 %u "
+		",RDA1A3 %u ,RDA1A2 %u ,RDA1A1 %u ,RDA1A0 %u | (%u)\n",
+		/*reg 12*/
+		"Reg 12: Output Control - DAC2 Digital Attenuation\n"
+		"|LDA2AM %u |RES    %u |LDA2A5 %u ,LDA2A4 %u "
+		",LDA2A3 %u ,LDA2A2 %u ,LDA2A1 %u ,LDA2A0 %u | (%u)\n"
+		"|RDA2AM %u |RES    %u |RDA2A5 %u ,RDA2A4 %u "
+		",RDA2A3 %u ,RDA2A2 %u ,RDA2A1 %u ,RDA2A0 %u | (%u)\n",
+		/*reg 13*/
+		"Reg 13: Digital Mix Control - ADC to DAC1\n"
+		"|LAD1MM %u |RES    %u |LAD1M5 %u ,LAD1M4 %u "
+		",LAD1M3 %u ,LAD1M2 %u ,LAD1M1 %u ,LAD1M0 %u | (%u)\n"
+		"|RAD1MM %u |RES    %u |RAD1M5 %u ,RAD1M4 %u "
+		",RAD1M3 %u ,RAD1M2 %u ,RAD1M1 %u ,RAD1M0 %u | (%u)\n",
+		/*reg 14*/
+		"Reg 14: Digital Mix Control - ADC to DAC2\n"
+		"|LAD2MM %u |RES    %u |LAD2M5 %u ,LAD2M4 %u "
+		",LAD2M3 %u ,LAD2M2 %u ,LAD2M1 %u ,LAD2M0 %u | (%u)\n"
+		"|RAD2MM %u |RES    %u |RAD2M5 %u ,RAD2M4 %u "
+		",RAD2M3 %u ,RAD2M2 %u ,RAD2M1 %u ,RAD2M0 %u | (%u)\n",
+		/*reg 15*/
+		"Reg 15: Codec Configuration - "
+			"Channel Sample Rate Source Select\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|DA2C1  %u ,DA2C0  %u |DA1C1  %u ,DA1C0  %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|ADRC1  %u ,ADRC0  %u |ADLC1  %u ,ADLC0  %u | (%u)\n",
+		/*reg 16*/
+		"Reg 16: Clock Generator 1 Control - Mode\n"
+		"|C1REF  %u |C1VID  %u |C1PLLG %u |C1P200 %u "
+		"|C1X8/7 %u |C1C128 %u |RES    %u |RES    %u | (%u)\n"
+		"|C1M7   %u ,C1M6   %u ,C1M5   %u ,C1M4   %u "
+		",C1M3   %u ,C1M2   %u ,C1M1   %u ,C1M0   %u | (%u)\n",
+		/*reg 17*/
+		"Reg 17: Clock Generator 1 Control - Sample Rate\n"
+		"|C1C15  %u ,C1C14  %u ,C1C13  %u ,C2C12  %u "
+		",C1C11  %u ,C1C10  %u ,C1C9   %u ,C1C8   %u , (%u)\n"
+		",C1C7   %u ,C1C6   %u ,C1C5   %u ,C1C4   %u "
+		",C1C3   %u ,C1C2   %u ,C1C1   %u ,C1C0   %u | (%u)\n",
+		/*reg 18*/
+		"Reg 18: Clock Generator 1 Control - Sample Phase Shift\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |C1PD   %u | (%u)\n"
+		"|C1P7   %u ,C1P6   %u ,C1P5   %u ,C1P4   %u "
+		",C1P3   %u ,C1P2   %u ,C1P1   %u ,C1P0   %u | (%u)\n",
+		/*reg 19*/
+		"Reg 19: Clock Generator 2 Control - Mode\n"
+		"|C2REF  %u |C2VID  %u |C2PLLG %u |C2P200 %u "
+		"|C2X8/7 %u |C2C128 %u |RES    %u |RES    %u | (%u)\n"
+		"|C2M7   %u ,C2M6   %u ,C2M5   %u ,C2M4   %u "
+		",C2M3   %u ,C2M2   %u ,C2M1   %u ,C2M0   %u | (%u)\n",
+		/*reg 20*/
+		"Reg 20: Clock Generator 2 Control - Sample Rate\n"
+		"|C2C15  %u ,C2C14  %u ,C2C13  %u ,C2C12  %u "
+		",C2C11  %u ,C2C10  %u ,C2C9   %u ,C2C8   %u , (%u)\n"
+		",C2C7   %u ,C2C6   %u ,C2C5   %u ,C2C4   %u "
+		",C2C3   %u ,C2C2   %u ,C2C1   %u ,C2C0   %u | (%u)\n",
+		/*reg 21*/
+		"Reg 21: Clock Generator 2 Control - Sample Phase Shift\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |C2PD   %u | (%u)\n"
+		"|C2P7   %u ,C2P6   %u ,C2P5   %u ,C2P4   %u "
+		",C2P3   %u ,C2P2   %u ,C2P1   %u ,C2P0   %u | (%u)\n",
+		/*reg 22*/
+		"Reg 22: Clock Generator 3 Control - Mode\n"
+		"|C3REF  %u |C3VID  %u |C3PLLG %u |C3P200 %u "
+		"|C3X8/7 %u |C3C128 %u |RES    %u |RES    %u | (%u)\n"
+		"|C3M7   %u ,C3M6   %u ,C3M5   %u ,C3M4   %u "
+		",C3M3   %u ,C3M2   %u ,C3M1   %u ,C3M0   %u | (%u)\n",
+		/*reg 23*/
+		"Reg 23: Clock Generator 3 Control - Sample Rate\n"
+		"|C3C15  %u ,C3C14  %u ,C3C13  %u ,C3C12  %u "
+		",C3C11  %u ,C3C10  %u ,C3C9   %u ,C3C8   %u , (%u)\n"
+		",C3C7   %u ,C3C6   %u ,C3C5   %u ,C3C4   %u "
+		",C3C3   %u ,C3C2   %u ,C3C1   %u ,C3C0   %u | (%u)\n",
+		/*reg 24*/
+		"Reg 24: Clock Generator 3 Control - Sample Phase Shift\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |C3PD   %u | (%u)\n"
+		"|C3P7   %u ,C3P6   %u ,C3P5   %u ,C3P4   %u "
+		",C3P3   %u ,C3P2   %u ,C3P1   %u ,C3P0   %u | (%u)\n",
+		/*reg 25*/
+		"Reg 25: Codec Configuration - Digital Filter and Mode Select\n"
+		"|DRSFLT %u |DAMIX  %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |DA2FLT %u |DA1FLT %u | (%u)\n"
+		"|DAADR1 %u ,DAADR0 %u |DAADL1 %u ,DAADL0 %u "
+		"|RES    %u |RES    %u |ADRFLT %u |ADLFLT %u | (%u)\n",
+		/*reg 26*/
+		"Reg 26: Codec Configuration - Serial Interface\n"
+		"|DA2SM  %u |DA1SM  %u |RES    %u |RES    %u "
+		"|DA2F1  %u ,DA2F0  %u |DA1F1  %u ,DA1F0  %u | (%u)\n"
+		"|SCF    %u |FRS    %u |FRST   %u |ADTLK  %u "
+		"|ADRF1  %u ,ADRF0  %u |ADLF1  %u ,ADLF0  %u | (%u)\n",
+		/*reg 27*/
+		"Reg 27: Codec Configuration - Channel Power Down\n"
+		"|DFREE  %u |RES    %u |RES    %u |DDMEM  %u "
+		"|RES    %u |RES    %u |DA2EN  %u |DA1EN  %u | (%u)\n"
+		"|ANAEN  %u |HPEN   %u |RES    %u |AAMEN  %u "
+		"|RES    %u |RES    %u |ADREN  %u |ADLEN  %u | (%u)\n",
+		/*reg 28*/
+		"Reg 28: Codec Configuration - Fundemental Settings\n"
+		"|PDNI   %u |ACEN   %u |C3EN   %u |C2EN   %u "
+		"|C1EN   %u |ENCLKO %u |XCTL1  %u |XCTL0  %u | (%u)\n"
+		"|ENCV3  %u |ENBT3  %u |ENCV2  %u |ENBT2  %u "
+		"|ENCV1  %u |ENBT1  %u |LINRSD %u |LINLSD %u | (%u)\n",
+		/*reg 29*/
+		"Reg 29: Reserved for Future Expansion\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n",
+		/*reg 30*/
+		"Reg 30: Reserved for Future Expansion\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n",
+		/*reg 31*/
+		"Reg 31: Reserved for Future Expansion\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n"
+		"|RES    %u |RES    %u |RES    %u |RES    %u "
+		"|RES    %u |RES    %u |RES    %u |RES    %u | (%u)\n",
+		};
+	unsigned int v1 = 0, v2 = 0;
+	int w = ad1843->read(ad1843->chip, reg);
+	char *str = vmalloc_user(255);
+
+	if (NULL==str) return NULL;
+
+	switch (reg) {
+	case 0:
+		v2 = w&0xF;
+		break;
+	case 2:
+		v1 = (w>>8)&0xF; v2 = w&0xF;
+		break;
+	case 3: case 4: case 5: case 6: case 7:
+		v1 = (w>>8)&0x1F; v2 = w&0x1F;
+		break;
+	case 8:
+		v1 = (w>>8)&0x1F;
+		break;
+	case 9: case 10: case 11: case 12: case 13: case 14:
+		v1 = (w>>8)&0x3F; v2 = w&0x3F;
+		break;
+	case 16: case 18: case 19: case 21: case 22: case 24:
+		v2 = w&0xFF;
+		break;
+	case 17: case 20: case 23:
+		v2 = w&0xFFFF;
+		break;
+
+	}
+
+	snprintf(str, 255, reg_fmt[reg], (w>>15)&1,
+		(w>>14)&1, (w>>13)&1, (w>>12)&1, (w>>11)&1, (w>>10)&1,
+		(w>>9)&1,  (w>>8)&1, v1, (w>>7)&1,  (w>>6)&1,  (w>>5)&1,
+		(w>>4)&1,  (w>>3)&1,  (w>>2)&1,  (w>>1)&1,  (w>>0)&1, v2);
+
+	return str;
+}

--- linux-2.6.21.6-b/sound/mips/mace_audio.c	1970-01-01 01:00:00.000000000 +0100
+++ linux-2.6.21.6/sound/mips/mace_audio.c	2007-07-09 20:29:50.000000000 +0100
@@ -0,0 +1,1347 @@
+/*
+ *   Sound driver for Silicon Graphics O2 Workstations MACE audio board.
+ *
+ *   Copyright 2007 Thorben Jändling <tj.trevelyan at gmail.com>
+ *   Based/Copied heavily on/from sgio2audio.c:
+ *   Copyright 2003 Vivien Chappelier <vivien.chappelier at linux-mips.org>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * Notes
+ *
+ * Mace take 24bit audio stored/transfered in 32 bits and converts
+ * it in hardware back to 16/8bit
+ *
+ * AD1843 spec says:
+ * "Data in all four formats is always transfered MSB first"
+ * To me that says big-endian
+ * So tell ALSA we only do SNDRV_PCM_FMTBIT_S24_BE
+ *
+ * Apparently RAD1 similar see IP30 patch set
+ *
+ * Why rewrite? Mostly so that I can understand the code, writing is
+ * better then reading for that purpose I find.
+ */
+/* channel 0/ADC, 1/DAC1 -> pcm0
+ * channel 2/DAC2 -> pcm1
+ */
+
+/*************************** include ******************************/
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/gfp.h>
+#include <linux/vmalloc.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/list.h>
+#include <linux/dma-mapping.h>
+#include <asm/io.h>
+
+#include <asm/ip32/ip32_ints.h>
+#include <asm/ip32/mace.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+
+#include <sound/ad1843.h>
+
+/**************************** defines *******************************/
+
+/* 1: reset audio interface */
+#define SMA_CTRL_RESET          0x1UL
+/* 1: codec detected */
+#define SMA_CTRL_CODEC_PRESENT  0x2UL
+/*  2-8  : channel 1 write ptr alias */
+/*  9-15 : channel 2 read ptr alias */
+/* 16-22 : channel 3 read ptr alias */
+/* latched volume button */
+#define SMA_CTRL_VOL_BUTTON_UP   BIT(24)
+/* latched volume button */
+#define SMA_CTRL_VOL_BUTTON_DOWN BIT(23)
+
+#define SMA_CODEC_READ(reg)  ((reg) <<17) | 0x00010000UL
+#define SMA_CODEC_WRITE(reg, val) (((reg) <<17)|(val)) & 0x00FEFFFFUL
+
+#define SMA_RING_OFFSET(chi) ((chi) << 12)
+#define SMA_RING_SIZE 0x1000
+#define SMA_RING_MASK 0x0FFFUL
+
+/* int on buffer >50% full */
+#define SMA_INT_THRESHOLD_50 (2 << 5)
+/* 1: enable DMA transfer */
+#define SMA_DMA_ENABLE   BIT(9)
+
+/*old, define still inherted from sgio2audio*/
+
+#define SMA_CTRL_CHAN_RESET BIT(10) /* 1: reset channel */
+
+#define SMA_INT_THRESHOLD_DISABLED  (0 << 5) /* interrupt disabled */
+#define SMA_INT_THRESHOLD_25 (1 << 5) /* int on buffer >25% full */
+#define SMA_INT_THRESHOLD_75 (3 << 5) /* int on buffer >75% full */
+#define SMA_INT_THRESHOLD_EMPTY     (4 << 5) /* int on buffer empty */
+#define SMA_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */
+#define SMA_INT_THRESHOLD_FULL      (6 << 5) /* int on buffer empty */
+#define SMA_INT_THRESHOLD_NOT_FULL  (7 << 5) /* int on buffer !empty */
+
+/****************************** structs/types ***********************/
+
+/* chip specific record */
+typedef struct snd_mace_audio {
+	struct snd_card *card;
+	struct snd_pcm  *pcm[2];
+
+	struct {
+		struct snd_pcm_substream *substream;
+		snd_pcm_uframes_t        frames;
+
+		int                  ack;
+		void                *buffer;
+		u64                  pos;
+		spinlock_t           lock;
+	} channel[3];
+
+	/* which gain should the O2 vol butons control?*/
+	unsigned int ext_vol_for;
+
+	struct snd_info_entry  *proc_debug,
+	                  *proc_ad1843,
+	                  *proc_mace;
+
+	ad1843_t   *ad1843;
+	spinlock_t ad1843_lock;
+
+	void        *ring_base;
+	dma_addr_t   ring_base_handle;
+	unsigned long maceisa_base;
+	unsigned int mace_offset;
+
+} snd_mace_audio_t;
+
+typedef struct sma_proc_debug {
+	struct list_head link;
+
+/* name to display for the entry */
+	char  *name;
+
+/* a string value */
+	char  *val_str;
+
+/* an int val used if val_str is null */
+	long val_int;
+} sma_proc_debug_t;
+
+/************************** func declaration ****************************/
+/* mace access */
+static int mace_audio_reg_read(snd_mace_audio_t *chip, int reg);
+static int mace_audio_reg_write(snd_mace_audio_t *chip, int reg, int word);
+/* pcm */
+static int sma_pcm_open(struct snd_pcm_substream *substream);
+static int sma_pcm_close(struct snd_pcm_substream *substream);
+static int sma_pcm_hwparam
+	(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params);
+static int sma_pcm_hwfree(struct snd_pcm_substream *substream);
+static int sma_pcm_prepare(struct snd_pcm_substream *substream);
+static int sma_pcm_trigger(struct snd_pcm_substream *substream, int cmd);
+static snd_pcm_uframes_t sma_pcm_pointer
+	(struct snd_pcm_substream *substream);
+static struct page *sma_pcm_page
+	(struct snd_pcm_substream *substream, unsigned long offset);
+static int sma_pcm_ack(struct snd_pcm_substream *substream);
+static int __devinit sma_pcm_new(snd_mace_audio_t *chip);
+/* interrupt and DMA engine */
+static int sma_dma_start(struct snd_pcm_substream *substream);
+static int sma_dma_stop(struct snd_pcm_substream *substream);
+static void sma_dma_ping(snd_mace_audio_t *chip, int chi);
+static void sma_dma_refill(snd_mace_audio_t *chip, int chi);
+static irqreturn_t sma_interrupt
+	(int irq, snd_mace_audio_t *chip, struct pt_regs *regs);
+static void sma_adjust_vol(snd_mace_audio_t *chip);
+/* mixer and controls */
+static int sma_gain_info
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo);
+static int sma_gain_get
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+static int sma_gain_put
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol);
+static int __devinit sma_control_new(snd_mace_audio_t *chip);
+/* proc */
+static void sma_proc_ad1843_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer);
+static void sma_proc_mace_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer);
+static void sma_proc_debug_read
+	(struct snd_info_entry *, struct snd_info_buffer *);
+static sma_proc_debug_t* sma_proc_debug_append(char *name);
+static int sma_proc_create(snd_mace_audio_t *chip);
+/* ALSA Setup */
+static int sma_free(snd_mace_audio_t *chip);
+static int __devinit sma_create
+	(struct snd_card *card, snd_mace_audio_t **rchip);
+static int __devinit sma_probe(void);
+/* module setup */
+static int __init snd_card_mace_audio_init(void);
+static void __exit snd_card_mace_audio_exit(void);
+
+
+/************************* define psudo-funcs ***************************/
+#define sma_proc_entry_assert(entry,name) \
+	if (NULL == entry) entry = sma_proc_entry_append(name);
+
+/* pcm0 substream0 = channel 1
+ * pcm0 substream1 = channel 0
+ * pcm1 substream0 = channel 2
+ */
+#define substream_to_channel_index(substream) \
+ ((((snd_mace_audio_t*)(substream->private_data))->pcm[0]==substream->pcm)\
+ ?!(substream->number):2)
+
+/******************************** globals *******************************/
+
+/* using same for all */
+static struct snd_pcm_hardware sma_pcm_hw[2] = {
+	{ /* Direct */
+	.info = (SNDRV_PCM_INFO_MMAP |
+	         SNDRV_PCM_INFO_INTERLEAVED |
+	         SNDRV_PCM_INFO_BLOCK_TRANSFER |
+	         SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =           SNDRV_PCM_FMTBIT_S24_BE,
+	.rates =             SNDRV_PCM_RATE_8000_48000,
+	.rate_min =          8000,
+	.rate_max =          48000,
+	.channels_min =      2,
+	.channels_max =      2,
+        .buffer_bytes_max =  4096,
+	.period_bytes_min =  32,
+	.period_bytes_max =  1024,
+	.periods_min =       4,
+	.periods_max =       128,
+
+	},{ /* inderct bounce buffer */
+
+	.info = (SNDRV_PCM_INFO_MMAP |
+	         SNDRV_PCM_INFO_INTERLEAVED |
+	         SNDRV_PCM_INFO_BLOCK_TRANSFER |
+	         SNDRV_PCM_INFO_MMAP_VALID),
+	.formats =           SNDRV_PCM_FMTBIT_S24_BE,
+	.rates =             SNDRV_PCM_RATE_8000_48000,
+	.rate_min =          8000,
+	.rate_max =          48000,
+	.channels_min =      2,
+	.channels_max =      2,
+        .buffer_bytes_max =  32768,
+	.period_bytes_min =  32,
+	.period_bytes_max =  32768,
+	.periods_min =       1,
+	.periods_max =       1024,
+	}
+};
+
+/* pcm operators */
+static struct snd_pcm_ops sma_pcm_ops[2] = {
+	{ /* Direct */
+	.open =       sma_pcm_open,
+	.close =      sma_pcm_close,
+	.ioctl =      snd_pcm_lib_ioctl,
+	.hw_params =  sma_pcm_hwparam,
+	.hw_free =    sma_pcm_hwfree,
+	.prepare =    sma_pcm_prepare,
+	.trigger =    sma_pcm_trigger,
+	.pointer =    sma_pcm_pointer,
+	.ack     =    sma_pcm_ack,
+	.page    =    sma_pcm_page
+
+	},{ /* inderct bounce buffer */
+	.open =       sma_pcm_open,
+	.close =      sma_pcm_close,
+	.ioctl =      snd_pcm_lib_ioctl,
+	.hw_params =  sma_pcm_hwparam,
+	.hw_free =    sma_pcm_hwfree,
+	.prepare =    sma_pcm_prepare,
+	.trigger =    sma_pcm_trigger,
+	.pointer =    sma_pcm_pointer,
+	.page    =    sma_pcm_page
+
+	}
+};
+
+/* record level mixer control */
+static struct snd_kcontrol_new sma_ctrl_reclevel __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Capture Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_RECLEV,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+/* line mixer control */
+static struct snd_kcontrol_new sma_ctrl_line __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Line Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_LINE,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+/* cd mixer control */
+static struct snd_kcontrol_new sma_ctrl_cd __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "CD Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_CD,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+/* mic mixer control */
+static struct snd_kcontrol_new sma_ctrl_mic __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Mic Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_MIC,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+/* dac1/pcm0 mixer control */
+static struct snd_kcontrol_new sma_ctrl_pcm0 __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Playback Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_PCM_0,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+/* dac2/pcm1 mixer control */
+static struct snd_kcontrol_new sma_ctrl_pcm1 __devinitdata = {
+	.iface          = SNDRV_CTL_ELEM_IFACE_MIXER,
+	.name           = "Playback Aux-out Volume",
+	.access         = SNDRV_CTL_ELEM_ACCESS_READWRITE,
+	.private_value  = AD1843_GAIN_PCM_1,
+	.info           = sma_gain_info,
+	.get            = sma_gain_get,
+	.put            = sma_gain_put,
+};
+
+
+LIST_HEAD(sma_proc_debug_list);
+
+static int   alsa_index = -1;
+static char *alsa_id    = NULL;
+static int   alsa_enable= 1;
+static int   sma_indirect = 1;
+
+/* Global needed for module exit */
+snd_mace_audio_t *snd_mace_audio_chip = NULL;
+
+/******************************* PCM ****************************************/
+
+/* temp debug func */
+
+static void print_pointers(const char *str)
+{
+	unsigned long rb, wp, rp, depth;
+	int chi;
+
+	rb = readq(&mace->perif.ctrl.ringbase);
+	snd_printk ("%s. ringbase=0x%lX,\n", str, rb);
+
+	for (chi=0; chi<3; chi++) {
+
+		wp = readq(&mace->perif.audio.chan[chi].write_ptr);
+		rp = readq(&mace->perif.audio.chan[chi].read_ptr);
+		depth = readq(&mace->perif.audio.chan[chi].depth);
+
+		snd_printk("\t%d[ writep=0x%lX (%lu), readp=0x%lX (%lu), "
+			"depth=0x%lX (%lu) ]\n",
+			chi, wp, wp, rp, rp, depth, depth);
+	}
+
+}
+
+
+/* play open callback */
+static int sma_pcm_open(struct snd_pcm_substream *substream)
+{
+	//snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int chi = substream_to_channel_index(substream);
+snd_printd("in pcm open for channel %d\n", chi);
+
+	runtime->hw = sma_pcm_hw[sma_indirect];
+
+	return 0;
+}
+
+/* play close callback */
+static int sma_pcm_close(struct snd_pcm_substream *substream)
+{
+	//snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	//FIXME code
+snd_printd("in pcm close\n");
+
+	return 0;
+}
+
+/* setup hw for pcm */
+/* maybe called more then once */
+static int sma_pcm_hwparam
+	(struct snd_pcm_substream *substream, struct snd_pcm_hw_params *hw_params)
+{
+	snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	int chi = substream_to_channel_index(substream);
+snd_printd("In hw param for channel %d\n", chi);
+
+	if (sma_indirect) {
+
+		if (NULL != substream->runtime->dma_area)
+			vfree(substream->runtime->dma_area);
+
+		substream->runtime->dma_bytes =
+			params_buffer_bytes(hw_params);
+
+		substream->runtime->dma_addr = 0;
+
+		substream->runtime->dma_area =
+			vmalloc_user(substream->runtime->dma_bytes);
+		
+	} else {
+		substream->runtime->dma_bytes = SMA_RING_SIZE;
+
+		substream->runtime->dma_addr =
+			(chip->ring_base_handle + SMA_RING_OFFSET(chi)
+			/*+ chip->mace_offset*/);
+			/*chip->maceisa_base + SMA_RING_OFFSET(chi);*/
+		substream->runtime->dma_area =
+			(chip->ring_base + SMA_RING_OFFSET(chi)
+			/*+ chip->mace_offset~*/);
+	}
+
+snd_printd("set alsa dma area=0x%lX (%lu) size=0x%lX (%lu)\n",
+	(unsigned long) substream->runtime->dma_area,
+	(unsigned long) substream->runtime->dma_area,
+	(unsigned long) substream->runtime->dma_bytes,
+	(unsigned long) substream->runtime->dma_bytes);
+
+	if (NULL == substream->runtime->dma_area) {
+		snd_printk(KERN_ERR "DMA area not allocated!\n");
+		return -ENOMEM;
+	}
+
+	return 1;
+}
+
+/* free hw for pcm */
+/* maybe called more then once */
+static int sma_pcm_hwfree(struct snd_pcm_substream *substream)
+{
+	snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	int chi = substream_to_channel_index(substream);
+snd_printd("In hw free for channel %d\n", chi);
+
+	if (sma_indirect && NULL != substream->runtime->dma_area) {
+		vfree(substream->runtime->dma_area);
+	}
+
+	substream->runtime->dma_bytes = 0;
+	substream->runtime->dma_addr = 0;
+	substream->runtime->dma_area = NULL;
+	chip->channel[chi].buffer = NULL;
+	chip->channel[chi].pos = 0;
+	chip->channel[chi].ack = 0;
+	chip->channel[chi].frames = 0;
+
+	return 1;
+}
+
+/* prepare callback */
+static int sma_pcm_prepare(struct snd_pcm_substream *substream)
+{
+	snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int chi = substream_to_channel_index(substream);
+	unsigned long flags;
+
+	spin_lock_irqsave(&chip->channel[chi].lock, flags);
+
+	chip->channel[chi].pos = 0;
+	chip->channel[chi].frames = 0;
+	chip->channel[chi].ack = 0;
+	chip->channel[chi].substream = substream;
+
+	if (0 == chi) { /* capture*/
+snd_printd("in prepare capture for channel %d\n", chi);
+		
+		ad1843_setup_adc(chip->ad1843,
+			runtime->rate,
+			SNDRV_PCM_FORMAT_S24_BE,
+			runtime->channels);
+	} else { /* playback */
+snd_printd("in prepare playback for channel %d, rate %d, sub-channels %d\n",
+	chi, runtime->rate, runtime->channels);
+
+		ad1843_setup_dac(chip->ad1843,
+			chi,
+			runtime->rate,
+			SNDRV_PCM_FORMAT_S24_BE,
+			runtime->channels);
+	}
+
+	spin_unlock_irqrestore(&chip->channel[chi].lock, flags);
+
+	return 0;
+}
+
+/* trigger callback */
+static int sma_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
+{
+snd_printd("In pcm trigger\n");
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START: /* start PCM engine */
+		sma_dma_start(substream);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP: /* stop PCM engine */
+		sma_dma_stop(substream);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/* pointer callback */
+static snd_pcm_uframes_t sma_pcm_pointer
+	(struct snd_pcm_substream *substream)
+{
+	snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	int chi = substream_to_channel_index(substream);
+	unsigned long p;
+
+	if (sma_indirect) {
+
+		p = chip->channel[chi].pos;
+
+	} else {
+
+		p = readq(&mace->perif.audio.chan[chi].read_ptr);
+
+	}
+
+//snd_printd("returning pointer 0x%lX (%lu) for channel %d\n", p, p, chi);
+
+
+	return  bytes_to_frames(substream->runtime, p);
+}
+
+/* get page callback */
+static struct page *sma_pcm_page
+	(struct snd_pcm_substream *substream, unsigned long offset)
+{
+	struct page *p;
+snd_printd("in page\n");
+
+	if (sma_indirect) {
+		p = vmalloc_to_page((substream->runtime->dma_area + offset));
+	} else {
+		/*FIXME using CAC_AADR until a proper alsa dma on mips fix */
+		p = virt_to_page(
+			CAC_ADDR((substream->runtime->dma_area + offset)));
+	}
+
+	return p;
+}
+
+/* ack callback, when some more data is written */
+static int sma_pcm_ack(struct snd_pcm_substream *substream)
+{
+	//snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	int chi = substream_to_channel_index(substream);
+	unsigned long ap;
+
+	ap = frames_to_bytes(substream->runtime,
+		substream->runtime->control->appl_ptr);
+
+	//ap =- SMA_RING_SIZE;
+	if (32 > ap) ap += SMA_RING_SIZE;
+	ap -= 32;
+	ap &= SMA_RING_MASK;
+
+snd_printd("in ack, ap=0x%lX (%lu), for channel %d\n", ap, ap, chi);
+	writeq(ap, &mace->perif.audio.chan[chi].write_ptr);
+
+	/* set DMA to wake on 50% empty and enable interrupt */
+/*	writeq(SMA_DMA_ENABLE | SMA_INT_THRESHOLD_50,
+		&mace->perif.audio.chan[chi].control);*/
+	mb();
+
+	return 0;
+}
+
+
+/* create pcm device */
+static int __devinit sma_pcm_new(snd_mace_audio_t *chip)
+{
+	struct snd_pcm *pcm;
+	int err, chi;
+
+	/* reset channels and leave them off */
+	for (chi=0; chi<3; chi++) {
+		writeq(0,
+			&mace->perif.audio.chan[chi].control);
+
+		writeq(0, &mace->perif.audio.chan[chi].write_ptr);
+		writeq(0, &mace->perif.audio.chan[chi].read_ptr);
+
+		writeq(SMA_CTRL_CHAN_RESET,
+			&mace->perif.audio.chan[chi].control);
+			
+		spin_lock_init(&chip->channel[chi].lock);
+	}
+
+	pcm = NULL;
+	err = snd_pcm_new(chip->card, "Mace Audio 1", 0, 1, 1, &pcm);
+	if (0 > err) return err;
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "Mace Audio Primary");
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+		&sma_pcm_ops[sma_indirect]);
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE,
+		&sma_pcm_ops[sma_indirect]);
+
+	chip->pcm[0] = pcm;
+
+
+	pcm = NULL;
+	err = snd_pcm_new(chip->card, "Mace Audio 1", 1, 1, 0, &pcm);
+	if (0 > err) return err; /*free pcm0 ? */
+
+	pcm->private_data = chip;
+	strcpy(pcm->name, "Mace Audio Secondary");
+
+	/* set operators */
+	snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_PLAYBACK,
+		&sma_pcm_ops[sma_indirect]);
+
+	chip->pcm[1] = pcm;
+
+	return 0;
+}
+
+/*************************** Interrupts and DMA engine ****************/
+static int sma_dma_start(struct snd_pcm_substream *substream)
+{
+	snd_mace_audio_t *chip = snd_pcm_substream_chip(substream);
+	int chi = substream_to_channel_index(substream);
+
+snd_printd("In DMA enable for channel %d\n", chi);
+
+	if (sma_indirect) {
+		sma_dma_refill(chip, chi);
+	}	
+
+	/* set DMA to wake on 50% empty and enable interrupt */
+	writeq(SMA_DMA_ENABLE | SMA_INT_THRESHOLD_50,
+		&mace->perif.audio.chan[chi].control);
+	mb();
+
+	return 0;
+}
+
+static int sma_dma_stop(struct snd_pcm_substream *substream)
+{
+	int chi = substream_to_channel_index(substream);
+snd_printd("In DMA stop for channel %d\n", chi);
+
+	writeq(SMA_CTRL_CHAN_RESET,
+		&mace->perif.audio.chan[chi].control);
+	mb();
+
+	return 0;
+}
+
+static void sma_dma_ping(snd_mace_audio_t *chip, int chi)
+{
+
+	if (sma_indirect){
+		sma_dma_refill(chip, chi);
+		snd_pcm_period_elapsed(chip->channel[chi].substream);
+	} else {
+		snd_pcm_period_elapsed(chip->channel[chi].substream);
+		/* Stop DMA if running low */
+		/*depth = readq(&mace->perif.audio.chan[chi].depth);
+		if (1024 > depth) sma_dma_stop(chip->channel[chi].substream);*/
+	}
+
+}
+
+static void sma_dma_refill(snd_mace_audio_t *chip, int chi)
+{
+	u64 *dst, dst_base, dst_pos, *src, src_base, src_pos, src_mask;
+	unsigned long flags, filled, available;
+	int i;
+	struct snd_pcm_runtime *runtime =
+		chip->channel[chi].substream->runtime;
+
+	spin_lock_irqsave(&chip->channel[chi].lock, flags);
+
+	dst_base = (u64)chip->ring_base + SMA_RING_OFFSET(chi) +
+		chip->mace_offset;
+	src_base = (u64)runtime->dma_area;
+
+	dst_pos = readq(&mace->perif.audio.chan[chi].write_ptr);
+	src_pos = chip->channel[chi].pos;
+
+	//src_mask = runtime->dma_bytes -1;
+	src_mask = frames_to_bytes(runtime, runtime->buffer_size) - 1;
+
+	dst = (u64*)(dst_base + dst_pos);
+	src = (u64*)(src_base + src_pos);
+;
+	filled = readq(&mace->perif.audio.chan[chi].depth);
+	available = SMA_RING_SIZE - filled;
+
+	/* don't catch up with the end, note 32 byte chunks*/
+	if (SMA_RING_SIZE <= available) available -= 32;
+
+	/* copy in 32 byte blocks */
+	while (31 < available) for (i=0; i<32; i+=sizeof(u64)) {
+
+		*dst = *src;
+
+		dst_pos += sizeof(u64);
+		dst_pos &= SMA_RING_MASK;
+		dst = (u64*)(dst_base + dst_pos);
+
+		src_pos += sizeof(u64);
+		src_pos &= src_mask;
+		src = (u64*)(src_base + src_pos);
+
+		available -= sizeof(u64);
+	}
+
+	writeq(dst_pos, &mace->perif.audio.chan[chi].write_ptr);
+	chip->channel[chi].pos = src_pos;
+	mb();
+
+	src_pos = readq(&mace->perif.audio.chan[chi].write_ptr);
+	if (src_pos != dst_pos)
+		snd_printk("ohoh ring boundary not matched! 0x%lX != 0x%lX\n",
+			src_pos, dst_pos);
+
+	spin_unlock_irqrestore(&chip->channel[chi].lock, flags);
+}
+
+static irqreturn_t sma_interrupt
+	(int irq, snd_mace_audio_t *chip, struct pt_regs *regs)
+{
+	switch (irq) {
+	case MACEISA_AUDIO_SW_IRQ:
+		printk("Got MACEISA AUDIO SW IRQ\n");
+		return IRQ_NONE; /*TODO*/
+
+	case MACEISA_AUDIO_SC_IRQ:
+		sma_adjust_vol(chip);
+		break;
+
+	case MACEISA_AUDIO1_DMAT_IRQ:
+		sma_dma_ping(chip, 0);
+		break;
+
+	case MACEISA_AUDIO1_OF_IRQ:
+		printk("Got MACEISA AUDIO1 OF IRQ\n");
+		return IRQ_NONE; /*TODO*/
+
+	case MACEISA_AUDIO2_DMAT_IRQ:
+		sma_dma_ping(chip, 1);
+		break;
+
+	case MACEISA_AUDIO2_MERR_IRQ:
+		printk("Got MACEISA AUDIO2 MERR IRQ\n");
+		return IRQ_NONE; /*TODO*/
+
+	case MACEISA_AUDIO3_DMAT_IRQ:
+		sma_dma_ping(chip, 2);
+		break;
+
+	case MACEISA_AUDIO3_MERR_IRQ:
+		printk("Got MACEISA AUDIO3 MERR IRQ\n");
+		return IRQ_NONE; /*TODO*/
+
+	default:
+		snd_printk(KERN_ERR "Unknown IRQ received!\n");
+		return IRQ_NONE;
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void sma_adjust_vol(snd_mace_audio_t *chip)
+{
+	unsigned long status, flags;
+	unsigned int rvol,lvol;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+
+	status = readq(&mace->perif.audio.control);
+	rvol = ad1843_get_gain(chip->ad1843, chip->ext_vol_for);
+	lvol = (rvol>>8)&0xFF;
+	rvol &= 0xFF;
+
+snd_printd("in volume adjust for %d, current vol %d|%d, status %ld\n",
+	chip->ext_vol_for, lvol, rvol, status);
+
+	if ((status & SMA_CTRL_VOL_BUTTON_UP) >0) {
+		status &= ~SMA_CTRL_VOL_BUTTON_UP;
+		writeq(status, &mace->perif.audio.control);
+		mb();
+
+		if (100 > lvol) lvol++;
+		if (100 > rvol) rvol++;
+	}
+
+	if ((status & SMA_CTRL_VOL_BUTTON_DOWN) >0) {
+		status &= ~SMA_CTRL_VOL_BUTTON_DOWN;
+		writeq(status, &mace->perif.audio.control);
+		mb();
+
+		if (0 < lvol) lvol--;
+		if (0 < rvol) rvol--;
+	}
+
+	ad1843_set_gain(chip->ad1843, chip->ext_vol_for, ((lvol<<8)|rvol));
+
+	mb();
+
+	status = readq(&mace->perif.audio.control);
+snd_printd("leaving volume adjust, vol now %d|%d, status %ld\n",
+	lvol, rvol, status);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+
+}
+
+/******************** Mixer and controls ********************/
+
+static int sma_gain_info
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_info *uinfo)
+{
+	uinfo->type = SNDRV_CTL_ELEM_TYPE_INTEGER;
+	uinfo->count = 2;
+	uinfo->value.integer.min = 0;
+	uinfo->value.integer.max = 100;
+
+	return 0;
+}
+
+static int sma_gain_get
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_mace_audio_t *chip = snd_kcontrol_chip(kcontrol);
+	int vol;
+
+	spin_lock_irq(&chip->ad1843_lock);
+
+	vol = ad1843_get_gain(chip->ad1843, (int)kcontrol->private_value);
+
+	ucontrol->value.integer.value[0] = vol & 0xFF;
+	ucontrol->value.integer.value[1] = (vol >> 8) & 0xFF;
+
+	spin_unlock_irq(&chip->ad1843_lock);
+
+	return 0;
+}
+
+static int sma_gain_put
+	(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol)
+{
+	snd_mace_audio_t *chip = snd_kcontrol_chip(kcontrol);
+	int newvol, oldvol;
+	unsigned long flags;
+
+	spin_lock_irq(&chip->ad1843_lock);
+
+	oldvol = ad1843_get_gain(chip->ad1843, kcontrol->private_value);
+	newvol = (ucontrol->value.integer.value[1] << 8) |
+		ucontrol->value.integer.value[0];
+
+	newvol = ad1843_set_gain(chip->ad1843, kcontrol->private_value,
+		newvol);
+
+	spin_unlock_irq(&chip->ad1843_lock);
+
+	return (newvol != oldvol);
+}
+
+static int __devinit sma_control_new(snd_mace_audio_t *chip)
+{
+	int err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_reclevel, chip));
+	if (0 > err) return err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_line, chip));
+	if (0 > err) return err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_cd, chip));
+	if (0 > err) return err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_mic, chip));
+	if (0 > err) return err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_pcm0, chip));
+	if (0 > err) return err;
+
+	err = snd_ctl_add(chip->card,
+		snd_ctl_new1(&sma_ctrl_pcm1, chip));
+	if (0 > err) return err;
+
+	/*plan a mixer control that can change this*/
+	chip->ext_vol_for = AD1843_GAIN_PCM_0;
+
+	return 0;
+}
+
+/****************************** Proc files **********************************/
+static void sma_proc_ad1843_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	int i;
+	char *str;
+	snd_mace_audio_t *chip = proc_entry->private_data;
+
+	snd_iprintf(buffer,
+		"|  15/7   |  14/6   |  13/5   |  12/4   "
+		"|  11/3   |  10/2   |   9/1   |   8/0   |\n");
+
+	for (i=0; i<32; i++){
+
+		str = ad1843_dump_reg(chip->ad1843, i);
+		if (NULL == str) continue;
+
+		snd_iprintf(buffer, str);
+
+		vfree(str);
+	}
+
+}
+
+static void sma_proc_mace_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	snd_mace_audio_t *chip = proc_entry->private_data;
+	unsigned long r,w,c,d;
+	int chi;
+
+	mb();
+	r = readq(&mace->perif.ctrl.ringbase);
+	snd_iprintf(buffer, "MACE Periferals Ringbase: 0x%lX (%lu)\n"
+		"\t(CPU Address: 0x%lX (%lu), Set Bus Address: 0x%lX (%lu))\n",
+		(unsigned long)r, (unsigned long)r,
+		(unsigned long)chip->ring_base, (unsigned long)chip->ring_base,
+		chip->ring_base_handle, chip->ring_base_handle);
+
+	c = readq(&mace->perif.audio.control);
+	snd_iprintf(buffer, "MACE Audio Control: 0x%lX (%lu)\n", c, c);
+
+	c = readq(&mace->perif.audio.codec_control);
+	snd_iprintf(buffer, "MACE Audio Codec Status Control: 0x%lX (%lu)\n",
+		c, c);
+
+	d = readq(&mace->perif.audio.codec_mask);
+	snd_iprintf(buffer, "MACE Audio Codec Status IRQ Mask: 0x%lX (%lu)\n",
+		d, d);
+
+	r = readq(&mace->perif.audio.codec_read);
+	snd_iprintf(buffer, "MACE Audio Codec Status Value: 0x%lX (%lu)\n",
+		r, r);
+
+	for (chi=0; chi<3; chi++) {
+
+		mb();
+		c = readq(&mace->perif.audio.chan[chi].control);
+		r = readq(&mace->perif.audio.chan[chi].read_ptr);
+		w = readq(&mace->perif.audio.chan[chi].write_ptr);
+		d = readq(&mace->perif.audio.chan[chi].depth);
+
+		snd_iprintf(buffer, "MACE Audio Channel %u:-\n"
+			"\tControl: 0x%lX (%lu)\n"
+			"\tRead Pointer: 0x%lX (%lu)\n"
+			"\tWrite Pointer: 0x%lX (%lu)\n"
+			"\tDepth: 0x%lX (%lu)\n",
+			chi, c, c, r, r, w, w, d, d);
+
+		if (chip->channel[chi].substream &&
+		    chip->channel[chi].substream->runtime ) {
+			unsigned long ap = frames_to_bytes(
+				chip->channel[chi].substream->runtime,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr);
+
+
+			snd_iprintf(buffer,
+				"\tALSA Runtime DMA area: 0x%lX (%lu)\n",
+				chip->channel[chi].substream->runtime->dma_area,
+				chip->channel[chi].substream->runtime->dma_area);
+
+			snd_iprintf(buffer,
+				"\tALSA App Pointer: 0x%lX (%lu) [0x%lX (%lu)]\n",
+				ap,ap,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr);
+
+		}
+	}
+}
+
+static void sma_proc_debug_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	sma_proc_debug_t *entry;
+
+	snd_iprintf(buffer, "MACE Audio debug info.\n");
+
+	list_for_each_entry(entry, &sma_proc_debug_list, link) {
+
+		if (NULL == entry->val_str)
+			snd_iprintf(buffer, "%s: \t%ld\n",
+				entry->name, entry->val_int);
+		else
+			snd_iprintf(buffer, "%s: \t%s\n",
+				entry->name, entry->val_str);
+	}
+
+}
+
+/* this is called inside the volume interrupt, but is it appropriate
+ * to be allocating memory during an interrupt? */
+static sma_proc_debug_t* sma_proc_debug_append
+	(char *name)
+{
+	sma_proc_debug_t *entry =
+		kzalloc(sizeof(sma_proc_debug_t), GFP_ATOMIC);
+
+	entry->name = name;
+
+	list_add_tail(&entry->link, &sma_proc_debug_list);
+
+	return entry;
+
+}
+
+static int sma_proc_create(snd_mace_audio_t *chip)
+{
+	int err;
+
+	err = snd_card_proc_new(chip->card, "debug", &chip->proc_debug);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_debug, chip,
+		sma_proc_debug_read);
+
+	err = snd_card_proc_new(chip->card, "ad1843", &chip->proc_ad1843);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_ad1843, chip,
+		sma_proc_ad1843_read);
+
+	err = snd_card_proc_new(chip->card, "mace", &chip->proc_mace);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_mace, chip,
+		sma_proc_mace_read);
+
+	return 0;
+}
+
+/******************************* ALSA setup **************************/
+
+/* chip specific destructor */
+static int sma_free(snd_mace_audio_t *chip)
+{
+	int irq;
+
+snd_printd("sma_free called\n");
+
+	/* reset the interface */
+	writeq(SMA_CTRL_RESET, &mace->perif.audio.control);
+	udelay(1);
+	writeq(0L, &mace->perif.audio.control);
+
+	/* undo DMA ? */
+	dma_free_coherent(NULL, MACEISA_RINGBUFFERS_SIZE + 62,
+		chip->ring_base, chip->ring_base_handle);
+			
+	/* release IRQ's */
+	for (irq=MACEISA_AUDIO_SW_IRQ; irq<=MACEISA_AUDIO3_MERR_IRQ; irq++){
+		free_irq(irq, chip);
+	}
+
+	/* shutdown the ad1843? */
+
+
+	/* free codec struct*/
+	kfree(chip->ad1843);
+	snd_mace_audio_chip = NULL;
+
+	return 0;
+}
+
+/* chip specific constructor */
+static int __devinit sma_create
+	(struct snd_card *card, snd_mace_audio_t **rchip)
+{
+	snd_mace_audio_t *chip;
+	int err, irq;
+	unsigned long mace_reg, mringbase;
+
+	*rchip = NULL;
+	chip = card->private_data;
+	chip->card = card;
+
+	/* DMA ring buffer */
+	chip->ring_base = dma_alloc_coherent (NULL,
+		MACEISA_RINGBUFFERS_SIZE + 62,
+		&chip->ring_base_handle, GFP_USER);
+
+	err = (NULL == chip->ring_base)?-ENOMEM:0; /* ugly */
+	if (0> err) goto ERROR_EXIT;
+
+	/* set ring base */
+	writeq(chip->ring_base_handle, &mace->perif.ctrl.ringbase);
+	mb();
+
+	chip->maceisa_base = readq(&mace->perif.ctrl.ringbase);
+	chip->mace_offset = chip->maceisa_base - chip->ring_base_handle;
+	/*chip->ring_base += chip->mace_offset;*/
+
+snd_printd("allocated ring base. CPU addr=0x%lX, Bus addr=0x%lX\n"
+	"\tmacebase=0x%lX, offset=0x%lX\n",
+	chip->ring_base, chip->ring_base_handle,
+	chip->maceisa_base, chip->mace_offset);
+
+	/* Allocate IRQs */
+	for (irq=MACEISA_AUDIO_SW_IRQ; irq<=MACEISA_AUDIO3_MERR_IRQ; irq++){
+		/*NOTE What does reqeust_irq return? */
+		err = request_irq(irq,
+			(irqreturn_t(*)(int, void*, struct pt_regs*))
+				&sma_interrupt,
+			SA_INTERRUPT|SA_SHIRQ, "Mace Audio", chip);
+		if (0 > err) goto ERROR_EXIT;
+	}
+
+	/* check if the codec is present */
+	mace_reg = readq(&mace->perif.audio.control);
+	err = (mace_reg & SMA_CTRL_CODEC_PRESENT)>0? 0 : -ENOENT;
+	if (0 > err) goto ERROR_EXIT;
+
+	/* reset the interface and ad1843 */
+	writeq(SMA_CTRL_RESET, &mace->perif.audio.control);
+	udelay(100);
+	writeq(0L, &mace->perif.audio.control);
+
+	/* spin locks */
+	spin_lock_init(&chip->ad1843_lock);
+
+	/* ad1843 setup */
+	chip->ad1843 = kmalloc(sizeof(ad1843_t), GFP_KERNEL);
+	err = (NULL == chip->ad1843)?-ENOMEM:0;
+	if (0 > err) goto ERROR_EXIT;
+
+	chip->ad1843->chip =  chip;
+	chip->ad1843->read =
+		(int(*)(void*, int))&mace_audio_reg_read;
+	chip->ad1843->write =
+		(int(*)(void*, int, int))&mace_audio_reg_write;
+
+	/* at mo we dont want any codec update irqs (SW) */
+	writeq(0L, &mace->perif.audio.codec_mask);
+
+	err = ad1843_init(chip->ad1843);
+	if (0 > err) goto ERROR_EXIT;
+
+	*rchip = chip;
+	snd_mace_audio_chip = chip;
+
+	return 0;
+
+ERROR_EXIT:
+	snd_printk(KERN_ERR "Attemp to initialise MACE Audio failed\n");
+	sma_free(chip);
+	return err;
+}
+
+/* constructer */
+static int __devinit sma_probe(void)
+{
+	struct snd_card      *card;
+	snd_mace_audio_t *chip;
+	int err;
+
+	/* create alsa stuff*/
+	card = snd_card_new(alsa_index, alsa_id,
+		THIS_MODULE, sizeof(snd_mace_audio_t));
+	if (NULL == card) return -ENOMEM;
+
+	/* sutup our stuff*/
+	err = sma_create(card, &chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	/* tell the world who we are */
+	strcpy(card->driver, "SGI O2 MACE Audio Dirver");
+	strcpy(card->shortname, "MACE Audio");
+	sprintf(card->longname, "%s",
+		card->shortname );
+
+	/* attach our bits'n'bobs */
+	err = sma_pcm_new(chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	err = sma_control_new(chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	err = sma_proc_create(chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	/* and we're done */
+	err = snd_card_register(card);
+	if (0 > err) goto ERROR_EXIT;
+
+	return 0;
+ERROR_EXIT:
+	snd_printk(KERN_ERR "Probe for MACE Audio failed\n");
+	snd_card_free(card);
+
+	return err;
+}
+
+/***************************** MACE access ***************************/
+static int mace_audio_reg_read(snd_mace_audio_t *chip, int reg)
+{
+	int val;
+	unsigned long flags;
+	unsigned int check;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+	writeq(SMA_CODEC_READ(reg),
+		&mace->perif.audio.codec_control);
+	mb();
+	udelay(200);
+
+	check = readq(&mace->perif.audio.codec_control); /* flush bus */
+	if (unlikely((SMA_CODEC_READ(reg)) != check))
+		snd_printk("MACE Audio reg read hic-up sent %lX, got %X\n",
+			(SMA_CODEC_READ(reg)), check);
+
+	val = (int)readq(&mace->perif.audio.codec_read);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+
+	return val;
+}
+
+static int mace_audio_reg_write(snd_mace_audio_t *chip, int reg, int word)
+{
+	unsigned long flags;
+	unsigned int check;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+
+	writeq(SMA_CODEC_WRITE(reg, word),
+		&mace->perif.audio.codec_control);
+	mb();
+
+	udelay(200);
+
+	check = readq(&mace->perif.audio.codec_control); /* flush bus */
+	if (unlikely((SMA_CODEC_WRITE(reg, word)) != check))
+		snd_printk("MACE Audio reg write hic-up sent %lX, got %X\n",
+			(SMA_CODEC_WRITE(reg, word)), check);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+
+	return 0;
+}
+
+/*********************** module setup ****************************/
+
+
+static int __init snd_card_mace_audio_init(void)
+{
+	int err;
+
+	err = sma_probe();
+	if (0 > err) goto ERROR_EXIT;
+
+	return 0;
+ERROR_EXIT:
+#ifdef MODULE
+	snd_printk(KERN_ERR "Mace Audio not found or device busy\n");
+#endif
+	return err;
+}
+
+static void __exit snd_card_mace_audio_exit(void)
+{
+	if (NULL != snd_mace_audio_chip) {
+		struct snd_card *card = snd_mace_audio_chip->card;
+		sma_free(snd_mace_audio_chip);
+		snd_card_free(card);
+	}
+}
+
+MODULE_AUTHOR("Thorben Jändling <tj.trevelyan at gmail.com>");
+MODULE_DESCRIPTION("SGI O2 MACE Audio");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 MACE Audio}}");
+
+module_param(alsa_index, int, 0444);
+MODULE_PARM_DESC(alsa_index, "Index value for MACE Audio");
+module_param(alsa_id, charp, 0444);
+MODULE_PARM_DESC(alsa_id, "ID string for MACE Audio");
+module_param(alsa_enable, bool, 0444);
+MODULE_PARM_DESC(alsa_enable, "Enable MACE Audio");
+
+module_init(snd_card_mace_audio_init)
+module_exit(snd_card_mace_audio_exit)
+

--- linux-2.6.21.6-b/sound/mips/mace_audio_spy.c	1970-01-01
01:00:00.000000000 +0100
+++ linux-2.6.21.6/sound/mips/mace_audio_spy.c	2007-07-09
19:51:22.000000000 +0100
@@ -0,0 +1,524 @@
+/*
+ *   Sound driver for Silicon Graphics O2 Workstations MACE audio board.
+ *
+ *   Copyright 2007 Thorben Jändling <tj.trevelyan at gmail.com>
+ *   Based/Copied heavily on/from sgio2audio.c:
+ *   Copyright 2003 Vivien Chappelier <vivien.chappelier at linux-mips.org>
+ *
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
+ *
+ */
+
+/*
+ * To snoop at the state mace audio is in after he ARCS jingle
+ *
+ */
+
+
+/*************************** include ******************************/
+
+#include <sound/driver.h>
+#include <linux/init.h>
+#include <linux/wait.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/gfp.h>
+#include <linux/vmalloc.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/list.h>
+#include <linux/dma-mapping.h>
+#include <asm/io.h>
+
+#include <asm/ip32/ip32_ints.h>
+#include <asm/ip32/mace.h>
+
+#include <sound/core.h>
+#include <sound/initval.h>
+#include <sound/control.h>
+#include <sound/pcm.h>
+#include <sound/initval.h>
+#include <sound/info.h>
+
+#include <sound/ad1843.h>
+
+/**************************** defines *******************************/
+
+/* 1: reset audio interface */
+#define SMA_CTRL_RESET          0x1UL
+/* 1: codec detected */
+#define SMA_CTRL_CODEC_PRESENT  0x2UL
+/*  2-8  : channel 1 write ptr alias */
+/*  9-15 : channel 2 read ptr alias */
+/* 16-22 : channel 3 read ptr alias */
+/* latched volume button */
+#define SMA_CTRL_VOL_BUTTON_UP   BIT(24)
+/* latched volume button */
+#define SMA_CTRL_VOL_BUTTON_DOWN BIT(23)
+
+#define SMA_CODEC_READ(reg)  ((reg) <<17) | 0x00010000UL
+#define SMA_CODEC_WRITE(reg, val) (((reg) <<17)|(val)) & 0x00FEFFFFUL
+
+#define SMA_RING_OFFSET(chi) ((chi) << 12)
+#define SMA_RING_SIZE 0x1000
+#define SMA_RING_MASK 0x0FFFUL
+
+/* int on buffer >50% full */
+#define SMA_INT_THRESHOLD_50 (2 << 5)
+/* 1: enable DMA transfer */
+#define SMA_DMA_ENABLE   BIT(9)
+
+/*old, define still inherted from sgio2audio*/
+
+#define SMA_CTRL_CHAN_RESET BIT(10) /* 1: reset channel */
+
+#define SMA_INT_THRESHOLD_DISABLED  (0 << 5) /* interrupt disabled */
+#define SMA_INT_THRESHOLD_25 (1 << 5) /* int on buffer >25% full */
+#define SMA_INT_THRESHOLD_75 (3 << 5) /* int on buffer >75% full */
+#define SMA_INT_THRESHOLD_EMPTY     (4 << 5) /* int on buffer empty */
+#define SMA_INT_THRESHOLD_NOT_EMPTY (5 << 5) /* int on buffer !empty */
+#define SMA_INT_THRESHOLD_FULL      (6 << 5) /* int on buffer empty */
+#define SMA_INT_THRESHOLD_NOT_FULL  (7 << 5) /* int on buffer !empty */
+
+/****************************** structs/types ***********************/
+
+/* chip specific record */
+typedef struct snd_mace_audio {
+	struct snd_card *card;
+	struct snd_pcm  *pcm[2];
+
+	struct {
+		struct snd_pcm_substream *substream;
+		snd_pcm_uframes_t        frames;
+
+		int                  ack;
+		void                *buffer;
+		u64                  pos;
+		spinlock_t           lock;
+	} channel[3];
+
+	/* which gain should the O2 vol butons control?*/
+	unsigned int ext_vol_for;
+
+	struct snd_info_entry  *proc_debug,
+	                  *proc_ad1843,
+	                  *proc_mace;
+
+	ad1843_t   *ad1843;
+	spinlock_t ad1843_lock;
+
+	void        *ring_base;
+	dma_addr_t   ring_base_handle;
+	unsigned long maceisa_base;
+	unsigned int mace_offset;
+
+} snd_mace_audio_t;
+
+typedef struct sma_proc_debug {
+	struct list_head link;
+
+/* name to display for the entry */
+	char  *name;
+
+/* a string value */
+	char  *val_str;
+
+/* an int val used if val_str is null */
+	long val_int;
+} sma_proc_debug_t;
+
+/************************** func declaration ****************************/
+/* mace access */
+static int mace_audio_reg_read(snd_mace_audio_t *chip, int reg);
+static int mace_audio_reg_write(snd_mace_audio_t *chip, int reg, int word);
+/* proc */
+static void sma_proc_ad1843_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer);
+static void sma_proc_mace_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer);
+static void sma_proc_debug_read
+	(struct snd_info_entry *, struct snd_info_buffer *);
+static sma_proc_debug_t* sma_proc_debug_append(char *name);
+static int sma_proc_create(snd_mace_audio_t *chip);
+/* ALSA Setup */
+static int sma_free(snd_mace_audio_t *chip);
+static int __devinit sma_create
+	(struct snd_card *card, snd_mace_audio_t **rchip);
+static int __devinit sma_probe(void);
+/* module setup */
+static int __init snd_card_mace_audio_init(void);
+static void __exit snd_card_mace_audio_exit(void);
+
+
+/************************* define psudo-funcs ***************************/
+#define sma_proc_entry_assert(entry,name) \
+	if (NULL == entry) entry = sma_proc_entry_append(name);
+
+/* pcm0 substream0 = channel 1
+ * pcm0 substream1 = channel 0
+ * pcm1 substream0 = channel 2
+ */
+#define substream_to_channel_index(substream) \
+ ((((snd_mace_audio_t*)(substream->private_data))->pcm[0]==substream->pcm)\
+ ?!(substream->number):2)
+
+/******************************** globals *******************************/
+
+
+LIST_HEAD(sma_proc_debug_list);
+
+static int   alsa_index = -1;
+static char *alsa_id    = NULL;
+static int   alsa_enable= 1;
+static int   sma_indirect = 1;
+
+/* Global needed for module exit */
+snd_mace_audio_t *snd_mace_audio_chip = NULL;
+
+
+/****************************** Proc files **********************************/
+static void sma_proc_ad1843_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	int i;
+	char *str;
+	snd_mace_audio_t *chip = proc_entry->private_data;
+
+	snd_iprintf(buffer,
+		"|  15/7   |  14/6   |  13/5   |  12/4   "
+		"|  11/3   |  10/2   |   9/1   |   8/0   |\n");
+
+	for (i=0; i<32; i++){
+
+		str = ad1843_dump_reg(chip->ad1843, i);
+		if (NULL == str) continue;
+
+		snd_iprintf(buffer, str);
+
+		vfree(str);
+	}
+
+}
+
+static void sma_proc_mace_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	snd_mace_audio_t *chip = proc_entry->private_data;
+	unsigned long r,w,c,d;
+	int chi;
+
+	mb();
+	r = readq(&mace->perif.ctrl.ringbase);
+	snd_iprintf(buffer, "MACE Periferals Ringbase: 0x%lX (%lu)\n"
+		"\t(CPU Address: 0x%lX (%lu), Set Bus Address: 0x%lX (%lu))\n",
+		(unsigned long)r, (unsigned long)r,
+		(unsigned long)chip->ring_base, (unsigned long)chip->ring_base,
+		chip->ring_base_handle, chip->ring_base_handle);
+
+	c = readq(&mace->perif.audio.control);
+	snd_iprintf(buffer, "MACE Audio Control: 0x%lX (%lu)\n", c, c);
+
+	c = readq(&mace->perif.audio.codec_control);
+	snd_iprintf(buffer, "MACE Audio Codec Status Control: 0x%lX (%lu)\n", c, c);
+
+	d = readq(&mace->perif.audio.codec_mask);
+	snd_iprintf(buffer, "MACE Audio Codec Status IRQ Mask: 0x%lX (%lu)\n", d, d);
+
+	r = readq(&mace->perif.audio.codec_read);
+	snd_iprintf(buffer, "MACE Audio Codec Status Value: 0x%lX (%lu)\n", r, r);
+
+	for (chi=0; chi<3; chi++) {
+
+		mb();
+		c = readq(&mace->perif.audio.chan[chi].control);
+		r = readq(&mace->perif.audio.chan[chi].read_ptr);
+		w = readq(&mace->perif.audio.chan[chi].write_ptr);
+		d = readq(&mace->perif.audio.chan[chi].depth);
+
+		snd_iprintf(buffer, "MACE Audio Channel %u:-\n"
+			"\tControl: 0x%lX (%lu)\n"
+			"\tRead Pointer: 0x%lX (%lu)\n"
+			"\tWrite Pointer: 0x%lX (%lu)\n"
+			"\tDepth: 0x%lX (%lu)\n",
+			chi, c, c, r, r, w, w, d, d);
+
+		if (chip->channel[chi].substream &&
+		    chip->channel[chi].substream->runtime ) {
+			unsigned long ap = frames_to_bytes(
+				chip->channel[chi].substream->runtime,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr);
+
+
+			snd_iprintf(buffer, "\tALSA Runtime DMA area: 0x%lX (%lu)\n",
+				chip->channel[chi].substream->runtime->dma_area,
+				chip->channel[chi].substream->runtime->dma_area);
+
+			snd_iprintf(buffer, "\tALSA App Pointer: 0x%lX (%lu) [0x%lX (%lu)]\n",
+				ap,ap,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr,
+				chip->channel[chi].substream->runtime->control
+					->appl_ptr);
+
+		}
+	}
+}
+
+static void sma_proc_debug_read
+	(struct snd_info_entry *proc_entry, struct snd_info_buffer *buffer)
+{
+	sma_proc_debug_t *entry;
+
+	snd_iprintf(buffer, "MACE Audio debug info.\n");
+
+	list_for_each_entry(entry, &sma_proc_debug_list, link) {
+
+		if (NULL == entry->val_str)
+			snd_iprintf(buffer, "%s: \t%ld\n",
+				entry->name, entry->val_int);
+		else
+			snd_iprintf(buffer, "%s: \t%s\n",
+				entry->name, entry->val_str);
+	}
+
+}
+
+/* this is called inside the volume interrupt, but is it appropriate
+ * to be allocating memory during an interrupt? */
+static sma_proc_debug_t* sma_proc_debug_append
+	(char *name)
+{
+	sma_proc_debug_t *entry =
+		kzalloc(sizeof(sma_proc_debug_t), GFP_ATOMIC);
+
+	entry->name = name;
+
+	list_add_tail(&entry->link, &sma_proc_debug_list);
+
+	return entry;
+
+}
+
+static int sma_proc_create(snd_mace_audio_t *chip)
+{
+	int err;
+
+	err = snd_card_proc_new(chip->card, "debug", &chip->proc_debug);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_debug, chip,
+		sma_proc_debug_read);
+
+	err = snd_card_proc_new(chip->card, "ad1843", &chip->proc_ad1843);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_ad1843, chip,
+		sma_proc_ad1843_read);
+
+	err = snd_card_proc_new(chip->card, "mace", &chip->proc_mace);
+	if (0 > err) return err;
+
+	snd_info_set_text_ops(chip->proc_mace, chip,
+		sma_proc_mace_read);
+
+	return 0;
+}
+
+/******************************* ALSA setup **************************/
+
+/* chip specific destructor */
+static int sma_free(snd_mace_audio_t *chip)
+{
+	int irq;
+
+printk("sma_free called\n");
+
+	/* free codec struct*/
+	kfree(chip->ad1843);
+	snd_mace_audio_chip = NULL;
+
+	return 0;
+}
+
+/* chip specific constructor, but we don't init anything */
+static int __devinit sma_create
+	(struct snd_card *card, snd_mace_audio_t **rchip)
+{
+	snd_mace_audio_t *chip;
+	int err, irq;
+	unsigned long mace_reg, mringbase;
+
+	*rchip = NULL;
+	chip = card->private_data;
+	chip->card = card;
+
+	/* check if the codec is present */
+	mace_reg = readq(&mace->perif.audio.control);
+	err = (mace_reg & SMA_CTRL_CODEC_PRESENT)>0? 0 : -ENOENT;
+	if (0 > err) goto ERROR_EXIT;
+
+	/* spin locks */
+	spin_lock_init(&chip->ad1843_lock);
+
+	/* ad1843 setup */
+	chip->ad1843 = kmalloc(sizeof(ad1843_t), GFP_KERNEL);
+	err = (NULL == chip->ad1843)?-ENOMEM:0;
+	if (0 > err) goto ERROR_EXIT;
+
+	chip->ad1843->chip =  chip;
+	chip->ad1843->read =
+		(int(*)(void*, int))&mace_audio_reg_read;
+	chip->ad1843->write =
+		(int(*)(void*, int, int))&mace_audio_reg_write;
+
+	*rchip = chip;
+	snd_mace_audio_chip = chip;
+
+	return 0;
+
+ERROR_EXIT:
+	snd_printk(KERN_ERR "Attemp to initialise MACE Audio failed\n");
+	sma_free(chip);
+	return err;
+}
+
+/* constructer */
+static int __devinit sma_probe(void)
+{
+	struct snd_card      *card;
+	snd_mace_audio_t *chip;
+	int err;
+
+	/* create alsa stuff*/
+	card = snd_card_new(alsa_index, alsa_id,
+		THIS_MODULE, sizeof(snd_mace_audio_t));
+	if (NULL == card) return -ENOMEM;
+
+	/* sutup our stuff*/
+	err = sma_create(card, &chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	/* tell the world who we are */
+	strcpy(card->driver, "SGI O2 MACE Audio Spy");
+	strcpy(card->shortname, "MACE Audio Spy");
+	sprintf(card->longname, "%s",
+		card->shortname );
+
+	err = sma_proc_create(chip);
+	if (0 > err) goto ERROR_EXIT;
+
+	/* and we're done */
+	err = snd_card_register(card);
+	if (0 > err) goto ERROR_EXIT;
+
+	return 0;
+ERROR_EXIT:
+	snd_printk(KERN_ERR "Probe for MACE Audio failed\n");
+	snd_card_free(card);
+
+	return err;
+}
+
+/***************************** MACE access ***************************/
+static int mace_audio_reg_read(snd_mace_audio_t *chip, int reg)
+{
+	int val;
+	unsigned long flags;
+	unsigned int check;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+	writeq(SMA_CODEC_READ(reg),
+		&mace->perif.audio.codec_control);
+	mb();
+	udelay(200);
+
+	check = readq(&mace->perif.audio.codec_control); /* flush bus */
+	if (unlikely((SMA_CODEC_READ(reg)) != check))
+		printk("MACE Audio reg read hic-up sent %lX, got %X\n",
+			(SMA_CODEC_READ(reg)), check);
+
+	val = (int)readq(&mace->perif.audio.codec_read);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+
+	return val;
+}
+
+static int mace_audio_reg_write(snd_mace_audio_t *chip, int reg, int word)
+{
+	unsigned long flags;
+	unsigned int check;
+
+	spin_lock_irqsave(&chip->ad1843_lock, flags);
+
+	writeq(SMA_CODEC_WRITE(reg, word),
+		&mace->perif.audio.codec_control);
+	mb();
+
+	udelay(200);
+
+	check = readq(&mace->perif.audio.codec_control); /* flush bus */
+	if (unlikely((SMA_CODEC_WRITE(reg, word)) != check))
+		printk("MACE Audio reg write hic-up sent %lX, got %X\n",
+			(SMA_CODEC_WRITE(reg, word)), check);
+
+	spin_unlock_irqrestore(&chip->ad1843_lock, flags);
+
+	return 0;
+}
+
+/*********************** module setup ****************************/
+
+
+static int __init snd_card_mace_audio_init(void)
+{
+	int err;
+
+	err = sma_probe();
+	if (0 > err) goto ERROR_EXIT;
+
+	return 0;
+ERROR_EXIT:
+#ifdef MODULE
+	printk(KERN_ERR "Mace Audio not found or device busy\n");
+#endif
+	return err;
+}
+
+static void __exit snd_card_mace_audio_exit(void)
+{
+	if (NULL != snd_mace_audio_chip) {
+		struct snd_card *card = snd_mace_audio_chip->card;
+		sma_free(snd_mace_audio_chip);
+		snd_card_free(card);
+	}
+}
+
+MODULE_AUTHOR("Thorben Jändling <tj.trevelyan at gmail.com>");
+MODULE_DESCRIPTION("SGI O2 MACE Audio Spy");
+MODULE_LICENSE("GPL");
+MODULE_SUPPORTED_DEVICE("{{Silicon Graphics, O2 MACE Audio}}");
+
+module_param(alsa_index, int, 0444);
+MODULE_PARM_DESC(alsa_index, "Index value for MACE Audio");
+module_param(alsa_id, charp, 0444);
+MODULE_PARM_DESC(alsa_id, "ID string for MACE Audio");
+module_param(alsa_enable, bool, 0444);
+MODULE_PARM_DESC(alsa_enable, "Enable MACE Audio");
+
+module_init(snd_card_mace_audio_init)
+module_exit(snd_card_mace_audio_exit)
+

--- linux-2.6.21.6-b/sound/mips/Kconfig	2007-07-09 11:57:38.000000000 +0100
+++ linux-2.6.21.6/sound/mips/Kconfig	2007-07-09 19:14:18.000000000 +0100
@@ -11,5 +11,18 @@
 	help
 	  ALSA Sound driver for the Au1x00's AC97 port.

+config SND_MACE_AUDIO
+	tristate "SGI O2 MACE Audio"
+	depends on  SND && SGI_IP32
+	help
+	Sound support for the SGI O2 Workstation.
+
+config SND_MACE_AUDIO_SPY
+	tristate "SGI O2 MACE Audio Spy"
+	depends on  SND && SGI_IP32
+	help
+	Spy on MACE Audio Hardware state. Do not use if you don't know
+	what this module is for or why you want to use it.
+
 endmenu


--- linux-2.6.21.6-b/sound/mips/Makefile	2007-07-09 11:57:38.000000000 +0100
+++ linux-2.6.21.6/sound/mips/Makefile	2007-07-09 19:14:18.000000000 +0100
@@ -3,6 +3,11 @@
 #

 snd-au1x00-objs := au1x00.o
+snd-mace-audio-objs := mace_audio.o ad1843.o
+snd-mace-audio-spy-objs := mace_audio_spy.o ad1843.o

 # Toplevel Module Dependency
 obj-$(CONFIG_SND_AU1X00) += snd-au1x00.o
+obj-$(CONFIG_SND_MACE_AUDIO) += snd-mace-audio.o
+obj-$(CONFIG_SND_MACE_AUDIO_SPY) += snd-mace-audio-spy.o
+


More information about the Alsa-devel mailing list